-
Notifications
You must be signed in to change notification settings - Fork 916
/
Copy pathpitcher.js
227 lines (201 loc) · 7.65 KB
/
pitcher.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const hash = require('hash-sum')
const selfPath = require.resolve('../index')
const templateLoaderPath = require.resolve('./templateLoader')
const stylePostLoaderPath = require.resolve('./stylePostLoader')
const { resolveCompiler } = require('../compiler')
const { testWebpack5 } = require('../codegen/utils')
const isESLintLoader = (l) => /(\/|\\|@)eslint-loader/.test(l.path)
const isNullLoader = (l) => /(\/|\\|@)null-loader/.test(l.path)
const isCSSLoader = (l) => /(\/|\\|@)css-loader/.test(l.path)
const isCacheLoader = (l) => /(\/|\\|@)cache-loader/.test(l.path)
const isPitcher = (l) => l.path !== __filename
const isPreLoader = (l) => !l.pitchExecuted
const isPostLoader = (l) => l.pitchExecuted
const dedupeESLintLoader = (loaders) => {
const res = []
let seen = false
loaders.forEach((l) => {
if (!isESLintLoader(l)) {
res.push(l)
} else if (!seen) {
seen = true
res.push(l)
}
})
return res
}
const shouldIgnoreCustomBlock = (loaders) => {
const actualLoaders = loaders.filter((loader) => {
// vue-loader
if (loader.path === selfPath) {
return false
}
// cache-loader
if (isCacheLoader(loader)) {
return false
}
return true
})
return actualLoaders.length === 0
}
module.exports = (code) => code
// This pitching loader is responsible for intercepting all vue block requests
// and transform it into appropriate requests.
module.exports.pitch = function (remainingRequest) {
const options = loaderUtils.getOptions(this)
const { cacheDirectory, cacheIdentifier } = options
const query = qs.parse(this.resourceQuery.slice(1))
const isWebpack5 = testWebpack5(this._compiler)
let loaders = this.loaders
// if this is a language block request, eslint-loader may get matched
// multiple times
if (query.type) {
// if this is an inline block, since the whole file itself is being linted,
// remove eslint-loader to avoid duplicate linting.
if (/\.vue$/.test(this.resourcePath)) {
loaders = loaders.filter((l) => !isESLintLoader(l))
} else {
// This is a src import. Just make sure there's not more than 1 instance
// of eslint present.
loaders = dedupeESLintLoader(loaders)
}
}
// remove self
loaders = loaders.filter(isPitcher)
// do not inject if user uses null-loader to void the type (#1239)
if (loaders.some(isNullLoader)) {
return
}
// Important: dedupe since both the original rule
// and the cloned rule would match a source import request.
// also make sure to dedupe based on loader path.
// assumes you'd probably never want to apply the same loader on the same
// file twice.
// Exception: in Vue CLI we do need two instances of postcss-loader
// for user config and inline minification. So we need to dedupe baesd on
// path AND query to be safe.
const loadersSeen = new Set()
loaders = loaders.filter((loader) => {
const identifier =
typeof loader === 'string' ? loader : loader.path + loader.query
if (!loadersSeen.has(identifier)) {
loadersSeen.add(identifier)
return true
}
return false
})
const genRequest = (loaders, lang) => {
const enableInlineMatchResource =
isWebpack5 && options.experimentalInlineMatchResource
const loaderStrings = loaders.map((loader) => {
const request = typeof loader === 'string' ? loader : loader.request
// loader.request contains both the resolved loader path and its options
// query (e.g. ??ref-0)
return request
})
if (enableInlineMatchResource) {
return loaderUtils.stringifyRequest(
this,
`${this.resourcePath}${lang ? `.${lang}` : ''}${
this.resourceQuery
}!=!-!${[...loaderStrings, this.resourcePath + this.resourceQuery].join('!')}`
)
}
return loaderUtils.stringifyRequest(
this,
'-!' +
[...loaderStrings, this.resourcePath + this.resourceQuery].join('!')
)
}
// Inject style-post-loader before css-loader for scoped CSS and trimming
if (query.type === `style`) {
if (isWebpack5 && this._compiler.options.experiments && this._compiler.options.experiments.css) {
// If user enables `experiments.css`, then we are trying to emit css code directly.
// Although we can target requests like `xxx.vue?type=style` to match `type: "css"`,
// it will make the plugin a mess.
if (!options.experimentalInlineMatchResource) {
this.emitError(
new Error(
'`experimentalInlineMatchResource` should be enabled if `experiments.css` enabled currently'
)
)
return ''
}
if (query.inline || query.module) {
this.emitError(
new Error(
'`inline` or `module` is currently not supported with `experiments.css` enabled'
)
)
return ''
}
const loaderString = [stylePostLoaderPath, ...loaders]
.map((loader) => {
return typeof loader === 'string' ? loader : loader.request
})
.join('!')
const styleRequest = loaderUtils.stringifyRequest(
this,
`${this.resourcePath}${query.lang ? `.${query.lang}` : ''}${
this.resourceQuery
}!=!-!${loaderString}!${this.resourcePath + this.resourceQuery}`
)
return `@import ${styleRequest};`
}
const cssLoaderIndex = loaders.findIndex(isCSSLoader)
if (cssLoaderIndex > -1) {
const afterLoaders = loaders.slice(0, cssLoaderIndex + 1)
const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
const request = genRequest(
[...afterLoaders, stylePostLoaderPath, ...beforeLoaders],
query.lang || 'css'
)
// console.log(request)
return query.module
? `export { default } from ${request}; export * from ${request}`
: `export * from ${request}`
}
}
// for templates: inject the template compiler & optional cache
if (query.type === `template`) {
const path = require('path')
const cacheLoader =
cacheDirectory && cacheIdentifier
? [
`${require.resolve('cache-loader')}?${JSON.stringify({
// For some reason, webpack fails to generate consistent hash if we
// use absolute paths here, even though the path is only used in a
// comment. For now we have to ensure cacheDirectory is a relative path.
cacheDirectory: (path.isAbsolute(cacheDirectory)
? path.relative(process.cwd(), cacheDirectory)
: cacheDirectory
).replace(/\\/g, '/'),
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
})}`
]
: []
const preLoaders = loaders.filter(isPreLoader)
const postLoaders = loaders.filter(isPostLoader)
const { is27 } = resolveCompiler(this.rootContext, this)
const request = genRequest([
...cacheLoader,
...postLoaders,
...(is27 ? [] : [templateLoaderPath + `??vue-loader-options`]),
...preLoaders
])
// the template compiler uses esm exports
return `export * from ${request}`
}
// if a custom block has no other matching loader other than vue-loader itself
// or cache-loader, we should ignore it
if (query.type === `custom` && shouldIgnoreCustomBlock(loaders)) {
return ``
}
// When the user defines a rule that has only resourceQuery but no test,
// both that rule and the cloned rule will match, resulting in duplicated
// loaders. Therefore it is necessary to perform a dedupe here.
const request = genRequest(loaders)
return `import mod from ${request}; export default mod; export * from ${request}`
}