@mpxjs/webpack-plugin 2.8.0 → 2.8.6

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,1312 @@
1
+ /* eslint-disable operator-linebreak */
2
+ /*
3
+ MIT License http://www.opensource.org/licenses/mit-license.php
4
+ Author Tobias Koppers @sokra
5
+ */
6
+ const { fileURLToPath } = require('url')
7
+ const path = require('path')
8
+ const isUrlRequest = require('../utils/is-url-request')
9
+ const modulesValues = require('postcss-modules-values')
10
+ const localByDefault = require('postcss-modules-local-by-default')
11
+ const extractImports = require('postcss-modules-extract-imports')
12
+ const modulesScope = require('postcss-modules-scope')
13
+
14
+ const WEBPACK_IGNORE_COMMENT_REGEXP = /webpackIgnore:(\s+)?(true|false)/
15
+
16
+ const matchRelativePath = /^\.\.?[/\\]/
17
+
18
+ function isAbsolutePath (str) {
19
+ return path.posix.isAbsolute(str) || path.win32.isAbsolute(str)
20
+ }
21
+
22
+ function isRelativePath (str) {
23
+ return matchRelativePath.test(str)
24
+ }
25
+
26
+ // TODO simplify for the next major release
27
+ function stringifyRequest (loaderContext, request) {
28
+ if (
29
+ typeof loaderContext.utils !== 'undefined' &&
30
+ typeof loaderContext.utils.contextify === 'function'
31
+ ) {
32
+ return JSON.stringify(
33
+ loaderContext.utils.contextify(
34
+ loaderContext.context || loaderContext.rootContext,
35
+ request
36
+ )
37
+ )
38
+ }
39
+
40
+ const splitted = request.split('!')
41
+ const { context } = loaderContext
42
+
43
+ return JSON.stringify(
44
+ splitted
45
+ .map((part) => {
46
+ // First, separate singlePath from query, because the query might contain paths again
47
+ const splittedPart = part.match(/^(.*?)(\?.*)/)
48
+ const query = splittedPart ? splittedPart[2] : ''
49
+ let singlePath = splittedPart ? splittedPart[1] : part
50
+
51
+ if (isAbsolutePath(singlePath) && context) {
52
+ singlePath = path.relative(context, singlePath)
53
+
54
+ if (isAbsolutePath(singlePath)) {
55
+ // If singlePath still matches an absolute path, singlePath was on a different drive than context.
56
+ // In this case, we leave the path platform-specific without replacing any separators.
57
+ // @see https://github.com/webpack/loader-utils/pull/14
58
+ return singlePath + query
59
+ }
60
+
61
+ if (isRelativePath(singlePath) === false) {
62
+ // Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules).
63
+ singlePath = `./${singlePath}`
64
+ }
65
+ }
66
+
67
+ return singlePath.replace(/\\/g, '/') + query
68
+ })
69
+ .join('!')
70
+ )
71
+ }
72
+
73
+ // We can't use path.win32.isAbsolute because it also matches paths starting with a forward slash
74
+ const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i
75
+ const IS_MODULE_REQUEST = /^[^?]*~/
76
+
77
+ function urlToRequest (url, root) {
78
+ let request
79
+
80
+ if (IS_NATIVE_WIN32_PATH.test(url)) {
81
+ // absolute windows path, keep it
82
+ request = url
83
+ } else if (typeof root !== 'undefined' && /^\//.test(url)) {
84
+ request = root + url
85
+ } else if (/^\.\.?\//.test(url)) {
86
+ // A relative url stays
87
+ request = url
88
+ } else {
89
+ // every other url is threaded like a relative url
90
+ request = `./${url}`
91
+ }
92
+
93
+ // A `~` makes the url an module
94
+ if (IS_MODULE_REQUEST.test(request)) {
95
+ request = request.replace(IS_MODULE_REQUEST, '')
96
+ }
97
+
98
+ return request
99
+ }
100
+
101
+ // eslint-disable-next-line no-useless-escape
102
+ const regexSingleEscape = /[ -,.\/:-@[\]\^`{-~]/
103
+ const regexExcessiveSpaces =
104
+ /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g
105
+
106
+ const preserveCamelCase = (string) => {
107
+ let result = string
108
+ let isLastCharLower = false
109
+ let isLastCharUpper = false
110
+ let isLastLastCharUpper = false
111
+
112
+ for (let i = 0; i < result.length; i++) {
113
+ const character = result[i]
114
+
115
+ if (isLastCharLower && /[\p{Lu}]/u.test(character)) {
116
+ result = `${result.slice(0, i)}-${result.slice(i)}`
117
+ isLastCharLower = false
118
+ isLastLastCharUpper = isLastCharUpper
119
+ isLastCharUpper = true
120
+ i += 1
121
+ } else if (
122
+ isLastCharUpper &&
123
+ isLastLastCharUpper &&
124
+ /[\p{Ll}]/u.test(character)
125
+ ) {
126
+ result = `${result.slice(0, i - 1)}-${result.slice(i - 1)}`
127
+ isLastLastCharUpper = isLastCharUpper
128
+ isLastCharUpper = false
129
+ isLastCharLower = true
130
+ } else {
131
+ isLastCharLower =
132
+ character.toLowerCase() === character &&
133
+ character.toUpperCase() !== character
134
+ isLastLastCharUpper = isLastCharUpper
135
+ isLastCharUpper =
136
+ character.toUpperCase() === character &&
137
+ character.toLowerCase() !== character
138
+ }
139
+ }
140
+
141
+ return result
142
+ }
143
+
144
+ function camelCase (input) {
145
+ let result = input.trim()
146
+
147
+ if (result.length === 0) {
148
+ return ''
149
+ }
150
+
151
+ if (result.length === 1) {
152
+ return result.toLowerCase()
153
+ }
154
+
155
+ const hasUpperCase = result !== result.toLowerCase()
156
+
157
+ if (hasUpperCase) {
158
+ result = preserveCamelCase(result)
159
+ }
160
+
161
+ return result
162
+ .replace(/^[_.\- ]+/, '')
163
+ .toLowerCase()
164
+ .replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toUpperCase())
165
+ .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, (m) => m.toUpperCase())
166
+ }
167
+
168
+ function escape (string) {
169
+ let output = ''
170
+ let counter = 0
171
+
172
+ while (counter < string.length) {
173
+ // eslint-disable-next-line no-plusplus
174
+ const character = string.charAt(counter++)
175
+
176
+ let value
177
+
178
+ // eslint-disable-next-line no-control-regex
179
+ if (/[\t\n\f\r\x0B]/.test(character)) {
180
+ const codePoint = character.charCodeAt()
181
+
182
+ value = `\\${codePoint.toString(16).toUpperCase()} `
183
+ } else if (character === '\\' || regexSingleEscape.test(character)) {
184
+ value = `\\${character}`
185
+ } else {
186
+ value = character
187
+ }
188
+
189
+ output += value
190
+ }
191
+
192
+ const firstChar = string.charAt(0)
193
+
194
+ if (/^-[-\d]/.test(output)) {
195
+ output = `\\-${output.slice(1)}`
196
+ } else if (/\d/.test(firstChar)) {
197
+ output = `\\3${firstChar} ${output.slice(1)}`
198
+ }
199
+
200
+ // Remove spaces after `\HEX` escapes that are not followed by a hex digit,
201
+ // since they’re redundant. Note that this is only possible if the escape
202
+ // sequence isn’t preceded by an odd number of backslashes.
203
+ output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => {
204
+ if ($1 && $1.length % 2) {
205
+ // It’s not safe to remove the space, so don’t.
206
+ return $0
207
+ }
208
+
209
+ // Strip the space.
210
+ return ($1 || '') + $2
211
+ })
212
+
213
+ return output
214
+ }
215
+
216
+ function gobbleHex (str) {
217
+ const lower = str.toLowerCase()
218
+ let hex = ''
219
+ let spaceTerminated = false
220
+
221
+ // eslint-disable-next-line no-undefined
222
+ for (let i = 0; i < 6 && lower[i] !== undefined; i++) {
223
+ const code = lower.charCodeAt(i)
224
+ // check to see if we are dealing with a valid hex char [a-f|0-9]
225
+ const valid = (code >= 97 && code <= 102) || (code >= 48 && code <= 57)
226
+ // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
227
+ spaceTerminated = code === 32
228
+
229
+ if (!valid) {
230
+ break
231
+ }
232
+
233
+ hex += lower[i]
234
+ }
235
+
236
+ if (hex.length === 0) {
237
+ // eslint-disable-next-line no-undefined
238
+ return undefined
239
+ }
240
+
241
+ const codePoint = parseInt(hex, 16)
242
+
243
+ const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff
244
+ // Add special case for
245
+ // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
246
+ // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
247
+ if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
248
+ return ['\uFFFD', hex.length + (spaceTerminated ? 1 : 0)]
249
+ }
250
+
251
+ return [
252
+ String.fromCodePoint(codePoint),
253
+ hex.length + (spaceTerminated ? 1 : 0)
254
+ ]
255
+ }
256
+
257
+ const CONTAINS_ESCAPE = /\\/
258
+
259
+ function unescape (str) {
260
+ const needToProcess = CONTAINS_ESCAPE.test(str)
261
+
262
+ if (!needToProcess) {
263
+ return str
264
+ }
265
+
266
+ let ret = ''
267
+
268
+ for (let i = 0; i < str.length; i++) {
269
+ if (str[i] === '\\') {
270
+ const gobbled = gobbleHex(str.slice(i + 1, i + 7))
271
+
272
+ // eslint-disable-next-line no-undefined
273
+ if (gobbled !== undefined) {
274
+ ret += gobbled[0]
275
+ i += gobbled[1]
276
+
277
+ // eslint-disable-next-line no-continue
278
+ continue
279
+ }
280
+
281
+ // Retain a pair of \\ if double escaped `\\\\`
282
+ // https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e
283
+ if (str[i + 1] === '\\') {
284
+ ret += '\\'
285
+ i += 1
286
+
287
+ // eslint-disable-next-line no-continue
288
+ continue
289
+ }
290
+
291
+ // if \\ is at the end of the string retain it
292
+ // https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb
293
+ if (str.length === i + 1) {
294
+ ret += str[i]
295
+ }
296
+
297
+ // eslint-disable-next-line no-continue
298
+ continue
299
+ }
300
+
301
+ ret += str[i]
302
+ }
303
+
304
+ return ret
305
+ }
306
+
307
+ function normalizePath (file) {
308
+ return path.sep === '\\' ? file.replace(/\\/g, '/') : file
309
+ }
310
+
311
+ // eslint-disable-next-line no-control-regex
312
+ const filenameReservedRegex = /[<>:"/\\|?*]/g
313
+ // eslint-disable-next-line no-control-regex
314
+ const reControlChars = /[\u0000-\u001f\u0080-\u009f]/g
315
+
316
+ function escapeLocalIdent (localident) {
317
+ // TODO simplify in the next major release
318
+ return escape(
319
+ localident
320
+ // For `[hash]` placeholder
321
+ .replace(/^((-?[0-9])|--)/, '_$1')
322
+ .replace(filenameReservedRegex, '-')
323
+ .replace(reControlChars, '-')
324
+ .replace(/\./g, '-')
325
+ )
326
+ }
327
+
328
+ function defaultGetLocalIdent (
329
+ loaderContext,
330
+ localIdentName,
331
+ localName,
332
+ options
333
+ ) {
334
+ const { context, hashSalt, hashStrategy } = options
335
+ const { resourcePath } = loaderContext
336
+ const relativeResourcePath = normalizePath(
337
+ path.relative(context, resourcePath)
338
+ )
339
+
340
+ // eslint-disable-next-line no-param-reassign
341
+ options.content =
342
+ hashStrategy === 'minimal-subset' && /\[local\]/.test(localIdentName)
343
+ ? relativeResourcePath
344
+ : `${relativeResourcePath}\x00${localName}`
345
+
346
+ let { hashFunction, hashDigest, hashDigestLength } = options
347
+ const matches = localIdentName.match(
348
+ /\[(?:([^:\]]+):)?(?:(hash|contenthash|fullhash))(?::([a-z]+\d*))?(?::(\d+))?\]/i
349
+ )
350
+
351
+ if (matches) {
352
+ const hashName = matches[2] || hashFunction
353
+
354
+ hashFunction = matches[1] || hashFunction
355
+ hashDigest = matches[3] || hashDigest
356
+ hashDigestLength = matches[4] || hashDigestLength
357
+
358
+ // `hash` and `contenthash` are same in `loader-utils` context
359
+ // let's keep `hash` for backward compatibility
360
+
361
+ // eslint-disable-next-line no-param-reassign
362
+ localIdentName = localIdentName.replace(
363
+ /\[(?:([^:\]]+):)?(?:hash|contenthash|fullhash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi,
364
+ () => (hashName === 'fullhash' ? '[fullhash]' : '[contenthash]')
365
+ )
366
+ }
367
+
368
+ let localIdentHash = ''
369
+
370
+ for (let tier = 0; localIdentHash.length < hashDigestLength; tier++) {
371
+ // TODO remove this in the next major release
372
+ const hash =
373
+ loaderContext.utils &&
374
+ typeof loaderContext.utils.createHash === 'function'
375
+ ? loaderContext.utils.createHash(hashFunction)
376
+ : // eslint-disable-next-line no-underscore-dangle
377
+ loaderContext._compiler.webpack.util.createHash(hashFunction)
378
+
379
+ if (hashSalt) {
380
+ hash.update(hashSalt)
381
+ }
382
+
383
+ const tierSalt = Buffer.allocUnsafe(4)
384
+
385
+ tierSalt.writeUInt32LE(tier)
386
+
387
+ hash.update(tierSalt)
388
+ // TODO: bug in webpack with unicode characters with strings
389
+ hash.update(Buffer.from(options.content, 'utf8'))
390
+
391
+ localIdentHash = (localIdentHash + hash.digest(hashDigest))
392
+ // Remove all leading digits
393
+ .replace(/^\d+/, '')
394
+ // Replace all slashes with underscores (same as in base64url)
395
+ .replace(/\//g, '_')
396
+ // Remove everything that is not an alphanumeric or underscore
397
+ .replace(/[^A-Za-z0-9_]+/g, '')
398
+ .slice(0, hashDigestLength)
399
+ }
400
+
401
+ // TODO need improve on webpack side, we should allow to pass hash/contentHash without chunk property, also `data` for `getPath` should be looks good without chunk property
402
+ const ext = path.extname(resourcePath)
403
+ const base = path.basename(resourcePath)
404
+ const name = base.slice(0, base.length - ext.length)
405
+ const data = {
406
+ filename: path.relative(context, resourcePath),
407
+ contentHash: localIdentHash,
408
+ chunk: {
409
+ name,
410
+ hash: localIdentHash,
411
+ contentHash: localIdentHash
412
+ }
413
+ }
414
+
415
+ // eslint-disable-next-line no-underscore-dangle
416
+ let result = loaderContext._compilation.getPath(localIdentName, data)
417
+
418
+ if (/\[folder\]/gi.test(result)) {
419
+ const dirname = path.dirname(resourcePath)
420
+ let directory = normalizePath(
421
+ path.relative(context, `${dirname + path.sep}_`)
422
+ )
423
+
424
+ directory = directory.substr(0, directory.length - 1)
425
+
426
+ let folder = ''
427
+
428
+ if (directory.length > 1) {
429
+ folder = path.basename(directory)
430
+ }
431
+
432
+ result = result.replace(/\[folder\]/gi, () => folder)
433
+ }
434
+
435
+ if (options.regExp) {
436
+ const match = resourcePath.match(options.regExp)
437
+
438
+ if (match) {
439
+ match.forEach((matched, i) => {
440
+ result = result.replace(new RegExp(`\\[${i}\\]`, 'ig'), matched)
441
+ })
442
+ }
443
+ }
444
+
445
+ return result
446
+ }
447
+
448
+ function fixedEncodeURIComponent (str) {
449
+ return str.replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16)}`)
450
+ }
451
+
452
+ function isDataUrl (url) {
453
+ if (/^data:/i.test(url)) {
454
+ return true
455
+ }
456
+
457
+ return false
458
+ }
459
+
460
+ const NATIVE_WIN32_PATH = /^[A-Z]:[/\\]|^\\\\/i
461
+
462
+ function normalizeUrl (url, isStringValue) {
463
+ let normalizedUrl = url
464
+ .replace(/^( |\t\n|\r\n|\r|\f)*/g, '')
465
+ .replace(/( |\t\n|\r\n|\r|\f)*$/g, '')
466
+
467
+ if (isStringValue && /\\(\n|\r\n|\r|\f)/.test(normalizedUrl)) {
468
+ normalizedUrl = normalizedUrl.replace(/\\(\n|\r\n|\r|\f)/g, '')
469
+ }
470
+
471
+ if (NATIVE_WIN32_PATH.test(url)) {
472
+ try {
473
+ normalizedUrl = decodeURI(normalizedUrl)
474
+ } catch (error) {
475
+ // Ignore
476
+ }
477
+
478
+ return normalizedUrl
479
+ }
480
+
481
+ normalizedUrl = unescape(normalizedUrl)
482
+
483
+ if (isDataUrl(url)) {
484
+ // Todo fixedEncodeURIComponent is workaround. Webpack resolver shouldn't handle "!" in dataURL
485
+ return fixedEncodeURIComponent(normalizedUrl)
486
+ }
487
+
488
+ try {
489
+ normalizedUrl = decodeURI(normalizedUrl)
490
+ } catch (error) {
491
+ // Ignore
492
+ }
493
+
494
+ return normalizedUrl
495
+ }
496
+
497
+ function requestify (url, rootContext, needToResolveURL = true) {
498
+ if (needToResolveURL) {
499
+ if (/^file:/i.test(url)) {
500
+ return fileURLToPath(url)
501
+ }
502
+
503
+ return url.charAt(0) === '/'
504
+ ? urlToRequest(url, rootContext)
505
+ : urlToRequest(url)
506
+ }
507
+
508
+ if (url.charAt(0) === '/' || /^file:/i.test(url)) {
509
+ return url
510
+ }
511
+
512
+ // A `~` makes the url an module
513
+ if (IS_MODULE_REQUEST.test(url)) {
514
+ return url.replace(IS_MODULE_REQUEST, '')
515
+ }
516
+
517
+ return url
518
+ }
519
+
520
+ function getFilter (filter, resourcePath) {
521
+ return (...args) => {
522
+ if (typeof filter === 'function') {
523
+ return filter(...args, resourcePath)
524
+ }
525
+
526
+ return true
527
+ }
528
+ }
529
+
530
+ function getValidLocalName (localName, exportLocalsConvention) {
531
+ const result = exportLocalsConvention(localName)
532
+
533
+ return Array.isArray(result) ? result[0] : result
534
+ }
535
+
536
+ const IS_MODULES = /\.module(s)?\.\w+$/i
537
+ const IS_ICSS = /\.icss\.\w+$/i
538
+
539
+ function getModulesOptions (rawOptions, exportType, loaderContext) {
540
+ if (typeof rawOptions.modules === 'boolean' && rawOptions.modules === false) {
541
+ return false
542
+ }
543
+
544
+ const resourcePath =
545
+ // eslint-disable-next-line no-underscore-dangle
546
+ (loaderContext._module && loaderContext._module.matchResource) ||
547
+ loaderContext.resourcePath
548
+
549
+ let auto
550
+ let rawModulesOptions
551
+
552
+ if (typeof rawOptions.modules === 'undefined') {
553
+ rawModulesOptions = {}
554
+ auto = true
555
+ } else if (typeof rawOptions.modules === 'boolean') {
556
+ rawModulesOptions = {}
557
+ } else if (typeof rawOptions.modules === 'string') {
558
+ rawModulesOptions = { mode: rawOptions.modules }
559
+ } else {
560
+ rawModulesOptions = rawOptions.modules;
561
+ ({ auto } = rawModulesOptions)
562
+ }
563
+
564
+ // eslint-disable-next-line no-underscore-dangle
565
+ const { outputOptions } = loaderContext._compilation
566
+ const needNamedExport =
567
+ exportType === 'css-style-sheet' || exportType === 'string'
568
+ const modulesOptions = {
569
+ auto,
570
+ mode: 'local',
571
+ exportGlobals: false,
572
+ localIdentName: '[hash:base64]',
573
+ localIdentContext: loaderContext.rootContext,
574
+ localIdentHashSalt: outputOptions.hashSalt,
575
+ localIdentHashFunction: outputOptions.hashFunction,
576
+ localIdentHashDigest: outputOptions.hashDigest,
577
+ localIdentHashDigestLength: outputOptions.hashDigestLength,
578
+ // eslint-disable-next-line no-undefined
579
+ localIdentRegExp: undefined,
580
+ // eslint-disable-next-line no-undefined
581
+ getLocalIdent: undefined,
582
+ namedExport: needNamedExport || false,
583
+ exportLocalsConvention:
584
+ (rawModulesOptions.namedExport === true || needNamedExport) &&
585
+ typeof rawModulesOptions.exportLocalsConvention === 'undefined'
586
+ ? 'camelCaseOnly'
587
+ : 'asIs',
588
+ exportOnlyLocals: false,
589
+ ...rawModulesOptions
590
+ }
591
+
592
+ let exportLocalsConventionType
593
+
594
+ if (typeof modulesOptions.exportLocalsConvention === 'string') {
595
+ exportLocalsConventionType = modulesOptions.exportLocalsConvention
596
+
597
+ modulesOptions.exportLocalsConvention = (name) => {
598
+ switch (exportLocalsConventionType) {
599
+ case 'camelCase': {
600
+ return [name, camelCase(name)]
601
+ }
602
+ case 'camelCaseOnly': {
603
+ return camelCase(name)
604
+ }
605
+ case 'dashes': {
606
+ return [name, dashesCamelCase(name)]
607
+ }
608
+ case 'dashesOnly': {
609
+ return dashesCamelCase(name)
610
+ }
611
+ case 'asIs':
612
+ default:
613
+ return name
614
+ }
615
+ }
616
+ }
617
+
618
+ if (typeof modulesOptions.auto === 'boolean') {
619
+ const isModules = modulesOptions.auto && IS_MODULES.test(resourcePath)
620
+
621
+ let isIcss
622
+
623
+ if (!isModules) {
624
+ isIcss = IS_ICSS.test(resourcePath)
625
+
626
+ if (isIcss) {
627
+ modulesOptions.mode = 'icss'
628
+ }
629
+ }
630
+
631
+ if (!isModules && !isIcss) {
632
+ return false
633
+ }
634
+ } else if (modulesOptions.auto instanceof RegExp) {
635
+ const isModules = modulesOptions.auto.test(resourcePath)
636
+
637
+ if (!isModules) {
638
+ return false
639
+ }
640
+ } else if (typeof modulesOptions.auto === 'function') {
641
+ const isModule = modulesOptions.auto(resourcePath)
642
+
643
+ if (!isModule) {
644
+ return false
645
+ }
646
+ }
647
+
648
+ if (typeof modulesOptions.mode === 'function') {
649
+ modulesOptions.mode = modulesOptions.mode(loaderContext.resourcePath)
650
+ }
651
+
652
+ if (needNamedExport) {
653
+ if (rawOptions.esModule === false) {
654
+ throw new Error(
655
+ "The 'exportType' option with the 'css-style-sheet' or 'string' value requires the 'esModules' option to be enabled"
656
+ )
657
+ }
658
+
659
+ if (modulesOptions.namedExport === false) {
660
+ throw new Error(
661
+ "The 'exportType' option with the 'css-style-sheet' or 'string' value requires the 'modules.namedExport' option to be enabled"
662
+ )
663
+ }
664
+ }
665
+
666
+ if (modulesOptions.namedExport === true) {
667
+ if (rawOptions.esModule === false) {
668
+ throw new Error(
669
+ "The 'modules.namedExport' option requires the 'esModules' option to be enabled"
670
+ )
671
+ }
672
+
673
+ if (
674
+ typeof exportLocalsConventionType === 'string' &&
675
+ exportLocalsConventionType !== 'camelCaseOnly' &&
676
+ exportLocalsConventionType !== 'dashesOnly'
677
+ ) {
678
+ throw new Error(
679
+ 'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly" or "dashesOnly"'
680
+ )
681
+ }
682
+ }
683
+
684
+ return modulesOptions
685
+ }
686
+
687
+ function normalizeOptions (rawOptions, loaderContext) {
688
+ const exportType =
689
+ typeof rawOptions.exportType === 'undefined'
690
+ ? 'array'
691
+ : rawOptions.exportType
692
+ const modulesOptions = getModulesOptions(
693
+ rawOptions,
694
+ exportType,
695
+ loaderContext
696
+ )
697
+
698
+ return {
699
+ url: typeof rawOptions.url === 'undefined' ? true : rawOptions.url,
700
+ import: typeof rawOptions.import === 'undefined' ? true : rawOptions.import,
701
+ modules: modulesOptions,
702
+ sourceMap:
703
+ typeof rawOptions.sourceMap === 'boolean'
704
+ ? rawOptions.sourceMap
705
+ : loaderContext.sourceMap,
706
+ importLoaders:
707
+ typeof rawOptions.importLoaders === 'string'
708
+ ? parseInt(rawOptions.importLoaders, 10)
709
+ : rawOptions.importLoaders,
710
+ esModule:
711
+ typeof rawOptions.esModule === 'undefined' ? false : rawOptions.esModule, // 默认改为 false
712
+ exportType
713
+ }
714
+ }
715
+
716
+ function shouldUseImportPlugin (options) {
717
+ if (options.modules.exportOnlyLocals) {
718
+ return false
719
+ }
720
+
721
+ if (typeof options.import === 'boolean') {
722
+ return options.import
723
+ }
724
+
725
+ return true
726
+ }
727
+
728
+ function shouldUseURLPlugin (options) {
729
+ if (options.modules.exportOnlyLocals) {
730
+ return false
731
+ }
732
+
733
+ if (typeof options.url === 'boolean') {
734
+ return options.url
735
+ }
736
+
737
+ return true
738
+ }
739
+
740
+ function shouldUseModulesPlugins (options) {
741
+ if (typeof options.modules === 'boolean' && options.modules === false) {
742
+ return false
743
+ }
744
+
745
+ return options.modules.mode !== 'icss'
746
+ }
747
+
748
+ function shouldUseIcssPlugin (options) {
749
+ return Boolean(options.modules)
750
+ }
751
+
752
+ function getModulesPlugins (options, loaderContext) {
753
+ const {
754
+ mode,
755
+ getLocalIdent,
756
+ localIdentName,
757
+ localIdentContext,
758
+ localIdentHashSalt,
759
+ localIdentHashFunction,
760
+ localIdentHashDigest,
761
+ localIdentHashDigestLength,
762
+ localIdentRegExp,
763
+ hashStrategy
764
+ } = options.modules
765
+
766
+ let plugins = []
767
+
768
+ try {
769
+ plugins = [
770
+ modulesValues,
771
+ localByDefault({ mode }),
772
+ extractImports(),
773
+ modulesScope({
774
+ generateScopedName (exportName) {
775
+ let localIdent
776
+
777
+ if (typeof getLocalIdent !== 'undefined') {
778
+ localIdent = getLocalIdent(
779
+ loaderContext,
780
+ localIdentName,
781
+ unescape(exportName),
782
+ {
783
+ context: localIdentContext,
784
+ hashSalt: localIdentHashSalt,
785
+ hashFunction: localIdentHashFunction,
786
+ hashDigest: localIdentHashDigest,
787
+ hashDigestLength: localIdentHashDigestLength,
788
+ hashStrategy,
789
+ regExp: localIdentRegExp
790
+ }
791
+ )
792
+ }
793
+
794
+ // A null/undefined value signals that we should invoke the default
795
+ // getLocalIdent method.
796
+ if (typeof localIdent === 'undefined' || localIdent === null) {
797
+ localIdent = defaultGetLocalIdent(
798
+ loaderContext,
799
+ localIdentName,
800
+ unescape(exportName),
801
+ {
802
+ context: localIdentContext,
803
+ hashSalt: localIdentHashSalt,
804
+ hashFunction: localIdentHashFunction,
805
+ hashDigest: localIdentHashDigest,
806
+ hashDigestLength: localIdentHashDigestLength,
807
+ hashStrategy,
808
+ regExp: localIdentRegExp
809
+ }
810
+ )
811
+
812
+ return escapeLocalIdent(localIdent).replace(
813
+ /\\\[local\\]/gi,
814
+ exportName
815
+ )
816
+ }
817
+
818
+ return escapeLocalIdent(localIdent)
819
+ },
820
+ exportGlobals: options.modules.exportGlobals
821
+ })
822
+ ]
823
+ } catch (error) {
824
+ loaderContext.emitError(error)
825
+ }
826
+
827
+ return plugins
828
+ }
829
+
830
+ const ABSOLUTE_SCHEME = /^[a-z0-9+\-.]+:/i
831
+
832
+ function getURLType (source) {
833
+ if (source[0] === '/') {
834
+ if (source[1] === '/') {
835
+ return 'scheme-relative'
836
+ }
837
+
838
+ return 'path-absolute'
839
+ }
840
+
841
+ if (IS_NATIVE_WIN32_PATH.test(source)) {
842
+ return 'path-absolute'
843
+ }
844
+
845
+ return ABSOLUTE_SCHEME.test(source) ? 'absolute' : 'path-relative'
846
+ }
847
+
848
+ function normalizeSourceMap (map, resourcePath) {
849
+ let newMap = map
850
+
851
+ // Some loader emit source map as string
852
+ // Strip any JSON XSSI avoidance prefix from the string (as documented in the source maps specification), and then parse the string as JSON.
853
+ if (typeof newMap === 'string') {
854
+ newMap = JSON.parse(newMap)
855
+ }
856
+
857
+ delete newMap.file
858
+
859
+ const { sourceRoot } = newMap
860
+
861
+ delete newMap.sourceRoot
862
+
863
+ if (newMap.sources) {
864
+ // Source maps should use forward slash because it is URLs (https://github.com/mozilla/source-map/issues/91)
865
+ // We should normalize path because previous loaders like `sass-loader` using backslash when generate source map
866
+ newMap.sources = newMap.sources.map((source) => {
867
+ // Non-standard syntax from `postcss`
868
+ if (source.indexOf('<') === 0) {
869
+ return source
870
+ }
871
+
872
+ const sourceType = getURLType(source)
873
+
874
+ // Do no touch `scheme-relative` and `absolute` URLs
875
+ if (sourceType === 'path-relative' || sourceType === 'path-absolute') {
876
+ const absoluteSource =
877
+ sourceType === 'path-relative' && sourceRoot
878
+ ? path.resolve(sourceRoot, normalizePath(source))
879
+ : normalizePath(source)
880
+
881
+ return path.relative(path.dirname(resourcePath), absoluteSource)
882
+ }
883
+
884
+ return source
885
+ })
886
+ }
887
+
888
+ return newMap
889
+ }
890
+
891
+ function getPreRequester ({ loaders, loaderIndex }) {
892
+ const cache = Object.create(null)
893
+
894
+ return (number) => {
895
+ if (cache[number]) {
896
+ return cache[number]
897
+ }
898
+
899
+ if (number === false) {
900
+ cache[number] = ''
901
+ } else {
902
+ const loadersRequest = loaders
903
+ .slice(
904
+ loaderIndex,
905
+ loaderIndex + 1 + (typeof number !== 'number' ? 0 : number + 1) // 与css-loader 不同,需要增加 1,因为 css 文件会经过 style-compiler loader 进行处理。
906
+ )
907
+ .map((x) => x.request)
908
+ .join('!')
909
+
910
+ cache[number] = `-!${loadersRequest}!`
911
+ }
912
+
913
+ return cache[number]
914
+ }
915
+ }
916
+
917
+ function getImportCode (imports, options) {
918
+ let code = ''
919
+
920
+ for (const item of imports) {
921
+ const { importName, url, icss, type } = item
922
+
923
+ if (options.esModule) {
924
+ if (icss && options.modules.namedExport) {
925
+ code += `import ${
926
+ options.modules.exportOnlyLocals ? '' : `${importName}, `
927
+ }* as ${importName}_NAMED___ from ${url};\n`
928
+ } else {
929
+ code +=
930
+ type === 'url'
931
+ ? `var ${importName} = new URL(${url}, import.meta.url);\n`
932
+ : `import ${importName} from ${url};\n`
933
+ }
934
+ } else {
935
+ code += `var ${importName} = require(${url});\n`
936
+ }
937
+ }
938
+
939
+ return code ? `// Imports\n${code}` : ''
940
+ }
941
+
942
+ function normalizeSourceMapForRuntime (map, loaderContext) {
943
+ const resultMap = map ? map.toJSON() : null
944
+
945
+ if (resultMap) {
946
+ delete resultMap.file
947
+
948
+ /* eslint-disable no-underscore-dangle */
949
+ if (
950
+ loaderContext._compilation &&
951
+ loaderContext._compilation.options &&
952
+ loaderContext._compilation.options.devtool &&
953
+ loaderContext._compilation.options.devtool.includes('nosources')
954
+ ) {
955
+ /* eslint-enable no-underscore-dangle */
956
+
957
+ delete resultMap.sourcesContent
958
+ }
959
+
960
+ resultMap.sourceRoot = ''
961
+ resultMap.sources = resultMap.sources.map((source) => {
962
+ // Non-standard syntax from `postcss`
963
+ if (source.indexOf('<') === 0) {
964
+ return source
965
+ }
966
+
967
+ const sourceType = getURLType(source)
968
+
969
+ if (sourceType !== 'path-relative') {
970
+ return source
971
+ }
972
+
973
+ const resourceDirname = path.dirname(loaderContext.resourcePath)
974
+ const absoluteSource = path.resolve(resourceDirname, source)
975
+ const contextifyPath = normalizePath(
976
+ path.relative(loaderContext.rootContext, absoluteSource)
977
+ )
978
+
979
+ return `webpack://./${contextifyPath}`
980
+ })
981
+ }
982
+
983
+ return JSON.stringify(resultMap)
984
+ }
985
+
986
+ function printParams (media, dedupe, supports, layer) {
987
+ let result = ''
988
+
989
+ if (typeof layer !== 'undefined') {
990
+ result = `, ${JSON.stringify(layer)}`
991
+ }
992
+
993
+ if (typeof supports !== 'undefined') {
994
+ result = `, ${JSON.stringify(supports)}${result}`
995
+ } else if (result.length > 0) {
996
+ result = `, undefined${result}`
997
+ }
998
+
999
+ if (dedupe) {
1000
+ result = `, true${result}`
1001
+ } else if (result.length > 0) {
1002
+ result = `, false${result}`
1003
+ }
1004
+
1005
+ if (media) {
1006
+ result = `${JSON.stringify(media)}${result}`
1007
+ } else if (result.length > 0) {
1008
+ result = `""${result}`
1009
+ }
1010
+
1011
+ return result
1012
+ }
1013
+
1014
+ function getModuleCode (
1015
+ result,
1016
+ api,
1017
+ replacements,
1018
+ options,
1019
+ loaderContext
1020
+ ) {
1021
+ if (options.modules.exportOnlyLocals === true) {
1022
+ return ''
1023
+ }
1024
+
1025
+ let sourceMapValue = ''
1026
+
1027
+ if (options.sourceMap) {
1028
+ const sourceMap = result.map
1029
+
1030
+ sourceMapValue = `,${normalizeSourceMapForRuntime(
1031
+ sourceMap,
1032
+ loaderContext
1033
+ )}`
1034
+ }
1035
+
1036
+ let code = JSON.stringify(result.css)
1037
+
1038
+ let beforeCode = `var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(${
1039
+ options.sourceMap
1040
+ ? '___CSS_LOADER_API_SOURCEMAP_IMPORT___'
1041
+ : '___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___'
1042
+ });\n`
1043
+
1044
+ for (const item of api) {
1045
+ const { url, layer, supports, media, dedupe } = item
1046
+
1047
+ if (url) {
1048
+ // eslint-disable-next-line no-undefined
1049
+ const printedParam = printParams(media, undefined, supports, layer)
1050
+
1051
+ beforeCode += `___CSS_LOADER_EXPORT___.push([module.id, ${JSON.stringify(
1052
+ `@import url(${url});`
1053
+ )}${printedParam.length > 0 ? `, ${printedParam}` : ''}]);\n`
1054
+ } else {
1055
+ // 符合css后缀名的文件经过mpx处理后会带上相应的后缀防止使用 WebPack 的默认解析规则,此时 require/import 相应路径时,导出的不是一段 css 代码了,事实上是一个文件路径。
1056
+ const printedParam = printParams(media, dedupe, supports, layer)
1057
+ const otherParams = printedParam.length > 0 ? printedParam : ''
1058
+ beforeCode += `___CSS_LOADER_EXPORT___.push([module.id, '@import "' + ${item.importName} + '";', ${JSON.stringify(otherParams)} ]);\n`
1059
+ }
1060
+ }
1061
+
1062
+ for (const item of replacements) {
1063
+ const { replacementName, importName, localName } = item
1064
+
1065
+ if (localName) {
1066
+ code = code.replace(new RegExp(replacementName, 'g'), () =>
1067
+ options.modules.namedExport
1068
+ ? `" + ${importName}_NAMED___[${JSON.stringify(
1069
+ getValidLocalName(
1070
+ localName,
1071
+ options.modules.exportLocalsConvention
1072
+ )
1073
+ )}] + "`
1074
+ : `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
1075
+ )
1076
+ } else {
1077
+ const { hash, needQuotes } = item
1078
+ const getUrlOptions = []
1079
+ .concat(hash ? [`hash: ${JSON.stringify(hash)}`] : [])
1080
+ .concat(needQuotes ? 'needQuotes: true' : [])
1081
+ const preparedOptions =
1082
+ getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(', ')} }` : ''
1083
+
1084
+ beforeCode += `var ${replacementName} = ___CSS_LOADER_GET_URL_IMPORT___(${importName}${preparedOptions});\n`
1085
+ code = code.replace(
1086
+ new RegExp(replacementName, 'g'),
1087
+ () => `" + ${replacementName} + "`
1088
+ )
1089
+ }
1090
+ }
1091
+
1092
+ // Indexes description:
1093
+ // 0 - module id
1094
+ // 1 - CSS code
1095
+ // 2 - media
1096
+ // 3 - source map
1097
+ // 4 - supports
1098
+ // 5 - layer
1099
+ return `${beforeCode}// Module\n___CSS_LOADER_EXPORT___.push([module.id, ${code}, ""${sourceMapValue}]);\n`
1100
+ }
1101
+
1102
+ function dashesCamelCase (str) {
1103
+ return str.replace(/-+(\w)/g, (match, firstLetter) =>
1104
+ firstLetter.toUpperCase()
1105
+ )
1106
+ }
1107
+
1108
+ function getExportCode (exports, replacements, icssPluginUsed, options) {
1109
+ let code = '// Exports\n'
1110
+
1111
+ if (icssPluginUsed) {
1112
+ let localsCode = ''
1113
+
1114
+ const addExportToLocalsCode = (names, value) => {
1115
+ const normalizedNames = Array.isArray(names)
1116
+ ? new Set(names)
1117
+ : new Set([names])
1118
+
1119
+ for (const name of normalizedNames) {
1120
+ if (options.modules.namedExport) {
1121
+ localsCode += `export var ${name} = ${JSON.stringify(value)};\n`
1122
+ } else {
1123
+ if (localsCode) {
1124
+ localsCode += ',\n'
1125
+ }
1126
+
1127
+ localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`
1128
+ }
1129
+ }
1130
+ }
1131
+
1132
+ for (const { name, value } of exports) {
1133
+ addExportToLocalsCode(
1134
+ options.modules.exportLocalsConvention(name),
1135
+ value
1136
+ )
1137
+ }
1138
+
1139
+ for (const item of replacements) {
1140
+ const { replacementName, localName } = item
1141
+
1142
+ if (localName) {
1143
+ const { importName } = item
1144
+
1145
+ localsCode = localsCode.replace(
1146
+ new RegExp(replacementName, 'g'),
1147
+ () => {
1148
+ if (options.modules.namedExport) {
1149
+ return `" + ${importName}_NAMED___[${JSON.stringify(
1150
+ getValidLocalName(
1151
+ localName,
1152
+ options.modules.exportLocalsConvention
1153
+ )
1154
+ )}] + "`
1155
+ } else if (options.modules.exportOnlyLocals) {
1156
+ return `" + ${importName}[${JSON.stringify(localName)}] + "`
1157
+ }
1158
+
1159
+ return `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
1160
+ }
1161
+ )
1162
+ } else {
1163
+ localsCode = localsCode.replace(
1164
+ new RegExp(replacementName, 'g'),
1165
+ () => `" + ${replacementName} + "`
1166
+ )
1167
+ }
1168
+ }
1169
+
1170
+ if (options.modules.exportOnlyLocals) {
1171
+ code += options.modules.namedExport
1172
+ ? localsCode
1173
+ : `${
1174
+ options.esModule ? 'export default' : 'module.exports ='
1175
+ } {\n${localsCode}\n};\n`
1176
+
1177
+ return code
1178
+ }
1179
+
1180
+ code += options.modules.namedExport
1181
+ ? localsCode
1182
+ : `___CSS_LOADER_EXPORT___.locals = {${
1183
+ localsCode ? `\n${localsCode}\n` : ''
1184
+ }};\n`
1185
+ }
1186
+
1187
+ const isCSSStyleSheetExport = options.exportType === 'css-style-sheet'
1188
+
1189
+ if (isCSSStyleSheetExport) {
1190
+ code += 'var ___CSS_LOADER_STYLE_SHEET___ = new CSSStyleSheet();\n'
1191
+ code +=
1192
+ '___CSS_LOADER_STYLE_SHEET___.replaceSync(___CSS_LOADER_EXPORT___.toString());\n'
1193
+ }
1194
+
1195
+ let finalExport
1196
+
1197
+ switch (options.exportType) {
1198
+ case 'string':
1199
+ finalExport = '___CSS_LOADER_EXPORT___.toString()'
1200
+ break
1201
+ case 'css-style-sheet':
1202
+ finalExport = '___CSS_LOADER_STYLE_SHEET___'
1203
+ break
1204
+ /* eslint-disable default-case-last */
1205
+ default:
1206
+ case 'array':
1207
+ finalExport = '___CSS_LOADER_EXPORT___'
1208
+ break
1209
+ }
1210
+
1211
+ code += `${
1212
+ options.esModule ? 'export default ' : 'module.exports ='
1213
+ } ${finalExport};\n`
1214
+
1215
+ return code
1216
+ }
1217
+
1218
+ async function resolveRequests (resolve, context, possibleRequests) {
1219
+ return resolve(context, possibleRequests[0])
1220
+ .then((result) => result)
1221
+ .catch((error) => {
1222
+ const [, ...tailPossibleRequests] = possibleRequests
1223
+
1224
+ if (tailPossibleRequests.length === 0) {
1225
+ throw error
1226
+ }
1227
+
1228
+ return resolveRequests(resolve, context, tailPossibleRequests)
1229
+ })
1230
+ }
1231
+
1232
+ function isURLRequestable (url, options = {}) {
1233
+ // 先用 mpx 内部维护方法判断
1234
+ if (!isUrlRequest(url, options.root, options.externals)) {
1235
+ return { requestable: false, needResolve: false }
1236
+ }
1237
+ // Protocol-relative URLs
1238
+ if (/^\/\//.test(url)) {
1239
+ return { requestable: false, needResolve: false }
1240
+ }
1241
+
1242
+ // `#` URLs
1243
+ if (/^#/.test(url)) {
1244
+ return { requestable: false, needResolve: false }
1245
+ }
1246
+
1247
+ // Data URI
1248
+ if (isDataUrl(url) && options.isSupportDataURL) {
1249
+ try {
1250
+ decodeURIComponent(url)
1251
+ } catch (ignoreError) {
1252
+ return { requestable: false, needResolve: false }
1253
+ }
1254
+
1255
+ return { requestable: true, needResolve: false }
1256
+ }
1257
+
1258
+ // `file:` protocol
1259
+ if (/^file:/i.test(url)) {
1260
+ return { requestable: true, needResolve: true }
1261
+ }
1262
+
1263
+ // Absolute URLs
1264
+ if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !NATIVE_WIN32_PATH.test(url)) {
1265
+ if (options.isSupportAbsoluteURL && /^https?:/i.test(url)) {
1266
+ return { requestable: true, needResolve: false }
1267
+ }
1268
+
1269
+ return { requestable: false, needResolve: false }
1270
+ }
1271
+
1272
+ return { requestable: true, needResolve: true }
1273
+ }
1274
+
1275
+ function sort (a, b) {
1276
+ return a.index - b.index
1277
+ }
1278
+
1279
+ function combineRequests (preRequest, url) {
1280
+ const idx = url.indexOf('!=!')
1281
+
1282
+ return idx !== -1
1283
+ ? url.slice(0, idx + 3) + preRequest + url.slice(idx + 3)
1284
+ : preRequest + url
1285
+ }
1286
+
1287
+ module.exports = {
1288
+ normalizeOptions,
1289
+ shouldUseModulesPlugins,
1290
+ shouldUseImportPlugin,
1291
+ shouldUseURLPlugin,
1292
+ shouldUseIcssPlugin,
1293
+ normalizeUrl,
1294
+ requestify,
1295
+ getFilter,
1296
+ getModulesOptions,
1297
+ getModulesPlugins,
1298
+ normalizeSourceMap,
1299
+ getPreRequester,
1300
+ getImportCode,
1301
+ getModuleCode,
1302
+ getExportCode,
1303
+ resolveRequests,
1304
+ isURLRequestable,
1305
+ sort,
1306
+ WEBPACK_IGNORE_COMMENT_REGEXP,
1307
+ combineRequests,
1308
+ camelCase,
1309
+ stringifyRequest,
1310
+ isDataUrl,
1311
+ defaultGetLocalIdent
1312
+ }