@rustwrap/webpack 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/bin/{rollpack.js → rustwrap-webpack.js} +3 -3
- package/lib/build.js +122 -10
- package/lib/compiler.js +17 -2
- package/lib/config.js +7 -4
- package/lib/const-enum.js +136 -0
- package/lib/css.js +4 -4
- package/lib/dev-server.js +11 -11
- package/lib/index.js +7 -7
- package/lib/loaders.js +2 -2
- package/lib/plugins.js +7 -7
- package/lib/shims/global.js +8 -0
- package/lib/shims/process.js +31 -0
- package/lib/stats.js +2 -2
- package/package.json +46 -45
package/README.md
CHANGED
|
@@ -78,12 +78,14 @@ Taking the **strictest** clause on each line yields the declared range. Reading
|
|
|
78
78
|
| **module.rules (loaders)** | Real loader chains via `loader-runner`: `test`/`include`/`exclude`/`resourceQuery`/`oneOf`/`enforce:pre|post`/`use` array/`options`/custom loaders. JS/TS/JSX transpilation is done by **Oxc** (transpile-only loaders `ts-loader`/`babel-loader`/`swc-loader`/`esbuild-loader` are skipped — equivalent, faster). |
|
|
79
79
|
| **CSS** | Native: `.scss`/`.sass` (consumer's `sass`), `.less` (consumer's `less`), `.css`; style-injected, or **extracted** when `MiniCssExtractPlugin` is present. **CSS Modules** (`*.module.*`) are properly scoped via `postcss-modules` (scoped class names + `{local:scoped}` export map + `composes`). |
|
|
80
80
|
| **Asset modules** | `asset/resource` (emit + URL), `asset/inline` (data URI), `asset/source`, `asset` (auto by `parser.dataUrlCondition.maxSize`); `generator.filename`/`output.assetModuleFilename`. |
|
|
81
|
-
| **
|
|
81
|
+
| **Node globals (browser target)** | Auto-polyfills `process`, `Buffer`, and `global` for `web` builds — parity with webpack's `ProvidePlugin`/node-libs-browser. Using real scope analysis (acorn + eslint-scope), a **`process` shim** (nextTick/env/platform/…), the **`buffer` package's `Buffer`**, and `global`→globalThis are injected **only into modules that reference them as free (unbound) identifiers**. So dependencies that assume Node (e.g. telemetry SDKs pulling in `readable-stream`/`buffer` such as `@aria/webjs-sdk`) work in the browser instead of throwing `process is not defined`. No-op on `target:'node'` and for code that never references them. |
|
|
82
|
+
| **externals** | object map / array / RegExp / sync-callback function (`({request},cb)` & `(ctx,req,cb)`) / `{root}` → external + `output.globals`. **Exact-request match** (webpack semantics): an entry like `{react:"React"}` externalizes `react` only, **not** subpaths like `react/jsx-runtime` (those are bundled, so e.g. the automatic JSX runtime keeps its `jsx`/`jsxs`). Externals are reachable from **both `import` and `require()`** — a bundled CommonJS dependency's internal `require("<external>")` is rewritten to the same global, so it never leaves a bare `require` (which would throw in the browser). |
|
|
83
|
+
| **TypeScript** | Transpiled by Oxc (types stripped, JSX per `tsconfig`). **Ambient `const enum` member accesses are inlined to literals** (tsc parity) by reading the const enums declared in the project's `.ts/.d.ts` and any pulled in via tsconfig `files`/`include` or triple-slash `/// <reference>` — without this, accesses like `XrmClientApi.Constants.X.Y` would survive as runtime references to a namespace that doesn't exist at runtime. Regular (non-const) `enum`s emit runtime objects as usual. |
|
|
82
84
|
| **resolve** | `alias`, `extensions`, `mainFields`, `mainFiles`, `conditionNames`, `modules`, `extensionAlias`, `symlinks`, `fallback` (`false`→empty module), nearest `tsconfig.json` paths. |
|
|
83
85
|
| **devtool (source maps)** | `source-map`, `inline-source-map`, `hidden-source-map`, `nosources-*`, `eval-*` (≈inline). Emits `.map` + `sourceMappingURL`. |
|
|
84
86
|
| **target** | `web` (default) and `node`/`node*` (→ Rolldown `platform:'node'`, node builtins external). |
|
|
85
87
|
| **optimization** | `minimize`; **`minimizer` TerserPlugin `terserOptions` mapped to Rolldown's minifier**: `compress.drop_console`/`drop_debugger`/`passes`/`ecma`(→target)/`keep_classnames`/`keep_fnames`, `mangle` on/off/`toplevel`/`keep_*`, `format.comments`→`legalComments`. `usedExports:false` disables tree-shaking. Tree-shaking + scope-hoisting are always on via Rolldown. |
|
|
86
|
-
| **Built-in plugins** | `DefinePlugin` (nested keys), `EnvironmentPlugin`, `ProvidePlugin` (**real free-variable scope analysis** via acorn + eslint-scope), `BannerPlugin`, `IgnorePlugin`, `NormalModuleReplacementPlugin`, `SourceMapDevToolPlugin`/`EvalSourceMapDevToolPlugin`, `ProgressPlugin`, `optimize.LimitChunkCountPlugin` (forces single chunk), `LoaderOptionsPlugin`, `WatchIgnorePlugin`, `HotModuleReplacementPlugin` (enables `module.hot`), `ContextReplacementPlugin`. |
|
|
88
|
+
| **Built-in plugins** | `DefinePlugin` (nested keys; **AST-aware** — replaces expressions only, never the contents of string/template literals or comments), `EnvironmentPlugin`, `ProvidePlugin` (**real free-variable scope analysis** via acorn + eslint-scope), `BannerPlugin`, `IgnorePlugin`, `NormalModuleReplacementPlugin`, `SourceMapDevToolPlugin`/`EvalSourceMapDevToolPlugin`, `ProgressPlugin`, `optimize.LimitChunkCountPlugin` (forces single chunk), `LoaderOptionsPlugin`, `WatchIgnorePlugin`, `HotModuleReplacementPlugin` (enables `module.hot`), `ContextReplacementPlugin`. |
|
|
87
89
|
| **Dev server / HMR** | `webpack serve` / `new webpack.DevServer(options, compiler)`. HTTP static serving (`static`), `historyApiFallback`, watch + rebuild, **SSE live-reload**, and a `module.hot`/`import.meta.hot` runtime so HMR-guarded code runs. (Hot updates apply via fast full reload — see Approximated.) |
|
|
88
90
|
| **Third-party plugins** | Anything tapping `compilation.hooks.processAssets`/`compiler.hooks.emit`/`done`/etc. works (Copy-style, Html-style, Banner, analyzers). |
|
|
89
91
|
| **performance** | `hints`/`maxAssetSize` enforced (warning/error). |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/*
|
|
4
|
-
*
|
|
4
|
+
* @rustwrap/webpack CLI — a minimal `webpack`-compatible command. pcf-scripts uses the programmatic API
|
|
5
5
|
* (require("webpack")(config, cb)), so this CLI only covers basic `webpack [--config f] [--mode m]`
|
|
6
6
|
* invocations for direct use.
|
|
7
7
|
*/
|
|
@@ -11,7 +11,7 @@ const Module = require("module");
|
|
|
11
11
|
|
|
12
12
|
// webpack.config.js files often `require()` webpack-ecosystem plugins (TerserPlugin, analyzer,
|
|
13
13
|
// eslint-webpack-plugin, ...) that Rolldown doesn't need. Stub any that aren't resolvable so the
|
|
14
|
-
// config loads;
|
|
14
|
+
// config loads; @rustwrap/webpack reads the relevant intent (mode, externals, Define/Banner) from the object.
|
|
15
15
|
const STUB_PKGS = new Set([
|
|
16
16
|
"terser-webpack-plugin", "webpack-bundle-analyzer", "eslint-webpack-plugin",
|
|
17
17
|
"css-minimizer-webpack-plugin", "mini-css-extract-plugin", "fork-ts-checker-webpack-plugin",
|
|
@@ -65,7 +65,7 @@ if (opts.mode) config.mode = opts.mode;
|
|
|
65
65
|
if (opts.watch) config.watch = true;
|
|
66
66
|
|
|
67
67
|
if (opts.serve) {
|
|
68
|
-
//
|
|
68
|
+
// `@rustwrap/webpack serve` / `webpack serve` — start the dev server.
|
|
69
69
|
if (!config.mode) config.mode = "development";
|
|
70
70
|
const devServerOpts = Object.assign({ hot: true }, config.devServer || {});
|
|
71
71
|
if (opts.port) devServerOpts.port = opts.port;
|
package/lib/build.js
CHANGED
|
@@ -116,16 +116,45 @@ function convertChunkTemplate(t) {
|
|
|
116
116
|
function definePlugin(define) {
|
|
117
117
|
const keys = Object.keys(define).sort((a, b) => b.length - a.length);
|
|
118
118
|
if (!keys.length) return null;
|
|
119
|
+
const acorn = require("acorn");
|
|
120
|
+
const tt = acorn.tokTypes;
|
|
119
121
|
const escaped = keys.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
120
122
|
const re = new RegExp(`(?<![\\w$.])(${escaped.join("|")})(?![\\w$])`, "g");
|
|
123
|
+
const replaceAll = (code) => code.replace(re, (m) => (define[m] != null ? define[m] : m));
|
|
121
124
|
return {
|
|
122
|
-
name: "
|
|
125
|
+
name: "rustwrap:define",
|
|
123
126
|
transform(code, id) {
|
|
124
127
|
if (/[/\\]node_modules[/\\]/.test(id) && !/process\.env|typeof process|__DEV__/.test(code)) return null;
|
|
125
128
|
re.lastIndex = 0;
|
|
126
129
|
if (!re.test(code)) return null;
|
|
130
|
+
// Collect ranges of string/template/regex literals and comments. webpack's DefinePlugin
|
|
131
|
+
// replaces expressions only — never the contents of strings or comments — so we must skip
|
|
132
|
+
// any match landing inside these ranges (a raw text replace would corrupt string literals).
|
|
133
|
+
const protectedRanges = [];
|
|
134
|
+
try {
|
|
135
|
+
acorn.parse(code, {
|
|
136
|
+
ecmaVersion: "latest",
|
|
137
|
+
sourceType: "module",
|
|
138
|
+
allowReturnOutsideFunction: true,
|
|
139
|
+
allowAwaitOutsideFunction: true,
|
|
140
|
+
allowHashBang: true,
|
|
141
|
+
onComment: (_block, _text, start, end) => protectedRanges.push([start, end]),
|
|
142
|
+
onToken: (t) => {
|
|
143
|
+
// `template` tokens are the literal chunks between `${}` — protecting them leaves the
|
|
144
|
+
// interpolated `${expr}` code (separate tokens) eligible for replacement, like webpack.
|
|
145
|
+
if (t.type === tt.string || t.type === tt.template || t.type === tt.regexp) protectedRanges.push([t.start, t.end]);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
} catch (_) {
|
|
149
|
+
// Unparseable (already-CJS/odd syntax) — fall back to the identifier-boundary regex rather
|
|
150
|
+
// than break the build. This preserves prior behavior for the rare unparseable module.
|
|
151
|
+
re.lastIndex = 0;
|
|
152
|
+
return { code: replaceAll(code), map: null };
|
|
153
|
+
}
|
|
154
|
+
const inProtected = (idx) => protectedRanges.some(([s, e]) => idx >= s && idx < e);
|
|
127
155
|
re.lastIndex = 0;
|
|
128
|
-
|
|
156
|
+
const out = code.replace(re, (m, _g1, offset) => (inProtected(offset) ? m : (define[m] != null ? define[m] : m)));
|
|
157
|
+
return { code: out, map: null };
|
|
129
158
|
},
|
|
130
159
|
};
|
|
131
160
|
}
|
|
@@ -137,13 +166,13 @@ function providePlugin(provide, _context) {
|
|
|
137
166
|
const eslintScope = require("eslint-scope");
|
|
138
167
|
const simple = entries.filter(([k]) => !k.includes("."));
|
|
139
168
|
return {
|
|
140
|
-
name: "
|
|
169
|
+
name: "rustwrap:provide",
|
|
141
170
|
transform(code, id) {
|
|
142
171
|
if (/\.(css|scss|sass|less|svg)$/.test(id) || !simple.length) return null;
|
|
143
172
|
if (!simple.some(([k]) => code.includes(k))) return null;
|
|
144
173
|
let ast;
|
|
145
174
|
try {
|
|
146
|
-
ast = acorn.parse(code, { ecmaVersion: "latest", sourceType: "module", allowReturnOutsideFunction: true, allowAwaitOutsideFunction: true, allowHashBang: true });
|
|
175
|
+
ast = acorn.parse(code, { ecmaVersion: "latest", sourceType: "module", ranges: true, allowReturnOutsideFunction: true, allowAwaitOutsideFunction: true, allowHashBang: true });
|
|
147
176
|
} catch (_) { return null; } // unparseable (already-CJS/odd) — skip rather than break the build
|
|
148
177
|
let manager;
|
|
149
178
|
try { manager = eslintScope.analyze(ast, { ecmaVersion: 2022, sourceType: "module", ignoreEval: true, optimistic: true }); }
|
|
@@ -168,6 +197,78 @@ function providePlugin(provide, _context) {
|
|
|
168
197
|
};
|
|
169
198
|
}
|
|
170
199
|
|
|
200
|
+
// Bundled CommonJS modules can `require("<external>")` (e.g. React's CJS `react/jsx-runtime` does
|
|
201
|
+
// `require("react")`). rolldown maps externals to globals for ESM imports via output.globals, but it
|
|
202
|
+
// does NOT rewrite a CJS module's internal `require(external)` -> the global, leaving a bare
|
|
203
|
+
// `require(...)` that throws "require is not defined" in the browser. This transform rewrites
|
|
204
|
+
// `require("<external>")` to the same platform/global the external resolves to, restoring webpack's
|
|
205
|
+
// behavior (where externals are reachable from both `import` and `require`).
|
|
206
|
+
function externalRequirePlugin(isExternal, globals) {
|
|
207
|
+
if (typeof isExternal !== "function") return null;
|
|
208
|
+
const RE = /(?<![\w.$])require\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
209
|
+
return {
|
|
210
|
+
name: "rustwrap:external-require",
|
|
211
|
+
transform(code) {
|
|
212
|
+
if (code.indexOf("require(") === -1) return null;
|
|
213
|
+
let changed = false;
|
|
214
|
+
const out = code.replace(RE, (m, _q, dep) => {
|
|
215
|
+
let ext;
|
|
216
|
+
try { ext = isExternal(dep); } catch { ext = false; }
|
|
217
|
+
if (!ext) return m;
|
|
218
|
+
let g;
|
|
219
|
+
try { g = globals(dep); } catch { g = undefined; }
|
|
220
|
+
if (!g || !/^[A-Za-z_$][\w$]*$/.test(g)) return m; // only simple global identifiers
|
|
221
|
+
changed = true;
|
|
222
|
+
// Reference the platform-provided global (same one output.globals binds for ESM imports);
|
|
223
|
+
// fall back to globalThis lookup if the bare binding isn't in scope.
|
|
224
|
+
return `(typeof ${g}!=="undefined"?${g}:(typeof globalThis!=="undefined"?globalThis:window)[${JSON.stringify(g)}])`;
|
|
225
|
+
});
|
|
226
|
+
return changed ? { code: out, map: null } : null;
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Node-builtin globals polyfill for browser targets (parity with webpack's ProvidePlugin +
|
|
232
|
+
// node-libs-browser, and pcf-scripts' esbuild `pcfEsbuildNodePolyfills`). Rolldown/webpack-5 do not
|
|
233
|
+
// auto-provide these, so a dependency that assumes Node (e.g. telemetry SDKs pulling in
|
|
234
|
+
// readable-stream/buffer) can reference a bare `process.nextTick`/`Buffer` that throws in the
|
|
235
|
+
// browser. Using the same free-variable scope analysis as ProvidePlugin, we inject `process`
|
|
236
|
+
// (inline shim), `global` (globalThis), and `Buffer` (the `buffer` package) ONLY into modules that
|
|
237
|
+
// reference them as free (unbound) identifiers — so it's a no-op for code that doesn't need them.
|
|
238
|
+
function nodePolyfillPlugin(platform) {
|
|
239
|
+
if (platform === "node") return null; // node target: real builtins, no shim
|
|
240
|
+
const acorn = require("acorn");
|
|
241
|
+
const eslintScope = require("eslint-scope");
|
|
242
|
+
const resolveSafe = (id) => { try { return require.resolve(id); } catch { return null; } };
|
|
243
|
+
const bufferPath = resolveSafe("buffer/");
|
|
244
|
+
const processPath = path.join(__dirname, "shims", "process.js");
|
|
245
|
+
const globalPath = path.join(__dirname, "shims", "global.js");
|
|
246
|
+
const SHIM_PATHS = new Set([processPath, globalPath, bufferPath].filter(Boolean));
|
|
247
|
+
return {
|
|
248
|
+
name: "rustwrap:node-polyfill",
|
|
249
|
+
transform(code, id) {
|
|
250
|
+
if (SHIM_PATHS.has(path.normalize(id))) return null; // never inject into the shims themselves
|
|
251
|
+
// Skip rolldown's internal runtime/virtual modules — their interop helpers reference `Buffer`
|
|
252
|
+
// in `typeof` guards, which would otherwise pull the whole buffer polyfill into every bundle.
|
|
253
|
+
if (id.startsWith("\0") || /(?:^|[/\\])rolldown[/\\]|rolldown:/.test(id)) return null;
|
|
254
|
+
if (/\.(css|scss|sass|less|svg)$/.test(id)) return null;
|
|
255
|
+
if (!/(?<![\w.$])(process|Buffer|global)\b/.test(code)) return null;
|
|
256
|
+
let ast;
|
|
257
|
+
try { ast = acorn.parse(code, { ecmaVersion: "latest", sourceType: "module", ranges: true, allowReturnOutsideFunction: true, allowAwaitOutsideFunction: true, allowHashBang: true }); }
|
|
258
|
+
catch { return null; }
|
|
259
|
+
let mgr;
|
|
260
|
+
try { mgr = eslintScope.analyze(ast, { ecmaVersion: 2022, sourceType: "module", ignoreEval: true, optimistic: true }); }
|
|
261
|
+
catch { return null; }
|
|
262
|
+
const free = new Set(mgr.globalScope.through.map((r) => r.identifier.name));
|
|
263
|
+
let header = "";
|
|
264
|
+
if (free.has("process")) header += `import __rp_process from ${JSON.stringify(processPath)};\nvar process=__rp_process;\n`;
|
|
265
|
+
if (free.has("global")) header += `import __rp_global from ${JSON.stringify(globalPath)};\nvar global=__rp_global;\n`;
|
|
266
|
+
if (free.has("Buffer") && bufferPath) header += `import { Buffer as __rp_Buffer } from ${JSON.stringify(bufferPath)};\nvar Buffer=__rp_Buffer;\n`;
|
|
267
|
+
return header ? { code: header + code, map: null } : null;
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
171
272
|
function ignorePlugin(ignores) {
|
|
172
273
|
if (!ignores || !ignores.length) return null;
|
|
173
274
|
const matches = (request, ctx) => ignores.some((opt) => {
|
|
@@ -177,9 +278,9 @@ function ignorePlugin(ignores) {
|
|
|
177
278
|
if (typeof opt.checkResource === "function") return opt.checkResource(request, ctx);
|
|
178
279
|
return false;
|
|
179
280
|
});
|
|
180
|
-
const VID = "\
|
|
281
|
+
const VID = "\0rustwrap-ignored";
|
|
181
282
|
return {
|
|
182
|
-
name: "
|
|
283
|
+
name: "rustwrap:ignore",
|
|
183
284
|
resolveId(source, importer) { if (matches(source, importer && path.dirname(importer))) return VID; return null; },
|
|
184
285
|
load(id) { if (id === VID) return "export default {};"; return null; },
|
|
185
286
|
};
|
|
@@ -188,7 +289,7 @@ function ignorePlugin(ignores) {
|
|
|
188
289
|
function normalReplacePlugin(replacements) {
|
|
189
290
|
if (!replacements || !replacements.length) return null;
|
|
190
291
|
return {
|
|
191
|
-
name: "
|
|
292
|
+
name: "rustwrap:normal-replace",
|
|
192
293
|
resolveId(source, importer) {
|
|
193
294
|
for (const [re, repl] of replacements) {
|
|
194
295
|
if (re.test(source)) {
|
|
@@ -209,17 +310,26 @@ function entryRel(e, chunkFileName) {
|
|
|
209
310
|
|
|
210
311
|
async function runMake(compilation, cfg, context) {
|
|
211
312
|
const compiler = compilation.compiler;
|
|
212
|
-
const rp = compiler.$
|
|
313
|
+
const rp = compiler.$rustwrap || {};
|
|
213
314
|
const prod = cfg.mode !== "development" && cfg.mode !== "none";
|
|
214
315
|
const minimize = readMinimize(cfg);
|
|
215
316
|
const entries = normalizeEntries(cfg, context);
|
|
216
317
|
const ext = buildExternals(cfg, context);
|
|
217
318
|
const define = buildDefine(cfg, prod, rp.define);
|
|
319
|
+
// Inline ambient `const enum` member accesses to literals (tsc parity). Without this, oxc/rolldown
|
|
320
|
+
// leave e.g. `XrmClientApi.Constants.GlobalNotificationLevel.success` as a runtime reference to a
|
|
321
|
+
// namespace that doesn't exist at runtime -> "XrmClientApi is not defined". We feed the collected
|
|
322
|
+
// `Ns.Enum.Member` -> literal map through the same Define replacement. Explicit user defines win.
|
|
323
|
+
try {
|
|
324
|
+
const { collectConstEnumDefines } = require("./const-enum");
|
|
325
|
+
const ce = collectConstEnumDefines(context);
|
|
326
|
+
for (const k of Object.keys(ce)) if (define[k] == null) define[k] = ce[k];
|
|
327
|
+
} catch { /* typescript unavailable or parse failure -> skip inlining (non-fatal) */ }
|
|
218
328
|
// When hot is enabled (dev server or HotModuleReplacementPlugin), make `module.hot` /
|
|
219
329
|
// `import.meta.hot` resolve to the injected client runtime so HMR-guarded code runs.
|
|
220
330
|
if (rp.hot) {
|
|
221
|
-
define["module.hot"] = "(typeof window!=='undefined'&&window.
|
|
222
|
-
define["import.meta.hot"] = "(typeof window!=='undefined'&&window.
|
|
331
|
+
define["module.hot"] = "(typeof window!=='undefined'&&window.__rustwrap_hot__)";
|
|
332
|
+
define["import.meta.hot"] = "(typeof window!=='undefined'&&window.__rustwrap_hot__)";
|
|
223
333
|
}
|
|
224
334
|
const resolve = buildResolve(cfg, context);
|
|
225
335
|
const { sourcemap } = devtoolToRolldown(cfg.devtool);
|
|
@@ -232,6 +342,8 @@ async function runMake(compilation, cfg, context) {
|
|
|
232
342
|
loaderPlugin(cfg, context, compilation),
|
|
233
343
|
providePlugin(rp.provide, context),
|
|
234
344
|
definePlugin(define),
|
|
345
|
+
externalRequirePlugin(ext.isExternal, ext.globals),
|
|
346
|
+
nodePolyfillPlugin(platform),
|
|
235
347
|
].filter(Boolean);
|
|
236
348
|
|
|
237
349
|
const t0 = Date.now();
|
package/lib/compiler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
*
|
|
3
|
+
* @rustwrap/webpack Compiler/Compilation — a tapable-based webpack lifecycle so real plugins (apply(compiler))
|
|
4
4
|
* run. The heavy lifting (producing entry bundles) happens in the `make` phase via Rolldown; plugins
|
|
5
5
|
* then add/modify compilation.assets through processAssets/emit hooks before they're written.
|
|
6
6
|
*/
|
|
@@ -255,9 +255,24 @@ class Compiler {
|
|
|
255
255
|
};
|
|
256
256
|
run();
|
|
257
257
|
let watcher;
|
|
258
|
+
// Ignore writes we cause ourselves (the build's own output dir) and irrelevant paths — otherwise
|
|
259
|
+
// emitting into output.path retriggers the watcher in an infinite rebuild/reload loop. fs.watch
|
|
260
|
+
// reports paths RELATIVE to root, so the output dir often has no leading separator (e.g.
|
|
261
|
+
// "out/controls/x/bundle.js") — anchor to start as well as after a separator.
|
|
262
|
+
const outPath = this.options && this.options.output && this.options.output.path;
|
|
263
|
+
const outRel = outPath ? path.relative(root, outPath).replace(/\\/g, "/") : "";
|
|
264
|
+
const ignored = (f) => {
|
|
265
|
+
if (!f) return false;
|
|
266
|
+
const n = f.replace(/\\/g, "/");
|
|
267
|
+
if (/(^|\/)(node_modules|\.git)(\/|$)/.test(n)) return true;
|
|
268
|
+
if (/\.d\.ts$/.test(n)) return true;
|
|
269
|
+
if (outRel && outRel !== "." && !outRel.startsWith("..") && (n === outRel || n.startsWith(outRel + "/"))) return true;
|
|
270
|
+
if (/^(out|dist)(\/|$)/.test(n)) return true; // fallback for default output dirs at the root
|
|
271
|
+
return false;
|
|
272
|
+
};
|
|
258
273
|
try {
|
|
259
274
|
watcher = fs.watch(root, { recursive: true }, (_ev, f) => {
|
|
260
|
-
if (
|
|
275
|
+
if (ignored(f)) return;
|
|
261
276
|
if (building) return;
|
|
262
277
|
this.hooks.invalid.call(f, Date.now());
|
|
263
278
|
clearTimeout(timer); timer = setTimeout(run, (watchOptions && watchOptions.aggregateTimeout) || 200);
|
package/lib/config.js
CHANGED
|
@@ -110,8 +110,11 @@ function buildExternals(cfg, context) {
|
|
|
110
110
|
return undefined;
|
|
111
111
|
};
|
|
112
112
|
const resolveGlobal = (id) => {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// Webpack object externals match the EXACT request only (never subpaths). Matching subpaths
|
|
114
|
+
// (e.g. `react/jsx-runtime` under a `react` external) wrongly maps the automatic JSX runtime to
|
|
115
|
+
// the platform React global, which lacks jsx/jsxs -> "jsxs is not a function". Exact-match keeps
|
|
116
|
+
// react/jsx-runtime (and other subpath entrypoints) bundled, exactly as webpack does.
|
|
117
|
+
if (Object.prototype.hasOwnProperty.call(map, id)) return map[id];
|
|
115
118
|
const f = callFn(id); if (f) return f;
|
|
116
119
|
return undefined;
|
|
117
120
|
};
|
|
@@ -125,8 +128,8 @@ function buildExternals(cfg, context) {
|
|
|
125
128
|
return { isExternal, globals };
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
function buildDefine(cfg, prod,
|
|
129
|
-
const def = Object.assign({ "process.env.NODE_ENV": JSON.stringify(prod ? "production" : "development") },
|
|
131
|
+
function buildDefine(cfg, prod, rustwrapDefine) {
|
|
132
|
+
const def = Object.assign({ "process.env.NODE_ENV": JSON.stringify(prod ? "production" : "development") }, rustwrapDefine || {});
|
|
130
133
|
return def;
|
|
131
134
|
}
|
|
132
135
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Ambient `const enum` inlining (tsc-parity).
|
|
4
|
+
*
|
|
5
|
+
* tsc/ts-loader inline ambient `const enum` member accesses to literal values at compile time
|
|
6
|
+
* (e.g. `XrmClientApi.Constants.GlobalNotificationLevel.success` -> `1`). Fast TS transformers
|
|
7
|
+
* (oxc/rolldown, esbuild, swc) do NOT inline ambient const enums declared in `.d.ts`, leaving the
|
|
8
|
+
* member access as a runtime reference to a namespace object that doesn't exist at runtime ->
|
|
9
|
+
* "XrmClientApi is not defined".
|
|
10
|
+
*
|
|
11
|
+
* This module parses const enums (with their full namespace path) from the project's `.ts/.tsx`
|
|
12
|
+
* sources and any `.d.ts` they pull in via triple-slash `/// <reference path=.../>`, and returns a
|
|
13
|
+
* map of `Ns.Sub.Enum.Member` -> literal. build.js merges that map into the Define map so the
|
|
14
|
+
* existing (AST-aware) define replacement inlines them exactly like tsc would.
|
|
15
|
+
*/
|
|
16
|
+
const fs = require("fs");
|
|
17
|
+
const path = require("path");
|
|
18
|
+
|
|
19
|
+
function loadTS(context) {
|
|
20
|
+
try { return require(require.resolve("typescript", { paths: [context, process.cwd()] })); }
|
|
21
|
+
catch { return null; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Walk a directory for .ts/.tsx sources (skipping node_modules/out/dist/dot-dirs).
|
|
25
|
+
function walkSources(dir, out, depth) {
|
|
26
|
+
if (depth > 10) return;
|
|
27
|
+
let entries;
|
|
28
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
if (e.name === "node_modules" || e.name === "out" || e.name === "dist" || e.name.startsWith(".")) continue;
|
|
31
|
+
const full = path.join(dir, e.name);
|
|
32
|
+
if (e.isDirectory()) walkSources(full, out, depth + 1);
|
|
33
|
+
else if (/\.(ts|tsx)$/.test(e.name)) out.add(path.normalize(full));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Resolve triple-slash `/// <reference path='...'/>` directives to absolute paths.
|
|
38
|
+
function refsIn(file) {
|
|
39
|
+
let text;
|
|
40
|
+
try { text = fs.readFileSync(file, "utf8"); } catch { return []; }
|
|
41
|
+
const out = [];
|
|
42
|
+
const re = /\/\/\/\s*<reference\s+path\s*=\s*['"]([^'"]+)['"]\s*\/>/g;
|
|
43
|
+
let m;
|
|
44
|
+
while ((m = re.exec(text))) out.push(path.resolve(path.dirname(file), m[1]));
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse one file's const enums into map[fullPath] = literalString.
|
|
49
|
+
function collectFile(ts, file, map) {
|
|
50
|
+
let text;
|
|
51
|
+
try { text = fs.readFileSync(file, "utf8"); } catch { return; }
|
|
52
|
+
if (!/const\s+enum/.test(text)) return; // fast skip — most files have none
|
|
53
|
+
let sf;
|
|
54
|
+
try { sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true); } catch { return; }
|
|
55
|
+
const nsStack = [];
|
|
56
|
+
const isConst = (node) => {
|
|
57
|
+
const mods = ts.canHaveModifiers ? ts.getModifiers(node) : node.modifiers;
|
|
58
|
+
return !!(mods && mods.some((m) => m.kind === ts.SyntaxKind.ConstKeyword));
|
|
59
|
+
};
|
|
60
|
+
const visit = (node) => {
|
|
61
|
+
if (ts.isModuleDeclaration(node) && node.name && node.body) {
|
|
62
|
+
nsStack.push(node.name.text);
|
|
63
|
+
ts.forEachChild(node.body, visit);
|
|
64
|
+
nsStack.pop();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (ts.isEnumDeclaration(node) && isConst(node)) {
|
|
68
|
+
const enumName = node.name.text;
|
|
69
|
+
let auto = 0;
|
|
70
|
+
for (const member of node.members) {
|
|
71
|
+
let memberName;
|
|
72
|
+
try { memberName = member.name.getText(sf).replace(/^['"]|['"]$/g, ""); } catch { continue; }
|
|
73
|
+
let value;
|
|
74
|
+
const init = member.initializer;
|
|
75
|
+
if (!init) {
|
|
76
|
+
value = auto; auto += 1;
|
|
77
|
+
} else if (ts.isNumericLiteral(init)) {
|
|
78
|
+
value = Number(init.text); auto = value + 1;
|
|
79
|
+
} else if (ts.isPrefixUnaryExpression(init) && init.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(init.operand)) {
|
|
80
|
+
value = -Number(init.operand.text); auto = value + 1;
|
|
81
|
+
} else if (ts.isStringLiteral(init)) {
|
|
82
|
+
value = init.text; // string enum member — no auto-increment afterwards
|
|
83
|
+
} else {
|
|
84
|
+
continue; // computed/complex initializer — cannot safely inline
|
|
85
|
+
}
|
|
86
|
+
const literal = typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
87
|
+
map[[...nsStack, enumName, memberName].join(".")] = literal;
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
ts.forEachChild(node, visit);
|
|
92
|
+
};
|
|
93
|
+
visit(sf);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Build the const-enum -> literal map for a build rooted at `context`.
|
|
97
|
+
function collectConstEnumDefines(context) {
|
|
98
|
+
const ts = loadTS(context);
|
|
99
|
+
if (!ts) return {}; // typescript not installed -> no-op (degrade to prior behavior)
|
|
100
|
+
|
|
101
|
+
const files = new Set();
|
|
102
|
+
|
|
103
|
+
// Primary: resolve the tsconfig exactly as tsc would (honoring `extends`, `files`, `include`,
|
|
104
|
+
// `exclude`). This is what pulls in ambient typings declared via `files` in a base tsconfig
|
|
105
|
+
// (e.g. XrmClientApi.d.ts), which triple-slash scanning alone would miss.
|
|
106
|
+
try {
|
|
107
|
+
const configPath = ts.findConfigFile(context, ts.sys.fileExists, "tsconfig.json");
|
|
108
|
+
if (configPath) {
|
|
109
|
+
const host = Object.assign({}, ts.sys, { onUnRecoverableConfigFileDiagnostic() {} });
|
|
110
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, host);
|
|
111
|
+
if (parsed && parsed.fileNames) for (const f of parsed.fileNames) files.add(path.normalize(f));
|
|
112
|
+
}
|
|
113
|
+
} catch { /* fall through to source walk */ }
|
|
114
|
+
|
|
115
|
+
// Fallback / supplement: walk sources under context when tsconfig resolution yields nothing.
|
|
116
|
+
if (files.size === 0) walkSources(context, files, 0);
|
|
117
|
+
|
|
118
|
+
// Always also follow triple-slash `/// <reference path>` directives transitively (covers files
|
|
119
|
+
// referenced directly from source but not listed by the tsconfig).
|
|
120
|
+
const queue = Array.from(files);
|
|
121
|
+
while (queue.length) {
|
|
122
|
+
const f = queue.shift();
|
|
123
|
+
for (const r of refsIn(f)) {
|
|
124
|
+
const n = path.normalize(r);
|
|
125
|
+
if (!files.has(n)) { files.add(n); queue.push(n); }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const map = {};
|
|
130
|
+
for (const f of files) {
|
|
131
|
+
try { collectFile(ts, f, map); } catch { /* keep going */ }
|
|
132
|
+
}
|
|
133
|
+
return map;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { collectConstEnumDefines };
|
package/lib/css.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
3
|
* Native CSS pipeline (webpack's css-loader/style-loader emit webpack-runtime-coupled output that a
|
|
4
|
-
* non-webpack bundler can't consume, so
|
|
4
|
+
* non-webpack bundler can't consume, so @rustwrap/webpack handles CSS itself, Vite-style):
|
|
5
5
|
* - .scss/.sass -> compiled with the consumer's `sass` (or node-sass) if available.
|
|
6
6
|
* - .less -> compiled with the consumer's `less` if available.
|
|
7
7
|
* - .css -> used as-is.
|
|
@@ -27,7 +27,7 @@ function compile(resource, context, compilation) {
|
|
|
27
27
|
if (sass && sass.compile) return sass.compile(resource, { style: "compressed", loadPaths: [path.dirname(resource)] }).css;
|
|
28
28
|
const nodeSass = resolveFrom("node-sass", dirs);
|
|
29
29
|
if (nodeSass && nodeSass.renderSync) return nodeSass.renderSync({ file: resource, outputStyle: "compressed" }).css.toString();
|
|
30
|
-
compilation && compilation.warnings.push({ message:
|
|
30
|
+
compilation && compilation.warnings.push({ message: `@rustwrap/webpack: no 'sass' available to compile ${path.basename(resource)} — emitting empty CSS` });
|
|
31
31
|
return "";
|
|
32
32
|
}
|
|
33
33
|
if (ext === ".less") {
|
|
@@ -37,7 +37,7 @@ function compile(resource, context, compilation) {
|
|
|
37
37
|
}
|
|
38
38
|
return fs.readFileSync(resource, "utf8");
|
|
39
39
|
} catch (e) {
|
|
40
|
-
compilation && compilation.errors.push({ message:
|
|
40
|
+
compilation && compilation.errors.push({ message: `@rustwrap/webpack CSS (${path.basename(resource)}): ${e && e.message || e}` });
|
|
41
41
|
return "";
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -91,7 +91,7 @@ async function handleCss(resource, cfg, compilation, prod, context, extract) {
|
|
|
91
91
|
return { code: exportDefault, moduleType: "js" };
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const inject = `(function(){if(typeof document==='undefined')return;var c=${JSON.stringify(css)};if(!c)return;var s=document.createElement('style');s.setAttribute('data-
|
|
94
|
+
const inject = `(function(){if(typeof document==='undefined')return;var c=${JSON.stringify(css)};if(!c)return;var s=document.createElement('style');s.setAttribute('data-rustwrap','');s.appendChild(document.createTextNode(c));document.head.appendChild(s);})();`;
|
|
95
95
|
return { code: `${inject}\n${exportDefault}`, moduleType: "js" };
|
|
96
96
|
}
|
|
97
97
|
|
package/lib/dev-server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
*
|
|
3
|
+
* @rustwrap/webpack dev server — a webpack-dev-server-shaped dev server over the @rustwrap/webpack Compiler.
|
|
4
4
|
*
|
|
5
5
|
* It serves the compiled assets (+ static dirs) over HTTP, watches sources and rebuilds via the
|
|
6
6
|
* Compiler, and pushes updates to the browser over Server-Sent Events. The injected client either
|
|
@@ -27,7 +27,7 @@ function mime(file) { return MIME[path.extname(file).toLowerCase()] || "applicat
|
|
|
27
27
|
// Browser client: connects SSE, applies hot dispose handlers, then reloads on each successful build.
|
|
28
28
|
const CLIENT = `(function(){
|
|
29
29
|
if (typeof window === "undefined" || !window.EventSource) return;
|
|
30
|
-
var hot = window.
|
|
30
|
+
var hot = window.__rustwrap_hot__ || (window.__rustwrap_hot__ = (function(){
|
|
31
31
|
var accepts = [], disposes = [], statusHandlers = [], status = "idle", data = {};
|
|
32
32
|
function setStatus(s){ status = s; statusHandlers.forEach(function(h){ try { h(s); } catch(e){} }); }
|
|
33
33
|
return {
|
|
@@ -40,15 +40,15 @@ const CLIENT = `(function(){
|
|
|
40
40
|
};
|
|
41
41
|
})());
|
|
42
42
|
var first = true;
|
|
43
|
-
var es = new EventSource("/
|
|
43
|
+
var es = new EventSource("/__rustwrap_sse");
|
|
44
44
|
es.addEventListener("message", function(ev){
|
|
45
45
|
var msg; try { msg = JSON.parse(ev.data); } catch(e){ return; }
|
|
46
46
|
if (msg.type === "connected") { first = false; return; }
|
|
47
|
-
if (msg.type === "errors") { console.error("%c[
|
|
47
|
+
if (msg.type === "errors") { console.error("%c[@rustwrap/webpack] build failed", "color:red", "\\n" + (msg.errors||[]).join("\\n")); return; }
|
|
48
48
|
if (msg.type === "ok") {
|
|
49
49
|
hot._setStatus("check");
|
|
50
50
|
try { hot._disposes.forEach(function(cb){ try { cb(hot.data); } catch(e){} }); } catch(e){}
|
|
51
|
-
console.log("[
|
|
51
|
+
console.log("[@rustwrap/webpack] update — reloading");
|
|
52
52
|
hot._setStatus("apply");
|
|
53
53
|
location.reload();
|
|
54
54
|
}
|
|
@@ -68,7 +68,7 @@ function normalizeStatic(stat, context) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function injectClient(html) {
|
|
71
|
-
const tag = `<script src="/
|
|
71
|
+
const tag = `<script src="/__rustwrap_hmr_client.js"></script>`;
|
|
72
72
|
if (/<\/body>/i.test(html)) return html.replace(/<\/body>/i, tag + "</body>");
|
|
73
73
|
if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, tag + "</head>");
|
|
74
74
|
return html + "\n" + tag;
|
|
@@ -98,14 +98,14 @@ class DevServer {
|
|
|
98
98
|
historyApiFallback: o.historyApiFallback,
|
|
99
99
|
};
|
|
100
100
|
// Enable hot defines for the build.
|
|
101
|
-
if (o.hot !== false) { compiler.$
|
|
101
|
+
if (o.hot !== false) { compiler.$rustwrap = compiler.$rustwrap || {}; compiler.$rustwrap.hot = true; }
|
|
102
102
|
|
|
103
103
|
this.server = http.createServer((req, res) => this.handle(req, res, ctx));
|
|
104
104
|
return new Promise((resolve) => {
|
|
105
105
|
this.server.listen(port, host, () => {
|
|
106
106
|
const actualPort = (this.server.address() && this.server.address().port) || port;
|
|
107
107
|
const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${actualPort}/`;
|
|
108
|
-
console.log(`\x1b[36m\x1b[
|
|
108
|
+
console.log(`\x1b[36m\x1b[1m@rustwrap/webpack\x1b[0m dev server running at \x1b[33m${url}\x1b[0m`);
|
|
109
109
|
// Build + watch, push updates to clients.
|
|
110
110
|
this.watching = compiler.watch(compiler.options.watchOptions || {}, (err, stats) => {
|
|
111
111
|
if (err) { console.error(err); return; }
|
|
@@ -128,8 +128,8 @@ class DevServer {
|
|
|
128
128
|
|
|
129
129
|
handle(req, res, ctx) {
|
|
130
130
|
const url = decodeURIComponent((req.url || "/").split("?")[0]);
|
|
131
|
-
if (url === "/
|
|
132
|
-
if (url === "/
|
|
131
|
+
if (url === "/__rustwrap_sse") return this.sse(res);
|
|
132
|
+
if (url === "/__rustwrap_hmr_client.js") { res.writeHead(200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" }); return res.end(CLIENT); }
|
|
133
133
|
const rel = url.replace(/^\/+/, "") || "index.html";
|
|
134
134
|
const bases = [ctx.outDir, ...ctx.staticDirs];
|
|
135
135
|
for (const b of bases) if (this.serveFile(path.join(b, rel), res)) return;
|
|
@@ -138,7 +138,7 @@ class DevServer {
|
|
|
138
138
|
for (const b of bases) if (this.serveFile(path.join(b, "index.html"), res)) return;
|
|
139
139
|
}
|
|
140
140
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
141
|
-
res.end("
|
|
141
|
+
res.end("@rustwrap/webpack dev server: not found — " + url);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
serveFile(file, res) {
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
*
|
|
3
|
+
* @rustwrap/webpack — a webpack-compatible Node API and CLI backed by the Rolldown bundler.
|
|
4
4
|
*
|
|
5
5
|
* This is the public entry: the `webpack(options, callback)` factory, the Compiler/Compilation
|
|
6
6
|
* lifecycle (tapable hooks, so real plugins run), and the full `webpack.*` namespace. The actual
|
|
@@ -32,11 +32,11 @@ function webpack(options, callback) {
|
|
|
32
32
|
function createCompiler(options) {
|
|
33
33
|
const context = options.context || process.cwd();
|
|
34
34
|
const compiler = new Compiler(context, options);
|
|
35
|
-
compiler.$
|
|
35
|
+
compiler.$rustwrap = { define: {}, provide: {}, ignore: [], normalReplace: [] };
|
|
36
36
|
compiler.hooks.environment.call();
|
|
37
37
|
compiler.hooks.afterEnvironment.call();
|
|
38
38
|
|
|
39
|
-
// Apply user plugins (real apply(compiler) — taps hooks / populates $
|
|
39
|
+
// Apply user plugins (real apply(compiler) — taps hooks / populates $rustwrap).
|
|
40
40
|
for (const p of options.plugins || []) {
|
|
41
41
|
try {
|
|
42
42
|
if (p && typeof p.apply === "function") p.apply(compiler);
|
|
@@ -47,16 +47,16 @@ function createCompiler(options) {
|
|
|
47
47
|
compiler.hooks.afterResolvers.call(compiler);
|
|
48
48
|
|
|
49
49
|
// The make phase: Rolldown build -> compilation.assets.
|
|
50
|
-
compiler.hooks.make.tapPromise("
|
|
50
|
+
compiler.hooks.make.tapPromise("rustwrap", (compilation) => runMake(compilation, options, context));
|
|
51
51
|
|
|
52
52
|
// After assets are produced: performance hints + ignoreWarnings (before Stats is created).
|
|
53
|
-
compiler.hooks.afterEmit.tapPromise("
|
|
53
|
+
compiler.hooks.afterEmit.tapPromise("rustwrap:post", async (compilation) => {
|
|
54
54
|
applyPerformance(options, compilation);
|
|
55
55
|
filterWarnings(options, compilation);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
// Default console summary (unless stats are silenced).
|
|
59
|
-
compiler.hooks.done.tapPromise("
|
|
59
|
+
compiler.hooks.done.tapPromise("rustwrap:summary", async (stats) => {
|
|
60
60
|
const quiet = options.stats === false || options.stats === "none" || options.stats === "errors-only"
|
|
61
61
|
|| (options.infrastructureLogging && (options.infrastructureLogging.level === "none" || options.infrastructureLogging.level === "error"));
|
|
62
62
|
if (!quiet) printSummary(stats);
|
|
@@ -95,7 +95,7 @@ function printSummary(stats) {
|
|
|
95
95
|
const total = (j.assets || []).reduce((s, a) => s + a.size, 0);
|
|
96
96
|
for (const e of j.errors || []) console.log(`${C.y}ERROR${C.r} ${e.message}`);
|
|
97
97
|
for (const w of j.warnings || []) console.log(`${C.y}WARNING${C.r} ${w.message}`);
|
|
98
|
-
console.log(`${C.c}${C.b}
|
|
98
|
+
console.log(`${C.c}${C.b}@rustwrap/webpack${C.r} ${C.g}${(j.assets || []).length} assets${C.r} ${C.m}${fmtSize(total)}${C.r} ${C.d}via rolldown in${C.r} ${C.y}${fmtTime(j.time || 0)}${C.r}`);
|
|
99
99
|
for (const a of j.assets || []) console.log(` ${C.d}asset${C.r} ${C.c}${a.name}${C.r} ${C.m}${fmtSize(a.size)}${C.r}`);
|
|
100
100
|
}
|
|
101
101
|
|
package/lib/loaders.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
*
|
|
3
|
+
* @rustwrap/webpack loader system — runs real webpack loader chains (module.rules) via loader-runner, so
|
|
4
4
|
* css-loader/style-loader/sass-loader/@svgr/raw-loader/url-loader/file-loader/custom loaders work.
|
|
5
5
|
*
|
|
6
6
|
* JS/TS/JSX transpilation is left to Rolldown/Oxc (faster, equivalent output), so the well-known
|
|
@@ -129,7 +129,7 @@ function loaderPlugin(cfg, context, compilation) {
|
|
|
129
129
|
const cssExtract = require("./css").cssExtractEnabled(cfg);
|
|
130
130
|
const hasRules = buckets.pre.length || buckets.normal.length || buckets.post.length || (buckets._oneOf && buckets._oneOf.length);
|
|
131
131
|
return {
|
|
132
|
-
name: "
|
|
132
|
+
name: "rustwrap:loaders",
|
|
133
133
|
async load(id) {
|
|
134
134
|
const [resource, query] = id.split("?");
|
|
135
135
|
// Native CSS pipeline, independent of configured css/style loaders.
|
package/lib/plugins.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
3
|
* Built-in webpack plugins. Bundler-affecting plugins (Define/Provide/Environment/Ignore/
|
|
4
|
-
* NormalModuleReplacement) populate `compiler.$
|
|
4
|
+
* NormalModuleReplacement) populate `compiler.$rustwrap`, which build.js reads during the Rolldown
|
|
5
5
|
* make phase. Asset-affecting plugins (Banner) tap compilation.processAssets. The rest are faithful
|
|
6
6
|
* no-ops or thin shims so configs load and run.
|
|
7
7
|
*/
|
|
@@ -18,14 +18,14 @@ function flattenDefinitions(defs, prefix, out) {
|
|
|
18
18
|
|
|
19
19
|
class DefinePlugin {
|
|
20
20
|
constructor(definitions) { this.definitions = definitions || {}; }
|
|
21
|
-
apply(compiler) { Object.assign(compiler.$
|
|
21
|
+
apply(compiler) { Object.assign(compiler.$rustwrap.define, flattenDefinitions(this.definitions, "", {})); }
|
|
22
22
|
}
|
|
23
23
|
DefinePlugin.runtimeValue = (fn, deps) => { const v = fn({}); return typeof v === "string" ? v : JSON.stringify(v); };
|
|
24
24
|
|
|
25
25
|
class EnvironmentPlugin {
|
|
26
26
|
constructor(...keys) { this.keys = Array.isArray(keys[0]) ? keys[0] : (typeof keys[0] === "object" ? keys[0] : keys); }
|
|
27
27
|
apply(compiler) {
|
|
28
|
-
const d = compiler.$
|
|
28
|
+
const d = compiler.$rustwrap.define;
|
|
29
29
|
if (Array.isArray(this.keys)) for (const k of this.keys) d[`process.env.${k}`] = JSON.stringify(process.env[k] !== undefined ? process.env[k] : "");
|
|
30
30
|
else for (const [k, def] of Object.entries(this.keys)) d[`process.env.${k}`] = JSON.stringify(process.env[k] !== undefined ? process.env[k] : def);
|
|
31
31
|
}
|
|
@@ -33,7 +33,7 @@ class EnvironmentPlugin {
|
|
|
33
33
|
|
|
34
34
|
class ProvidePlugin {
|
|
35
35
|
constructor(definitions) { this.definitions = definitions || {}; }
|
|
36
|
-
apply(compiler) { Object.assign(compiler.$
|
|
36
|
+
apply(compiler) { Object.assign(compiler.$rustwrap.provide, this.definitions); }
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
class BannerPlugin {
|
|
@@ -69,12 +69,12 @@ class BannerPlugin {
|
|
|
69
69
|
|
|
70
70
|
class IgnorePlugin {
|
|
71
71
|
constructor(options) { this.options = (options && options.resourceRegExp) ? options : { resourceRegExp: options && options.checkResource ? undefined : options, checkResource: options && options.checkResource }; }
|
|
72
|
-
apply(compiler) { compiler.$
|
|
72
|
+
apply(compiler) { compiler.$rustwrap.ignore.push(this.options); }
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
class NormalModuleReplacementPlugin {
|
|
76
76
|
constructor(resourceRegExp, newResource) { this.resourceRegExp = resourceRegExp; this.newResource = newResource; }
|
|
77
|
-
apply(compiler) { compiler.$
|
|
77
|
+
apply(compiler) { compiler.$rustwrap.normalReplace.push([this.resourceRegExp, this.newResource]); }
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
class ContextReplacementPlugin {
|
|
@@ -110,7 +110,7 @@ class RuntimeChunkPlugin extends NoopPlugin {}
|
|
|
110
110
|
class SplitChunksPlugin extends NoopPlugin {}
|
|
111
111
|
class WatchIgnorePlugin extends NoopPlugin {}
|
|
112
112
|
class LoaderOptionsPlugin extends NoopPlugin {}
|
|
113
|
-
class HotModuleReplacementPlugin { apply(compiler) { compiler.$
|
|
113
|
+
class HotModuleReplacementPlugin { apply(compiler) { compiler.$rustwrap = compiler.$rustwrap || {}; compiler.$rustwrap.hot = true; } }
|
|
114
114
|
class DllPlugin extends NoopPlugin {}
|
|
115
115
|
class DllReferencePlugin extends NoopPlugin {}
|
|
116
116
|
class HashedModuleIdsPlugin extends NoopPlugin {}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Browser `process` shim (parity with webpack's process/browser). Covers what readable-stream and
|
|
2
|
+
// most Node-assuming libraries touch (nextTick, env, platform, versions, no-op event methods).
|
|
3
|
+
var process = {
|
|
4
|
+
env: {},
|
|
5
|
+
argv: [],
|
|
6
|
+
browser: true,
|
|
7
|
+
platform: "browser",
|
|
8
|
+
arch: "browser",
|
|
9
|
+
version: "",
|
|
10
|
+
versions: {},
|
|
11
|
+
title: "browser",
|
|
12
|
+
nextTick: function (cb) {
|
|
13
|
+
var args = [].slice.call(arguments, 1);
|
|
14
|
+
var run = function () { cb.apply(null, args); };
|
|
15
|
+
(typeof queueMicrotask !== "undefined" ? queueMicrotask : function (f) { setTimeout(f, 0); })(run);
|
|
16
|
+
},
|
|
17
|
+
cwd: function () { return "/"; },
|
|
18
|
+
chdir: function () {},
|
|
19
|
+
on: function () { return process; },
|
|
20
|
+
once: function () { return process; },
|
|
21
|
+
off: function () { return process; },
|
|
22
|
+
addListener: function () { return process; },
|
|
23
|
+
removeListener: function () { return process; },
|
|
24
|
+
removeAllListeners: function () { return process; },
|
|
25
|
+
emit: function () { return false; },
|
|
26
|
+
prependListener: function () { return process; },
|
|
27
|
+
listeners: function () { return []; },
|
|
28
|
+
binding: function () { throw new Error("process.binding is not supported in the browser"); },
|
|
29
|
+
umask: function () { return 0; },
|
|
30
|
+
};
|
|
31
|
+
export default process;
|
package/lib/stats.js
CHANGED
|
@@ -21,7 +21,7 @@ function createStats(compilation) {
|
|
|
21
21
|
toJson(opts) {
|
|
22
22
|
return {
|
|
23
23
|
version: require("../package.json").version,
|
|
24
|
-
|
|
24
|
+
rustwrap: true,
|
|
25
25
|
hash: compilation.hash || "",
|
|
26
26
|
time,
|
|
27
27
|
builtAt: Date.now(),
|
|
@@ -47,7 +47,7 @@ function createStats(compilation) {
|
|
|
47
47
|
for (const e of errors) lines.push(`${C.y}ERROR${C.r} ` + (e.message || String(e)));
|
|
48
48
|
for (const w of warnings) lines.push(`${C.y}WARNING${C.r} ` + (w.message || String(w)));
|
|
49
49
|
const total = assetList.reduce((s, a) => s + a.size, 0);
|
|
50
|
-
lines.push(`${C.c}
|
|
50
|
+
lines.push(`${C.c}@rustwrap/webpack${C.r} ${C.g}${assetList.length} assets${C.r} ${C.d}${fmt(total)} in ${time}ms${C.r}`);
|
|
51
51
|
for (const a of assetList) lines.push(` ${C.d}asset${C.r} ${C.c}${a.name}${C.r} ${fmt(a.size)}`);
|
|
52
52
|
return lines.join("\n");
|
|
53
53
|
},
|
package/package.json
CHANGED
|
@@ -1,45 +1,46 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@rustwrap/webpack",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "A webpack-compatible Node API and CLI backed by the Rolldown bundler (Rust/Oxc) for fast builds and excellent tree-shaking. Drop-in `webpack` override for pcf-scripts and webpack-based pipelines.",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"publishConfig": {
|
|
7
|
-
"access": "public"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"test": "node test/run.js"
|
|
11
|
-
},
|
|
12
|
-
"bin": {
|
|
13
|
-
"webpack": "bin/
|
|
14
|
-
"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"lib/",
|
|
18
|
-
"bin/",
|
|
19
|
-
"README.md"
|
|
20
|
-
],
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"acorn": "^8.17.0",
|
|
23
|
-
"eslint-scope": "^9.1.2",
|
|
24
|
-
"loader-runner": "^4.3.2",
|
|
25
|
-
"mime-types": "^3.0.2",
|
|
26
|
-
"postcss": "^8.5.16",
|
|
27
|
-
"postcss-modules": "^6.0.1",
|
|
28
|
-
"rolldown": "^1.1.3",
|
|
29
|
-
"schema-utils": "^4.3.3",
|
|
30
|
-
"tapable": "^2.3.3",
|
|
31
|
-
"terser-webpack-plugin": "^5.6.1",
|
|
32
|
-
"webpack-sources": "^3.5.0"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@rustwrap/webpack",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "A webpack-compatible Node API and CLI backed by the Rolldown bundler (Rust/Oxc) for fast builds and excellent tree-shaking. Drop-in `webpack` override for pcf-scripts and webpack-based pipelines.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node test/run.js"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"webpack": "bin/rustwrap-webpack.js",
|
|
14
|
+
"rustwrap-webpack": "bin/rustwrap-webpack.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"lib/",
|
|
18
|
+
"bin/",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"acorn": "^8.17.0",
|
|
23
|
+
"eslint-scope": "^9.1.2",
|
|
24
|
+
"loader-runner": "^4.3.2",
|
|
25
|
+
"mime-types": "^3.0.2",
|
|
26
|
+
"postcss": "^8.5.16",
|
|
27
|
+
"postcss-modules": "^6.0.1",
|
|
28
|
+
"rolldown": "^1.1.3",
|
|
29
|
+
"schema-utils": "^4.3.3",
|
|
30
|
+
"tapable": "^2.3.3",
|
|
31
|
+
"terser-webpack-plugin": "^5.6.1",
|
|
32
|
+
"webpack-sources": "^3.5.0",
|
|
33
|
+
"buffer": "^6.0.3"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"webpack",
|
|
37
|
+
"rolldown",
|
|
38
|
+
"bundler",
|
|
39
|
+
"pcf",
|
|
40
|
+
"tree-shaking"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
|
45
|
+
}
|
|
46
|
+
}
|