Initial commit

This commit is contained in:
Developer
2025-04-21 16:03:20 +02:00
commit 2832896157
22874 changed files with 3092801 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
"use strict";
const path = require("path");
const os = require("os");
const fs = require("fs");
// A typical sass error looks like this
const SassError = { // eslint-disable-line no-unused-vars
message: "invalid property name",
column: 14,
line: 1,
file: "stdin",
status: 1
};
/**
* Enhances the sass error with additional information about what actually went wrong.
*
* @param {SassError} err
* @param {string} resourcePath
*/
function formatSassError(err, resourcePath) {
// Instruct webpack to hide the JS stack from the console
// Usually you're only interested in the SASS stack in this case.
err.hideStack = true;
// The file property is missing in rare cases.
// No improvement in the error is possible.
if (!err.file) {
return;
}
let msg = err.message;
if (err.file === "stdin") {
err.file = resourcePath;
}
// node-sass returns UNIX-style paths
err.file = path.normalize(err.file);
// The 'Current dir' hint of node-sass does not help us, we're providing
// additional information by reading the err.file property
msg = msg.replace(/\s*Current dir:\s*/, "");
err.message = getFileExcerptIfPossible(err) +
msg.charAt(0).toUpperCase() + msg.slice(1) + os.EOL +
" in " + err.file + " (line " + err.line + ", column " + err.column + ")";
}
/**
* Tries to get an excerpt of the file where the error happened.
* Uses err.line and err.column.
*
* Returns an empty string if the excerpt could not be retrieved.
*
* @param {SassError} err
* @returns {string}
*/
function getFileExcerptIfPossible(err) {
try {
const content = fs.readFileSync(err.file, "utf8");
return os.EOL +
content.split(os.EOL)[err.line - 1] + os.EOL +
new Array(err.column - 1).join(" ") + "^" + os.EOL +
" ";
} catch (err) {
// If anything goes wrong here, we don't want any errors to be reported to the user
return "";
}
}
module.exports = formatSassError;

View File

@@ -0,0 +1,58 @@
"use strict";
const path = require("path");
// libsass uses this precedence when importing files without extension
const extPrecedence = [".scss", ".sass", ".css"];
/**
* When libsass tries to resolve an import, it uses a special algorithm.
* Since the sass-loader uses webpack to resolve the modules, we need to simulate that algorithm. This function
* returns an array of import paths to try.
*
* @param {string} request
* @returns {Array<string>}
*/
function importsToResolve(request) {
// libsass' import algorithm works like this:
// In case there is no file extension...
// - Prefer modules starting with '_'.
// - File extension precedence: .scss, .sass, .css.
// In case there is a file extension...
// - If the file is a CSS-file, do not include it all, but just link it via @import url().
// - The exact file name must match (no auto-resolving of '_'-modules).
// Keep in mind: ext can also be something like '.datepicker' when the true extension is omitted and the filename contains a dot.
// @see https://github.com/webpack-contrib/sass-loader/issues/167
const ext = path.extname(request);
const basename = path.basename(request);
const dirname = path.dirname(request);
const startsWithUnderscore = basename.charAt(0) === "_";
const hasCssExt = ext === ".css";
const hasSassExt = ext === ".scss" || ext === ".sass";
// a module import is an identifier like 'bootstrap-sass'
// We also need to check for dirname since it might also be a deep import like 'bootstrap-sass/something'
let isModuleImport = request.charAt(0) !== "." && dirname === ".";
if (dirname.charAt(0) === "@") {
// Check whether it is a deep import from scoped npm package
// (i.e. @pkg/foo/file), if so, process import as file import;
// otherwise, if we import from root npm scoped package (i.e. @pkg/foo)
// process import as a module import.
isModuleImport = !(dirname.indexOf("/") > -1);
}
return (isModuleImport && [request]) || // Do not modify module imports
(hasCssExt && []) || // Do not import css files
(hasSassExt && [request]) || // Do not modify imports with explicit extensions
(startsWithUnderscore ? [] : extPrecedence) // Do not add underscore imports if there is already an underscore
.map(ext => "_" + basename + ext)
.concat(
extPrecedence.map(ext => basename + ext)
).map(
file => dirname + "/" + file // No path.sep required here, because imports inside SASS are usually with /
);
}
module.exports = importsToResolve;

View File

@@ -0,0 +1,83 @@
"use strict";
const sass = require("node-sass");
const path = require("path");
const async = require("neo-async");
const formatSassError = require("./formatSassError");
const webpackImporter = require("./webpackImporter");
const normalizeOptions = require("./normalizeOptions");
const pify = require("pify");
// This queue makes sure node-sass leaves one thread available for executing
// fs tasks when running the custom importer code.
// This can be removed as soon as node-sass implements a fix for this.
const threadPoolSize = process.env.UV_THREADPOOL_SIZE || 4;
const asyncSassJobQueue = async.queue(sass.render, threadPoolSize - 1);
/**
* The sass-loader makes node-sass available to webpack modules.
*
* @this {LoaderContext}
* @param {string} content
*/
function sassLoader(content) {
const callback = this.async();
const isSync = typeof callback !== "function";
const self = this;
const resourcePath = this.resourcePath;
function addNormalizedDependency(file) {
// node-sass returns POSIX paths
self.dependency(path.normalize(file));
}
if (isSync) {
throw new Error("Synchronous compilation is not supported anymore. See https://github.com/webpack-contrib/sass-loader/issues/333");
}
const options = normalizeOptions(this, content, webpackImporter(
resourcePath,
pify(this.resolve.bind(this)),
addNormalizedDependency
));
// Skip empty files, otherwise it will stop webpack, see issue #21
if (options.data.trim() === "") {
callback(null, "");
return;
}
// start the actual rendering
asyncSassJobQueue.push(options, (err, result) => {
if (err) {
formatSassError(err, this.resourcePath);
err.file && this.dependency(err.file);
callback(err);
return;
}
if (result.map && result.map !== "{}") {
result.map = JSON.parse(result.map);
// result.map.file is an optional property that provides the output filename.
// Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
delete result.map.file;
// The first source is 'stdin' according to node-sass because we've used the data input.
// Now let's override that value with the correct relative path.
// Since we specified options.sourceMap = path.join(process.cwd(), "/sass.map"); in normalizeOptions,
// we know that this path is relative to process.cwd(). This is how node-sass works.
result.map.sources[0] = path.relative(process.cwd(), resourcePath);
// node-sass returns POSIX paths, that's why we need to transform them back to native paths.
// This fixes an error on windows where the source-map module cannot resolve the source maps.
// @see https://github.com/webpack-contrib/sass-loader/issues/366#issuecomment-279460722
result.map.sourceRoot = path.normalize(result.map.sourceRoot);
result.map.sources = result.map.sources.map(path.normalize);
} else {
result.map = null;
}
result.stats.includedFiles.forEach(addNormalizedDependency);
callback(null, result.css.toString(), result.map);
});
}
module.exports = sassLoader;

View File

@@ -0,0 +1,78 @@
"use strict";
const os = require("os");
const utils = require("loader-utils");
const cloneDeep = require("clone-deep");
const path = require("path");
const proxyCustomImporters = require("./proxyCustomImporters");
/**
* Derives the sass options from the loader context and normalizes its values with sane defaults.
*
* Please note: If loaderContext.query is an options object, it will be re-used across multiple invocations.
* That's why we must not modify the object directly.
*
* @param {LoaderContext} loaderContext
* @param {string} content
* @param {Function} webpackImporter
* @returns {Object}
*/
function normalizeOptions(loaderContext, content, webpackImporter) {
const options = cloneDeep(utils.getOptions(loaderContext)) || {};
const resourcePath = loaderContext.resourcePath;
options.data = options.data ? (options.data + os.EOL + content) : content;
// opt.outputStyle
if (!options.outputStyle && loaderContext.minimize) {
options.outputStyle = "compressed";
}
// opt.sourceMap
// Not using the `this.sourceMap` flag because css source maps are different
// @see https://github.com/webpack/css-loader/pull/40
if (options.sourceMap) {
// Deliberately overriding the sourceMap option here.
// node-sass won't produce source maps if the data option is used and options.sourceMap is not a string.
// In case it is a string, options.sourceMap should be a path where the source map is written.
// But since we're using the data option, the source map will not actually be written, but
// all paths in sourceMap.sources will be relative to that path.
// Pretty complicated... :(
options.sourceMap = path.join(process.cwd(), "/sass.map");
if ("sourceMapRoot" in options === false) {
options.sourceMapRoot = process.cwd();
}
if ("omitSourceMapUrl" in options === false) {
// The source map url doesn't make sense because we don't know the output path
// The css-loader will handle that for us
options.omitSourceMapUrl = true;
}
if ("sourceMapContents" in options === false) {
// If sourceMapContents option is not set, set it to true otherwise maps will be empty/null
// when exported by webpack-extract-text-plugin.
options.sourceMapContents = true;
}
}
// indentedSyntax is a boolean flag.
const ext = path.extname(resourcePath);
// If we are compiling sass and indentedSyntax isn't set, automatically set it.
if (ext && ext.toLowerCase() === ".sass" && "indentedSyntax" in options === false) {
options.indentedSyntax = true;
} else {
options.indentedSyntax = Boolean(options.indentedSyntax);
}
// Allow passing custom importers to `node-sass`. Accepts `Function` or an array of `Function`s.
options.importer = options.importer ? proxyCustomImporters(options.importer, resourcePath) : [];
options.importer.push(webpackImporter);
// `node-sass` uses `includePaths` to resolve `@import` paths. Append the currently processed file.
options.includePaths = options.includePaths || [];
options.includePaths.push(path.dirname(resourcePath));
return options;
}
module.exports = normalizeOptions;

View File

@@ -0,0 +1,29 @@
"use strict";
/**
* Creates new custom importers that use the given `resourcePath` if libsass calls the custom importer with `prev`
* being 'stdin'.
*
* Why do we need this? We have to use the `data` option of node-sass in order to compile our sass because
* the `resourcePath` might not be an actual file on disk. When using the `data` option, libsass uses the string
* 'stdin' instead of a filename.
*
* We have to fix this behavior in order to provide a consistent experience to the webpack user.
*
* @param {function|Array<function>} importer
* @param {string} resourcePath
* @returns {Array<function>}
*/
function proxyCustomImporters(importer, resourcePath) {
return [].concat(importer).map((importer) => {
return function (url, prev, done) {
return importer.apply(
this, // eslint-disable-line no-invalid-this
Array.from(arguments)
.map((arg, i) => i === 1 && arg === "stdin" ? resourcePath : arg)
);
};
});
}
module.exports = proxyCustomImporters;

View File

@@ -0,0 +1,73 @@
"use strict";
/**
* @name PromisedResolve
* @type {Function}
* @param {string} dir
* @param {string} request
* @returns Promise
*/
/**
* @name Importer
* @type {Function}
* @param {string} url
* @param {string} prev
* @param {Function<Error, string>} done
*/
const path = require("path");
const utils = require("loader-utils");
const tail = require("lodash.tail");
const importsToResolve = require("./importsToResolve");
const matchCss = /\.css$/;
/**
* Returns an importer that uses webpack's resolving algorithm.
*
* It's important that the returned function has the correct number of arguments
* (based on whether the call is sync or async) because otherwise node-sass doesn't exit.
*
* @param {string} resourcePath
* @param {PromisedResolve} resolve
* @param {Function<string>} addNormalizedDependency
* @returns {Importer}
*/
function webpackImporter(resourcePath, resolve, addNormalizedDependency) {
function dirContextFrom(fileContext) {
return path.dirname(
// The first file is 'stdin' when we're using the data option
fileContext === "stdin" ? resourcePath : fileContext
);
}
function startResolving(dir, importsToResolve) {
return importsToResolve.length === 0 ?
Promise.reject() :
resolve(dir, importsToResolve[0])
.then(resolvedFile => {
// Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come
// in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass.
addNormalizedDependency(resolvedFile);
return {
// By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
file: resolvedFile.replace(matchCss, "")
};
}, () => startResolving(
dir,
tail(importsToResolve)
));
}
return (url, prev, done) => {
startResolving(
dirContextFrom(prev),
importsToResolve(utils.urlToRequest(url))
) // Catch all resolving errors, return the original file and pass responsibility back to other custom importers
.catch(() => ({ file: url }))
.then(done);
};
}
module.exports = webpackImporter;