-
Notifications
You must be signed in to change notification settings - Fork 29
JS Packaging and bundling with ESBuild #8792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 26 commits
97cfd62
71adb3c
fe467c9
f348e3f
ed5aa9e
4008d88
cbef713
aea8e11
1406071
e3a4d61
562d3f4
13efe94
3f94638
95c9c5d
baed423
50e792c
30e38a4
fa489fc
a9f802b
6bde645
2ed9654
1cb89c5
16d681a
ba39e02
42bcd80
2a1e6fb
2da3880
3c386c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,167 @@ | ||||||||||||||||||||||||||||||
const esbuild = require("esbuild"); | ||||||||||||||||||||||||||||||
const path = require("node:path"); | ||||||||||||||||||||||||||||||
const fs = require("node:fs"); | ||||||||||||||||||||||||||||||
const os = require("node:os"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const srcPath = path.resolve(__dirname, "frontend/javascripts/"); | ||||||||||||||||||||||||||||||
const outputPath = path.resolve(__dirname, "public/bundle/"); | ||||||||||||||||||||||||||||||
const protoPath = path.join(__dirname, "webknossos-datastore/proto"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Community plugins | ||||||||||||||||||||||||||||||
const browserslistToEsbuild = require("browserslist-to-esbuild"); | ||||||||||||||||||||||||||||||
const { lessLoader } = require("esbuild-plugin-less"); | ||||||||||||||||||||||||||||||
const copyPlugin = require("esbuild-plugin-copy").default; | ||||||||||||||||||||||||||||||
const polyfillNode = require("esbuild-plugin-polyfill-node").polyfillNode; | ||||||||||||||||||||||||||||||
const esbuildPluginWorker = require("@chialab/esbuild-plugin-worker").default; | ||||||||||||||||||||||||||||||
const { wasmLoader } = require("esbuild-plugin-wasm"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Custom Plugins for Webknossos | ||||||||||||||||||||||||||||||
const { createWorkerPlugin } = require("./tools/esbuild/workerPlugin.js"); | ||||||||||||||||||||||||||||||
const { createProtoPlugin } = require("./tools/esbuild/protoPlugin.js"); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const target = browserslistToEsbuild([ | ||||||||||||||||||||||||||||||
"last 3 Chrome versions", | ||||||||||||||||||||||||||||||
"last 3 Firefox versions", | ||||||||||||||||||||||||||||||
"last 2 Edge versions", | ||||||||||||||||||||||||||||||
"last 1 Safari versions", | ||||||||||||||||||||||||||||||
"last 1 iOS versions", | ||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
async function build(env = {}) { | ||||||||||||||||||||||||||||||
const isProduction = env.production || process.env.NODE_ENV === "production"; | ||||||||||||||||||||||||||||||
const isWatch = env.watch; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Determine output directory for bundles. | ||||||||||||||||||||||||||||||
// In watch mode, it's a temp dir. In production, it's the public bundle dir. | ||||||||||||||||||||||||||||||
const buildOutDir = isWatch | ||||||||||||||||||||||||||||||
? fs.mkdtempSync(path.join(os.tmpdir(), "esbuild-dev")) | ||||||||||||||||||||||||||||||
: outputPath; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const plugins = [ | ||||||||||||||||||||||||||||||
polyfillNode(), | ||||||||||||||||||||||||||||||
createProtoPlugin(protoPath), | ||||||||||||||||||||||||||||||
wasmLoader(), | ||||||||||||||||||||||||||||||
lessLoader({ | ||||||||||||||||||||||||||||||
javascriptEnabled: true, | ||||||||||||||||||||||||||||||
}), | ||||||||||||||||||||||||||||||
copyPlugin({ | ||||||||||||||||||||||||||||||
patterns: [ | ||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||
from: "node_modules/@zip.js/zip.js/dist/z-worker.js", | ||||||||||||||||||||||||||||||
to: path.join(buildOutDir, "z-worker.js"), | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
], | ||||||||||||||||||||||||||||||
}), | ||||||||||||||||||||||||||||||
createWorkerPlugin({ logLevel: env.logLevel }), // Resolves import Worker from myFunc.worker; | ||||||||||||||||||||||||||||||
esbuildPluginWorker() // Resolves new Worker(myWorker.js) | ||||||||||||||||||||||||||||||
]; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const buildOptions = { | ||||||||||||||||||||||||||||||
entryPoints: { | ||||||||||||||||||||||||||||||
main: path.resolve(srcPath, "main.tsx"), | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
bundle: true, | ||||||||||||||||||||||||||||||
outdir: buildOutDir, | ||||||||||||||||||||||||||||||
format: "esm", | ||||||||||||||||||||||||||||||
target: target, | ||||||||||||||||||||||||||||||
platform: "browser", | ||||||||||||||||||||||||||||||
splitting: true, | ||||||||||||||||||||||||||||||
chunkNames: "[name].[hash]", | ||||||||||||||||||||||||||||||
assetNames: "[name].[hash]", | ||||||||||||||||||||||||||||||
sourcemap: isProduction ? "external" : "inline", | ||||||||||||||||||||||||||||||
Comment on lines
+117
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing cache busting for worker files The PR objectives mention "cache-busting for workers (emit hashed filenames or append nocache)" as a follow-up item. The current configuration generates hashed chunk names but workers might not benefit from this. The Would you like me to help implement cache-busted filenames for workers or create an issue to track this task? |
||||||||||||||||||||||||||||||
minify: isProduction, | ||||||||||||||||||||||||||||||
define: { | ||||||||||||||||||||||||||||||
"process.env.NODE_ENV": JSON.stringify(isProduction ? "production" : "development"), | ||||||||||||||||||||||||||||||
"process.env.BABEL_ENV": JSON.stringify(process.env.BABEL_ENV || "development"), | ||||||||||||||||||||||||||||||
"process.browser": "true", | ||||||||||||||||||||||||||||||
"global": "globalThis" | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
loader: { | ||||||||||||||||||||||||||||||
".woff": "file", | ||||||||||||||||||||||||||||||
".woff2": "file", | ||||||||||||||||||||||||||||||
".ttf": "file", | ||||||||||||||||||||||||||||||
".eot": "file", | ||||||||||||||||||||||||||||||
".svg": "file", | ||||||||||||||||||||||||||||||
".png": "file", | ||||||||||||||||||||||||||||||
".jpg": "file", | ||||||||||||||||||||||||||||||
".jpeg": "file", | ||||||||||||||||||||||||||||||
".gif": "file", | ||||||||||||||||||||||||||||||
".webp": "file", | ||||||||||||||||||||||||||||||
".ico": "file", | ||||||||||||||||||||||||||||||
".mp4": "file", | ||||||||||||||||||||||||||||||
".webm": "file", | ||||||||||||||||||||||||||||||
".ogg": "file", | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
resolveExtensions: [".ts", ".tsx", ".js", ".json", ".proto", ".wasm"], | ||||||||||||||||||||||||||||||
alias: { | ||||||||||||||||||||||||||||||
react: path.resolve(__dirname, "node_modules/react"), | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
plugins: plugins, | ||||||||||||||||||||||||||||||
external: ["/assets/images/*", "fs"], | ||||||||||||||||||||||||||||||
logLevel: env.logLevel || "info", | ||||||||||||||||||||||||||||||
legalComments: isProduction ? "inline" : "none", | ||||||||||||||||||||||||||||||
publicPath: "/assets/bundle/", | ||||||||||||||||||||||||||||||
metafile: !isWatch, // Don't generate metafile for dev server | ||||||||||||||||||||||||||||||
logOverride: { | ||||||||||||||||||||||||||||||
"direct-eval": "silent", | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if (env.watch) { | ||||||||||||||||||||||||||||||
// Development server mode | ||||||||||||||||||||||||||||||
const ctx = await esbuild.context(buildOptions); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const { host, port } = await ctx.serve({ | ||||||||||||||||||||||||||||||
servedir: buildOutDir, | ||||||||||||||||||||||||||||||
port: env.PORT || 9002, | ||||||||||||||||||||||||||||||
onRequest: (args) => { | ||||||||||||||||||||||||||||||
if (env.logLevel === "verbose") { | ||||||||||||||||||||||||||||||
console.log(`[${args.method}] ${args.path} - status ${args.status}`); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
console.log(`Development server running at http://${host}:${port}`); | ||||||||||||||||||||||||||||||
console.log(`Serving files from temporary directory: ${buildOutDir}`); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
await ctx.watch(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
process.on("SIGINT", async () => { | ||||||||||||||||||||||||||||||
await ctx.dispose(); | ||||||||||||||||||||||||||||||
process.exit(0); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
Comment on lines
+191
to
+194
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major SIGINT handler should handle cleanup errors The signal handler for graceful shutdown doesn't handle potential errors during context disposal. process.on("SIGINT", async () => {
+ console.log("\nShutting down development server...");
+ try {
await ctx.dispose();
+ console.log("Build context disposed successfully");
+ } catch (error) {
+ console.error("Error disposing build context:", error);
+ }
process.exit(0);
}); 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||
// Production build | ||||||||||||||||||||||||||||||
const result = await esbuild.build(buildOptions); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if (result.metafile) { | ||||||||||||||||||||||||||||||
await fs.promises.writeFile( | ||||||||||||||||||||||||||||||
path.join(buildOutDir, "metafile.json"), | ||||||||||||||||||||||||||||||
JSON.stringify(result.metafile, null, 2) | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
console.log("Build completed successfully!"); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
module.exports = { build }; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// If called directly | ||||||||||||||||||||||||||||||
if (require.main === module) { | ||||||||||||||||||||||||||||||
const args = process.argv.slice(2); | ||||||||||||||||||||||||||||||
const env = { | ||||||||||||||||||||||||||||||
logLevel: "info", // Default log level | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
args.forEach(arg => { | ||||||||||||||||||||||||||||||
if (arg === "--production") env.production = true; | ||||||||||||||||||||||||||||||
if (arg === "--watch") env.watch = true; | ||||||||||||||||||||||||||||||
if (arg.startsWith("--port=")) env.PORT = Number.parseInt(arg.split("=")[1]); | ||||||||||||||||||||||||||||||
if (arg === "--verbose") env.logLevel = "verbose"; | ||||||||||||||||||||||||||||||
if (arg === "--silent") env.logLevel = "silent"; | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
build(env).catch(console.error); | ||||||||||||||||||||||||||||||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
}, | ||
"devDependencies": { | ||
"@biomejs/biome": "^1.9.4", | ||
"@chialab/esbuild-plugin-worker": "^0.18.1", | ||
"@redux-saga/testing-utils": "^1.1.5", | ||
"@shaderfrog/glsl-parser": "^0.3.0", | ||
"@types/color-hash": "^1.0.2", | ||
|
@@ -35,20 +36,20 @@ | |
"@vitest/coverage-v8": "3.1.1", | ||
"abort-controller": "^3.0.0", | ||
"browserslist-to-esbuild": "^1.2.0", | ||
"copy-webpack-plugin": "^12.0.2", | ||
"coveralls": "^3.0.2", | ||
"css-loader": "^6.5.1", | ||
"dependency-cruiser": "^16.10.0", | ||
"documentation": "^14.0.2", | ||
"dpdm": "^3.14.0", | ||
"esbuild": "^0.25", | ||
"esbuild": "^0.25.8", | ||
"esbuild-plugin-copy": "^2.1.1", | ||
"esbuild-plugin-less": "^1.3.24", | ||
"esbuild-plugin-polyfill-node": "^0.3.0", | ||
"esbuild-plugin-wasm": "^1.1.0", | ||
"espree": "^3.5.4", | ||
"husky": "^9.1.5", | ||
"jsdoc": "^3.5.5", | ||
"jsdom": "^26.1.0", | ||
"json-loader": "^0.5.7", | ||
"less": "^4.0.0", | ||
"less-loader": "^10.2.0", | ||
"lz4-wasm-nodejs": "^0.9.2", | ||
"merge-img": "^2.1.2", | ||
"pg": "^7.4.1", | ||
|
@@ -59,25 +60,21 @@ | |
"redux-mock-store": "^1.2.2", | ||
"shelljs": "^0.8.5", | ||
"tmp": "0.0.33", | ||
"ts-loader": "^9.4.1", | ||
"typescript": "^5.8.0", | ||
"typescript-coverage-report": "^0.8.0", | ||
"vite-tsconfig-paths": "^5.1.4", | ||
"vitest": "^3.1.1", | ||
"webpack": "^5.97.1", | ||
"webpack-cli": "^5.1.4", | ||
"webpack-dev-server": "^5.2.1" | ||
"vitest": "^3.1.1" | ||
}, | ||
"scripts": { | ||
"start": "node tools/proxy/proxy.js", | ||
"build": "node --max-old-space-size=4096 node_modules/.bin/webpack --env production", | ||
"build": "node esbuild_config.js --production", | ||
"@comment build-backend": "Only check for errors in the backend code like done by the CI. This command is not needed to run WEBKNOSSOS", | ||
"build-backend": "yarn build-wk-backend && yarn build-wk-datastore && yarn build-wk-tracingstore && rm webknossos-tracingstore/conf/messages webknossos-datastore/conf/messages", | ||
"build-wk-backend": "sbt -no-colors -DfailOnWarning compile stage", | ||
"build-wk-datastore": "sbt -no-colors -DfailOnWarning \"project webknossosDatastore\" copyMessages compile stage", | ||
"build-wk-tracingstore": "sbt -no-colors -DfailOnWarning \"project webknossosTracingstore\" copyMessages compile stage", | ||
"build-dev": "node_modules/.bin/webpack", | ||
"build-watch": "node_modules/.bin/webpack -w", | ||
"build-dev": "node esbuild_config.js", | ||
"build-watch": "node esbuild_config.js --watch", | ||
"listening": "lsof -i:5005,7155,9000,9001,9002", | ||
"kill-listeners": "kill -9 $(lsof -t -i:5005,7155,9000,9001,9002)", | ||
"rm-fossil-lock": "rm fossildb/data/LOCK", | ||
|
@@ -135,7 +132,7 @@ | |
"antd": "5.22", | ||
"ball-morphology": "^0.1.0", | ||
"base64-js": "^1.2.1", | ||
"beautiful-react-hooks": "^3.11.1", | ||
"beautiful-react-hooks": "^3.12", | ||
"chalk": "^5.0.1", | ||
"classnames": "^2.2.5", | ||
"color-hash": "^2.0.1", | ||
|
@@ -147,7 +144,6 @@ | |
"deep-freeze": "0.0.1", | ||
"dice-coefficient": "^2.1.0", | ||
"distance-transform": "^1.0.2", | ||
"esbuild-loader": "^4.1.0", | ||
"file-saver": "^2.0.1", | ||
"flexlayout-react": "0.7.15", | ||
"hammerjs": "^2.0.8", | ||
|
@@ -160,7 +156,6 @@ | |
"lz-string": "^1.4.4", | ||
"lz4-wasm": "^0.9.2", | ||
"memoize-one": "^6.0.0", | ||
"mini-css-extract-plugin": "^2.5.2", | ||
"minisearch": "^5.0.0", | ||
"mjs": "^1.0.0", | ||
"ml-matrix": "^6.10.4", | ||
|
@@ -196,8 +191,7 @@ | |
"tween.js": "^16.3.1", | ||
"typed-redux-saga": "^1.4.0", | ||
"url": "^0.11.0", | ||
"url-join": "^4.0.0", | ||
"worker-loader": "^3.0.8" | ||
"url-join": "^4.0.0" | ||
}, | ||
"packageManager": "[email protected]" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modern browser targets may break compatibility
The browser targets are set to very recent versions (last 3 Chrome/Firefox, last 2 Edge, last 1 Safari/iOS). This aggressive targeting will drop support for older browsers and could affect users on enterprise or locked-down systems.
Based on the PR objectives mentioning "drops legacy browser support" as an expected impact, this appears intentional. However, ensure this aligns with your user base requirements. Consider documenting the minimum supported browser versions explicitly in your documentation.
The current configuration effectively requires:
If you need to support slightly older browsers, consider adjusting to:
📝 Committable suggestion
🤖 Prompt for AI Agents