@nx/webpack
Version:
165 lines (164 loc) • 6.69 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostcssCliResources = PostcssCliResources;
const loader_utils_1 = require("loader-utils");
const path = require("path");
const url = require("node:url");
function wrapUrl(url) {
let wrappedUrl;
const hasSingleQuotes = url.indexOf("'") >= 0;
if (hasSingleQuotes) {
wrappedUrl = `"${url}"`;
}
else {
wrappedUrl = `'${url}'`;
}
return `url(${wrappedUrl})`;
}
async function resolve(file, base, resolver) {
try {
return await resolver(`./${file}`, base);
}
catch {
return resolver(file, base);
}
}
module.exports.postcss = true;
function PostcssCliResources(options) {
const { deployUrl = '', baseHref = '', resourcesOutputPath = '', rebaseRootRelative = false, filename, loader, publicPath = '', } = options;
const dedupeSlashes = (url) => url.replace(/\/\/+/g, '/');
const process = async (inputUrl, context, resourceCache) => {
// If root-relative, absolute or protocol relative url, leave as is
if (/^((?:\w+:)?\/\/|data:|chrome:|#)/.test(inputUrl)) {
return inputUrl;
}
if (!rebaseRootRelative && /^\//.test(inputUrl)) {
return inputUrl;
}
// If starts with a caret, remove and return remainder
// this supports bypassing asset processing
if (inputUrl.startsWith('^')) {
return inputUrl.slice(1);
}
const cacheKey = path.resolve(context, inputUrl);
const cachedUrl = resourceCache.get(cacheKey);
if (cachedUrl) {
return cachedUrl;
}
if (inputUrl.startsWith('~')) {
inputUrl = inputUrl.slice(1);
}
if (inputUrl.startsWith('/')) {
let outputUrl = '';
if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) {
// If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
outputUrl = `${deployUrl.replace(/\/$/, '')}${inputUrl}`;
}
else if (baseHref.match(/:\/\//)) {
// If baseHref contains a scheme, include it as is.
outputUrl =
baseHref.replace(/\/$/, '') +
dedupeSlashes(`/${deployUrl}/${inputUrl}`);
}
else {
// Join together base-href, deploy-url and the original URL.
outputUrl = dedupeSlashes(`/${baseHref}/${deployUrl}/${publicPath}/${inputUrl}`);
}
resourceCache.set(cacheKey, outputUrl);
return outputUrl;
}
const { pathname, hash, search } = url.parse(inputUrl.replace(/\\/g, '/'));
const resolver = (file, base) => new Promise((resolve, reject) => {
loader.resolve(base, decodeURI(file), (err, result) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
const result = await resolve(pathname, context, resolver);
return new Promise((resolve, reject) => {
loader.fs.readFile(result, (err, content) => {
if (err) {
reject(err);
return;
}
let outputPath = (0, loader_utils_1.interpolateName)({ resourcePath: result }, filename, { content });
if (resourcesOutputPath) {
outputPath = path.posix.join(resourcesOutputPath, outputPath);
}
loader.addDependency(result);
loader.emitFile(outputPath, content, undefined);
let outputUrl = outputPath.replace(/\\/g, '/');
if (hash || search) {
outputUrl = url.format({ pathname: outputUrl, hash, search });
}
const loaderOptions = loader.loaders[loader.loaderIndex].options;
if (deployUrl && loaderOptions.ident !== 'extracted') {
outputUrl = url.resolve(deployUrl, outputUrl);
}
resourceCache.set(cacheKey, outputUrl);
resolve(outputUrl);
});
});
};
return {
postcssPlugin: 'postcss-cli-resources',
Once(root) {
const urlDeclarations = [];
/**
* TODO: Explore if this can be rewritten using the new `Declaration()`
* listener added in postcss v8
*/
root.walkDecls((decl) => {
if (decl.value && decl.value.includes('url')) {
urlDeclarations.push(decl);
}
});
if (urlDeclarations.length === 0) {
return;
}
const resourceCache = new Map();
return Promise.all(urlDeclarations.map(async (decl) => {
const value = decl.value;
const urlRegex = /url(?:\(\s*(['"]?))(.*?)(?:\1\s*\))/g;
const segments = [];
let match;
let lastIndex = 0;
let modified = false;
// We want to load it relative to the file that imports
const inputFile = decl.source && decl.source.input.file;
const context = (inputFile && path.dirname(inputFile)) || loader.context;
while ((match = urlRegex.exec(value))) {
const originalUrl = match[2];
let processedUrl;
try {
processedUrl = await process(originalUrl, context, resourceCache);
}
catch (err) {
loader.emitError(decl.error(err.message, { word: originalUrl }));
continue;
}
if (lastIndex < match.index) {
segments.push(value.slice(lastIndex, match.index));
}
if (!processedUrl || originalUrl === processedUrl) {
segments.push(match[0]);
}
else {
segments.push(wrapUrl(processedUrl));
modified = true;
}
lastIndex = match.index + match[0].length;
}
if (lastIndex < value.length) {
segments.push(value.slice(lastIndex));
}
if (modified) {
decl.value = segments.join('');
}
}));
},
};
}
;