You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
94 lines
3.2 KiB
JavaScript
94 lines
3.2 KiB
JavaScript
3 years ago
|
"use strict";
|
||
|
|
||
|
const pirates = require("pirates");
|
||
|
const path = require("path");
|
||
|
const defaultValue = require("default-value");
|
||
|
const resolveFrom = require("resolve-from");
|
||
|
const syncpipe = require("syncpipe");
|
||
|
const insertCSS = require("insert-css");
|
||
|
const unreachable = require("@joepie91/unreachable")("icss-register");
|
||
|
|
||
|
const postcss = require("postcss");
|
||
|
const postcssLocalByDefault = require("postcss-modules-local-by-default");
|
||
|
const postcssExtractImports = require("postcss-modules-extract-imports");
|
||
|
const postcssScope = require("postcss-modules-scope");
|
||
|
const postcssValues = require("postcss-modules-values");
|
||
|
const postcssICSSParser = require("postcss-icss-parser");
|
||
|
const genericNames = require("generic-names");
|
||
|
|
||
|
module.exports = function registerICSS(options = {}) {
|
||
|
let processedFiles = new Map();
|
||
|
|
||
|
let generateScopedName = defaultValue(options.generateScopedName, genericNames("[name]__[local]---[hash:base64:5]"));
|
||
|
|
||
|
function getExports(fullPath) {
|
||
|
if (path.isAbsolute(fullPath)) {
|
||
|
if (processedFiles.has(fullPath)) {
|
||
|
return processedFiles.get(fullPath);
|
||
|
} else {
|
||
|
let htmlExports = require(fullPath);
|
||
|
|
||
|
processedFiles.set(fullPath, htmlExports);
|
||
|
return htmlExports;
|
||
|
}
|
||
|
} else {
|
||
|
throw unreachable(`Path must be absolute, but isn't (${fullPath})`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function process(fullPath, code) {
|
||
|
// We cannot reuse the PostCSS instance across files here yet, because the keyReplacer fallback is dependent on the path of the file we're currently processing.
|
||
|
// TODO: Figure out a sensible way to improve upon that.
|
||
|
let postcssInstance = postcss([
|
||
|
... defaultValue(options.before, []),
|
||
|
postcssValues,
|
||
|
postcssLocalByDefault({ mode: defaultValue(options.mode, "local") }),
|
||
|
postcssExtractImports(),
|
||
|
postcssScope({
|
||
|
generateScopedName: generateScopedName
|
||
|
}),
|
||
|
postcssICSSParser({
|
||
|
autoExportImports: defaultValue(options.autoExportImports, true),
|
||
|
keyReplacer: ({ url, remoteKey }) => {
|
||
|
let resolvedSourcePath = resolveFrom(path.dirname(fullPath), url);
|
||
|
let htmlExports = getExports(resolvedSourcePath);
|
||
|
let mangledName = htmlExports[remoteKey];
|
||
|
|
||
|
if (mangledName == null) {
|
||
|
// TODO: Error type
|
||
|
throw new Error(`No export named '${remoteKey}' found in file ${url}`);
|
||
|
}
|
||
|
|
||
|
/* The replacement is to deal with the difference in multiple-class notation between CSS and HTML; in CSS they are dot-delimited, but in HTML (which the ICSS spec targets) they are space-delimited. We need the CSS notation here. */
|
||
|
return mangledName.replace(/ /g, ".");
|
||
|
}
|
||
|
}),
|
||
|
... defaultValue(options.after, [])
|
||
|
]);
|
||
|
|
||
|
let result = postcssInstance.process(code, {
|
||
|
from: fullPath,
|
||
|
... defaultValue(options.postcssOptions, {})
|
||
|
});
|
||
|
|
||
|
let icssExports = syncpipe(result.messages, [
|
||
|
(_) => _.filter((message) => message.pluginName === "postcss-icss-parser" && message.type === "icss-export"),
|
||
|
(_) => _.map(({ item }) => [ item.name, item.value ]),
|
||
|
(_) => Object.fromEntries(_)
|
||
|
]);
|
||
|
|
||
|
insertCSS(result.css);
|
||
|
|
||
|
return `module.exports = ${JSON.stringify(icssExports)}`;
|
||
|
}
|
||
|
|
||
|
let extensions = defaultValue(options.extensions, [ ".css" ]);
|
||
|
|
||
|
let unhook = pirates.addHook(
|
||
|
(code, fullPath) => process(fullPath, code),
|
||
|
{ exts: extensions }
|
||
|
);
|
||
|
|
||
|
return unhook;
|
||
|
};
|