utils.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.normalizeOptions = normalizeOptions;
  6. exports.shouldUseModulesPlugins = shouldUseModulesPlugins;
  7. exports.shouldUseImportPlugin = shouldUseImportPlugin;
  8. exports.shouldUseURLPlugin = shouldUseURLPlugin;
  9. exports.shouldUseIcssPlugin = shouldUseIcssPlugin;
  10. exports.normalizeUrl = normalizeUrl;
  11. exports.requestify = requestify;
  12. exports.getFilter = getFilter;
  13. exports.getModulesOptions = getModulesOptions;
  14. exports.getModulesPlugins = getModulesPlugins;
  15. exports.normalizeSourceMap = normalizeSourceMap;
  16. exports.getPreRequester = getPreRequester;
  17. exports.getImportCode = getImportCode;
  18. exports.getModuleCode = getModuleCode;
  19. exports.getExportCode = getExportCode;
  20. exports.resolveRequests = resolveRequests;
  21. exports.isUrlRequestable = isUrlRequestable;
  22. exports.sort = sort;
  23. var _url = require("url");
  24. var _path = _interopRequireDefault(require("path"));
  25. var _loaderUtils = require("loader-utils");
  26. var _cssesc = _interopRequireDefault(require("cssesc"));
  27. var _postcssModulesValues = _interopRequireDefault(require("postcss-modules-values"));
  28. var _postcssModulesLocalByDefault = _interopRequireDefault(require("postcss-modules-local-by-default"));
  29. var _postcssModulesExtractImports = _interopRequireDefault(require("postcss-modules-extract-imports"));
  30. var _postcssModulesScope = _interopRequireDefault(require("postcss-modules-scope"));
  31. var _camelcase = _interopRequireDefault(require("camelcase"));
  32. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  33. /*
  34. MIT License http://www.opensource.org/licenses/mit-license.php
  35. Author Tobias Koppers @sokra
  36. */
  37. const whitespace = '[\\x20\\t\\r\\n\\f]';
  38. const unescapeRegExp = new RegExp(`\\\\([\\da-f]{1,6}${whitespace}?|(${whitespace})|.)`, 'ig');
  39. const matchNativeWin32Path = /^[A-Z]:[/\\]|^\\\\/i;
  40. function unescape(str) {
  41. return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => {
  42. const high = `0x${escaped}` - 0x10000;
  43. /* eslint-disable line-comment-position */
  44. // NaN means non-codepoint
  45. // Workaround erroneous numeric interpretation of +"0x"
  46. // eslint-disable-next-line no-self-compare
  47. return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint
  48. String.fromCharCode(high + 0x10000) : // Supplemental Plane codepoint (surrogate pair)
  49. // eslint-disable-next-line no-bitwise
  50. String.fromCharCode(high >> 10 | 0xd800, high & 0x3ff | 0xdc00);
  51. /* eslint-enable line-comment-position */
  52. });
  53. }
  54. function normalizePath(file) {
  55. return _path.default.sep === '\\' ? file.replace(/\\/g, '/') : file;
  56. } // eslint-disable-next-line no-control-regex
  57. const filenameReservedRegex = /[<>:"/\\|?*]/g; // eslint-disable-next-line no-control-regex
  58. const reControlChars = /[\u0000-\u001f\u0080-\u009f]/g;
  59. function defaultGetLocalIdent(loaderContext, localIdentName, localName, options) {
  60. const {
  61. context,
  62. hashPrefix
  63. } = options;
  64. const {
  65. resourcePath
  66. } = loaderContext;
  67. const request = normalizePath(_path.default.relative(context, resourcePath)); // eslint-disable-next-line no-param-reassign
  68. options.content = `${hashPrefix + request}\x00${unescape(localName)}`; // Using `[path]` placeholder outputs `/` we need escape their
  69. // Also directories can contains invalid characters for css we need escape their too
  70. return (0, _cssesc.default)((0, _loaderUtils.interpolateName)(loaderContext, localIdentName, options) // For `[hash]` placeholder
  71. .replace(/^((-?[0-9])|--)/, '_$1').replace(filenameReservedRegex, '-').replace(reControlChars, '-').replace(/\./g, '-'), {
  72. isIdentifier: true
  73. }).replace(/\\\[local\\]/gi, localName);
  74. }
  75. function normalizeUrl(url, isStringValue) {
  76. let normalizedUrl = url;
  77. if (isStringValue && /\\(\n|\r\n|\r|\f)/.test(normalizedUrl)) {
  78. normalizedUrl = normalizedUrl.replace(/\\(\n|\r\n|\r|\f)/g, '');
  79. }
  80. if (matchNativeWin32Path.test(url)) {
  81. return decodeURIComponent(normalizedUrl);
  82. }
  83. return decodeURIComponent(unescape(normalizedUrl));
  84. }
  85. function requestify(url, rootContext) {
  86. if (/^file:/i.test(url)) {
  87. return (0, _url.fileURLToPath)(url);
  88. }
  89. return url.charAt(0) === '/' ? (0, _loaderUtils.urlToRequest)(url, rootContext) : (0, _loaderUtils.urlToRequest)(url);
  90. }
  91. function getFilter(filter, resourcePath) {
  92. return (...args) => {
  93. if (typeof filter === 'function') {
  94. return filter(...args, resourcePath);
  95. }
  96. return true;
  97. };
  98. }
  99. const moduleRegExp = /\.module\.\w+$/i;
  100. function getModulesOptions(rawOptions, loaderContext) {
  101. const {
  102. resourcePath
  103. } = loaderContext;
  104. if (typeof rawOptions.modules === 'undefined') {
  105. const isModules = moduleRegExp.test(resourcePath);
  106. if (!isModules) {
  107. return false;
  108. }
  109. } else if (typeof rawOptions.modules === 'boolean' && rawOptions.modules === false) {
  110. return false;
  111. }
  112. let modulesOptions = {
  113. compileType: rawOptions.icss ? 'icss' : 'module',
  114. auto: true,
  115. mode: 'local',
  116. exportGlobals: false,
  117. localIdentName: '[hash:base64]',
  118. localIdentContext: loaderContext.rootContext,
  119. localIdentHashPrefix: '',
  120. // eslint-disable-next-line no-undefined
  121. localIdentRegExp: undefined,
  122. getLocalIdent: defaultGetLocalIdent,
  123. namedExport: false,
  124. exportLocalsConvention: 'asIs',
  125. exportOnlyLocals: false
  126. };
  127. if (typeof rawOptions.modules === 'boolean' || typeof rawOptions.modules === 'string') {
  128. modulesOptions.mode = typeof rawOptions.modules === 'string' ? rawOptions.modules : 'local';
  129. } else {
  130. if (rawOptions.modules) {
  131. if (typeof rawOptions.modules.auto === 'boolean') {
  132. const isModules = rawOptions.modules.auto && moduleRegExp.test(resourcePath);
  133. if (!isModules) {
  134. return false;
  135. }
  136. } else if (rawOptions.modules.auto instanceof RegExp) {
  137. const isModules = rawOptions.modules.auto.test(resourcePath);
  138. if (!isModules) {
  139. return false;
  140. }
  141. } else if (typeof rawOptions.modules.auto === 'function') {
  142. const isModule = rawOptions.modules.auto(resourcePath);
  143. if (!isModule) {
  144. return false;
  145. }
  146. }
  147. if (rawOptions.modules.namedExport === true && typeof rawOptions.modules.exportLocalsConvention === 'undefined') {
  148. modulesOptions.exportLocalsConvention = 'camelCaseOnly';
  149. }
  150. }
  151. modulesOptions = { ...modulesOptions,
  152. ...(rawOptions.modules || {})
  153. };
  154. }
  155. if (typeof modulesOptions.mode === 'function') {
  156. modulesOptions.mode = modulesOptions.mode(loaderContext.resourcePath);
  157. }
  158. if (modulesOptions.namedExport === true) {
  159. if (rawOptions.esModule === false) {
  160. throw new Error('The "modules.namedExport" option requires the "esModules" option to be enabled');
  161. }
  162. if (modulesOptions.exportLocalsConvention !== 'camelCaseOnly') {
  163. throw new Error('The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly"');
  164. }
  165. }
  166. return modulesOptions;
  167. }
  168. function normalizeOptions(rawOptions, loaderContext) {
  169. if (rawOptions.icss) {
  170. loaderContext.emitWarning(new Error('The "icss" option is deprecated, use "modules.compileType: "icss"" instead'));
  171. }
  172. const modulesOptions = getModulesOptions(rawOptions, loaderContext);
  173. return {
  174. url: typeof rawOptions.url === 'undefined' ? true : rawOptions.url,
  175. import: typeof rawOptions.import === 'undefined' ? true : rawOptions.import,
  176. modules: modulesOptions,
  177. // TODO remove in the next major release
  178. icss: typeof rawOptions.icss === 'undefined' ? false : rawOptions.icss,
  179. sourceMap: typeof rawOptions.sourceMap === 'boolean' ? rawOptions.sourceMap : loaderContext.sourceMap,
  180. importLoaders: typeof rawOptions.importLoaders === 'string' ? parseInt(rawOptions.importLoaders, 10) : rawOptions.importLoaders,
  181. esModule: typeof rawOptions.esModule === 'undefined' ? true : rawOptions.esModule
  182. };
  183. }
  184. function shouldUseImportPlugin(options) {
  185. if (options.modules.exportOnlyLocals) {
  186. return false;
  187. }
  188. if (typeof options.import === 'boolean') {
  189. return options.import;
  190. }
  191. return true;
  192. }
  193. function shouldUseURLPlugin(options) {
  194. if (options.modules.exportOnlyLocals) {
  195. return false;
  196. }
  197. if (typeof options.url === 'boolean') {
  198. return options.url;
  199. }
  200. return true;
  201. }
  202. function shouldUseModulesPlugins(options) {
  203. return options.modules.compileType === 'module';
  204. }
  205. function shouldUseIcssPlugin(options) {
  206. return options.icss === true || Boolean(options.modules);
  207. }
  208. function getModulesPlugins(options, loaderContext) {
  209. const {
  210. mode,
  211. getLocalIdent,
  212. localIdentName,
  213. localIdentContext,
  214. localIdentHashPrefix,
  215. localIdentRegExp
  216. } = options.modules;
  217. let plugins = [];
  218. try {
  219. plugins = [_postcssModulesValues.default, (0, _postcssModulesLocalByDefault.default)({
  220. mode
  221. }), (0, _postcssModulesExtractImports.default)(), (0, _postcssModulesScope.default)({
  222. generateScopedName(exportName) {
  223. return getLocalIdent(loaderContext, localIdentName, exportName, {
  224. context: localIdentContext,
  225. hashPrefix: localIdentHashPrefix,
  226. regExp: localIdentRegExp
  227. });
  228. },
  229. exportGlobals: options.modules.exportGlobals
  230. })];
  231. } catch (error) {
  232. loaderContext.emitError(error);
  233. }
  234. return plugins;
  235. }
  236. const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
  237. const ABSOLUTE_SCHEME = /^[a-z0-9+\-.]+:/i;
  238. function getURLType(source) {
  239. if (source[0] === '/') {
  240. if (source[1] === '/') {
  241. return 'scheme-relative';
  242. }
  243. return 'path-absolute';
  244. }
  245. if (IS_NATIVE_WIN32_PATH.test(source)) {
  246. return 'path-absolute';
  247. }
  248. return ABSOLUTE_SCHEME.test(source) ? 'absolute' : 'path-relative';
  249. }
  250. function normalizeSourceMap(map, resourcePath) {
  251. let newMap = map; // Some loader emit source map as string
  252. // Strip any JSON XSSI avoidance prefix from the string (as documented in the source maps specification), and then parse the string as JSON.
  253. if (typeof newMap === 'string') {
  254. newMap = JSON.parse(newMap);
  255. }
  256. delete newMap.file;
  257. const {
  258. sourceRoot
  259. } = newMap;
  260. delete newMap.sourceRoot;
  261. if (newMap.sources) {
  262. // Source maps should use forward slash because it is URLs (https://github.com/mozilla/source-map/issues/91)
  263. // We should normalize path because previous loaders like `sass-loader` using backslash when generate source map
  264. newMap.sources = newMap.sources.map(source => {
  265. // Non-standard syntax from `postcss`
  266. if (source.indexOf('<') === 0) {
  267. return source;
  268. }
  269. const sourceType = getURLType(source); // Do no touch `scheme-relative` and `absolute` URLs
  270. if (sourceType === 'path-relative' || sourceType === 'path-absolute') {
  271. const absoluteSource = sourceType === 'path-relative' && sourceRoot ? _path.default.resolve(sourceRoot, normalizePath(source)) : normalizePath(source);
  272. return _path.default.relative(_path.default.dirname(resourcePath), absoluteSource);
  273. }
  274. return source;
  275. });
  276. }
  277. return newMap;
  278. }
  279. function getPreRequester({
  280. loaders,
  281. loaderIndex
  282. }) {
  283. const cache = Object.create(null);
  284. return number => {
  285. if (cache[number]) {
  286. return cache[number];
  287. }
  288. if (number === false) {
  289. cache[number] = '';
  290. } else {
  291. const loadersRequest = loaders.slice(loaderIndex, loaderIndex + 1 + (typeof number !== 'number' ? 0 : number)).map(x => x.request).join('!');
  292. cache[number] = `-!${loadersRequest}!`;
  293. }
  294. return cache[number];
  295. };
  296. }
  297. function getImportCode(imports, options) {
  298. let code = '';
  299. for (const item of imports) {
  300. const {
  301. importName,
  302. url,
  303. icss
  304. } = item;
  305. if (options.esModule) {
  306. if (icss && options.modules.namedExport) {
  307. code += `import ${options.modules.exportOnlyLocals ? '' : `${importName}, `}* as ${importName}_NAMED___ from ${url};\n`;
  308. } else {
  309. code += `import ${importName} from ${url};\n`;
  310. }
  311. } else {
  312. code += `var ${importName} = require(${url});\n`;
  313. }
  314. }
  315. return code ? `// Imports\n${code}` : '';
  316. }
  317. function normalizeSourceMapForRuntime(map, loaderContext) {
  318. const resultMap = map ? map.toJSON() : null;
  319. if (resultMap) {
  320. delete resultMap.file;
  321. resultMap.sourceRoot = '';
  322. resultMap.sources = resultMap.sources.map(source => {
  323. // Non-standard syntax from `postcss`
  324. if (source.indexOf('<') === 0) {
  325. return source;
  326. }
  327. const sourceType = getURLType(source);
  328. if (sourceType !== 'path-relative') {
  329. return source;
  330. }
  331. const resourceDirname = _path.default.dirname(loaderContext.resourcePath);
  332. const absoluteSource = _path.default.resolve(resourceDirname, source);
  333. const contextifyPath = normalizePath(_path.default.relative(loaderContext.rootContext, absoluteSource));
  334. return `webpack://${contextifyPath}`;
  335. });
  336. }
  337. return JSON.stringify(resultMap);
  338. }
  339. function getModuleCode(result, api, replacements, options, loaderContext) {
  340. if (options.modules.exportOnlyLocals === true) {
  341. return '';
  342. }
  343. const sourceMapValue = options.sourceMap ? `,${normalizeSourceMapForRuntime(result.map, loaderContext)}` : '';
  344. let code = JSON.stringify(result.css);
  345. let beforeCode = `var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(${options.sourceMap});\n`;
  346. for (const item of api) {
  347. const {
  348. url,
  349. media,
  350. dedupe
  351. } = item;
  352. beforeCode += url ? `___CSS_LOADER_EXPORT___.push([module.id, ${JSON.stringify(`@import url(${url});`)}${media ? `, ${JSON.stringify(media)}` : ''}]);\n` : `___CSS_LOADER_EXPORT___.i(${item.importName}${media ? `, ${JSON.stringify(media)}` : dedupe ? ', ""' : ''}${dedupe ? ', true' : ''});\n`;
  353. }
  354. for (const item of replacements) {
  355. const {
  356. replacementName,
  357. importName,
  358. localName
  359. } = item;
  360. if (localName) {
  361. code = code.replace(new RegExp(replacementName, 'g'), () => options.modules.namedExport ? `" + ${importName}_NAMED___[${JSON.stringify((0, _camelcase.default)(localName))}] + "` : `" + ${importName}.locals[${JSON.stringify(localName)}] + "`);
  362. } else {
  363. const {
  364. hash,
  365. needQuotes
  366. } = item;
  367. const getUrlOptions = [].concat(hash ? [`hash: ${JSON.stringify(hash)}`] : []).concat(needQuotes ? 'needQuotes: true' : []);
  368. const preparedOptions = getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(', ')} }` : '';
  369. beforeCode += `var ${replacementName} = ___CSS_LOADER_GET_URL_IMPORT___(${importName}${preparedOptions});\n`;
  370. code = code.replace(new RegExp(replacementName, 'g'), () => `" + ${replacementName} + "`);
  371. }
  372. }
  373. return `${beforeCode}// Module\n___CSS_LOADER_EXPORT___.push([module.id, ${code}, ""${sourceMapValue}]);\n`;
  374. }
  375. function dashesCamelCase(str) {
  376. return str.replace(/-+(\w)/g, (match, firstLetter) => firstLetter.toUpperCase());
  377. }
  378. function getExportCode(exports, replacements, options) {
  379. let code = '// Exports\n';
  380. let localsCode = '';
  381. const addExportToLocalsCode = (name, value) => {
  382. if (options.modules.namedExport) {
  383. localsCode += `export const ${(0, _camelcase.default)(name)} = ${JSON.stringify(value)};\n`;
  384. } else {
  385. if (localsCode) {
  386. localsCode += `,\n`;
  387. }
  388. localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
  389. }
  390. };
  391. for (const {
  392. name,
  393. value
  394. } of exports) {
  395. switch (options.modules.exportLocalsConvention) {
  396. case 'camelCase':
  397. {
  398. addExportToLocalsCode(name, value);
  399. const modifiedName = (0, _camelcase.default)(name);
  400. if (modifiedName !== name) {
  401. addExportToLocalsCode(modifiedName, value);
  402. }
  403. break;
  404. }
  405. case 'camelCaseOnly':
  406. {
  407. addExportToLocalsCode((0, _camelcase.default)(name), value);
  408. break;
  409. }
  410. case 'dashes':
  411. {
  412. addExportToLocalsCode(name, value);
  413. const modifiedName = dashesCamelCase(name);
  414. if (modifiedName !== name) {
  415. addExportToLocalsCode(modifiedName, value);
  416. }
  417. break;
  418. }
  419. case 'dashesOnly':
  420. {
  421. addExportToLocalsCode(dashesCamelCase(name), value);
  422. break;
  423. }
  424. case 'asIs':
  425. default:
  426. addExportToLocalsCode(name, value);
  427. break;
  428. }
  429. }
  430. for (const item of replacements) {
  431. const {
  432. replacementName,
  433. localName
  434. } = item;
  435. if (localName) {
  436. const {
  437. importName
  438. } = item;
  439. localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () => {
  440. if (options.modules.namedExport) {
  441. return `" + ${importName}_NAMED___[${JSON.stringify((0, _camelcase.default)(localName))}] + "`;
  442. } else if (options.modules.exportOnlyLocals) {
  443. return `" + ${importName}[${JSON.stringify(localName)}] + "`;
  444. }
  445. return `" + ${importName}.locals[${JSON.stringify(localName)}] + "`;
  446. });
  447. } else {
  448. localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () => `" + ${replacementName} + "`);
  449. }
  450. }
  451. if (options.modules.exportOnlyLocals) {
  452. code += options.modules.namedExport ? localsCode : `${options.esModule ? 'export default' : 'module.exports ='} {\n${localsCode}\n};\n`;
  453. return code;
  454. }
  455. if (localsCode) {
  456. code += options.modules.namedExport ? localsCode : `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
  457. }
  458. code += `${options.esModule ? 'export default' : 'module.exports ='} ___CSS_LOADER_EXPORT___;\n`;
  459. return code;
  460. }
  461. async function resolveRequests(resolve, context, possibleRequests) {
  462. return resolve(context, possibleRequests[0]).then(result => {
  463. return result;
  464. }).catch(error => {
  465. const [, ...tailPossibleRequests] = possibleRequests;
  466. if (tailPossibleRequests.length === 0) {
  467. throw error;
  468. }
  469. return resolveRequests(resolve, context, tailPossibleRequests);
  470. });
  471. }
  472. function isUrlRequestable(url) {
  473. // Protocol-relative URLs
  474. if (/^\/\//.test(url)) {
  475. return false;
  476. } // `file:` protocol
  477. if (/^file:/i.test(url)) {
  478. return true;
  479. } // Absolute URLs
  480. if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !matchNativeWin32Path.test(url)) {
  481. return false;
  482. } // `#` URLs
  483. if (/^#/.test(url)) {
  484. return false;
  485. }
  486. return true;
  487. }
  488. function sort(a, b) {
  489. return a.index - b.index;
  490. }