Skip to content

Commit cd07b1c

Browse files
h-a-n-afreddy38510
authored andcommitted
feat: support experimental inline match resource (vuejs#2046)
1 parent c1e6267 commit cd07b1c

15 files changed

+382
-122
lines changed

Diff for: .github/workflows/ci.yml

+12
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,15 @@ jobs:
3030
cache: 'yarn'
3131
- run: yarn install
3232
- run: yarn test
33+
34+
test-webpack5-inline-match-resource:
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v2
38+
- name: Set node version to 16
39+
uses: actions/setup-node@v2
40+
with:
41+
node-version: 16
42+
cache: 'yarn'
43+
- run: yarn install
44+
- run: yarn test:match-resource

Diff for: README.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
55
- [Documentation](https://vue-loader.vuejs.org)
66

7+
## v17.3+ Only Options
8+
9+
- `experimentalInlineMatchResource: boolean`: enable [Inline matchResource](https://webpack.js.org/api/loaders/#inline-matchresource) for rule matching for vue-loader.
10+
711
## v16+ Only Options
812

913
- `reactivityTransform: boolean`: enable [Vue Reactivity Transform](https://github.com/vuejs/rfcs/discussions/369) (SFCs only).

Diff for: jest.config.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
console.log(`running tests with webpack ${process.env.WEBPACK4 ? '4' : '5'}...`)
1+
const isWebpack4 = process.env.WEBPACK4
2+
3+
console.log(
4+
`running tests with webpack ${isWebpack4 ? '4' : '5'}${
5+
!isWebpack4 && process.env.INLINE_MATCH_RESOURCE
6+
? ' with inline match resource enabled'
7+
: ''
8+
}...`,
9+
)
210

311
module.exports = {
412
preset: 'ts-jest',

Diff for: package.json

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"build": "tsc",
1515
"pretest": "tsc",
1616
"test": "jest",
17+
"pretest:match-resource": "tsc",
18+
"test:match-resource": "INLINE_MATCH_RESOURCE=true jest",
1719
"pretest:webpack4": "tsc",
1820
"test:webpack4": "WEBPACK4=true jest --forceExit",
1921
"dev-example": "node example/devServer.js --config example/webpack.config.js --inline --hot",

Diff for: src/index.ts

+86-18
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ import { formatError } from './formatError'
2222
import VueLoaderPlugin from './plugin'
2323
import { canInlineTemplate } from './resolveScript'
2424
import { setDescriptor } from './descriptorCache'
25-
import { getOptions, stringifyRequest as _stringifyRequest } from './util'
25+
import {
26+
getOptions,
27+
stringifyRequest as _stringifyRequest,
28+
genMatchResource,
29+
testWebpack5,
30+
} from './util'
2631

2732
export { VueLoaderPlugin }
2833

@@ -53,6 +58,7 @@ export interface VueLoaderOptions {
5358
exposeFilename?: boolean
5459
appendExtension?: boolean
5560
enableTsInTemplate?: boolean
61+
experimentalInlineMatchResource?: boolean
5662

5763
isServerBuild?: boolean
5864
}
@@ -64,7 +70,7 @@ const exportHelperPath = require.resolve('./exportHelper')
6470

6571
export default function loader(
6672
this: LoaderContext<VueLoaderOptions>,
67-
source: string
73+
source: string,
6874
) {
6975
const loaderContext = this
7076

@@ -77,8 +83,8 @@ export default function loader(
7783
loaderContext.emitError(
7884
new Error(
7985
`vue-loader was used without the corresponding plugin. ` +
80-
`Make sure to include VueLoaderPlugin in your webpack config.`
81-
)
86+
`Make sure to include VueLoaderPlugin in your webpack config.`,
87+
),
8288
)
8389
errorEmitted = true
8490
}
@@ -92,18 +98,23 @@ export default function loader(
9298
rootContext,
9399
resourcePath,
94100
resourceQuery: _resourceQuery = '',
101+
_compiler,
95102
} = loaderContext
96103

104+
const isWebpack5 = testWebpack5(_compiler)
97105
const rawQuery = _resourceQuery.slice(1)
98106
const incomingQuery = qs.parse(rawQuery)
99107
const resourceQuery = rawQuery ? `&${rawQuery}` : ''
100108
const options = (getOptions(loaderContext) || {}) as VueLoaderOptions
109+
const enableInlineMatchResource =
110+
isWebpack5 && Boolean(options.experimentalInlineMatchResource)
101111

102112
const isServer = options.isServerBuild ?? target === 'node'
103113
const isProduction =
104114
mode === 'production' || process.env.NODE_ENV === 'production'
105115

106116
const filename = resourcePath.replace(/\?.*$/, '')
117+
107118
const { descriptor, errors } = parse(source, {
108119
filename,
109120
sourceMap,
@@ -133,7 +144,7 @@ export default function loader(
133144
const id = hash(
134145
isProduction
135146
? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
136-
: shortFilePath
147+
: shortFilePath,
137148
)
138149

139150
// if the query has a type field, this is a language block request
@@ -146,7 +157,7 @@ export default function loader(
146157
options,
147158
loaderContext,
148159
incomingQuery,
149-
!!options.appendExtension
160+
!!options.appendExtension,
150161
)
151162
}
152163

@@ -169,10 +180,23 @@ export default function loader(
169180
if (script || scriptSetup) {
170181
const lang = script?.lang || scriptSetup?.lang
171182
isTS = !!(lang && /tsx?/.test(lang))
183+
const externalQuery = Boolean(script && !scriptSetup && script.src)
184+
? `&external`
185+
: ``
172186
const src = (script && !scriptSetup && script.src) || resourcePath
173187
const attrsQuery = attrsToQuery((scriptSetup || script)!.attrs, 'js')
174-
const query = `?vue&type=script${attrsQuery}${resourceQuery}`
175-
const scriptRequest = stringifyRequest(src + query)
188+
const query = `?vue&type=script${attrsQuery}${resourceQuery}${externalQuery}`
189+
190+
let scriptRequest: string
191+
192+
if (enableInlineMatchResource) {
193+
scriptRequest = stringifyRequest(
194+
genMatchResource(this, src, query, lang || 'js'),
195+
)
196+
} else {
197+
scriptRequest = stringifyRequest(src + query)
198+
}
199+
176200
scriptImport =
177201
`import script from ${scriptRequest}\n` +
178202
// support named exports
@@ -186,13 +210,27 @@ export default function loader(
186210
const useInlineTemplate = canInlineTemplate(descriptor, isProduction)
187211
if (descriptor.template && !useInlineTemplate) {
188212
const src = descriptor.template.src || resourcePath
213+
const externalQuery = Boolean(descriptor.template.src) ? `&external` : ``
189214
const idQuery = `&id=${id}`
190215
const scopedQuery = hasScoped ? `&scoped=true` : ``
191216
const attrsQuery = attrsToQuery(descriptor.template.attrs)
192217
const tsQuery =
193218
options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``
194-
const query = `?vue&type=template${idQuery}${scopedQuery}${tsQuery}${attrsQuery}${resourceQuery}`
195-
templateRequest = stringifyRequest(src + query)
219+
const query = `?vue&type=template${idQuery}${scopedQuery}${tsQuery}${attrsQuery}${resourceQuery}${externalQuery}`
220+
221+
if (enableInlineMatchResource) {
222+
templateRequest = stringifyRequest(
223+
genMatchResource(
224+
this,
225+
src,
226+
query,
227+
options.enableTsInTemplate !== false && isTS ? 'ts' : 'js',
228+
),
229+
)
230+
} else {
231+
templateRequest = stringifyRequest(src + query)
232+
}
233+
196234
templateImport = `import { ${renderFnName} } from ${templateRequest}`
197235
propsToAttach.push([renderFnName, renderFnName])
198236
}
@@ -209,19 +247,29 @@ export default function loader(
209247
.forEach((style, i) => {
210248
const src = style.src || resourcePath
211249
const attrsQuery = attrsToQuery(style.attrs, 'css')
250+
const lang = String(style.attrs.lang || 'css')
212251
// make sure to only pass id when necessary so that we don't inject
213252
// duplicate tags when multiple components import the same css file
214253
const idQuery = !style.src || style.scoped ? `&id=${id}` : ``
215254
const inlineQuery = asCustomElement ? `&inline` : ``
216-
const query = `?vue&type=style&index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}`
217-
const styleRequest = stringifyRequest(src + query)
255+
const externalQuery = Boolean(style.src) ? `&external` : ``
256+
const query = `?vue&type=style&index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}${externalQuery}`
257+
258+
let styleRequest
259+
if (enableInlineMatchResource) {
260+
styleRequest = stringifyRequest(
261+
genMatchResource(this, src, query, lang),
262+
)
263+
} else {
264+
styleRequest = stringifyRequest(src + query)
265+
}
218266

219267
if (style.module) {
220268
if (asCustomElement) {
221269
loaderContext.emitError(
222270
new Error(
223-
`<style module> is not supported in custom element mode.`
224-
)
271+
`<style module> is not supported in custom element mode.`,
272+
),
225273
)
226274
}
227275
if (!hasCSSModules) {
@@ -234,7 +282,7 @@ export default function loader(
234282
i,
235283
styleRequest,
236284
style.module,
237-
needsHotReload
285+
needsHotReload,
238286
)
239287
} else {
240288
if (!isServer) {
@@ -300,9 +348,27 @@ export default function loader(
300348
const issuerQuery = block.attrs.src
301349
? `&issuerPath=${qs.escape(resourcePath)}`
302350
: ''
303-
const query = `?vue&type=custom&index=${i}${blockTypeQuery}${issuerQuery}${attrsQuery}${resourceQuery}`
351+
352+
const externalQuery = Boolean(block.attrs.src) ? `&external` : ``
353+
const query = `?vue&type=custom&index=${i}${blockTypeQuery}${issuerQuery}${attrsQuery}${resourceQuery}${externalQuery}`
354+
355+
let customRequest
356+
357+
if (enableInlineMatchResource) {
358+
customRequest = stringifyRequest(
359+
genMatchResource(
360+
this,
361+
src as string,
362+
query,
363+
block.attrs.lang as string,
364+
),
365+
)
366+
} else {
367+
customRequest = stringifyRequest(src + query)
368+
}
369+
304370
return (
305-
`import block${i} from ${stringifyRequest(src + query)}\n` +
371+
`import block${i} from ${customRequest}\n` +
306372
`if (typeof block${i} === 'function') block${i}(script)`
307373
)
308374
})
@@ -332,7 +398,9 @@ export default function loader(
332398
if (!propsToAttach.length) {
333399
code += `\n\nconst __exports__ = script;`
334400
} else {
335-
code += `\n\nimport exportComponent from ${stringifyRequest(exportHelperPath)}`
401+
code += `\n\nimport exportComponent from ${stringifyRequest(
402+
exportHelperPath,
403+
)}`
336404
code += `\nconst __exports__ = /*#__PURE__*/exportComponent(script, [${propsToAttach
337405
.map(([key, val]) => `['${key}',${val}]`)
338406
.join(',')}])`

Diff for: src/pitcher.ts

+63-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { LoaderDefinitionFunction, LoaderContext } from 'webpack'
22
import * as qs from 'querystring'
3-
import { stringifyRequest } from './util'
3+
import { getOptions, stringifyRequest, testWebpack5 } from './util'
44
import { VueLoaderOptions } from '.'
55

66
const selfPath = require.resolve('./index')
@@ -58,7 +58,40 @@ export const pitch = function () {
5858
})
5959

6060
// Inject style-post-loader before css-loader for scoped CSS and trimming
61+
const isWebpack5 = testWebpack5(context._compiler)
62+
const options = (getOptions(context) || {}) as VueLoaderOptions
6163
if (query.type === `style`) {
64+
if (isWebpack5 && context._compiler?.options.experiments.css) {
65+
// If user enables `experiments.css`, then we are trying to emit css code directly.
66+
// Although we can target requests like `xxx.vue?type=style` to match `type: "css"`,
67+
// it will make the plugin a mess.
68+
if (!options.experimentalInlineMatchResource) {
69+
context.emitError(
70+
new Error(
71+
'`experimentalInlineMatchResource` should be enabled if `experiments.css` enabled currently',
72+
),
73+
)
74+
return ''
75+
}
76+
77+
if (query.inline || query.module) {
78+
context.emitError(
79+
new Error(
80+
'`inline` or `module` is currently not supported with `experiments.css` enabled',
81+
),
82+
)
83+
return ''
84+
}
85+
86+
const loaderString = [stylePostLoaderPath, ...loaders]
87+
.map((loader) => {
88+
return typeof loader === 'string' ? loader : loader.request
89+
})
90+
.join('!')
91+
return `@import "${context.resourcePath}${
92+
query.lang ? `.${query.lang}` : ''
93+
}${context.resourceQuery}!=!-!${loaderString}!${context.resource}";`
94+
}
6295
const cssLoaderIndex = loaders.findIndex(isCSSLoader)
6396
if (cssLoaderIndex > -1) {
6497
// if inlined, ignore any loaders after css-loader and replace w/ inline
@@ -71,7 +104,8 @@ export const pitch = function () {
71104
return genProxyModule(
72105
[...afterLoaders, stylePostLoaderPath, ...beforeLoaders],
73106
context,
74-
!!query.module || query.inline != null
107+
!!query.module || query.inline != null,
108+
(query.lang as string) || 'css',
75109
)
76110
}
77111
}
@@ -84,15 +118,21 @@ export const pitch = function () {
84118

85119
// Rewrite request. Technically this should only be done when we have deduped
86120
// loaders. But somehow this is required for block source maps to work.
87-
return genProxyModule(loaders, context, query.type !== 'template')
121+
return genProxyModule(
122+
loaders,
123+
context,
124+
query.type !== 'template',
125+
query.ts ? 'ts' : (query.lang as string),
126+
)
88127
}
89128

90129
function genProxyModule(
91130
loaders: (Loader | string)[],
92131
context: LoaderContext<VueLoaderOptions>,
93-
exportDefault = true
132+
exportDefault = true,
133+
lang = 'js',
94134
) {
95-
const request = genRequest(loaders, context)
135+
const request = genRequest(loaders, lang, context)
96136
// return a proxy module which simply re-exports everything from the
97137
// actual request. Note for template blocks the compiled module has no
98138
// default export.
@@ -104,15 +144,31 @@ function genProxyModule(
104144

105145
function genRequest(
106146
loaders: (Loader | string)[],
107-
context: LoaderContext<VueLoaderOptions>
147+
lang: string,
148+
context: LoaderContext<VueLoaderOptions>,
108149
) {
150+
const isWebpack5 = testWebpack5(context._compiler)
151+
const options = (getOptions(context) || {}) as VueLoaderOptions
152+
const enableInlineMatchResource =
153+
isWebpack5 && options.experimentalInlineMatchResource
154+
109155
const loaderStrings = loaders.map((loader) => {
110156
return typeof loader === 'string' ? loader : loader.request
111157
})
112158
const resource = context.resourcePath + context.resourceQuery
159+
160+
if (enableInlineMatchResource) {
161+
return stringifyRequest(
162+
context,
163+
`${context.resourcePath}${lang ? `.${lang}` : ''}${
164+
context.resourceQuery
165+
}!=!-!${[...loaderStrings, resource].join('!')}`,
166+
)
167+
}
168+
113169
return stringifyRequest(
114170
context,
115-
'-!' + [...loaderStrings, resource].join('!')
171+
'-!' + [...loaderStrings, resource].join('!'),
116172
)
117173
}
118174

0 commit comments

Comments
 (0)