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