Skip to content

Commit fc9b73d

Browse files
committed
feat: init
feat: finish inline match resource chore: cleanup chore: cleanup feat: add experimental css support feat: dispatch plugin dynammically feat: support option `experimentalInlineMatchResource` chore: enable test on CI chore: disable sourceMap generation ] docs: add option doc docs: correct version fix: fix lang matching feat: use a relatively better version test fix: fix incorrect match with `experiments.css` enabled fix: more accurate when matching styles feat: optimize for `Rule.loader` fix: import
1 parent 6ad8056 commit fc9b73d

15 files changed

+337
-71
lines changed

.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

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.1+ 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).

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',

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"build": "tsc",
1515
"pretest": "tsc",
1616
"test": "jest",
17+
"test:match-resource": "INLINE_MATCH_RESOURCE=true jest",
1718
"pretest:webpack4": "tsc",
1819
"test:webpack4": "WEBPACK4=true jest",
1920
"dev-example": "node example/devServer.js --config example/webpack.config.js --inline --hot",

src/index.ts

+76-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import { formatError } from './formatError'
2020
import VueLoaderPlugin from './plugin'
2121
import { canInlineTemplate } from './resolveScript'
2222
import { setDescriptor } from './descriptorCache'
23-
import { getOptions, stringifyRequest as _stringifyRequest } from './util'
23+
import {
24+
getOptions,
25+
stringifyRequest as _stringifyRequest,
26+
genMatchResource,
27+
testWebpack5,
28+
} from './util'
2429

2530
export { VueLoaderPlugin }
2631

@@ -51,6 +56,7 @@ export interface VueLoaderOptions {
5156
exposeFilename?: boolean
5257
appendExtension?: boolean
5358
enableTsInTemplate?: boolean
59+
experimentalInlineMatchResource?: boolean
5460

5561
isServerBuild?: boolean
5662
}
@@ -90,18 +96,23 @@ export default function loader(
9096
rootContext,
9197
resourcePath,
9298
resourceQuery: _resourceQuery = '',
99+
_compiler,
93100
} = loaderContext
94101

102+
const isWebpack5 = testWebpack5(_compiler)
95103
const rawQuery = _resourceQuery.slice(1)
96104
const incomingQuery = qs.parse(rawQuery)
97105
const resourceQuery = rawQuery ? `&${rawQuery}` : ''
98106
const options = (getOptions(loaderContext) || {}) as VueLoaderOptions
107+
const enableInlineMatchResource =
108+
isWebpack5 && Boolean(options.experimentalInlineMatchResource)
99109

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

104114
const filename = resourcePath.replace(/\?.*$/, '')
115+
105116
const { descriptor, errors } = parse(source, {
106117
filename,
107118
sourceMap,
@@ -167,10 +178,23 @@ export default function loader(
167178
if (script || scriptSetup) {
168179
const lang = script?.lang || scriptSetup?.lang
169180
isTS = !!(lang && /tsx?/.test(lang))
181+
const externalQuery = Boolean(script && !scriptSetup && script.src)
182+
? `&external`
183+
: ``
170184
const src = (script && !scriptSetup && script.src) || resourcePath
171185
const attrsQuery = attrsToQuery((scriptSetup || script)!.attrs, 'js')
172-
const query = `?vue&type=script${attrsQuery}${resourceQuery}`
173-
const scriptRequest = stringifyRequest(src + query)
186+
const query = `?vue&type=script${attrsQuery}${resourceQuery}${externalQuery}`
187+
188+
let scriptRequest: string
189+
190+
if (enableInlineMatchResource) {
191+
scriptRequest = stringifyRequest(
192+
genMatchResource(this, src, query, lang || 'js')
193+
)
194+
} else {
195+
scriptRequest = stringifyRequest(src + query)
196+
}
197+
174198
scriptImport =
175199
`import script from ${scriptRequest}\n` +
176200
// support named exports
@@ -184,13 +208,27 @@ export default function loader(
184208
const useInlineTemplate = canInlineTemplate(descriptor, isProduction)
185209
if (descriptor.template && !useInlineTemplate) {
186210
const src = descriptor.template.src || resourcePath
211+
const externalQuery = Boolean(descriptor.template.src) ? `&external` : ``
187212
const idQuery = `&id=${id}`
188213
const scopedQuery = hasScoped ? `&scoped=true` : ``
189214
const attrsQuery = attrsToQuery(descriptor.template.attrs)
190215
const tsQuery =
191216
options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``
192-
const query = `?vue&type=template${idQuery}${scopedQuery}${tsQuery}${attrsQuery}${resourceQuery}`
193-
templateRequest = stringifyRequest(src + query)
217+
const query = `?vue&type=template${idQuery}${scopedQuery}${tsQuery}${attrsQuery}${resourceQuery}${externalQuery}`
218+
219+
if (enableInlineMatchResource) {
220+
templateRequest = stringifyRequest(
221+
genMatchResource(
222+
this,
223+
src,
224+
query,
225+
options.enableTsInTemplate !== false && isTS ? 'ts' : 'js'
226+
)
227+
)
228+
} else {
229+
templateRequest = stringifyRequest(src + query)
230+
}
231+
194232
templateImport = `import { ${renderFnName} } from ${templateRequest}`
195233
propsToAttach.push([renderFnName, renderFnName])
196234
}
@@ -205,12 +243,23 @@ export default function loader(
205243
.forEach((style, i) => {
206244
const src = style.src || resourcePath
207245
const attrsQuery = attrsToQuery(style.attrs, 'css')
246+
const lang = String(style.attrs.lang || 'css')
208247
// make sure to only pass id when necessary so that we don't inject
209248
// duplicate tags when multiple components import the same css file
210249
const idQuery = !style.src || style.scoped ? `&id=${id}` : ``
211250
const inlineQuery = asCustomElement ? `&inline` : ``
212-
const query = `?vue&type=style&index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}`
213-
const styleRequest = stringifyRequest(src + query)
251+
const externalQuery = Boolean(style.src) ? `&external` : ``
252+
const query = `?vue&type=style&index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}${externalQuery}`
253+
254+
let styleRequest
255+
if (enableInlineMatchResource) {
256+
styleRequest = stringifyRequest(
257+
genMatchResource(this, src, query, lang)
258+
)
259+
} else {
260+
styleRequest = stringifyRequest(src + query)
261+
}
262+
214263
if (style.module) {
215264
if (asCustomElement) {
216265
loaderContext.emitError(
@@ -283,9 +332,27 @@ export default function loader(
283332
const issuerQuery = block.attrs.src
284333
? `&issuerPath=${qs.escape(resourcePath)}`
285334
: ''
286-
const query = `?vue&type=custom&index=${i}${blockTypeQuery}${issuerQuery}${attrsQuery}${resourceQuery}`
335+
336+
const externalQuery = Boolean(block.attrs.src) ? `&external` : ``
337+
const query = `?vue&type=custom&index=${i}${blockTypeQuery}${issuerQuery}${attrsQuery}${resourceQuery}${externalQuery}`
338+
339+
let customRequest
340+
341+
if (enableInlineMatchResource) {
342+
customRequest = stringifyRequest(
343+
genMatchResource(
344+
this,
345+
src as string,
346+
query,
347+
block.attrs.lang as string
348+
)
349+
)
350+
} else {
351+
customRequest = stringifyRequest(src + query)
352+
}
353+
287354
return (
288-
`import block${i} from ${stringifyRequest(src + query)}\n` +
355+
`import block${i} from ${customRequest}\n` +
289356
`if (typeof block${i} === 'function') block${i}(script)`
290357
)
291358
})

src/pitcher.ts

+61-5
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,12 +144,28 @@ function genProxyModule(
104144

105145
function genRequest(
106146
loaders: (Loader | string)[],
147+
lang: string,
107148
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,
115171
'-!' + [...loaderStrings, resource].join('!')

src/plugin.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1-
import webpack from 'webpack'
21
import type { Compiler } from 'webpack'
2+
import { testWebpack5 } from './util'
33

44
declare class VueLoaderPlugin {
55
static NS: string
66
apply(compiler: Compiler): void
77
}
88

9-
let Plugin: typeof VueLoaderPlugin
9+
const NS = 'vue-loader'
1010

11-
if (webpack.version && webpack.version[0] > '4') {
12-
// webpack5 and upper
13-
Plugin = require('./pluginWebpack5').default
14-
} else {
15-
// webpack4 and lower
16-
Plugin = require('./pluginWebpack4').default
11+
class Plugin {
12+
static NS = NS
13+
apply(compiler: Compiler) {
14+
let Ctor: typeof VueLoaderPlugin
15+
if (testWebpack5(compiler)) {
16+
// webpack5 and upper
17+
Ctor = require('./pluginWebpack5').default
18+
} else {
19+
// webpack4 and lower
20+
Ctor = require('./pluginWebpack4').default
21+
}
22+
new Ctor().apply(compiler)
23+
}
1724
}
1825

1926
export default Plugin

0 commit comments

Comments
 (0)