UNPKG

6.79 kBJavaScriptView Raw
1const qs = require('querystring')
2const id = 'vue-loader-plugin'
3const NS = 'vue-loader'
4const BasicEffectRulePlugin = require('webpack/lib/rules/BasicEffectRulePlugin')
5const BasicMatcherRulePlugin = require('webpack/lib/rules/BasicMatcherRulePlugin')
6const DescriptionDataMatcherRulePlugin = require('webpack/lib/rules/DescriptionDataMatcherRulePlugin')
7const RuleSetCompiler = require('webpack/lib/rules/RuleSetCompiler')
8const UseEffectRulePlugin = require('webpack/lib/rules/UseEffectRulePlugin')
9
10const ruleSetCompiler = new RuleSetCompiler([
11 new BasicMatcherRulePlugin('test', 'resource'),
12 new BasicMatcherRulePlugin('mimetype'),
13 new BasicMatcherRulePlugin('dependency'),
14 new BasicMatcherRulePlugin('include', 'resource'),
15 new BasicMatcherRulePlugin('exclude', 'resource', true),
16 new BasicMatcherRulePlugin('conditions'),
17 new BasicMatcherRulePlugin('resource'),
18 new BasicMatcherRulePlugin('resourceQuery'),
19 new BasicMatcherRulePlugin('resourceFragment'),
20 new BasicMatcherRulePlugin('realResource'),
21 new BasicMatcherRulePlugin('issuer'),
22 new BasicMatcherRulePlugin('compiler'),
23 new DescriptionDataMatcherRulePlugin(),
24 new BasicEffectRulePlugin('type'),
25 new BasicEffectRulePlugin('sideEffects'),
26 new BasicEffectRulePlugin('parser'),
27 new BasicEffectRulePlugin('resolve'),
28 new BasicEffectRulePlugin('generator'),
29 new UseEffectRulePlugin()
30])
31
32class VueLoaderPlugin {
33 apply (compiler) {
34 const normalModule = compiler.webpack
35 ? compiler.webpack.NormalModule
36 : require('webpack/lib/NormalModule')
37 // add NS marker so that the loader can detect and report missing plugin
38 compiler.hooks.compilation.tap(id, compilation => {
39 const normalModuleLoader = normalModule.getCompilationHooks(compilation).loader
40 normalModuleLoader.tap(id, loaderContext => {
41 loaderContext[NS] = true
42 })
43 })
44
45 const rules = compiler.options.module.rules
46 let rawVueRules
47 let vueRules = []
48
49 for (const rawRule of rules) {
50 // skip rules with 'enforce'. eg. rule for eslint-loader
51 if (rawRule.enforce) {
52 continue
53 }
54 // skip the `include` check when locating the vue rule
55 const clonedRawRule = Object.assign({}, rawRule)
56 delete clonedRawRule.include
57
58 const ruleSet = ruleSetCompiler.compile([{
59 rules: [clonedRawRule]
60 }])
61 vueRules = ruleSet.exec({
62 resource: 'foo.vue'
63 })
64
65 if (!vueRules.length) {
66 vueRules = ruleSet.exec({
67 resource: 'foo.vue.html'
68 })
69 }
70 if (vueRules.length > 0) {
71 if (rawRule.oneOf) {
72 throw new Error(
73 `[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
74 )
75 }
76 rawVueRules = rawRule
77 break
78 }
79 }
80 if (!vueRules.length) {
81 throw new Error(
82 `[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
83 `Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
84 )
85 }
86
87 // get the normlized "use" for vue files
88 const vueUse = vueRules.filter(rule => rule.type === 'use').map(rule => rule.value)
89
90 // get vue-loader options
91 const vueLoaderUseIndex = vueUse.findIndex(u => {
92 return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
93 })
94
95 if (vueLoaderUseIndex < 0) {
96 throw new Error(
97 `[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
98 `Make sure the rule matching .vue files include vue-loader in its use.`
99 )
100 }
101
102 // make sure vue-loader options has a known ident so that we can share
103 // options by reference in the template-loader by using a ref query like
104 // template-loader??vue-loader-options
105 const vueLoaderUse = vueUse[vueLoaderUseIndex]
106 vueLoaderUse.ident = 'vue-loader-options'
107 vueLoaderUse.options = vueLoaderUse.options || {}
108
109 // for each user rule (expect the vue rule), create a cloned rule
110 // that targets the corresponding language blocks in *.vue files.
111 const refs = new Map()
112 const clonedRules = rules
113 .filter(r => r !== rawVueRules)
114 .map((rawRule) => cloneRule(rawRule, refs))
115
116 // fix conflict with config.loader and config.options when using config.use
117 delete rawVueRules.loader
118 delete rawVueRules.options
119 rawVueRules.use = vueUse
120
121 // global pitcher (responsible for injecting template compiler loader & CSS
122 // post loader)
123 const pitcher = {
124 loader: require.resolve('./loaders/pitcher'),
125 resourceQuery: query => {
126 if (!query) { return false }
127 const parsed = qs.parse(query.slice(1))
128 return parsed.vue != null
129 },
130 options: {
131 cacheDirectory: vueLoaderUse.options.cacheDirectory,
132 cacheIdentifier: vueLoaderUse.options.cacheIdentifier
133 }
134 }
135
136 // replace original rules
137 compiler.options.module.rules = [
138 pitcher,
139 ...clonedRules,
140 ...rules
141 ]
142 }
143}
144
145let uid = 0
146function cloneRule (rawRule, refs) {
147 const rules = ruleSetCompiler.compileRules(`clonedRuleSet-${++uid}`, [{
148 rules: [rawRule]
149 }], refs)
150 let currentResource
151
152 const conditions = rules[0].rules
153 .map(rule => rule.conditions)
154 // shallow flat
155 .reduce((prev, next) => prev.concat(next), [])
156
157 // do not process rule with enforce
158 if (!rawRule.enforce) {
159 const ruleUse = rules[0].rules
160 .map(rule => rule.effects
161 .filter(effect => effect.type === 'use')
162 .map(effect => effect.value)
163 )
164 // shallow flat
165 .reduce((prev, next) => prev.concat(next), [])
166
167 // fix conflict with config.loader and config.options when using config.use
168 delete rawRule.loader
169 delete rawRule.options
170 rawRule.use = ruleUse
171 }
172
173 const res = Object.assign({}, rawRule, {
174 resource: resources => {
175 currentResource = resources
176 return true
177 },
178 resourceQuery: query => {
179 if (!query) { return false }
180 const parsed = qs.parse(query.slice(1))
181 if (parsed.vue == null) {
182 return false
183 }
184 if (!conditions) {
185 return false
186 }
187 const fakeResourcePath = `${currentResource}.${parsed.lang}`
188 for (const condition of conditions) {
189 // add support for resourceQuery
190 const request = condition.property === 'resourceQuery' ? query : fakeResourcePath
191 if (condition && !condition.fn(request)) {
192 return false
193 }
194 }
195 return true
196 }
197 })
198
199 delete res.test
200
201 if (rawRule.rules) {
202 res.rules = rawRule.rules.map(rule => cloneRule(rule, refs))
203 }
204
205 if (rawRule.oneOf) {
206 res.oneOf = rawRule.oneOf.map(rule => cloneRule(rule, refs))
207 }
208
209 return res
210}
211
212VueLoaderPlugin.NS = NS
213module.exports = VueLoaderPlugin