You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
585 lines
17 KiB
585 lines
17 KiB
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.getStylusOptions = getStylusOptions;
|
|
exports.urlResolver = urlResolver;
|
|
exports.createEvaluator = createEvaluator;
|
|
exports.resolveFilename = resolveFilename;
|
|
exports.readFile = readFile;
|
|
exports.normalizeSourceMap = normalizeSourceMap;
|
|
exports.getStylusImplementation = getStylusImplementation;
|
|
|
|
var _url = require("url");
|
|
|
|
var _path = _interopRequireDefault(require("path"));
|
|
|
|
var _stylus = require("stylus");
|
|
|
|
var _depsResolver = _interopRequireDefault(require("stylus/lib/visitor/deps-resolver"));
|
|
|
|
var _full = require("klona/full");
|
|
|
|
var _fastGlob = _interopRequireDefault(require("fast-glob"));
|
|
|
|
var _normalizePath = _interopRequireDefault(require("normalize-path"));
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
// Examples:
|
|
// - ~package
|
|
// - ~package/
|
|
// - ~@org
|
|
// - ~@org/
|
|
// - ~@org/package
|
|
// - ~@org/package/
|
|
const IS_MODULE_IMPORT = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
|
|
const MODULE_REQUEST_REGEX = /^[^?]*~/;
|
|
|
|
function isProductionLikeMode(loaderContext) {
|
|
return loaderContext.mode === "production" || !loaderContext.mode;
|
|
}
|
|
|
|
function getStylusOptions(loaderContext, loaderOptions) {
|
|
const stylusOptions = (0, _full.klona)(typeof loaderOptions.stylusOptions === "function" ? loaderOptions.stylusOptions(loaderContext) || {} : loaderOptions.stylusOptions || {});
|
|
stylusOptions.filename = loaderContext.resourcePath;
|
|
stylusOptions.dest = _path.default.dirname(loaderContext.resourcePath); // Keep track of imported files (used by Stylus CLI watch mode)
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
|
|
stylusOptions._imports = []; // https://github.com/stylus/stylus/issues/2119
|
|
|
|
stylusOptions.resolveURL = typeof stylusOptions.resolveURL === "boolean" && !stylusOptions.resolveURL ? false : typeof stylusOptions.resolveURL === "object" ? stylusOptions.resolveURL : {
|
|
nocheck: true
|
|
};
|
|
|
|
if (typeof stylusOptions.compress === "undefined" && isProductionLikeMode(loaderContext)) {
|
|
stylusOptions.compress = true;
|
|
}
|
|
|
|
return stylusOptions;
|
|
}
|
|
|
|
function getStylusImplementation(loaderContext, implementation) {
|
|
let resolvedImplementation = implementation;
|
|
|
|
if (!implementation || typeof implementation === "string") {
|
|
const stylusImplPkg = implementation || "stylus";
|
|
|
|
try {
|
|
// eslint-disable-next-line import/no-dynamic-require, global-require
|
|
resolvedImplementation = require(stylusImplPkg);
|
|
} catch (error) {
|
|
loaderContext.emitError(error); // eslint-disable-next-line consistent-return
|
|
|
|
return;
|
|
}
|
|
} // eslint-disable-next-line consistent-return
|
|
|
|
|
|
return resolvedImplementation;
|
|
}
|
|
|
|
function getPossibleRequests(loaderContext, filename) {
|
|
let request = filename; // A `~` makes the url an module
|
|
|
|
if (MODULE_REQUEST_REGEX.test(filename)) {
|
|
request = request.replace(MODULE_REQUEST_REGEX, "");
|
|
}
|
|
|
|
if (IS_MODULE_IMPORT.test(filename)) {
|
|
request = request[request.length - 1] === "/" ? request : `${request}/`;
|
|
}
|
|
|
|
return [...new Set([request, filename])];
|
|
}
|
|
|
|
async function resolveFilename(loaderContext, fileResolver, globResolver, isGlob, context, filename) {
|
|
const possibleRequests = getPossibleRequests(loaderContext, filename);
|
|
let result;
|
|
|
|
try {
|
|
result = await resolveRequests(context, possibleRequests, fileResolver);
|
|
} catch (error) {
|
|
if (isGlob) {
|
|
const [globTask] = _fastGlob.default.generateTasks(filename);
|
|
|
|
if (globTask.base === ".") {
|
|
throw new Error('Glob resolving without a glob base ("~**/*") is not supported, please specify a glob base ("~package/**/*")');
|
|
}
|
|
|
|
const possibleGlobRequests = getPossibleRequests(loaderContext, globTask.base);
|
|
let globResult;
|
|
|
|
try {
|
|
globResult = await resolveRequests(context, possibleGlobRequests, globResolver);
|
|
} catch (globError) {
|
|
throw globError;
|
|
}
|
|
|
|
loaderContext.addContextDependency(globResult);
|
|
const patterns = filename.replace(new RegExp(`^${globTask.base}`), (0, _normalizePath.default)(globResult));
|
|
const paths = await (0, _fastGlob.default)(patterns, {
|
|
absolute: true,
|
|
cwd: globResult
|
|
});
|
|
return paths.sort().filter(file => /\.styl$/i.test(file));
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async function resolveRequests(context, possibleRequests, resolve) {
|
|
if (possibleRequests.length === 0) {
|
|
return Promise.reject();
|
|
}
|
|
|
|
let result;
|
|
|
|
try {
|
|
result = await resolve(context, possibleRequests[0]);
|
|
} catch (error) {
|
|
const [, ...tailPossibleRequests] = possibleRequests;
|
|
|
|
if (tailPossibleRequests.length === 0) {
|
|
throw error;
|
|
}
|
|
|
|
result = await resolveRequests(context, tailPossibleRequests, resolve);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const URL_RE = /^(?:url\s*\(\s*)?['"]?(?:[#/]|(?:https?:)?\/\/)/i;
|
|
|
|
async function getDependencies(resolvedDependencies, loaderContext, fileResolver, globResolver, seen, code, filename, options) {
|
|
seen.add(filename); // See https://github.com/stylus/stylus/issues/2108
|
|
|
|
const newOptions = (0, _full.klona)({ ...options,
|
|
filename,
|
|
cache: false
|
|
});
|
|
const parser = new _stylus.Parser(code, newOptions);
|
|
let ast;
|
|
|
|
try {
|
|
ast = parser.parse();
|
|
} catch (error) {
|
|
loaderContext.emitError(error);
|
|
return;
|
|
}
|
|
|
|
const dependencies = [];
|
|
|
|
class ImportVisitor extends _depsResolver.default {
|
|
// eslint-disable-next-line class-methods-use-this
|
|
visitImport(node) {
|
|
let firstNode = node.path.first;
|
|
|
|
if (firstNode.name === "url") {
|
|
return;
|
|
}
|
|
|
|
if (!firstNode.val) {
|
|
const evaluator = new _stylus.Evaluator(ast);
|
|
firstNode = evaluator.visit.call(evaluator, firstNode).first;
|
|
}
|
|
|
|
const originalNodePath = !firstNode.val.isNull && firstNode.val || firstNode.name;
|
|
let nodePath = originalNodePath;
|
|
|
|
if (!nodePath) {
|
|
return;
|
|
}
|
|
|
|
let found;
|
|
let oldNodePath;
|
|
const literal = /\.css(?:"|$)/.test(nodePath);
|
|
|
|
if (!literal && !/\.styl$/i.test(nodePath)) {
|
|
oldNodePath = nodePath;
|
|
nodePath += ".styl";
|
|
}
|
|
|
|
const isGlob = _fastGlob.default.isDynamicPattern(nodePath);
|
|
|
|
found = _stylus.utils.find(nodePath, this.paths, this.filename);
|
|
|
|
if (found && isGlob) {
|
|
const [globTask] = _fastGlob.default.generateTasks(nodePath);
|
|
|
|
const context = globTask.base === "." ? _path.default.dirname(this.filename) : _path.default.join(_path.default.dirname(this.filename), globTask.base);
|
|
loaderContext.addContextDependency(context);
|
|
}
|
|
|
|
if (!found && oldNodePath) {
|
|
found = _stylus.utils.lookupIndex(oldNodePath, this.paths, this.filename);
|
|
}
|
|
|
|
if (found) {
|
|
dependencies.push({
|
|
originalLineno: firstNode.lineno,
|
|
originalColumn: firstNode.column,
|
|
originalNodePath,
|
|
resolved: found.map(item => _path.default.isAbsolute(item) ? item : _path.default.join(process.cwd(), item))
|
|
});
|
|
return;
|
|
}
|
|
|
|
dependencies.push({
|
|
originalLineno: firstNode.lineno,
|
|
originalColumn: firstNode.column,
|
|
originalNodePath,
|
|
resolved: resolveFilename(loaderContext, fileResolver, globResolver, isGlob, _path.default.dirname(this.filename), originalNodePath)
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
new ImportVisitor(ast, newOptions).visit(ast);
|
|
await Promise.all(Array.from(dependencies).map(async result => {
|
|
let {
|
|
resolved
|
|
} = result;
|
|
|
|
try {
|
|
resolved = await resolved;
|
|
} catch (ignoreError) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
delete result.resolved; // eslint-disable-next-line no-param-reassign
|
|
|
|
result.error = ignoreError;
|
|
return;
|
|
}
|
|
|
|
const isArray = Array.isArray(resolved); // `stylus` returns forward slashes on windows
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
result.resolved = isArray ? resolved.map(item => _path.default.normalize(item)) : _path.default.normalize(resolved);
|
|
const dependenciesOfDependencies = [];
|
|
|
|
for (const dependency of isArray ? result.resolved : [result.resolved]) {
|
|
// Avoid loop, the file is imported by itself
|
|
if (seen.has(dependency)) {
|
|
return;
|
|
} // Avoid search nested imports in .css
|
|
|
|
|
|
if (_path.default.extname(dependency) === ".css") {
|
|
return;
|
|
}
|
|
|
|
loaderContext.addDependency(dependency);
|
|
dependenciesOfDependencies.push((async () => {
|
|
let dependencyCode;
|
|
|
|
try {
|
|
dependencyCode = (await readFile(loaderContext.fs, dependency)).toString();
|
|
} catch (error) {
|
|
loaderContext.emitError(error);
|
|
}
|
|
|
|
await getDependencies(resolvedDependencies, loaderContext, fileResolver, globResolver, seen, dependencyCode, dependency, options);
|
|
})());
|
|
}
|
|
|
|
await Promise.all(dependenciesOfDependencies);
|
|
}));
|
|
|
|
if (dependencies.length > 0) {
|
|
resolvedDependencies.set(filename, dependencies);
|
|
}
|
|
}
|
|
|
|
function mergeBlocks(blocks) {
|
|
let finalBlock;
|
|
|
|
const adding = item => {
|
|
finalBlock.push(item);
|
|
};
|
|
|
|
for (const block of blocks) {
|
|
if (finalBlock) {
|
|
block.nodes.forEach(adding);
|
|
} else {
|
|
finalBlock = block;
|
|
}
|
|
}
|
|
|
|
return finalBlock;
|
|
}
|
|
|
|
async function createEvaluator(loaderContext, code, options) {
|
|
const fileResolve = loaderContext.getResolve({
|
|
dependencyType: "stylus",
|
|
conditionNames: ["styl", "stylus", "style"],
|
|
mainFields: ["styl", "style", "stylus", "main", "..."],
|
|
mainFiles: ["index", "..."],
|
|
extensions: [".styl", ".css"],
|
|
restrictions: [/\.(css|styl)$/i],
|
|
preferRelative: true
|
|
}); // Get cwd for `fastGlob()`
|
|
// No need extra options, because they do not used when `resolveToContext` is `true`
|
|
|
|
const globResolve = loaderContext.getResolve({
|
|
conditionNames: ["styl", "stylus", "style"],
|
|
resolveToContext: true,
|
|
preferRelative: true
|
|
});
|
|
const resolvedImportDependencies = new Map();
|
|
const resolvedDependencies = new Map();
|
|
const seen = new Set();
|
|
await getDependencies(resolvedDependencies, loaderContext, fileResolve, globResolve, seen, code, loaderContext.resourcePath, options);
|
|
const optionsImports = [];
|
|
|
|
for (const importPath of options.imports) {
|
|
const isGlob = _fastGlob.default.isDynamicPattern(importPath);
|
|
|
|
optionsImports.push({
|
|
importPath,
|
|
resolved: resolveFilename(loaderContext, fileResolve, globResolve, isGlob, _path.default.dirname(loaderContext.resourcePath), importPath)
|
|
});
|
|
}
|
|
|
|
await Promise.all(optionsImports.map(async result => {
|
|
const {
|
|
importPath
|
|
} = result;
|
|
let {
|
|
resolved
|
|
} = result;
|
|
|
|
try {
|
|
resolved = await resolved;
|
|
} catch (ignoreError) {
|
|
return;
|
|
}
|
|
|
|
const isArray = Array.isArray(resolved); // `stylus` returns forward slashes on windows
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
result.resolved = isArray ? resolved.map(item => _path.default.normalize(item)) : _path.default.normalize(resolved);
|
|
resolvedImportDependencies.set(importPath, result);
|
|
const dependenciesOfImportDependencies = [];
|
|
|
|
for (const dependency of isArray ? result.resolved : [result.resolved]) {
|
|
dependenciesOfImportDependencies.push((async () => {
|
|
let dependencyCode;
|
|
|
|
try {
|
|
dependencyCode = (await readFile(loaderContext.fs, dependency)).toString();
|
|
} catch (error) {
|
|
loaderContext.emitError(error);
|
|
}
|
|
|
|
await getDependencies(resolvedDependencies, loaderContext, fileResolve, globResolve, seen, dependencyCode, dependency, options);
|
|
})());
|
|
}
|
|
|
|
await Promise.all(dependenciesOfImportDependencies);
|
|
}));
|
|
return class CustomEvaluator extends _stylus.Evaluator {
|
|
visitImport(imported) {
|
|
this.return += 1;
|
|
const node = this.visit(imported.path).first;
|
|
const nodePath = !node.val.isNull && node.val || node.name;
|
|
this.return -= 1;
|
|
let webpackResolveError;
|
|
|
|
if (node.name !== "url" && nodePath && !URL_RE.test(nodePath)) {
|
|
let dependency;
|
|
const isEntrypoint = loaderContext.resourcePath === node.filename;
|
|
|
|
if (isEntrypoint) {
|
|
dependency = resolvedImportDependencies.get(nodePath);
|
|
}
|
|
|
|
if (!dependency) {
|
|
const dependencies = resolvedDependencies.get(_path.default.normalize(node.filename));
|
|
|
|
if (dependencies) {
|
|
dependency = dependencies.find(item => {
|
|
if (item.originalLineno === node.lineno && item.originalColumn === node.column && item.originalNodePath === nodePath) {
|
|
if (item.error) {
|
|
webpackResolveError = item.error;
|
|
} else {
|
|
return item.resolved;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
if (dependency) {
|
|
const {
|
|
resolved
|
|
} = dependency;
|
|
|
|
if (!Array.isArray(resolved)) {
|
|
// Avoid re globbing when resolved import contains glob characters
|
|
node.string = _fastGlob.default.escapePath(resolved);
|
|
} else if (resolved.length > 0) {
|
|
let hasError = false;
|
|
const blocks = resolved.map(item => {
|
|
const clonedImported = imported.clone();
|
|
const clonedNode = this.visit(clonedImported.path).first; // Avoid re globbing when resolved import contains glob characters
|
|
|
|
clonedNode.string = _fastGlob.default.escapePath(item);
|
|
let result;
|
|
|
|
try {
|
|
result = super.visitImport(clonedImported);
|
|
} catch (error) {
|
|
hasError = true;
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
if (!hasError) {
|
|
return mergeBlocks(blocks);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let result;
|
|
|
|
try {
|
|
result = super.visitImport(imported);
|
|
} catch (error) {
|
|
loaderContext.emitError(new Error(`Stylus resolver error: ${error.message}${webpackResolveError ? `\n\nWebpack resolver error: ${webpackResolveError.message}${webpackResolveError.details ? `\n\nWebpack resolver error details:\n${webpackResolveError.details}` : ""}${webpackResolveError.missing ? `\n\nWebpack resolver error missing:\n${webpackResolveError.missing.join("\n")}` : ""}` : ""}`));
|
|
return imported;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
};
|
|
}
|
|
|
|
function urlResolver(options = {}) {
|
|
function resolver(url) {
|
|
const compiler = new _stylus.Compiler(url);
|
|
const {
|
|
filename
|
|
} = url;
|
|
compiler.isURL = true;
|
|
const visitedUrl = url.nodes.map(node => compiler.visit(node)).join("");
|
|
const splitted = visitedUrl.split("!");
|
|
const parsedUrl = (0, _url.parse)(splitted.pop()); // Parse literal
|
|
|
|
const literal = new _stylus.nodes.Literal(`url("${parsedUrl.href}")`);
|
|
let {
|
|
pathname
|
|
} = parsedUrl;
|
|
let {
|
|
dest
|
|
} = this.options;
|
|
let tail = "";
|
|
let res; // Absolute or hash
|
|
|
|
if (parsedUrl.protocol || !pathname || pathname[0] === "/") {
|
|
return literal;
|
|
} // Check that file exists
|
|
|
|
|
|
if (!options.nocheck) {
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
const _paths = options.paths || [];
|
|
|
|
pathname = _stylus.utils.lookup(pathname, _paths.concat(this.paths));
|
|
|
|
if (!pathname) {
|
|
return literal;
|
|
}
|
|
}
|
|
|
|
if (this.includeCSS && _path.default.extname(pathname) === ".css") {
|
|
return new _stylus.nodes.Literal(parsedUrl.href);
|
|
}
|
|
|
|
if (parsedUrl.search) {
|
|
tail += parsedUrl.search;
|
|
}
|
|
|
|
if (parsedUrl.hash) {
|
|
tail += parsedUrl.hash;
|
|
}
|
|
|
|
if (dest && _path.default.extname(dest) === ".css") {
|
|
dest = _path.default.dirname(dest);
|
|
}
|
|
|
|
res = _path.default.relative(dest || _path.default.dirname(this.filename), options.nocheck ? _path.default.join(_path.default.dirname(filename), pathname) : pathname) + tail;
|
|
|
|
if (_path.default.sep === "\\") {
|
|
res = res.replace(/\\/g, "/");
|
|
}
|
|
|
|
splitted.push(res);
|
|
return new _stylus.nodes.Literal(`url("${splitted.join("!")}")`);
|
|
}
|
|
|
|
resolver.options = options;
|
|
resolver.raw = true;
|
|
return resolver;
|
|
}
|
|
|
|
function readFile(inputFileSystem, filepath) {
|
|
return new Promise((resolve, reject) => {
|
|
inputFileSystem.readFile(filepath, (error, stats) => {
|
|
if (error) {
|
|
reject(error);
|
|
}
|
|
|
|
resolve(stats);
|
|
});
|
|
});
|
|
}
|
|
|
|
const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
|
|
const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/;
|
|
|
|
function getURLType(source) {
|
|
if (source[0] === "/") {
|
|
if (source[1] === "/") {
|
|
return "scheme-relative";
|
|
}
|
|
|
|
return "path-absolute";
|
|
}
|
|
|
|
if (IS_NATIVE_WIN32_PATH.test(source)) {
|
|
return "path-absolute";
|
|
}
|
|
|
|
return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative";
|
|
}
|
|
|
|
function normalizeSourceMap(map, rootContext) {
|
|
const newMap = 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.
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
delete newMap.file; // eslint-disable-next-line no-param-reassign
|
|
|
|
newMap.sourceRoot = ""; // eslint-disable-next-line no-param-reassign
|
|
|
|
newMap.sources = newMap.sources.map(source => {
|
|
const sourceType = getURLType(source); // Do no touch `scheme-relative`, `path-absolute` and `absolute` types
|
|
|
|
if (sourceType === "path-relative") {
|
|
return _path.default.resolve(rootContext, _path.default.normalize(source));
|
|
}
|
|
|
|
return source;
|
|
});
|
|
return newMap;
|
|
}
|