@mpxjs/webpack-plugin 2.8.1 → 2.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ const { extractICSS, replaceValueSymbols, replaceSymbols } = require('icss-utils')
2
+
3
+ const { normalizeUrl, resolveRequests, requestify } = require('../utils')
4
+
5
+ const plugin = (options = {}) => {
6
+ return {
7
+ postcssPlugin: 'postcss-icss-parser',
8
+ async OnceExit (root) {
9
+ const importReplacements = Object.create(null)
10
+ const { icssImports, icssExports } = extractICSS(root)
11
+ const imports = new Map()
12
+ const tasks = []
13
+
14
+ const { loaderContext } = options
15
+ const resolver = loaderContext.getResolve({
16
+ dependencyType: 'icss',
17
+ conditionNames: ['style'],
18
+ extensions: ['...'],
19
+ mainFields: ['css', 'style', 'main', '...'],
20
+ mainFiles: ['index', '...'],
21
+ preferRelative: true
22
+ })
23
+
24
+ // eslint-disable-next-line guard-for-in
25
+ for (const url in icssImports) {
26
+ const tokens = icssImports[url]
27
+
28
+ if (Object.keys(tokens).length === 0) {
29
+ // eslint-disable-next-line no-continue
30
+ continue
31
+ }
32
+
33
+ let normalizedUrl = url
34
+ let prefix = ''
35
+
36
+ const queryParts = normalizedUrl.split('!')
37
+
38
+ if (queryParts.length > 1) {
39
+ normalizedUrl = queryParts.pop()
40
+ prefix = queryParts.join('!')
41
+ }
42
+
43
+ const request = requestify(
44
+ normalizeUrl(normalizedUrl, true),
45
+ loaderContext.rootContext
46
+ )
47
+ const doResolve = async () => {
48
+ const resolvedUrl = await resolveRequests(
49
+ resolver,
50
+ loaderContext.context,
51
+ [...new Set([normalizedUrl, request])]
52
+ )
53
+
54
+ if (!resolvedUrl) {
55
+ return
56
+ }
57
+
58
+ // eslint-disable-next-line consistent-return
59
+ return { url: resolvedUrl, prefix, tokens }
60
+ }
61
+
62
+ tasks.push(doResolve())
63
+ }
64
+
65
+ const results = await Promise.all(tasks)
66
+
67
+ for (let index = 0; index <= results.length - 1; index++) {
68
+ const item = results[index]
69
+
70
+ if (!item) {
71
+ // eslint-disable-next-line no-continue
72
+ continue
73
+ }
74
+
75
+ const newUrl = item.prefix ? `${item.prefix}!${item.url}` : item.url
76
+ const importKey = newUrl
77
+ let importName = imports.get(importKey)
78
+
79
+ if (!importName) {
80
+ importName = `___CSS_LOADER_ICSS_IMPORT_${imports.size}___`
81
+ imports.set(importKey, importName)
82
+
83
+ options.imports.push({
84
+ type: 'icss_import',
85
+ importName,
86
+ url: options.urlHandler(newUrl),
87
+ icss: true,
88
+ index
89
+ })
90
+
91
+ options.api.push({ importName, dedupe: true, index })
92
+ }
93
+
94
+ for (const [replacementIndex, token] of Object.keys(
95
+ item.tokens
96
+ ).entries()) {
97
+ const replacementName = `___CSS_LOADER_ICSS_IMPORT_${index}_REPLACEMENT_${replacementIndex}___`
98
+ const localName = item.tokens[token]
99
+
100
+ importReplacements[token] = replacementName
101
+
102
+ options.replacements.push({ replacementName, importName, localName })
103
+ }
104
+ }
105
+
106
+ if (Object.keys(importReplacements).length > 0) {
107
+ replaceSymbols(root, importReplacements)
108
+ }
109
+
110
+ for (const name of Object.keys(icssExports)) {
111
+ const value = replaceValueSymbols(
112
+ icssExports[name],
113
+ importReplacements
114
+ )
115
+
116
+ options.exports.push({ name, value })
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ plugin.postcss = true
123
+
124
+ module.exports = plugin
@@ -0,0 +1,372 @@
1
+ const valueParser = require('postcss-value-parser')
2
+
3
+ const {
4
+ normalizeUrl,
5
+ resolveRequests,
6
+ isURLRequestable,
7
+ requestify,
8
+ WEBPACK_IGNORE_COMMENT_REGEXP
9
+ } = require('../utils')
10
+
11
+ const MPX_IMPORT_REGEXP = /^(@mpx-import\s+)/ // 例如匹配: ' @mpx-import "xxx"'
12
+
13
+ function parseNode (atRule, key, options) {
14
+ // Convert only top-level @import
15
+
16
+ if (atRule.parent.type !== 'root') {
17
+ return
18
+ }
19
+
20
+ if (
21
+ atRule.raws &&
22
+ atRule.raws.afterName &&
23
+ atRule.raws.afterName.trim().length > 0
24
+ ) {
25
+ const lastCommentIndex = atRule.raws.afterName.lastIndexOf('/*')
26
+
27
+ const matched = atRule.raws.afterName
28
+ .slice(lastCommentIndex)
29
+ .match(WEBPACK_IGNORE_COMMENT_REGEXP)
30
+
31
+ if (matched && matched[2] === 'true') {
32
+ return
33
+ }
34
+ }
35
+
36
+ const prevNode = atRule.prev()
37
+
38
+ if (prevNode && prevNode.type === 'comment') {
39
+ const matched = prevNode.text.match(WEBPACK_IGNORE_COMMENT_REGEXP)
40
+
41
+ if (matched && matched[2] === 'true') {
42
+ return
43
+ }
44
+ }
45
+
46
+ // Nodes do not exists - `@import url('http://') :root {}`
47
+ if (atRule.nodes) {
48
+ const error = new Error(
49
+ "It looks like you didn't end your @import statement correctly. Child nodes are attached to it."
50
+ )
51
+
52
+ error.node = atRule
53
+
54
+ throw error
55
+ }
56
+
57
+ const rawParams =
58
+ atRule.raws &&
59
+ atRule.raws[key] &&
60
+ typeof atRule.raws[key].raw !== 'undefined'
61
+ ? atRule.raws[key].raw
62
+ : atRule[key]
63
+ const { nodes: paramsNodes } = valueParser(rawParams)
64
+
65
+ // No nodes - `@import ;`
66
+ // Invalid type - `@import foo-bar;`
67
+ if (
68
+ paramsNodes.length === 0 ||
69
+ (paramsNodes[0].type !== 'string' && paramsNodes[0].type !== 'function')
70
+ ) {
71
+ const error = new Error(`Unable to find uri in "${atRule.toString()}"`)
72
+
73
+ error.node = atRule
74
+
75
+ throw error
76
+ }
77
+
78
+ let isStringValue
79
+ let url
80
+
81
+ if (paramsNodes[0].type === 'string') {
82
+ isStringValue = true
83
+ url = paramsNodes[0].value
84
+ } else {
85
+ // Invalid function - `@import nourl(test.css);`
86
+ if (paramsNodes[0].value.toLowerCase() !== 'url') {
87
+ const error = new Error(`Unable to find uri in "${atRule.toString()}"`)
88
+
89
+ error.node = atRule
90
+
91
+ throw error
92
+ }
93
+
94
+ isStringValue =
95
+ paramsNodes[0].nodes.length !== 0 &&
96
+ paramsNodes[0].nodes[0].type === 'string'
97
+ url = isStringValue
98
+ ? paramsNodes[0].nodes[0].value
99
+ : valueParser.stringify(paramsNodes[0].nodes)
100
+ }
101
+
102
+ url = normalizeUrl(url, isStringValue)
103
+
104
+ const { requestable, needResolve } = isURLRequestable(url, options)
105
+
106
+ let prefix
107
+
108
+ if (requestable && needResolve) {
109
+ const queryParts = url.split('!')
110
+
111
+ if (queryParts.length > 1) {
112
+ url = queryParts.pop()
113
+ prefix = queryParts.join('!')
114
+ }
115
+ }
116
+
117
+ // Empty url - `@import "";` or `@import url();`
118
+ if (url.trim().length === 0) {
119
+ const error = new Error(`Unable to find uri in "${atRule.toString()}"`)
120
+
121
+ error.node = atRule
122
+
123
+ throw error
124
+ }
125
+
126
+ const additionalNodes = paramsNodes.slice(1)
127
+
128
+ let supports
129
+ let layer
130
+ let media
131
+
132
+ if (additionalNodes.length > 0) {
133
+ let nodes = []
134
+
135
+ for (const node of additionalNodes) {
136
+ nodes.push(node)
137
+
138
+ const isLayerFunction =
139
+ node.type === 'function' && node.value.toLowerCase() === 'layer'
140
+ const isLayerWord =
141
+ node.type === 'word' && node.value.toLowerCase() === 'layer'
142
+
143
+ if (isLayerFunction || isLayerWord) {
144
+ if (isLayerFunction) {
145
+ nodes.splice(nodes.length - 1, 1, ...node.nodes)
146
+ } else {
147
+ nodes.splice(nodes.length - 1, 1, {
148
+ type: 'string',
149
+ value: '',
150
+ unclosed: false
151
+ })
152
+ }
153
+
154
+ layer = valueParser.stringify(nodes).trim().toLowerCase()
155
+ nodes = []
156
+ } else if (
157
+ node.type === 'function' &&
158
+ node.value.toLowerCase() === 'supports'
159
+ ) {
160
+ nodes.splice(nodes.length - 1, 1, ...node.nodes)
161
+
162
+ supports = valueParser.stringify(nodes).trim().toLowerCase()
163
+ nodes = []
164
+ }
165
+ }
166
+
167
+ if (nodes.length > 0) {
168
+ media = valueParser.stringify(nodes).trim().toLowerCase()
169
+ }
170
+ }
171
+
172
+ // eslint-disable-next-line consistent-return
173
+ return {
174
+ atRule,
175
+ prefix,
176
+ url,
177
+ layer,
178
+ supports,
179
+ media,
180
+ requestable,
181
+ needResolve
182
+ }
183
+ }
184
+
185
+ const plugin = (options = {}) => {
186
+ return {
187
+ postcssPlugin: 'postcss-import-parser',
188
+ prepare (result) {
189
+ const parsedAtRules = []
190
+
191
+ return {
192
+ Once (root, { AtRule }) {
193
+ // Calls once per file, since every file has single Root
194
+ // 遍历AST 找到注释节点(/* @mpx-import "xxx" */)进行@import 替换
195
+ root.walkComments((comment) => {
196
+ if (MPX_IMPORT_REGEXP.test(comment.text)) {
197
+ const importStatement = comment.text.replace(MPX_IMPORT_REGEXP, (matchStr, $1) => {
198
+ return matchStr.replace($1, '')
199
+ })
200
+
201
+ const matched = importStatement.match(/(["'].+["'])/)
202
+
203
+ if (matched && matched[1]) {
204
+ const url = matched[1]
205
+ const importNode = new AtRule({ name: 'import', params: url, source: comment.source })
206
+ comment.before(importNode)
207
+ comment.remove()
208
+ }
209
+ }
210
+ })
211
+ },
212
+ AtRule: {
213
+ import (atRule) {
214
+ if (options.isCSSStyleSheet) {
215
+ options.loaderContext.emitError(
216
+ new Error(
217
+ atRule.error(
218
+ "'@import' rules are not allowed here and will not be processed"
219
+ ).message
220
+ )
221
+ )
222
+
223
+ return
224
+ }
225
+
226
+ const { isSupportDataURL, isSupportAbsoluteURL } = options
227
+
228
+ let parsedAtRule
229
+
230
+ try {
231
+ parsedAtRule = parseNode(atRule, 'params', {
232
+ isSupportAbsoluteURL,
233
+ isSupportDataURL,
234
+ externals: options.externals
235
+ })
236
+ } catch (error) {
237
+ result.warn(error.message, { node: error.node })
238
+ }
239
+
240
+ if (!parsedAtRule) {
241
+ return
242
+ }
243
+
244
+ parsedAtRules.push(parsedAtRule)
245
+ }
246
+ },
247
+ async OnceExit () {
248
+ if (parsedAtRules.length === 0) {
249
+ return
250
+ }
251
+
252
+ const { loaderContext } = options
253
+ const resolver = loaderContext.getResolve({
254
+ dependencyType: 'css',
255
+ conditionNames: ['style'],
256
+ mainFields: ['css', 'style', 'main', '...'],
257
+ mainFiles: ['index', '...'],
258
+ extensions: ['.css', '...'],
259
+ preferRelative: true
260
+ })
261
+
262
+ const resolvedAtRules = await Promise.all(
263
+ parsedAtRules.map(async (parsedAtRule) => {
264
+ const {
265
+ atRule,
266
+ requestable,
267
+ needResolve,
268
+ prefix,
269
+ url,
270
+ layer,
271
+ supports,
272
+ media
273
+ } = parsedAtRule
274
+
275
+ if (options.filter) {
276
+ const needKeep = await options.filter(
277
+ url,
278
+ media,
279
+ loaderContext.resourcePath,
280
+ supports,
281
+ layer
282
+ )
283
+
284
+ if (!needKeep) {
285
+ return
286
+ }
287
+ }
288
+
289
+ if (needResolve) {
290
+ const request = requestify(url, loaderContext.rootContext)
291
+ const resolvedUrl = await resolveRequests(
292
+ resolver,
293
+ loaderContext.context,
294
+ [...new Set([request, url])]
295
+ )
296
+
297
+ if (!resolvedUrl) {
298
+ return
299
+ }
300
+
301
+ if (resolvedUrl === loaderContext.resourcePath) {
302
+ atRule.remove()
303
+
304
+ return
305
+ }
306
+
307
+ atRule.remove()
308
+
309
+ // eslint-disable-next-line consistent-return
310
+ return {
311
+ url: resolvedUrl,
312
+ layer,
313
+ supports,
314
+ media,
315
+ prefix,
316
+ requestable
317
+ }
318
+ }
319
+
320
+ atRule.remove()
321
+
322
+ // eslint-disable-next-line consistent-return
323
+ return { url, layer, supports, media, prefix, requestable }
324
+ })
325
+ )
326
+
327
+ const urlToNameMap = new Map()
328
+
329
+ for (let index = 0; index <= resolvedAtRules.length - 1; index++) {
330
+ const resolvedAtRule = resolvedAtRules[index]
331
+
332
+ if (!resolvedAtRule) {
333
+ // eslint-disable-next-line no-continue
334
+ continue
335
+ }
336
+
337
+ const { url, requestable, layer, supports, media } = resolvedAtRule
338
+
339
+ if (!requestable) {
340
+ options.api.push({ url, layer, supports, media, index })
341
+
342
+ // eslint-disable-next-line no-continue
343
+ continue
344
+ }
345
+
346
+ const { prefix } = resolvedAtRule
347
+ const newUrl = prefix ? `${prefix}!${url}` : url
348
+ let importName = urlToNameMap.get(newUrl)
349
+
350
+ if (!importName) {
351
+ importName = `___CSS_LOADER_AT_RULE_IMPORT_${urlToNameMap.size}___`
352
+ urlToNameMap.set(newUrl, importName)
353
+
354
+ options.imports.push({
355
+ type: 'rule_import',
356
+ importName,
357
+ url: options.urlHandler(newUrl),
358
+ index
359
+ })
360
+
361
+ options.api.push({ importName, layer, supports, media, index })
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ }
369
+
370
+ plugin.postcss = true
371
+
372
+ module.exports = plugin