1 | const qs = require('querystring')
|
2 | const id = 'vue-loader-plugin'
|
3 | const NS = 'vue-loader'
|
4 | const BasicEffectRulePlugin = require('webpack/lib/rules/BasicEffectRulePlugin')
|
5 | const BasicMatcherRulePlugin = require('webpack/lib/rules/BasicMatcherRulePlugin')
|
6 | const DescriptionDataMatcherRulePlugin = require('webpack/lib/rules/DescriptionDataMatcherRulePlugin')
|
7 | const RuleSetCompiler = require('webpack/lib/rules/RuleSetCompiler')
|
8 | const UseEffectRulePlugin = require('webpack/lib/rules/UseEffectRulePlugin')
|
9 |
|
10 | const 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 |
|
32 | class VueLoaderPlugin {
|
33 | apply (compiler) {
|
34 | const normalModule = compiler.webpack
|
35 | ? compiler.webpack.NormalModule
|
36 | : require('webpack/lib/NormalModule')
|
37 |
|
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 |
|
51 | if (rawRule.enforce) {
|
52 | continue
|
53 | }
|
54 |
|
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 |
|
88 | const vueUse = vueRules.filter(rule => rule.type === 'use').map(rule => rule.value)
|
89 |
|
90 |
|
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 |
|
103 |
|
104 |
|
105 | const vueLoaderUse = vueUse[vueLoaderUseIndex]
|
106 | vueLoaderUse.ident = 'vue-loader-options'
|
107 | vueLoaderUse.options = vueLoaderUse.options || {}
|
108 |
|
109 |
|
110 |
|
111 | const refs = new Map()
|
112 | const clonedRules = rules
|
113 | .filter(r => r !== rawVueRules)
|
114 | .map((rawRule) => cloneRule(rawRule, refs))
|
115 |
|
116 |
|
117 | delete rawVueRules.loader
|
118 | delete rawVueRules.options
|
119 | rawVueRules.use = vueUse
|
120 |
|
121 |
|
122 |
|
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 |
|
137 | compiler.options.module.rules = [
|
138 | pitcher,
|
139 | ...clonedRules,
|
140 | ...rules
|
141 | ]
|
142 | }
|
143 | }
|
144 |
|
145 | let uid = 0
|
146 | function 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 |
|
155 | .reduce((prev, next) => prev.concat(next), [])
|
156 |
|
157 |
|
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 |
|
165 | .reduce((prev, next) => prev.concat(next), [])
|
166 |
|
167 |
|
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 |
|
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 |
|
212 | VueLoaderPlugin.NS = NS
|
213 | module.exports = VueLoaderPlugin
|