Skip to content

Commit bf339d6

Browse files
jeromehanh-a-n-ahaoqunjiang
authored
feat: v15 support experimental inline match resource (#2058)
Co-authored-by: Hana <andywangsy@gmail.com> Co-authored-by: Haoqun Jiang <haoqunjiang@gmail.com>
1 parent 4f5727d commit bf339d6

File tree

16 files changed

+325
-89
lines changed

16 files changed

+325
-89
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ jobs:
4343
run: pnpm install --no-frozen-lockfile
4444

4545
- name: Run unit tests for webpack 5
46-
run: pnpm run test
46+
run: pnpm run test && pnpm run test:match-resource

lib/codegen/customBlocks.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
const qs = require('querystring')
2-
const { attrsToQuery } = require('./utils')
2+
const { attrsToQuery, genMatchResource } = require('./utils')
33

44
module.exports = function genCustomBlocksCode(
5+
loaderContext,
56
blocks,
67
resourcePath,
78
resourceQuery,
8-
stringifyRequest
9+
stringifyRequest,
10+
enableInlineMatchResource
911
) {
1012
return (
1113
`\n/* custom blocks */\n` +
@@ -17,11 +19,22 @@ module.exports = function genCustomBlocksCode(
1719
? `&issuerPath=${qs.escape(resourcePath)}`
1820
: ''
1921
const inheritQuery = resourceQuery ? `&${resourceQuery.slice(1)}` : ''
22+
const externalQuery = block.attrs.src ? `&external` : ``
2023
const query = `?vue&type=custom&index=${i}&blockType=${qs.escape(
2124
block.type
22-
)}${issuerQuery}${attrsQuery}${inheritQuery}`
25+
)}${issuerQuery}${attrsQuery}${inheritQuery}${externalQuery}`
26+
27+
let customRequest
28+
29+
if (enableInlineMatchResource) {
30+
customRequest = stringifyRequest(
31+
genMatchResource(loaderContext, src, query, block.attrs.lang)
32+
)
33+
} else {
34+
customRequest = stringifyRequest(src + query)
35+
}
2336
return (
24-
`import block${i} from ${stringifyRequest(src + query)}\n` +
37+
`import block${i} from ${customRequest}\n` +
2538
`if (typeof block${i} === 'function') block${i}(component)`
2639
)
2740
})

lib/codegen/styleInjection.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { attrsToQuery } = require('./utils')
1+
const { attrsToQuery, genMatchResource } = require('./utils')
22
const hotReloadAPIPath = JSON.stringify(require.resolve('vue-hot-reload-api'))
33
const nonWhitespaceRE = /\S+/
44

@@ -10,7 +10,8 @@ module.exports = function genStyleInjectionCode(
1010
stringifyRequest,
1111
needsHotReload,
1212
needsExplicitInjection,
13-
isProduction
13+
isProduction,
14+
enableInlineMatchResource
1415
) {
1516
let styleImportsCode = ``
1617
let styleInjectionCode = ``
@@ -22,13 +23,23 @@ module.exports = function genStyleInjectionCode(
2223
function genStyleRequest(style, i) {
2324
const src = style.src || resourcePath
2425
const attrsQuery = attrsToQuery(style.attrs, 'css')
25-
const inheritQuery = `&${loaderContext.resourceQuery.slice(1)}`
26+
const lang = String(style.attrs.lang || 'css')
27+
const inheritQuery = loaderContext.resourceQuery.slice(1)
28+
? `&${loaderContext.resourceQuery.slice(1)}`
29+
: ''
2630
// make sure to only pass id not src importing so that we don't inject
2731
// duplicate tags when multiple components import the same css file
2832
const idQuery = !style.src || style.scoped ? `&id=${id}` : ``
2933
const prodQuery = isProduction ? `&prod` : ``
30-
const query = `?vue&type=style&index=${i}${idQuery}${prodQuery}${attrsQuery}${inheritQuery}`
31-
return stringifyRequest(src + query)
34+
const externalQuery = style.src ? `&external` : ``
35+
const query = `?vue&type=style&index=${i}${idQuery}${prodQuery}${attrsQuery}${inheritQuery}${externalQuery}`
36+
let styleRequest
37+
if (enableInlineMatchResource) {
38+
styleRequest = stringifyRequest(genMatchResource(loaderContext, src, query, lang))
39+
} else {
40+
styleRequest = stringifyRequest(src + query)
41+
}
42+
return styleRequest
3243
}
3344

3445
function genCSSModulesCode(style, request, i) {

lib/codegen/utils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,31 @@ exports.attrsToQuery = (attrs, langFallback) => {
1818
}
1919
return query
2020
}
21+
22+
exports.genMatchResource = (context, resourcePath, resourceQuery, lang) => {
23+
resourceQuery = resourceQuery || ''
24+
25+
const loaders = []
26+
const parsedQuery = qs.parse(resourceQuery.slice(1))
27+
28+
// process non-external resources
29+
if ('vue' in parsedQuery && !('external' in parsedQuery)) {
30+
const currentRequest = context.loaders
31+
.slice(context.loaderIndex)
32+
.map((obj) => obj.request)
33+
loaders.push(...currentRequest)
34+
}
35+
const loaderString = loaders.join('!')
36+
37+
return `${resourcePath}${lang ? `.${lang}` : ''}${resourceQuery}!=!${
38+
loaderString ? `${loaderString}!` : ''
39+
}${resourcePath}${resourceQuery}`
40+
}
41+
42+
exports.testWebpack5 = (compiler) => {
43+
if (!compiler) {
44+
return false
45+
}
46+
const webpackVersion = compiler.webpack && compiler.webpack.version
47+
return Boolean(webpackVersion && Number(webpackVersion.split('.')[0]) > 4)
48+
}

lib/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ declare namespace VueLoader {
1818
cacheIdentifier?: string
1919
prettify?: boolean
2020
exposeFilename?: boolean
21+
experimentalInlineMatchResource?: boolean
2122
}
2223
}
2324

lib/index.js

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ const qs = require('querystring')
44
const plugin = require('./plugin')
55
const selectBlock = require('./select')
66
const loaderUtils = require('loader-utils')
7-
const { attrsToQuery } = require('./codegen/utils')
7+
const {
8+
attrsToQuery,
9+
testWebpack5,
10+
genMatchResource
11+
} = require('./codegen/utils')
812
const genStylesCode = require('./codegen/styleInjection')
913
const { genHotReloadCode } = require('./codegen/hotReload')
1014
const genCustomBlocksCode = require('./codegen/customBlocks')
@@ -38,14 +42,16 @@ module.exports = function (source) {
3842
sourceMap,
3943
rootContext,
4044
resourcePath,
41-
resourceQuery = ''
45+
resourceQuery: _resourceQuery = '',
46+
_compiler
4247
} = loaderContext
43-
44-
const rawQuery = resourceQuery.slice(1)
45-
const inheritQuery = `&${rawQuery}`
48+
const isWebpack5 = testWebpack5(_compiler)
49+
const rawQuery = _resourceQuery.slice(1)
50+
const resourceQuery = rawQuery ? `&${rawQuery}` : ''
4651
const incomingQuery = qs.parse(rawQuery)
4752
const options = loaderUtils.getOptions(loaderContext) || {}
48-
53+
const enableInlineMatchResource =
54+
isWebpack5 && Boolean(options.experimentalInlineMatchResource)
4955
const isServer = target === 'node'
5056
const isShadow = !!options.shadowMode
5157
const isProduction =
@@ -111,29 +117,47 @@ module.exports = function (source) {
111117
// let isTS = false
112118
const { script, scriptSetup } = descriptor
113119
if (script || scriptSetup) {
114-
// const lang = script?.lang || scriptSetup?.lang
120+
const lang = script.lang || (scriptSetup && scriptSetup.lang)
115121
// isTS = !!(lang && /tsx?/.test(lang))
122+
const externalQuery =
123+
script && !scriptSetup && script.src ? `&external` : ``
116124
const src = (script && !scriptSetup && script.src) || resourcePath
117125
const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js')
118-
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
119-
const request = stringifyRequest(src + query)
126+
const query = `?vue&type=script${attrsQuery}${resourceQuery}${externalQuery}`
127+
128+
let scriptRequest
129+
if (enableInlineMatchResource) {
130+
scriptRequest = stringifyRequest(
131+
genMatchResource(loaderContext, src, query, lang || 'js')
132+
)
133+
} else {
134+
scriptRequest = stringifyRequest(src + query)
135+
}
120136
scriptImport =
121-
`import script from ${request}\n` + `export * from ${request}` // support named exports
137+
`import script from ${scriptRequest}\n` + `export * from ${scriptRequest}` // support named exports
122138
}
123139

124140
// template
125141
let templateImport = `var render, staticRenderFns`
126142
let templateRequest
127143
if (descriptor.template) {
128144
const src = descriptor.template.src || resourcePath
145+
const externalQuery = descriptor.template.src ? `&external` : ``
129146
const idQuery = `&id=${id}`
130147
const scopedQuery = hasScoped ? `&scoped=true` : ``
131148
const attrsQuery = attrsToQuery(descriptor.template.attrs)
132149
// const tsQuery =
133150
// options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``
134-
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
135-
const request = (templateRequest = stringifyRequest(src + query))
136-
templateImport = `import { render, staticRenderFns } from ${request}`
151+
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${resourceQuery}${externalQuery}`
152+
if (enableInlineMatchResource) {
153+
templateRequest = stringifyRequest(
154+
// TypeScript syntax in template expressions is not supported in Vue 2, so the lang is always 'js'
155+
genMatchResource(loaderContext, src, query, 'js')
156+
)
157+
} else {
158+
templateRequest = stringifyRequest(src + query)
159+
}
160+
templateImport = `import { render, staticRenderFns } from ${templateRequest}`
137161
}
138162

139163
// styles
@@ -147,7 +171,8 @@ module.exports = function (source) {
147171
stringifyRequest,
148172
needsHotReload,
149173
isServer || isShadow, // needs explicit injection?
150-
isProduction
174+
isProduction,
175+
enableInlineMatchResource
151176
)
152177
}
153178

@@ -173,10 +198,12 @@ var component = normalizer(
173198

174199
if (descriptor.customBlocks && descriptor.customBlocks.length) {
175200
code += genCustomBlocksCode(
201+
loaderContext,
176202
descriptor.customBlocks,
177203
resourcePath,
178204
resourceQuery,
179-
stringifyRequest
205+
stringifyRequest,
206+
enableInlineMatchResource
180207
)
181208
}
182209

lib/loaders/pitcher.js

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const selfPath = require.resolve('../index')
55
const templateLoaderPath = require.resolve('./templateLoader')
66
const stylePostLoaderPath = require.resolve('./stylePostLoader')
77
const { resolveCompiler } = require('../compiler')
8+
const { testWebpack5 } = require('../codegen/utils')
89

910
const isESLintLoader = (l) => /(\/|\\|@)eslint-loader/.test(l.path)
1011
const isNullLoader = (l) => /(\/|\\|@)null-loader/.test(l.path)
@@ -53,6 +54,7 @@ module.exports.pitch = function (remainingRequest) {
5354
const options = loaderUtils.getOptions(this)
5455
const { cacheDirectory, cacheIdentifier } = options
5556
const query = qs.parse(this.resourceQuery.slice(1))
57+
const isWebpack5 = testWebpack5(this._compiler)
5658

5759
let loaders = this.loaders
5860

@@ -78,7 +80,7 @@ module.exports.pitch = function (remainingRequest) {
7880
return
7981
}
8082

81-
const genRequest = (loaders) => {
83+
const genRequest = (loaders, lang) => {
8284
// Important: dedupe since both the original rule
8385
// and the cloned rule would match a source import request.
8486
// also make sure to dedupe based on loader path.
@@ -89,6 +91,8 @@ module.exports.pitch = function (remainingRequest) {
8991
// path AND query to be safe.
9092
const seen = new Map()
9193
const loaderStrings = []
94+
const enableInlineMatchResource =
95+
isWebpack5 && options.experimentalInlineMatchResource
9296

9397
loaders.forEach((loader) => {
9498
const identifier =
@@ -101,6 +105,14 @@ module.exports.pitch = function (remainingRequest) {
101105
loaderStrings.push(request)
102106
}
103107
})
108+
if (enableInlineMatchResource) {
109+
return loaderUtils.stringifyRequest(
110+
this,
111+
`${this.resourcePath}${lang ? `.${lang}` : ''}${
112+
this.resourceQuery
113+
}!=!-!${[...loaderStrings, this.resourcePath + this.resourceQuery].join('!')}`
114+
)
115+
}
104116

105117
return loaderUtils.stringifyRequest(
106118
this,
@@ -111,15 +123,51 @@ module.exports.pitch = function (remainingRequest) {
111123

112124
// Inject style-post-loader before css-loader for scoped CSS and trimming
113125
if (query.type === `style`) {
126+
if (isWebpack5 && this._compiler.options.experiments && this._compiler.options.experiments.css) {
127+
// If user enables `experiments.css`, then we are trying to emit css code directly.
128+
// Although we can target requests like `xxx.vue?type=style` to match `type: "css"`,
129+
// it will make the plugin a mess.
130+
if (!options.experimentalInlineMatchResource) {
131+
this.emitError(
132+
new Error(
133+
'`experimentalInlineMatchResource` should be enabled if `experiments.css` enabled currently'
134+
)
135+
)
136+
return ''
137+
}
138+
139+
if (query.inline || query.module) {
140+
this.emitError(
141+
new Error(
142+
'`inline` or `module` is currently not supported with `experiments.css` enabled'
143+
)
144+
)
145+
return ''
146+
}
147+
148+
const loaderString = [stylePostLoaderPath, ...loaders]
149+
.map((loader) => {
150+
return typeof loader === 'string' ? loader : loader.request
151+
})
152+
.join('!')
153+
154+
const styleRequest = loaderUtils.stringifyRequest(
155+
this,
156+
`${this.resourcePath}${query.lang ? `.${query.lang}` : ''}${
157+
this.resourceQuery
158+
}!=!-!${loaderString}!${this.resourcePath + this.resourceQuery}`
159+
)
160+
return `@import ${styleRequest};`
161+
}
162+
114163
const cssLoaderIndex = loaders.findIndex(isCSSLoader)
115164
if (cssLoaderIndex > -1) {
116165
const afterLoaders = loaders.slice(0, cssLoaderIndex + 1)
117166
const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
118-
const request = genRequest([
119-
...afterLoaders,
120-
stylePostLoaderPath,
121-
...beforeLoaders
122-
])
167+
const request = genRequest(
168+
[...afterLoaders, stylePostLoaderPath, ...beforeLoaders],
169+
query.lang || 'css'
170+
)
123171
// console.log(request)
124172
return query.module
125173
? `export { default } from ${request}; export * from ${request}`

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy