utils.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.getCompileFn = getCompileFn;
  6. exports.getModernWebpackImporter = getModernWebpackImporter;
  7. exports.getSassImplementation = getSassImplementation;
  8. exports.getSassOptions = getSassOptions;
  9. exports.getWebpackImporter = getWebpackImporter;
  10. exports.getWebpackResolver = getWebpackResolver;
  11. exports.isSupportedFibers = isSupportedFibers;
  12. exports.normalizeSourceMap = normalizeSourceMap;
  13. var _url = _interopRequireDefault(require("url"));
  14. var _path = _interopRequireDefault(require("path"));
  15. var _full = require("klona/full");
  16. var _neoAsync = _interopRequireDefault(require("neo-async"));
  17. var _SassWarning = _interopRequireDefault(require("./SassWarning"));
  18. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  19. function getDefaultSassImplementation() {
  20. let sassImplPkg = "sass";
  21. try {
  22. require.resolve("sass");
  23. } catch (ignoreError) {
  24. try {
  25. require.resolve("node-sass");
  26. sassImplPkg = "node-sass";
  27. } catch (_ignoreError) {
  28. try {
  29. require.resolve("sass-embedded");
  30. sassImplPkg = "sass-embedded";
  31. } catch (__ignoreError) {
  32. sassImplPkg = "sass";
  33. }
  34. }
  35. } // eslint-disable-next-line import/no-dynamic-require, global-require
  36. return require(sassImplPkg);
  37. }
  38. /**
  39. * This function is not Webpack-specific and can be used by tools wishing to mimic `sass-loader`'s behaviour, so its signature should not be changed.
  40. */
  41. function getSassImplementation(loaderContext, implementation) {
  42. let resolvedImplementation = implementation;
  43. if (!resolvedImplementation) {
  44. try {
  45. resolvedImplementation = getDefaultSassImplementation();
  46. } catch (error) {
  47. loaderContext.emitError(error);
  48. return;
  49. }
  50. }
  51. if (typeof resolvedImplementation === "string") {
  52. try {
  53. // eslint-disable-next-line import/no-dynamic-require, global-require
  54. resolvedImplementation = require(resolvedImplementation);
  55. } catch (error) {
  56. loaderContext.emitError(error); // eslint-disable-next-line consistent-return
  57. return;
  58. }
  59. }
  60. const {
  61. info
  62. } = resolvedImplementation;
  63. if (!info) {
  64. loaderContext.emitError(new Error("Unknown Sass implementation."));
  65. return;
  66. }
  67. const infoParts = info.split("\t");
  68. if (infoParts.length < 2) {
  69. loaderContext.emitError(new Error(`Unknown Sass implementation "${info}".`));
  70. return;
  71. }
  72. const [implementationName] = infoParts;
  73. if (implementationName === "dart-sass") {
  74. // eslint-disable-next-line consistent-return
  75. return resolvedImplementation;
  76. } else if (implementationName === "node-sass") {
  77. // eslint-disable-next-line consistent-return
  78. return resolvedImplementation;
  79. } else if (implementationName === "sass-embedded") {
  80. // eslint-disable-next-line consistent-return
  81. return resolvedImplementation;
  82. }
  83. loaderContext.emitError(new Error(`Unknown Sass implementation "${implementationName}".`));
  84. }
  85. /**
  86. * @param {any} loaderContext
  87. * @returns {boolean}
  88. */
  89. function isProductionLikeMode(loaderContext) {
  90. return loaderContext.mode === "production" || !loaderContext.mode;
  91. }
  92. function proxyCustomImporters(importers, loaderContext) {
  93. return [].concat(importers).map(importer => function proxyImporter(...args) {
  94. const self = { ...this,
  95. webpackLoaderContext: loaderContext
  96. };
  97. return importer.apply(self, args);
  98. });
  99. }
  100. function isSupportedFibers() {
  101. const [nodeVersion] = process.versions.node.split(".");
  102. return Number(nodeVersion) < 16;
  103. }
  104. /**
  105. * Derives the sass options from the loader context and normalizes its values with sane defaults.
  106. *
  107. * @param {object} loaderContext
  108. * @param {object} loaderOptions
  109. * @param {string} content
  110. * @param {object} implementation
  111. * @param {boolean} useSourceMap
  112. * @returns {Object}
  113. */
  114. async function getSassOptions(loaderContext, loaderOptions, content, implementation, useSourceMap) {
  115. const options = (0, _full.klona)(loaderOptions.sassOptions ? typeof loaderOptions.sassOptions === "function" ? loaderOptions.sassOptions(loaderContext) || {} : loaderOptions.sassOptions : {});
  116. const isDartSass = implementation.info.includes("dart-sass");
  117. const isModernAPI = loaderOptions.api === "modern";
  118. options.data = loaderOptions.additionalData ? typeof loaderOptions.additionalData === "function" ? await loaderOptions.additionalData(content, loaderContext) : `${loaderOptions.additionalData}\n${content}` : content;
  119. if (!options.logger) {
  120. // TODO set me to `true` by default in the next major release
  121. const needEmitWarning = loaderOptions.warnRuleAsWarning === true;
  122. const logger = loaderContext.getLogger("sass-loader");
  123. const formatSpan = span => `${span.url || "-"}:${span.start.line}:${span.start.column}: `;
  124. options.logger = {
  125. debug(message, loggerOptions) {
  126. let builtMessage = "";
  127. if (loggerOptions.span) {
  128. builtMessage = formatSpan(loggerOptions.span);
  129. }
  130. builtMessage += message;
  131. logger.debug(builtMessage);
  132. },
  133. warn(message, loggerOptions) {
  134. let builtMessage = "";
  135. if (loggerOptions.deprecation) {
  136. builtMessage += "Deprecation ";
  137. }
  138. if (loggerOptions.span && !loggerOptions.stack) {
  139. builtMessage = formatSpan(loggerOptions.span);
  140. }
  141. builtMessage += message;
  142. if (loggerOptions.stack) {
  143. builtMessage += `\n\n${loggerOptions.stack}`;
  144. }
  145. if (needEmitWarning) {
  146. loaderContext.emitWarning(new _SassWarning.default(builtMessage, loggerOptions));
  147. } else {
  148. logger.warn(builtMessage);
  149. }
  150. }
  151. };
  152. }
  153. const {
  154. resourcePath
  155. } = loaderContext;
  156. if (isModernAPI) {
  157. options.url = _url.default.pathToFileURL(resourcePath); // opt.outputStyle
  158. if (!options.style && isProductionLikeMode(loaderContext)) {
  159. options.style = "compressed";
  160. }
  161. if (useSourceMap) {
  162. options.sourceMap = true;
  163. } // If we are compiling sass and indentedSyntax isn't set, automatically set it.
  164. if (typeof options.syntax === "undefined") {
  165. const ext = _path.default.extname(resourcePath);
  166. if (ext && ext.toLowerCase() === ".scss") {
  167. options.syntax = "scss";
  168. } else if (ext && ext.toLowerCase() === ".sass") {
  169. options.syntax = "indented";
  170. } else if (ext && ext.toLowerCase() === ".css") {
  171. options.syntax = "css";
  172. }
  173. }
  174. options.importers = options.importers ? proxyCustomImporters(Array.isArray(options.importers) ? options.importers : [options.importers], loaderContext) : [];
  175. } else {
  176. options.file = resourcePath;
  177. if (isDartSass && isSupportedFibers()) {
  178. const shouldTryToResolveFibers = !options.fiber && options.fiber !== false;
  179. if (shouldTryToResolveFibers) {
  180. let fibers;
  181. try {
  182. fibers = require.resolve("fibers");
  183. } catch (_error) {// Nothing
  184. }
  185. if (fibers) {
  186. // eslint-disable-next-line global-require, import/no-dynamic-require
  187. options.fiber = require(fibers);
  188. }
  189. } else if (options.fiber === false) {
  190. // Don't pass the `fiber` option for `sass` (`Dart Sass`)
  191. delete options.fiber;
  192. }
  193. } else {
  194. // Don't pass the `fiber` option for `node-sass`
  195. delete options.fiber;
  196. } // opt.outputStyle
  197. if (!options.outputStyle && isProductionLikeMode(loaderContext)) {
  198. options.outputStyle = "compressed";
  199. }
  200. if (useSourceMap) {
  201. // Deliberately overriding the sourceMap option here.
  202. // node-sass won't produce source maps if the data option is used and options.sourceMap is not a string.
  203. // In case it is a string, options.sourceMap should be a path where the source map is written.
  204. // But since we're using the data option, the source map will not actually be written, but
  205. // all paths in sourceMap.sources will be relative to that path.
  206. // Pretty complicated... :(
  207. options.sourceMap = true;
  208. options.outFile = _path.default.join(loaderContext.rootContext, "style.css.map");
  209. options.sourceMapContents = true;
  210. options.omitSourceMapUrl = true;
  211. options.sourceMapEmbed = false;
  212. }
  213. const ext = _path.default.extname(resourcePath); // If we are compiling sass and indentedSyntax isn't set, automatically set it.
  214. if (ext && ext.toLowerCase() === ".sass" && typeof options.indentedSyntax === "undefined") {
  215. options.indentedSyntax = true;
  216. } else {
  217. options.indentedSyntax = Boolean(options.indentedSyntax);
  218. } // Allow passing custom importers to `sass`/`node-sass`. Accepts `Function` or an array of `Function`s.
  219. options.importer = options.importer ? proxyCustomImporters(Array.isArray(options.importer) ? options.importer : [options.importer], loaderContext) : [];
  220. options.includePaths = [].concat(process.cwd()).concat( // We use `includePaths` in context for resolver, so it should be always absolute
  221. (options.includePaths || []).map(includePath => _path.default.isAbsolute(includePath) ? includePath : _path.default.join(process.cwd(), includePath))).concat(process.env.SASS_PATH ? process.env.SASS_PATH.split(process.platform === "win32" ? ";" : ":") : []);
  222. if (typeof options.charset === "undefined") {
  223. options.charset = true;
  224. }
  225. }
  226. return options;
  227. }
  228. const MODULE_REQUEST_REGEX = /^[^?]*~/; // Examples:
  229. // - ~package
  230. // - ~package/
  231. // - ~@org
  232. // - ~@org/
  233. // - ~@org/package
  234. // - ~@org/package/
  235. const IS_MODULE_IMPORT = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
  236. /**
  237. * When `sass`/`node-sass` tries to resolve an import, it uses a special algorithm.
  238. * Since the `sass-loader` uses webpack to resolve the modules, we need to simulate that algorithm.
  239. * This function returns an array of import paths to try.
  240. * The last entry in the array is always the original url to enable straight-forward webpack.config aliases.
  241. *
  242. * We don't need emulate `dart-sass` "It's not clear which file to import." errors (when "file.ext" and "_file.ext" files are present simultaneously in the same directory).
  243. * This reduces performance and `dart-sass` always do it on own side.
  244. *
  245. * @param {string} url
  246. * @param {boolean} forWebpackResolver
  247. * @param {boolean} fromImport
  248. * @returns {Array<string>}
  249. */
  250. function getPossibleRequests( // eslint-disable-next-line no-shadow
  251. url, forWebpackResolver = false, fromImport = false) {
  252. let request = url; // In case there is module request, send this to webpack resolver
  253. if (forWebpackResolver) {
  254. if (MODULE_REQUEST_REGEX.test(url)) {
  255. request = request.replace(MODULE_REQUEST_REGEX, "");
  256. }
  257. if (IS_MODULE_IMPORT.test(url)) {
  258. request = request[request.length - 1] === "/" ? request : `${request}/`;
  259. return [...new Set([request, url])];
  260. }
  261. } // Keep in mind: ext can also be something like '.datepicker' when the true extension is omitted and the filename contains a dot.
  262. // @see https://github.com/webpack-contrib/sass-loader/issues/167
  263. const extension = _path.default.extname(request).toLowerCase(); // Because @import is also defined in CSS, Sass needs a way of compiling plain CSS @imports without trying to import the files at compile time.
  264. // To accomplish this, and to ensure SCSS is as much of a superset of CSS as possible, Sass will compile any @imports with the following characteristics to plain CSS imports:
  265. // - imports where the URL ends with .css.
  266. // - imports where the URL begins http:// or https://.
  267. // - imports where the URL is written as a url().
  268. // - imports that have media queries.
  269. //
  270. // The `node-sass` package sends `@import` ending on `.css` to importer, it is bug, so we skip resolve
  271. if (extension === ".css") {
  272. return [];
  273. }
  274. const dirname = _path.default.dirname(request);
  275. const normalizedDirname = dirname === "." ? "" : `${dirname}/`;
  276. const basename = _path.default.basename(request);
  277. const basenameWithoutExtension = _path.default.basename(request, extension);
  278. return [...new Set([].concat(fromImport ? [`${normalizedDirname}_${basenameWithoutExtension}.import${extension}`, `${normalizedDirname}${basenameWithoutExtension}.import${extension}`] : []).concat([`${normalizedDirname}_${basename}`, `${normalizedDirname}${basename}`]).concat(forWebpackResolver ? [url] : []))];
  279. }
  280. function promiseResolve(callbackResolve) {
  281. return (context, request) => new Promise((resolve, reject) => {
  282. callbackResolve(context, request, (error, result) => {
  283. if (error) {
  284. reject(error);
  285. } else {
  286. resolve(result);
  287. }
  288. });
  289. });
  290. }
  291. async function startResolving(resolutionMap) {
  292. if (resolutionMap.length === 0) {
  293. return Promise.reject();
  294. }
  295. const [{
  296. possibleRequests
  297. }] = resolutionMap;
  298. if (possibleRequests.length === 0) {
  299. return Promise.reject();
  300. }
  301. const [{
  302. resolve,
  303. context
  304. }] = resolutionMap;
  305. try {
  306. return await resolve(context, possibleRequests[0]);
  307. } catch (_ignoreError) {
  308. const [, ...tailResult] = possibleRequests;
  309. if (tailResult.length === 0) {
  310. const [, ...tailResolutionMap] = resolutionMap;
  311. return startResolving(tailResolutionMap);
  312. } // eslint-disable-next-line no-param-reassign
  313. resolutionMap[0].possibleRequests = tailResult;
  314. return startResolving(resolutionMap);
  315. }
  316. }
  317. const IS_SPECIAL_MODULE_IMPORT = /^~[^/]+$/; // `[drive_letter]:\` + `\\[server]\[sharename]\`
  318. const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
  319. /**
  320. * @public
  321. * Create the resolve function used in the custom Sass importer.
  322. *
  323. * Can be used by external tools to mimic how `sass-loader` works, for example
  324. * in a Jest transform. Such usages will want to wrap `resolve.create` from
  325. * [`enhanced-resolve`]{@link https://github.com/webpack/enhanced-resolve} to
  326. * pass as the `resolverFactory` argument.
  327. *
  328. * @param {Function} resolverFactory - A factory function for creating a Webpack
  329. * resolver.
  330. * @param {Object} implementation - The imported Sass implementation, both
  331. * `sass` (Dart Sass) and `node-sass` are supported.
  332. * @param {string[]} [includePaths] - The list of include paths passed to Sass.
  333. *
  334. * @throws If a compatible Sass implementation cannot be found.
  335. */
  336. function getWebpackResolver(resolverFactory, implementation, includePaths = []) {
  337. const isDartSass = implementation && implementation.info.includes("dart-sass"); // We only have one difference with the built-in sass resolution logic and out resolution logic:
  338. // First, we look at the files starting with `_`, then without `_` (i.e. `_name.sass`, `_name.scss`, `_name.css`, `name.sass`, `name.scss`, `name.css`),
  339. // although `sass` look together by extensions (i.e. `_name.sass`/`name.sass`/`_name.scss`/`name.scss`/`_name.css`/`name.css`).
  340. // It shouldn't be a problem because `sass` throw errors:
  341. // - on having `_name.sass` and `name.sass` (extension can be `sass`, `scss` or `css`) in the same directory
  342. // - on having `_name.sass` and `_name.scss` in the same directory
  343. //
  344. // Also `sass` prefer `sass`/`scss` over `css`.
  345. const sassModuleResolve = promiseResolve(resolverFactory({
  346. alias: [],
  347. aliasFields: [],
  348. conditionNames: [],
  349. descriptionFiles: [],
  350. extensions: [".sass", ".scss", ".css"],
  351. exportsFields: [],
  352. mainFields: [],
  353. mainFiles: ["_index", "index"],
  354. modules: [],
  355. restrictions: [/\.((sa|sc|c)ss)$/i],
  356. preferRelative: true
  357. }));
  358. const sassImportResolve = promiseResolve(resolverFactory({
  359. alias: [],
  360. aliasFields: [],
  361. conditionNames: [],
  362. descriptionFiles: [],
  363. extensions: [".sass", ".scss", ".css"],
  364. exportsFields: [],
  365. mainFields: [],
  366. mainFiles: ["_index.import", "_index", "index.import", "index"],
  367. modules: [],
  368. restrictions: [/\.((sa|sc|c)ss)$/i],
  369. preferRelative: true
  370. }));
  371. const webpackModuleResolve = promiseResolve(resolverFactory({
  372. dependencyType: "sass",
  373. conditionNames: ["sass", "style"],
  374. mainFields: ["sass", "style", "main", "..."],
  375. mainFiles: ["_index", "index", "..."],
  376. extensions: [".sass", ".scss", ".css"],
  377. restrictions: [/\.((sa|sc|c)ss)$/i],
  378. preferRelative: true
  379. }));
  380. const webpackImportResolve = promiseResolve(resolverFactory({
  381. dependencyType: "sass",
  382. conditionNames: ["sass", "style"],
  383. mainFields: ["sass", "style", "main", "..."],
  384. mainFiles: ["_index.import", "_index", "index.import", "index", "..."],
  385. extensions: [".sass", ".scss", ".css"],
  386. restrictions: [/\.((sa|sc|c)ss)$/i],
  387. preferRelative: true
  388. }));
  389. return (context, request, fromImport) => {
  390. // See https://github.com/webpack/webpack/issues/12340
  391. // Because `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`
  392. // custom importer may not return `{ file: '/path/to/name.ext' }` and therefore our `context` will be relative
  393. if (!isDartSass && !_path.default.isAbsolute(context)) {
  394. return Promise.reject();
  395. }
  396. const originalRequest = request;
  397. const isFileScheme = originalRequest.slice(0, 5).toLowerCase() === "file:";
  398. if (isFileScheme) {
  399. try {
  400. // eslint-disable-next-line no-param-reassign
  401. request = _url.default.fileURLToPath(originalRequest);
  402. } catch (ignoreError) {
  403. // eslint-disable-next-line no-param-reassign
  404. request = request.slice(7);
  405. }
  406. }
  407. let resolutionMap = [];
  408. const needEmulateSassResolver = // `sass` doesn't support module import
  409. !IS_SPECIAL_MODULE_IMPORT.test(request) && // We need improve absolute paths handling.
  410. // Absolute paths should be resolved:
  411. // - Server-relative URLs - `<context>/path/to/file.ext` (where `<context>` is root context)
  412. // - Absolute path - `/full/path/to/file.ext` or `C:\\full\path\to\file.ext`
  413. !isFileScheme && !originalRequest.startsWith("/") && !IS_NATIVE_WIN32_PATH.test(originalRequest);
  414. if (includePaths.length > 0 && needEmulateSassResolver) {
  415. // The order of import precedence is as follows:
  416. //
  417. // 1. Filesystem imports relative to the base file.
  418. // 2. Custom importer imports.
  419. // 3. Filesystem imports relative to the working directory.
  420. // 4. Filesystem imports relative to an `includePaths` path.
  421. // 5. Filesystem imports relative to a `SASS_PATH` path.
  422. //
  423. // `sass` run custom importers before `3`, `4` and `5` points, we need to emulate this behavior to avoid wrong resolution.
  424. const sassPossibleRequests = getPossibleRequests(request, false, fromImport); // `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`, so we need emulate this too
  425. if (!isDartSass) {
  426. resolutionMap = resolutionMap.concat({
  427. resolve: fromImport ? sassImportResolve : sassModuleResolve,
  428. context: _path.default.dirname(context),
  429. possibleRequests: sassPossibleRequests
  430. });
  431. }
  432. resolutionMap = resolutionMap.concat( // eslint-disable-next-line no-shadow
  433. includePaths.map(context => {
  434. return {
  435. resolve: fromImport ? sassImportResolve : sassModuleResolve,
  436. context,
  437. possibleRequests: sassPossibleRequests
  438. };
  439. }));
  440. }
  441. const webpackPossibleRequests = getPossibleRequests(request, true, fromImport);
  442. resolutionMap = resolutionMap.concat({
  443. resolve: fromImport ? webpackImportResolve : webpackModuleResolve,
  444. context: _path.default.dirname(context),
  445. possibleRequests: webpackPossibleRequests
  446. });
  447. return startResolving(resolutionMap);
  448. };
  449. }
  450. const MATCH_CSS = /\.css$/i;
  451. function getModernWebpackImporter() {
  452. return {
  453. async canonicalize() {
  454. return null;
  455. },
  456. load() {// TODO implement
  457. }
  458. };
  459. }
  460. function getWebpackImporter(loaderContext, implementation, includePaths) {
  461. const resolve = getWebpackResolver(loaderContext.getResolve, implementation, includePaths);
  462. return function importer(originalUrl, prev, done) {
  463. const {
  464. fromImport
  465. } = this;
  466. resolve(prev, originalUrl, fromImport).then(result => {
  467. // Add the result as dependency.
  468. // Although we're also using stats.includedFiles, this might come in handy when an error occurs.
  469. // In this case, we don't get stats.includedFiles from node-sass/sass.
  470. loaderContext.addDependency(_path.default.normalize(result)); // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
  471. done({
  472. file: result.replace(MATCH_CSS, "")
  473. });
  474. }) // Catch all resolving errors, return the original file and pass responsibility back to other custom importers
  475. .catch(() => {
  476. done({
  477. file: originalUrl
  478. });
  479. });
  480. };
  481. }
  482. let nodeSassJobQueue = null;
  483. /**
  484. * Verifies that the implementation and version of Sass is supported by this loader.
  485. *
  486. * @param {Object} implementation
  487. * @param {Object} options
  488. * @returns {Function}
  489. */
  490. function getCompileFn(implementation, options) {
  491. const isNewSass = implementation.info.includes("dart-sass") || implementation.info.includes("sass-embedded");
  492. if (isNewSass) {
  493. if (options.api === "modern") {
  494. return sassOptions => {
  495. const {
  496. data,
  497. ...rest
  498. } = sassOptions;
  499. return implementation.compileStringAsync(data, rest);
  500. };
  501. }
  502. return sassOptions => new Promise((resolve, reject) => {
  503. implementation.render(sassOptions, (error, result) => {
  504. if (error) {
  505. reject(error);
  506. return;
  507. }
  508. resolve(result);
  509. });
  510. });
  511. }
  512. if (options.api === "modern") {
  513. throw new Error("Modern API is not supported for 'node-sass'");
  514. } // There is an issue with node-sass when async custom importers are used
  515. // See https://github.com/sass/node-sass/issues/857#issuecomment-93594360
  516. // We need to use a job queue to make sure that one thread is always available to the UV lib
  517. if (nodeSassJobQueue === null) {
  518. const threadPoolSize = Number(process.env.UV_THREADPOOL_SIZE || 4);
  519. nodeSassJobQueue = _neoAsync.default.queue(implementation.render.bind(implementation), threadPoolSize - 1);
  520. }
  521. return sassOptions => new Promise((resolve, reject) => {
  522. nodeSassJobQueue.push.bind(nodeSassJobQueue)(sassOptions, (error, result) => {
  523. if (error) {
  524. reject(error);
  525. return;
  526. }
  527. resolve(result);
  528. });
  529. });
  530. }
  531. const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/;
  532. /**
  533. * @param {string} source
  534. * @returns {"absolute" | "scheme-relative" | "path-absolute" | "path-absolute"}
  535. */
  536. function getURLType(source) {
  537. if (source[0] === "/") {
  538. if (source[1] === "/") {
  539. return "scheme-relative";
  540. }
  541. return "path-absolute";
  542. }
  543. if (IS_NATIVE_WIN32_PATH.test(source)) {
  544. return "path-absolute";
  545. }
  546. return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative";
  547. }
  548. function normalizeSourceMap(map, rootContext) {
  549. const newMap = map; // result.map.file is an optional property that provides the output filename.
  550. // Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
  551. // eslint-disable-next-line no-param-reassign
  552. if (typeof newMap.file !== "undefined") {
  553. delete newMap.file;
  554. } // eslint-disable-next-line no-param-reassign
  555. newMap.sourceRoot = ""; // node-sass returns POSIX paths, that's why we need to transform them back to native paths.
  556. // This fixes an error on windows where the source-map module cannot resolve the source maps.
  557. // @see https://github.com/webpack-contrib/sass-loader/issues/366#issuecomment-279460722
  558. // eslint-disable-next-line no-param-reassign
  559. newMap.sources = newMap.sources.map(source => {
  560. const sourceType = getURLType(source); // Do no touch `scheme-relative`, `path-absolute` and `absolute` types (except `file:`)
  561. if (sourceType === "absolute" && /^file:/i.test(source)) {
  562. return _url.default.fileURLToPath(source);
  563. } else if (sourceType === "path-relative") {
  564. return _path.default.resolve(rootContext, _path.default.normalize(source));
  565. }
  566. return source;
  567. });
  568. return newMap;
  569. }