@graysonlang/esp
v1.6.1
Published
A collection of esbuild plugins and utilities.
Downloads
1,290
Maintainers
Readme
@graysonlang/esp
A collection of esbuild plugins and utilities.
Installation
npm install @graysonlang/espPeer dependencies vary by plugin — install only what you need:
npm install --save-dev esbuild # required by all plugins
npm install --save-dev eslint # required by esbuild-plugin-eslint
npm install --save-dev @stylistic/eslint-plugin # optional, for stylistic rulesExample project
graysonlang/esd is a minimal but complete example of using @graysonlang/esp in an independent repo. It includes a working scripts/build.mjs, the full set of recommended package.json scripts, and the .vscode/tasks.json / .vscode/launch.json files described in the VS Code Integration section below. Use it as a boilerplate when starting a new project.
Scripts
| Script | Command | Description |
|--------|---------|-------------|
| build | node ./scripts/build.mjs --lint --minify | One-shot production build (linted, minified) |
| serve | node ./scripts/build.mjs --lint --sourcemap --watch --serve | Watch + dev server with live reload |
| serve:https | ESP_DEV_CERT_NAME=$npm_package_config_esp_dev_cert_name npm run serve -- --host=0.0.0.0 --port=8443 | HTTPS watch + dev server using the configured development cert |
| dev | npm run serve -- --proxy --launch | Watch + dev server with proxy toasts and Chrome launch |
| dev:coi | npm run dev -- --cross-origin-isolation | Same as dev, but cross-origin isolated (SharedArrayBuffer enabled) |
| dev:https | npm run serve:https -- --proxy --launch | HTTPS watch + dev server with proxy toasts and Chrome launch |
| dev:https:coi | npm run dev:https -- --cross-origin-isolation | Same as dev:https, but cross-origin isolated (SharedArrayBuffer enabled) |
| vscode:build | npm run build -- --vscode | One-shot build with VS Code problem matcher output |
| vscode:debug | npm run serve -- --vscode | Watch + dev server with VS Code problem matcher output |
| vscode:debug:https | npm run serve:https -- --vscode | HTTPS watch + dev server with VS Code problem matcher output |
| cert:dev | ESP_DEV_CERT_NAME=$npm_package_config_esp_dev_cert_name esp-generate-dev-cert | Generate a trusted HTTPS development certificate |
| lint | eslint . --ignore-pattern 'dist' | Lint source files |
Runner CLI flags
runBuild parses CLI flags from process.argv automatically. All flags are optional:
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| --minify | | false | Minify output |
| --lint | | false | Run ESLint after each build |
| --serve | | false | Start esbuild's dev server |
| --watch | | false | Rebuild on file changes |
| --proxy | | false | Run a proxy server that forwards console logs to the browser as toasts |
| --cross-origin-isolation | | false | Add COOP/COEP/CORP headers to proxied responses so the page is cross-origin isolated (SharedArrayBuffer available). Requires --proxy |
| --launch | | false | Launch a dedicated Chrome instance when the dev server starts |
| --vscode | | false | Emit VS Code problem matcher output and print [esbuild-ready] <url> when ready |
| --reuse | | false | Open/reload an existing Chrome tab instead of launching a dedicated instance (macOS only — see Browser launching) |
| --verbose | -v | false | Enable verbose logging |
| --certfile | | | Explicit HTTPS certificate path |
| --keyfile | | | Explicit HTTPS private key path |
| --host | | 127.0.0.1 | Dev server host |
| --port | | 8000 | Dev server port |
| --chrome-arg | | | Extra flag forwarded to the dedicated Chrome launched by --launch (repeatable) |
Any unrecognized flags are forwarded to esbuild as build options (e.g. --sourcemap).
Browser launching
--launch works on macOS, Windows, and Linux. The runner locates a Chrome/Chromium binary by checking the standard install locations for the platform (including Chrome Canary, and Chromium on Linux). If your browser is installed somewhere non-standard — or you want to pin a specific build — set the CHROME_PATH environment variable to the executable:
# macOS
CHROME_PATH="/Applications/Chromium.app/Contents/MacOS/Chromium" npm run dev
# Windows (PowerShell)
$env:CHROME_PATH="C:\Program Files\Google\Chrome Beta\Application\chrome.exe"; npm run dev
# Linux
CHROME_PATH=/usr/bin/brave-browser npm run devIf no browser is found, the runner exits with a message telling you to set CHROME_PATH.
The launched instance uses a throwaway profile under the OS temp directory, so it won't touch your everyday Chrome session. Forward extra Chrome flags with --chrome-arg (repeatable).
--reuse (focus/reload an already-open tab instead of launching a dedicated instance) relies on AppleScript and is macOS only. On Windows and Linux it logs a notice and falls back to launching a dedicated instance.
HTTPS Development
The package includes a certificate helper (esp-generate-dev-cert) for running esbuild's dev server over HTTPS locally — useful when testing on iOS/iPadOS or when a browser feature requires a secure context. It creates a server certificate under .esp_dev_certs/ using mkcert and uses mkcert's configured CA root directly.
Add these scripts to your project's package.json:
{
"cert:dev": "ESP_DEV_CERT_NAME=<project>-dev esp-generate-dev-cert",
"serve": "node ./scripts/build.mjs --watch --serve",
"serve:https": "ESP_DEV_CERT_NAME=<project>-dev npm run serve -- --host=0.0.0.0 --port=8443",
"vscode:debug": "npm run serve -- --vscode",
"vscode:debug:https": "npm run serve:https -- --vscode"
}By default, generated cert files live in .esp_dev_certs/. For certificates you want to
keep across repo cleanup commands such as git clean, set ESP_DEV_CERTS_DIR to a
stable location outside the repository in your shell environment, for example in
.zshrc. The certificate helper and runner both use ESP_DEV_CERTS_DIR when it is
set.
When a certificate is generated, the helper also trusts the mkcert CA. On macOS it adds the CA from mkcert -CAROOT to the login keychain; on other platforms it runs mkcert -install. Pass --skip-trust to generate without changing local trust, or --trust to retrust an existing CA. Set ESP_DEV_CERT_FORCE=1 to regenerate an existing certificate (e.g. when your LAN IP changes). Pass ESP_DEV_CERT_NAME to the runner to enable HTTPS with the matching generated certificate:
ESP_DEV_CERT_NAME=<project>-dev node ./scripts/build.mjs --watch --serve --host=0.0.0.0 --port=8443See docs/https-development-certificates.md for the full setup guide, including iOS/iPadOS installation, all CLI flags and environment variables, and troubleshooting.
Cross-Origin Isolation
Some browser APIs — most notably SharedArrayBuffer (used by threaded WASM and pthreads-compiled Emscripten output) — are only available when the page is cross-origin isolated. A page becomes isolated when it is served with the right COOP/COEP response headers, at which point crossOriginIsolated === true in the browser.
esbuild's own dev server can't set these headers, so the --cross-origin-isolation flag works through the runner's proxy server. When enabled, the proxy adds the following headers to every response it forwards:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-originBecause the headers are applied by the proxy, --cross-origin-isolation requires --proxy — on its own it has no effect.
The recommended way to enable it is via the dedicated dev scripts, which already include --proxy:
npm run dev:coi # HTTP, cross-origin isolated
npm run dev:https:coi # HTTPS, cross-origin isolatedThese compose on the existing dev / dev:https scripts:
{
"dev": "npm run serve -- --proxy --launch",
"dev:coi": "npm run dev -- --cross-origin-isolation",
"dev:https": "npm run serve:https -- --proxy --launch",
"dev:https:coi": "npm run dev:https -- --cross-origin-isolation"
}Note: With COEP set to
require-corp, every cross-origin subresource (scripts, images, fonts, etc.) must itself opt in viaCross-Origin-Resource-Policyor CORS, or the browser will block it. If subresources fail to load after enabling isolation, this is usually why.
Plugins
esbuild-plugin-emcc
Compiles C/C++ source files via Emscripten (emcc) during an esbuild build. Skips recompilation when sources are unchanged using content-hash freshness tracking.
import createEmccPlugin from '@graysonlang/esp/esbuild-plugin-emcc';
await esbuild.build({
plugins: [createEmccPlugin({ emccPath: 'emcc', emccOptions: ['-sSINGLE_FILE=1'] })],
});Options: emccPath, emccOptions, verbose, logger
esbuild-plugin-eslint
Runs ESLint on loaded source files at the end of each build. Only re-lints files that have changed since the last build.
import createEslintPlugin from '@graysonlang/esp/esbuild-plugin-eslint';
await esbuild.build({
plugins: [createEslintPlugin({ fix: false, throwOnErrors: true })],
});Options: candidateExtensions, throwOnWarnings, throwOnErrors, warnIgnored, plus any ESLint constructor options.
esbuild-plugin-glob-copy
Resolves virtual:glob imports and copies matched files to the output directory.
import 'virtual:glob' with { pattern: 'assets/**', baseDir: 'src' };import createGlobCopyPlugin from '@graysonlang/esp/esbuild-plugin-glob-copy';
await esbuild.build({
plugins: [createGlobCopyPlugin({ verbose: true })],
});Options: sideEffects, verbose, logger
esbuild-plugin-imp
Copies a single file to the output directory via a virtual:copy import.
import 'virtual:copy' with { path: './assets/logo.png', dest: 'images/' };import createImpPlugin from '@graysonlang/esp/esbuild-plugin-imp';
await esbuild.build({
plugins: [createImpPlugin()],
});Options: verbose, logger
esbuild-plugin-vscode-problem-matcher
Emits [watch] build started and formats esbuild errors/warnings in a format compatible with VS Code's problem matcher.
import createVSCodePlugin from '@graysonlang/esp/esbuild-plugin-vscode-problem-matcher';
await esbuild.build({
plugins: [createVSCodePlugin()],
});Utilities
esbuild-runner
The runBuild helper wraps esbuild context management, CLI flag parsing, dev server setup, live reload, and browser launching in a single call. Your build script provides a getOptions factory; the runner injects resolved flags and wires up plugins automatically.
import { runBuild } from '@graysonlang/esp/esbuild-runner';
function getOptions(args, verbose, logger) {
return {
bundle: true,
entryPoints: ['src/index.js'],
outdir: 'dist',
plugins: [
pluginGlobCopy({ logger }),
],
...args, // spreads minify, live-reload banner for watch/serve, etc.
};
}
runBuild(getOptions);The runner automatically adds esbuild-plugin-eslint (when --lint) and esbuild-plugin-vscode-problem-matcher (when --vscode) to the plugin list.
runBuild accepts an optional second argument to override the injected plugins:
runBuild(getOptions, {
lintPlugin: () => myCustomLintPlugin(), // replace the default eslint plugin
vscodePlugin: null, // null/falsy disables the plugin entirely
});When --launch is set, the runner opens a dedicated Chrome instance using a temporary profile, discovering the browser binary cross-platform (override with CHROME_PATH — see Browser launching). When --reuse is also set, it instead opens or reloads an existing Chrome tab (macOS only; falls back to a dedicated instance elsewhere). When --vscode is set, the runner prints [esbuild-ready] <url> once the server is ready — a signal VS Code tasks can use as a background.endsPattern.
esbuild-problem-format
Formats esbuild diagnostics into VS Code problem matcher output.
import { formatDiagnostic, printErrorsAndWarnings } from '@graysonlang/esp/esbuild-problem-format';freshness
Tracks file content changes using SHA-1 hashes and mtimes to detect when files have actually changed.
import Freshness from '@graysonlang/esp/freshness';
const freshness = new Freshness();
const isUpToDate = await freshness.check(filePathSet);
const { changed, removed } = await freshness.update(fileMapOrSet);glglob
A lightweight async glob implementation with **, *, ?, and {a,b} expansion. No external dependencies.
import glob from '@graysonlang/esp/glglob';
const files = await glob('src/**/*.js');helpers
Internal utilities: computeUrlSafeBase64Digest, consolidateDirs, parsePathsString.
VS Code Integration
The repository includes example .vscode/ configuration files that demonstrate a full VS Code debug workflow built on esbuild-runner.
How it works
The --vscode flag tells the runner to:
- Attach
esbuild-plugin-vscode-problem-matcher, which formats build errors/warnings so VS Code can parse them and surface them in the Problems panel. - Print
[esbuild-ready] <url>to stdout once the dev server is ready. VS Code uses this as thebackground.endsPatternto know the server is up before launching the debugger.
.vscode/tasks.json
Four tasks are defined:
build— one-shot build (vscode:buildscript). Configured as the default build task (Ctrl+Shift+B/Cmd+Shift+B). Uses an inline problem matcher that parses esbuild's> file:line:col: error: messageformat.debug— HTTP watch-mode server (vscode:debugscript). Runs in the background. Thebackgroundproblem matcher waits for[esbuild-ready] <url>before signaling readiness to the launch configuration.debug:https— HTTPS watch-mode server (vscode:debug:httpsscript). Uses the same readiness signal asdebugand serveshttps://localhost:8443.Kill debug server— sendsSIGTERMto the watch process. Runs as thepostDebugTaskso the server shuts down when the debug session ends.
.vscode/launch.json
Three Chrome configurations are provided:
- "Debug in Chrome" launches
http://localhost:8000after running thedebugtask. - "Debug in Chrome (https)" launches
https://localhost:8443after running thedebug:httpstask. - "Attach to Chrome" attaches to an already-running Chrome instance on the remote debugging port (9222).
- The two launch configurations set
postDebugTasktoKill debug serverand useoutFilesfor source map resolution. - All three set
webRootto"${workspaceFolder}"plus asourceMapPathOverridesrule so breakpoints bind against esbuild's spec-correct (outdir-relative) source maps. See Source maps & VS Code breakpoints for the full rationale and trade-offs.
Launch usage: open the Run & Debug panel, choose the HTTP or HTTPS Chrome configuration, and press Start Debugging (F5). VS Code starts the matching watch server, waits for [esbuild-ready], launches Chrome with the debugger attached, and tears the server down when you stop.
Attach usage: Chrome must be running with remote debugging enabled. Quit any existing Chrome instance first, then relaunch it with the flag:
# macOS
open -a "Google Chrome" --args --remote-debugging-port=9222
# Windows (PowerShell)
& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
# Linux
google-chrome --remote-debugging-port=9222Then start npm run dev (or npm run serve), navigate Chrome to the dev server URL, select "Attach to Chrome" in the Run & Debug panel, and press F5. VS Code attaches to the open tab without managing the server lifecycle.
