Skip to content

Commit 0b7c1a9

Browse files
author
ivanliu
committed
feat: rewrite VueLoaderPlugin to support webpack5
1 parent d0ccd17 commit 0b7c1a9

File tree

1 file changed

+111
-78
lines changed

1 file changed

+111
-78
lines changed

lib/plugin.js

Lines changed: 111 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,79 @@
11
const qs = require('querystring')
2-
const RuleSet = require('webpack/lib/RuleSet')
3-
42
const id = 'vue-loader-plugin'
53
const NS = 'vue-loader'
4+
const BasicEffectRulePlugin = require('webpack/lib/rules/BasicEffectRulePlugin')
5+
const BasicMatcherRulePlugin = require('webpack/lib/rules/BasicMatcherRulePlugin')
6+
const RuleSetCompiler = require('webpack/lib/rules/RuleSetCompiler')
7+
const UseEffectRulePlugin = require('webpack/lib/rules/UseEffectRulePlugin')
8+
9+
const ruleSetCompiler = new RuleSetCompiler([
10+
new BasicMatcherRulePlugin('test', 'resource'),
11+
new BasicMatcherRulePlugin('include', 'resource'),
12+
new BasicMatcherRulePlugin('exclude', 'resource', true),
13+
new BasicMatcherRulePlugin('resource'),
14+
new BasicMatcherRulePlugin('conditions'),
15+
new BasicMatcherRulePlugin('resourceQuery'),
16+
new BasicMatcherRulePlugin('realResource'),
17+
new BasicMatcherRulePlugin('issuer'),
18+
new BasicMatcherRulePlugin('compiler'),
19+
new BasicEffectRulePlugin('type'),
20+
new BasicEffectRulePlugin('sideEffects'),
21+
new BasicEffectRulePlugin('parser'),
22+
new BasicEffectRulePlugin('resolve'),
23+
new UseEffectRulePlugin()
24+
])
625

726
class VueLoaderPlugin {
827
apply (compiler) {
928
// add NS marker so that the loader can detect and report missing plugin
10-
if (compiler.hooks) {
11-
// webpack 4
12-
compiler.hooks.compilation.tap(id, compilation => {
13-
let normalModuleLoader
14-
if (Object.isFrozen(compilation.hooks)) {
15-
// webpack 5
16-
normalModuleLoader = require('webpack/lib/NormalModule').getCompilationHooks(compilation).loader
17-
} else {
18-
normalModuleLoader = compilation.hooks.normalModuleLoader
19-
}
20-
normalModuleLoader.tap(id, loaderContext => {
21-
loaderContext[NS] = true
22-
})
23-
})
24-
} else {
25-
// webpack < 4
26-
compiler.plugin('compilation', compilation => {
27-
compilation.plugin('normal-module-loader', loaderContext => {
28-
loaderContext[NS] = true
29-
})
29+
compiler.hooks.compilation.tap(id, compilation => {
30+
const normalModuleLoader = require('webpack/lib/NormalModule').getCompilationHooks(compilation).loader
31+
normalModuleLoader.tap(id, loaderContext => {
32+
loaderContext[NS] = true
3033
})
31-
}
34+
})
3235

33-
// use webpack's RuleSet utility to normalize user rules
34-
const rawRules = compiler.options.module.rules
35-
const { rules } = new RuleSet(rawRules)
36+
const rules = compiler.options.module.rules
37+
let rawVueRules
38+
let vueRules = []
3639

37-
// find the rule that applies to vue files
38-
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
39-
if (vueRuleIndex < 0) {
40-
vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
41-
}
42-
const vueRule = rules[vueRuleIndex]
40+
for (const rawRule of rules) {
41+
// skip the `include` check when locating the vue rule
42+
const clonedRawRule = Object.assign({}, rawRule)
43+
delete clonedRawRule.include
44+
45+
const ruleSet = ruleSetCompiler.compile([{
46+
rules: [clonedRawRule]
47+
}])
48+
vueRules = ruleSet.exec({
49+
resource: 'foo.vue'
50+
})
4351

44-
if (!vueRule) {
52+
if (!vueRules.length) {
53+
vueRules = ruleSet.exec({
54+
resource: 'foo.vue.html'
55+
})
56+
}
57+
if (vueRules.length > 0) {
58+
if (rawRule.oneOf) {
59+
throw new Error(
60+
`[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
61+
)
62+
}
63+
rawVueRules = rawRule
64+
break
65+
}
66+
}
67+
if (!vueRules.length) {
4568
throw new Error(
4669
`[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
4770
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
4871
)
4972
}
5073

51-
if (vueRule.oneOf) {
52-
throw new Error(
53-
`[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
54-
)
55-
}
56-
5774
// get the normlized "use" for vue files
58-
const vueUse = vueRule.use
75+
const vueUse = vueRules.filter(rule => rule.type === 'use').map(rule => rule.value)
76+
5977
// get vue-loader options
6078
const vueLoaderUseIndex = vueUse.findIndex(u => {
6179
return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
@@ -77,14 +95,20 @@ class VueLoaderPlugin {
7795

7896
// for each user rule (expect the vue rule), create a cloned rule
7997
// that targets the corresponding language blocks in *.vue files.
98+
const refs = new Map()
8099
const clonedRules = rules
81-
.filter(r => r !== vueRule)
82-
.map(cloneRule)
100+
.filter(r => r !== rawVueRules)
101+
.map((rawRule) => cloneRule(rawRule, ruleSetCompiler, refs))
102+
103+
// fix conflict with config.loader and config.options when using config.use
104+
delete rawVueRules.loader;
105+
delete rawVueRules.options;
106+
rawVueRules.use = vueUse;
83107

84108
// global pitcher (responsible for injecting template compiler loader & CSS
85109
// post loader)
86110
const pitcher = {
87-
loader: require.resolve('./loaders/pitcher'),
111+
loader: require.resolve('vue-loader/lib/loaders/pitcher'),
88112
resourceQuery: query => {
89113
const parsed = qs.parse(query.slice(1))
90114
return parsed.vue != null
@@ -104,56 +128,65 @@ class VueLoaderPlugin {
104128
}
105129
}
106130

107-
function createMatcher (fakeFile) {
108-
return (rule, i) => {
109-
// #1201 we need to skip the `include` check when locating the vue rule
110-
const clone = Object.assign({}, rule)
111-
delete clone.include
112-
const normalized = RuleSet.normalizeRule(clone, {}, '')
113-
return (
114-
!rule.enforce &&
115-
normalized.resource &&
116-
normalized.resource(fakeFile)
117-
)
118-
}
119-
}
120-
121-
function cloneRule (rule) {
122-
const { resource, resourceQuery } = rule
123-
// Assuming `test` and `resourceQuery` tests are executed in series and
124-
// synchronously (which is true based on RuleSet's implementation), we can
125-
// save the current resource being matched from `test` so that we can access
126-
// it in `resourceQuery`. This ensures when we use the normalized rule's
127-
// resource check, include/exclude are matched correctly.
131+
function cloneRule (rawRule, ruleSetCompiler, refs) {
132+
const rules = ruleSetCompiler.compileRules("ruleSet", [{
133+
rules: [rawRule]
134+
}], refs)
128135
let currentResource
129-
const res = Object.assign({}, rule, {
130-
resource: {
131-
test: resource => {
132-
currentResource = resource
133-
return true
134-
}
136+
137+
const conditions = rules[0].rules
138+
.map(rule => rule.conditions)
139+
.flat();
140+
141+
// do not process rule with enforce
142+
if (!rawRule.enforce) {
143+
const ruleUse = rules[0].rules
144+
.map(rule => rule.effects
145+
.filter(effect => effect.type === 'use')
146+
.map(effect => effect.value)
147+
)
148+
.flat()
149+
150+
// fix conflict with config.loader and config.options when using config.use
151+
delete rawRule.loader
152+
delete rawRule.options
153+
rawRule.use = ruleUse
154+
}
155+
156+
// fix conflict with config.loader and config.options when using config.use
157+
delete rawRule.loader;
158+
delete rawRule.options;
159+
rawRule.use = ruleUse;
160+
161+
const res = Object.assign({}, rawRule, {
162+
resource: resources => {
163+
currentResource = resources
164+
return true
135165
},
136166
resourceQuery: query => {
137167
const parsed = qs.parse(query.slice(1))
138168
if (parsed.vue == null) {
139169
return false
140170
}
141-
if (resource && parsed.lang == null) {
171+
if (!conditions) {
142172
return false
143173
}
144174
const fakeResourcePath = `${currentResource}.${parsed.lang}`
145-
if (resource && !resource(fakeResourcePath)) {
146-
return false
147-
}
148-
if (resourceQuery && !resourceQuery(query)) {
149-
return false
175+
for (const condition of conditions) {
176+
// add support for resourceQuery
177+
const request = condition.property === 'resourceQuery' ? query : fakeResourcePath;
178+
if (condition && !condition.fn(request)) {
179+
return false
180+
}
150181
}
151182
return true
152183
}
153184
})
154185

155-
if (rule.oneOf) {
156-
res.oneOf = rule.oneOf.map(cloneRule)
186+
delete res.test
187+
188+
if (rawRule.oneOf) {
189+
res.oneOf = rawRule.oneOf.map(rule => cloneRule(rule, ruleSetCompiler, refs))
157190
}
158191

159192
return res

0 commit comments

Comments
 (0)