UNPKG

4.74 kBJavaScriptView Raw
1const qs = require('querystring')
2const RuleSet = require('webpack/lib/RuleSet')
3
4const id = 'vue-loader-plugin'
5const NS = 'vue-loader'
6
7class VueLoaderPlugin {
8 apply (compiler) {
9 // 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 compilation.hooks.normalModuleLoader.tap(id, loaderContext => {
14 loaderContext[NS] = true
15 })
16 })
17 } else {
18 // webpack < 4
19 compiler.plugin('compilation', compilation => {
20 compilation.plugin('normal-module-loader', loaderContext => {
21 loaderContext[NS] = true
22 })
23 })
24 }
25
26 // use webpack's RuleSet utility to normalize user rules
27 const rawRules = compiler.options.module.rules
28 const { rules } = new RuleSet(rawRules)
29
30 // find the rule that applies to vue files
31 let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
32 if (vueRuleIndex < 0) {
33 vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
34 }
35 const vueRule = rules[vueRuleIndex]
36
37 if (!vueRule) {
38 throw new Error(
39 `[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
40 `Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
41 )
42 }
43
44 if (vueRule.oneOf) {
45 throw new Error(
46 `[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
47 )
48 }
49
50 // get the normlized "use" for vue files
51 const vueUse = vueRule.use
52 // get vue-loader options
53 const vueLoaderUseIndex = vueUse.findIndex(u => {
54 return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
55 })
56
57 if (vueLoaderUseIndex < 0) {
58 throw new Error(
59 `[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
60 `Make sure the rule matching .vue files include vue-loader in its use.`
61 )
62 }
63
64 // make sure vue-loader options has a known ident so that we can share
65 // options by reference in the template-loader by using a ref query like
66 // template-loader??vue-loader-options
67 const vueLoaderUse = vueUse[vueLoaderUseIndex]
68 vueLoaderUse.ident = 'vue-loader-options'
69 vueLoaderUse.options = vueLoaderUse.options || {}
70
71 // for each user rule (expect the vue rule), create a cloned rule
72 // that targets the corresponding language blocks in *.vue files.
73 const clonedRules = rules
74 .filter(r => r !== vueRule)
75 .map(cloneRule)
76
77 // global pitcher (responsible for injecting template compiler loader & CSS
78 // post loader)
79 const pitcher = {
80 loader: require.resolve('./loaders/pitcher'),
81 resourceQuery: query => {
82 const parsed = qs.parse(query.slice(1))
83 return parsed.vue != null
84 },
85 options: {
86 cacheDirectory: vueLoaderUse.options.cacheDirectory,
87 cacheIdentifier: vueLoaderUse.options.cacheIdentifier
88 }
89 }
90
91 // replace original rules
92 compiler.options.module.rules = [
93 pitcher,
94 ...clonedRules,
95 ...rules
96 ]
97 }
98}
99
100function createMatcher (fakeFile) {
101 return (rule, i) => {
102 // #1201 we need to skip the `include` check when locating the vue rule
103 const clone = Object.assign({}, rule)
104 delete clone.include
105 const normalized = RuleSet.normalizeRule(clone, {}, '')
106 return (
107 !rule.enforce &&
108 normalized.resource &&
109 normalized.resource(fakeFile)
110 )
111 }
112}
113
114function cloneRule (rule) {
115 const { resource, resourceQuery } = rule
116 // Assuming `test` and `resourceQuery` tests are executed in series and
117 // synchronously (which is true based on RuleSet's implementation), we can
118 // save the current resource being matched from `test` so that we can access
119 // it in `resourceQuery`. This ensures when we use the normalized rule's
120 // resource check, include/exclude are matched correctly.
121 let currentResource
122 const res = Object.assign({}, rule, {
123 resource: {
124 test: resource => {
125 currentResource = resource
126 return true
127 }
128 },
129 resourceQuery: query => {
130 const parsed = qs.parse(query.slice(1))
131 if (parsed.vue == null) {
132 return false
133 }
134 if (resource && parsed.lang == null) {
135 return false
136 }
137 const fakeResourcePath = `${currentResource}.${parsed.lang}`
138 if (resource && !resource(fakeResourcePath)) {
139 return false
140 }
141 if (resourceQuery && !resourceQuery(query)) {
142 return false
143 }
144 return true
145 }
146 })
147
148 if (rule.oneOf) {
149 res.oneOf = rule.oneOf.map(cloneRule)
150 }
151
152 return res
153}
154
155VueLoaderPlugin.NS = NS
156module.exports = VueLoaderPlugin