1
1
const qs = require ( 'querystring' )
2
- const RuleSet = require ( 'webpack/lib/RuleSet' )
3
-
4
2
const id = 'vue-loader-plugin'
5
3
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
+ ] )
6
25
7
26
class VueLoaderPlugin {
8
27
apply ( compiler ) {
9
28
// 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
30
33
} )
31
- }
34
+ } )
32
35
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 = [ ]
36
39
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
+ } )
43
51
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 ) {
45
68
throw new Error (
46
69
`[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
47
70
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
48
71
)
49
72
}
50
73
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
-
57
74
// 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
+
59
77
// get vue-loader options
60
78
const vueLoaderUseIndex = vueUse . findIndex ( u => {
61
79
return / ^ v u e - l o a d e r | ( \/ | \\ | @ ) v u e - l o a d e r / . test ( u . loader )
@@ -77,14 +95,20 @@ class VueLoaderPlugin {
77
95
78
96
// for each user rule (expect the vue rule), create a cloned rule
79
97
// that targets the corresponding language blocks in *.vue files.
98
+ const refs = new Map ( )
80
99
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 ;
83
107
84
108
// global pitcher (responsible for injecting template compiler loader & CSS
85
109
// post loader)
86
110
const pitcher = {
87
- loader : require . resolve ( '. /loaders/pitcher' ) ,
111
+ loader : require . resolve ( 'vue-loader/lib /loaders/pitcher' ) ,
88
112
resourceQuery : query => {
89
113
const parsed = qs . parse ( query . slice ( 1 ) )
90
114
return parsed . vue != null
@@ -104,56 +128,65 @@ class VueLoaderPlugin {
104
128
}
105
129
}
106
130
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 )
128
135
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
135
165
} ,
136
166
resourceQuery : query => {
137
167
const parsed = qs . parse ( query . slice ( 1 ) )
138
168
if ( parsed . vue == null ) {
139
169
return false
140
170
}
141
- if ( resource && parsed . lang == null ) {
171
+ if ( ! conditions ) {
142
172
return false
143
173
}
144
174
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
+ }
150
181
}
151
182
return true
152
183
}
153
184
} )
154
185
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 ) )
157
190
}
158
191
159
192
return res
0 commit comments