From a4db2dc460ec05e76ef8d37e599d03cbc6f07158 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Mon, 26 Oct 2026 06:28:27 +0000 Subject: [PATCH] Resolve browser polyfills from web_ui Lib and plugins import Node.js modules such as events and buffer, which the build swaps for browser packages. Vite resolves an aliased import from the importing package, and pnpm only lets a package see its own dependencies, so the polyfills had to be dependencies of lib. Resolve them from web_ui with a small resolver plugin instead, also used by the dependency optimizer. Hosts no longer install the browser packages, and plugins get the same polyfills webpack gave them. Co-Authored-By: Claude Opus 5.5 --- docs/devs/plugin-migration-to-2.1.md | 3 +- packages/lib/package.json | 9 ------ packages/lib/src/browser_polyfills.ts | 5 ++-- packages/web_ui/package.json | 8 +++++ packages/web_ui/vite.mjs | 42 ++++++++++++++++++--------- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/docs/devs/plugin-migration-to-2.1.md b/docs/devs/plugin-migration-to-2.1.md index 697fafbb..677b8053 100644 --- a/docs/devs/plugin-migration-to-2.1.md +++ b/docs/devs/plugin-migration-to-2.1.md @@ -54,7 +54,6 @@ These are left as imports in the plugin bundle and resolved by the browser, ther Everything else is bundled into the plugin, including `@ant-design/icons`. Import `react-dom/client` and `react/jsx-runtime` by those names, other subpaths of the shared packages are bundled as a separate copy. -Vite does not provide Node.js modules to plugin code. -If your web code imports `events`, `path` or similar, add the browser package for it (`events`, `path-browserify`) to your dependencies. +Imports of `assert`, `buffer`, `events`, `os`, `path`, `process`, `stream`, `util` and `zlib` resolve to the same browser packages as before, provided by `@clusterio/web_ui`. The `container` property of `WebPluginContext` is now the plugin's ES module namespace, `await container.get("./web")` returns the module instead of a factory function. diff --git a/packages/lib/package.json b/packages/lib/package.json index 79605060..b38ab998 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -54,19 +54,10 @@ "@types/yauzl": "catalog:", "@types/yazl": "catalog:", "ajv": "^8.20.0", - "assert": "^2.1.0", - "browserify-zlib": "^0.2.0", - "buffer": "^6.0.3", "chalk": "catalog:", - "events": "^3.3.0", "fast-deep-equal": "^3.1.3", - "os-browserify": "^0.3.0", - "path-browserify": "^1.0.1", - "process": "^0.11.10", "set-blocking": "catalog:", - "stream-browserify": "^3.0.0", "triple-beam": "^1.4.1", - "util": "^0.12.5", "winston": "catalog:", "winston-transport": "catalog:", "ws": "catalog:", diff --git a/packages/lib/src/browser_polyfills.ts b/packages/lib/src/browser_polyfills.ts index 6f901993..1c442d68 100644 --- a/packages/lib/src/browser_polyfills.ts +++ b/packages/lib/src/browser_polyfills.ts @@ -1,7 +1,6 @@ // Node.js globals used by lib and its dependencies when running in a browser. -// @ts-expect-error process has no type declarations -import process from "process/browser.js"; -import { Buffer } from "buffer/"; +import process from "process"; +import { Buffer } from "buffer"; // Vite's development server may already have created process.env if (typeof globalThis.process?.nextTick !== "function") { diff --git a/packages/web_ui/package.json b/packages/web_ui/package.json index 647277f2..3f010034 100644 --- a/packages/web_ui/package.json +++ b/packages/web_ui/package.json @@ -38,7 +38,13 @@ "@sinclair/typebox": "catalog:", "@vitejs/plugin-react": "catalog:", "antd": "catalog:", + "assert": "^2.1.0", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", "events": "^3.3.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", "rc-field-form": "^1.44.0", "react": "catalog:", "react-dom": "catalog:", @@ -46,7 +52,9 @@ "react-router": "catalog:", "react-router-dom": "catalog:", "semver": "catalog:", + "stream-browserify": "^3.0.0", "typescript": "catalog:", + "util": "^0.12.5", "vite": "catalog:" }, "devDependencies": { diff --git a/packages/web_ui/vite.mjs b/packages/web_ui/vite.mjs index 3532a96f..48f0bd2a 100644 --- a/packages/web_ui/vite.mjs +++ b/packages/web_ui/vite.mjs @@ -5,6 +5,7 @@ import path from "node:path"; import util from "node:util"; import { createRequire } from "node:module"; import { execFile } from "node:child_process"; +import { fileURLToPath } from "node:url"; import react from "@vitejs/plugin-react"; import { normalizePath } from "vite"; @@ -32,6 +33,20 @@ const esmSharedModules = new Set(["@clusterio/lib", "@clusterio/web_ui"]); // Only the console transport of winston works in the browser, and the colour // library used by it does not. const stubbedModules = /node_modules\/(winston\/dist\/winston\/transports\/(http|file)\.js|@colors\/colors\/)/; +// Browser versions of Node.js modules, resolved from web_ui so lib and plugins +// don't need them as dependencies. +const polyfills = { + assert: "assert/", + buffer: "buffer/", + events: "events/", + os: "os-browserify/browser", + path: "path-browserify", + process: "process/browser.js", + stream: "stream-browserify", + util: "util/", + zlib: "browserify-zlib", +}; +const webUiPackage = fileURLToPath(new URL("./package.json", import.meta.url)); const sharedPrefix = "\0clusterio:shared/"; const pluginPrefix = "\0clusterio:plugin/"; @@ -134,9 +149,19 @@ const stubs = { }, }; +const polyfillResolver = { + name: "clusterio:polyfills", + resolveId(id, _importer, options) { + const target = polyfills[id.replace(/^node:/, "")]; + if (!target) { + return null; + } + return this.resolve(target, webUiPackage, { ...options, skipSelf: true }); + }, +}; + /** Base config needed to build code that imports @clusterio/lib for the browser. */ function browserCompat() { - const alias = (find, replacement) => ({ find: new RegExp(`^(node:)?${find}$`), replacement }); return { name: "clusterio:browser-compat", enforce: "pre", @@ -153,20 +178,10 @@ function browserCompat() { "process.env.BUILD_OS": JSON.stringify(os.platform()), }, optimizeDeps: { - rolldownOptions: { plugins: [stubs] }, + rolldownOptions: { plugins: [polyfillResolver, stubs] }, }, resolve: { - alias: [ - alias("assert", "assert/"), - alias("buffer", "buffer/"), - alias("events", "events/"), - alias("os", "os-browserify/browser"), - alias("path", "path-browserify"), - alias("stream", "stream-browserify"), - alias("util", "util/"), - alias("zlib", "browserify-zlib"), - { find: /^node:(.*)$/, replacement: "$1" }, - ], + alias: [{ find: /^node:(.*)$/, replacement: "$1" }], }, build: { target: "es2022", @@ -185,6 +200,7 @@ function browserCompat() { }, }; }, + resolveId: polyfillResolver.resolveId, load: stubs.load, transform(code, id) { if (id.endsWith(".md")) {