From d03e46a481954edf797fda4ce548534fea8130d1 Mon Sep 17 00:00:00 2001 From: Bauke Date: Fri, 14 Apr 2023 11:48:52 +0200 Subject: [PATCH] Add the build and web-ext files. --- source/build.ts | 111 ++++++++++++++++++++++++++++++++++++++++++++++ source/web-ext.ts | 75 +++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 source/build.ts create mode 100644 source/web-ext.ts diff --git a/source/build.ts b/source/build.ts new file mode 100644 index 0000000..5072881 --- /dev/null +++ b/source/build.ts @@ -0,0 +1,111 @@ +// Import native Node libraries. +import path from "node:path"; +import process from "node:process"; +import fsp from "node:fs/promises"; + +// Import Esbuild and associated plugins. +import esbuild from "esbuild"; +import copyPlugin from "esbuild-copy-static-files"; +import {sassPlugin} from "esbuild-sass-plugin"; + +// Import PostCSS and associated plugins. +import cssnano from "cssnano"; +import postcss from "postcss"; + +// Import local functions. +import {createManifest} from "./manifest.js"; +import {createWebExtConfig} from "./web-ext.js"; + +/** + * Create an absolute path from a given relative one, using the directory + * this file is located in as the base. + * + * @param relative The relative path to make absolute. + * @returns The absolute path. + */ +function toAbsolutePath(relative: string): string { + return new URL(relative, import.meta.url).pathname; +} + +// Create variables based on the environment. +const browser = process.env.BROWSER ?? "firefox"; +const dev = process.env.NODE_ENV === "development"; +const test = process.env.TEST === "true"; +const watch = process.env.WATCH === "true"; + +// Create absolute paths to various directories. +const buildDir = toAbsolutePath("build"); +const outDir = path.join(buildDir, browser); +const sourceDir = toAbsolutePath("source"); + +// Ensure that the output directory exists. +await fsp.mkdir(outDir, {recursive: true}); + +// Write the WebExtension manifest file. +await fsp.writeFile( + path.join(outDir, "manifest.json"), + JSON.stringify(createManifest(browser)), +); + +// Write the web-ext configuration file. +await fsp.writeFile( + path.join(buildDir, `web-ext-${browser}.json`), + JSON.stringify(createWebExtConfig(browser, buildDir, dev, outDir)), +); + +const cssProcessor = postcss([cssnano()]); + +const options: esbuild.BuildOptions = { + bundle: true, + // Define variables to be replaced in the code. Note that these are replaced + // "as is" and so we have to stringify them as JSON, otherwise a string won't + // have its quotes for example. + define: { + $browser: JSON.stringify(browser), + $dev: JSON.stringify(dev), + $test: JSON.stringify(test), + }, + entryPoints: [ + path.join(sourceDir, "background/setup.ts"), + path.join(sourceDir, "options/setup.tsx"), + ], + format: "esm", + logLevel: "info", + minify: !dev, + outdir: outDir, + plugins: [ + // Copy all files from `source/assets/` to the output directory. + copyPlugin({src: path.join(sourceDir, "assets/"), dest: outDir}), + + // Compile SCSS to CSS. + sassPlugin({ + type: "style", + async transform(source) { + // In development, don't do any extra processing. + if (dev) { + return source; + } + + // But in production, run the CSS through PostCSS. + const {css} = await cssProcessor.process(source, {from: undefined}); + return css; + }, + }), + ], + // Link sourcemaps in development but omit them in production. + sourcemap: dev ? "linked" : false, + // Currently code splitting can't be used because we use ES modules and + // Firefox doesn't run the background script with `type="module"`. + // Once Firefox properly supports Manifest V3 this should be possible though. + splitting: false, + // Target ES2022, and the first Chromium and Firefox releases from 2022. + target: ["es2022", "chrome97", "firefox102"], + treeShaking: true, +}; + +if (watch) { + const context = await esbuild.context(options); + await context.watch(); +} else { + await esbuild.build(options); +} diff --git a/source/web-ext.ts b/source/web-ext.ts new file mode 100644 index 0000000..2172bc9 --- /dev/null +++ b/source/web-ext.ts @@ -0,0 +1,75 @@ +import path from "node:path"; + +/** + * Barebones type definition for web-ext configuration. + * + * Since web-ext doesn't export any types this is done by ourselves. The keys + * mostly follow a camelCased version of the CLI options + * (ie. --start-url becomes startUrl). + */ +type WebExtConfig = { + artifactsDir: string; + sourceDir: string; + verbose?: boolean; + + build: { + filename: string; + overwriteDest: boolean; + }; + + run: { + browserConsole: boolean; + firefoxProfile: string; + keepProfileChanges: boolean; + profileCreateIfMissing: boolean; + startUrl: string[]; + target: string[]; + }; +}; + +/** + * Create the web-ext configuration. + * + * @param browser The browser target ("firefox" or "chromium"). + * @param buildDir The path to the build directory. + * @param dev Is this for development or production. + * @param outDir The path to the output directory. + * @returns The configuration for web-ext. + */ +export function createWebExtConfig( + browser: string, + buildDir: string, + dev: boolean, + outDir: string, +): WebExtConfig { + const config: WebExtConfig = { + artifactsDir: path.join(buildDir, "artifacts"), + sourceDir: outDir, + + build: { + filename: `{name}-{version}-${browser}.zip`, + overwriteDest: true, + }, + + run: { + browserConsole: dev, + firefoxProfile: path.join(buildDir, "firefox-profile/"), + keepProfileChanges: true, + profileCreateIfMissing: true, + startUrl: [], + target: [], + }, + }; + + if (browser === "firefox") { + config.run.startUrl.push("about:debugging#/runtime/this-firefox"); + config.run.target.push("firefox-desktop"); + } else if (browser === "chromium") { + config.run.startUrl.push("chrome://extensions/"); + config.run.target.push("chromium"); + } else { + throw new Error(`Unknown target browser: ${browser}`); + } + + return config; +}