utils.js 27 KB

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