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

"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;
};