storybook-addon-stencil
Version:
A Stencil compiler integration for Storybook.
86 lines (85 loc) • 3.78 kB
JavaScript
import { transpile } from "@stencil/core/compiler";
import { readFile } from "fs/promises";
import path from "path";
import { compile } from 'sass';
/**
* Check if an URL is an import from node_modules made with the stencil `~` prefix.
* @param url The URL to check.
*/
const isCssNodeModule = (url) => url.startsWith("~");
export default function stencilSassPlugin(options = {}) {
const importMap = new Map();
return {
name: "vite:stencil-sass",
resolveId(importPath, importer) {
if (importPath.match(/\.scss\.js\?tag=/)) {
const [fileName, ...args] = importPath.split("?");
const filePath = path.resolve(path.dirname(importer), fileName);
return `${filePath}?${args.join("?")}`;
}
},
async load(id) {
if (!id.includes(".scss")) {
return;
}
const [baseFileName, queryParamsString] = id.split("?");
const fileName = baseFileName.replace(".scss.js", ".scss");
let code = await readFile(fileName, "utf-8");
const importStatements = code.match(/@import ['"](.*)['"].*;/g) ?? [];
for (const importStatement of importStatements) {
const [, importPath] = importStatement.match(/@import ['"](.*)['"]/);
// handle imports from node_modules
if (isCssNodeModule(importPath)) {
const fixedImport = importStatement.replace(importPath, path.resolve(process.cwd(), "node_modules", importPath.slice(1)));
code = code.replace(importStatement, fixedImport);
}
}
if (id.match(/\.scss\.js\?tag=/)) {
const res = compile(baseFileName);
console.log(res);
debugger;
// Transpile the css file
let { code: transpiled, imports } = await transpile(code, {
...options,
sourceMap: "inline",
target: "es2017",
file: `${fileName}?${queryParamsString}`,
styleImportData: null,
style: null,
});
code = transpiled;
imports.forEach((input) => {
code = code.replace(path.basename(input.path), path.basename(input.path) + "?inline");
const list = new Set(importMap.get(input.path));
list.add(id);
importMap.set(path.resolve(path.dirname(id), input.path), list);
});
}
return { code };
},
/**
* Handle hot updates for CSS files of the Stencil components.
*/
handleHotUpdate({ file, server, timestamp }) {
if (!file.includes(".css")) {
return [];
}
const invalidatedModules = new Set();
const cssFiles = importMap.get(file) || new Set();
// invalidate all importers of the css file that triggered the update
cssFiles.forEach((cssFile) => {
const module = server.moduleGraph.getModuleById(cssFile);
if (!module) {
return;
}
server.moduleGraph.invalidateModule(module, invalidatedModules, timestamp, true);
});
// module of the css file that triggered the update
const mod = [...server.moduleGraph.idToModuleMap.entries()].find(([k]) => k.includes(file))?.[1];
if (mod) {
server.moduleGraph.invalidateModule(mod, invalidatedModules, timestamp, true);
}
return [...invalidatedModules];
},
};
}