import esbuild, { PluginBuild } from "esbuild"; // import linaria from "@linaria/esbuild"; import fs from "fs"; import path from "path"; import postcss from "postcss"; import sass from "sass"; import postcssrc from "postcss-load-config"; // this should give us the current directory where // the project is being built const BASE = process.cwd(); const preact = path.join( BASE, "node_modules", "preact", "compat", "dist", "compat.module.js", ); // https://preactjs.com/guide/v8/switching-to-preact/#how-to-alias-preact-compat const preactCompatPlugin: esbuild.Plugin = { name: "preact-compat", setup(build) { build.onResolve({ filter: /^(react-dom|react)$/ }, (args) => { return { path: preact, }; }); }, }; export function getFilesInDirectory( startPath: string, regex?: RegExp, ): string[] { if (!fs.existsSync(startPath)) { return []; } const files = fs.readdirSync(startPath); const result = files .flatMap((file) => { const filename = path.join(startPath, file); const stat = fs.lstatSync(filename); if (stat.isDirectory()) { return getFilesInDirectory(filename, regex); } if (!regex || regex.test(filename)) { return [filename]; } return []; }) .filter((x) => !!x); return result; } let GIT_ROOT = BASE; while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") { GIT_ROOT = path.join(GIT_ROOT, "../"); } if (GIT_ROOT === "/") { // eslint-disable-next-line no-undef console.log("not found"); // eslint-disable-next-line no-undef process.exit(1); } const GIT_HASH = GIT_ROOT === "/" ? undefined : git_hash(); const buf = fs.readFileSync(path.join(BASE, "package.json")); let _package = JSON.parse(buf.toString("utf-8")); function git_hash() { const rev = fs .readFileSync(path.join(GIT_ROOT, ".git", "HEAD")) .toString() .trim() .split(/.*[: ]/) .slice(-1)[0]; if (rev.indexOf("/") === -1) { return rev; } else { return fs.readFileSync(path.join(GIT_ROOT, ".git", rev)).toString().trim(); } } // FIXME: Put this into some helper library. function copyFilesPlugin(files: Array) { return { name: "copy-files", setup(build: PluginBuild) { const outDir = build.initialOptions.outdir; if (outDir === undefined) throw Error("esbuild build options does not specify outdir"); build.onEnd(() => { for (const file of files) { const name = path.parse(file).base; fs.copyFileSync(file, path.join(outDir, name)); } }); }, }; } const DEFAULT_SASS_FILTER = /\.(s[ac]ss|css)$/; const sassPlugin: esbuild.Plugin = { name: "custom-build-sass", setup(build) { build.onLoad({ filter: DEFAULT_SASS_FILTER }, ({ path: file }) => { const resolveDir = path.dirname(file); const { css: contents } = sass.compile(file, { loadPaths: ["./"] }); return { resolveDir, loader: "css", contents, }; }); }, }; const postCssPlugin: esbuild.Plugin = { name: "custom-build-postcss", setup(build) { build.onLoad({ filter: DEFAULT_SASS_FILTER }, async ({ path: file }) => { const resolveDir = path.dirname(file); const sourceBuffer = fs.readFileSync(file); const source = sourceBuffer.toString("utf-8"); const postCssConfig = await postcssrc(); postCssConfig.options.from = file; const { css: contents } = await postcss(postCssConfig.plugins).process( source, postCssConfig.options, ); return { resolveDir, loader: "css", contents, }; }); }, }; function linariaPlugin() { // const linariaCssPlugin: esbuild.Plugin = linaria.default({ // babelOptions: { // babelrc: false, // configFile: "./babel.config-linaria.json", // }, // sourceMap: true, // }); // return linariaCssPlugin; } const defaultEsBuildConfig: esbuild.BuildOptions = { bundle: true, minify: false, loader: { ".svg": "file", ".png": "dataurl", ".jpeg": "dataurl", ".ttf": "file", ".woff": "file", ".woff2": "file", ".eot": "file", }, target: ["es6"], format: "esm", platform: "browser", sourcemap: true, jsxFactory: "h", jsxFragment: "Fragment", define: { __VERSION__: `"${_package.version}"`, __GIT_HASH__: `"${GIT_HASH}"`, }, }; export interface BuildParams { source: { assets: string[]; js: string[]; }; destination: string; css: "sass" | "postcss"; // | "linaria"; } export function computeConfig(params: BuildParams) { const plugins: Array = [ preactCompatPlugin, copyFilesPlugin(params.source.assets), ]; switch (params.css) { case "sass": { plugins.push(sassPlugin); break; } case "postcss": { plugins.push(postCssPlugin); break; } // case "linaria": { // plugins.push(linariaPlugin()); // break; // } default: { const cssType: never = params.css; throw Error(`not supported: ${cssType}`); } } return { ...defaultEsBuildConfig, entryPoints: params.source.js, outdir: params.destination, plugins, }; } /** * Build sources for prod environment */ export function build(config: BuildParams) { return esbuild.build(computeConfig(config)); } const LIVE_RELOAD_SCRIPT = "./node_modules/@gnu-taler/web-util/lib/live-reload.mjs"; /** * Do startup for development environment */ export function initializeDev( config: BuildParams, ): () => Promise { function buildDevelopment() { const result = computeConfig(config); result.inject = [LIVE_RELOAD_SCRIPT]; return esbuild.build(result); } return buildDevelopment; }