@rustwrap/webpack 1.0.1
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 +106 -0
- package/bin/rollpack.js +82 -0
- package/lib/assets.js +58 -0
- package/lib/build.js +295 -0
- package/lib/compiler.js +307 -0
- package/lib/config.js +133 -0
- package/lib/css.js +103 -0
- package/lib/dev-server.js +176 -0
- package/lib/empty-module.js +2 -0
- package/lib/index.js +162 -0
- package/lib/loaders.js +211 -0
- package/lib/plugins.js +130 -0
- package/lib/sourcemap.js +13 -0
- package/lib/stats.js +60 -0
- package/lib/template.js +144 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @rustwrap/webpack
|
|
2
|
+
|
|
3
|
+
A **webpack-compatible** Node API and CLI backed by the [Rolldown](https://rolldown.rs) bundler
|
|
4
|
+
(Rust / Oxc). Drop-in replacement for `webpack` in `pcf-scripts` and webpack-based pipelines —
|
|
5
|
+
**much smaller bundles** (rollup-grade tree-shaking) and **far faster builds**, while honouring the
|
|
6
|
+
webpack config/API surface.
|
|
7
|
+
|
|
8
|
+
## Why
|
|
9
|
+
Webpack bundles for Fluent-v9 PCF controls can approach the 5 MB PCF limit. Rolldown's tree-shaking
|
|
10
|
+
removes far more dead code. `@rustwrap/webpack` exposes the webpack Node API + tapable plugin lifecycle that
|
|
11
|
+
`pcf-scripts` and webpack plugins expect, and translates the config onto Rolldown.
|
|
12
|
+
|
|
13
|
+
### Measured (representative builds)
|
|
14
|
+
| Build | webpack | @rustwrap/webpack |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Fluent-v9 PCF control | 0.84 MB | **0.73 MB** |
|
|
17
|
+
| Multi-entry client bundle (14 entries) | 2.37 MB | **2.06 MB** |
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
- **`lib/index.js`** — `webpack(options, cb)` factory + the full `webpack.*` namespace.
|
|
21
|
+
- **`lib/compiler.js`** — tapable `Compiler`/`Compilation`/`MultiCompiler` with the standard webpack
|
|
22
|
+
lifecycle hooks (`run`, `make`, `thisCompilation`, `compilation`, `processAssets` (staged), `emit`,
|
|
23
|
+
`afterEmit`, `done`, …). This is what lets **real third-party plugins** (`apply(compiler)`) run.
|
|
24
|
+
- **`lib/build.js`** — the `make` phase: runs Rolldown per entry and writes results into
|
|
25
|
+
`compilation.assets` (the compiler emits them after plugins' `processAssets`).
|
|
26
|
+
- **`lib/loaders.js`** — runs real webpack loader chains (`module.rules`) via `loader-runner`.
|
|
27
|
+
- **`lib/css.js`** — native CSS pipeline (sass/less compile + style-inject or extract).
|
|
28
|
+
- **`lib/assets.js`** — asset modules. **`lib/plugins.js`** — built-in plugins.
|
|
29
|
+
- **`lib/template.js`** — output filename templates. **`lib/stats.js`** — Stats. **`lib/sourcemap.js`** — devtool.
|
|
30
|
+
|
|
31
|
+
## Use as a webpack override
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"overrides": { "webpack": "npm:@rustwrap/webpack@^1" },
|
|
35
|
+
"devDependencies": { "webpack": "npm:@rustwrap/webpack@^1" }
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
The CLI stubs unresolved webpack-only plugin requires (`terser-webpack-plugin`, `webpack-bundle-analyzer`,
|
|
39
|
+
`eslint-webpack-plugin`, …) so existing `webpack.config.js` files load unchanged.
|
|
40
|
+
|
|
41
|
+
## Support matrix
|
|
42
|
+
|
|
43
|
+
### ✅ Supported
|
|
44
|
+
| Area | Notes |
|
|
45
|
+
|---|---|
|
|
46
|
+
| **Node API** | `webpack(options, cb)`, `webpack(options)`→Compiler (`run`/`watch`/`close`), **MultiCompiler** (array of configs → MultiStats), function config `(env,argv)=>…`. |
|
|
47
|
+
| **Plugin system** | Real **tapable** `Compiler`/`Compilation` hooks. `apply(compiler)` plugins run. `compilation.hooks.processAssets` is **stage-ordered**. `emitAsset`/`updateAsset`/`getAsset`/`deleteAsset`/`renameAsset`. `webpack.sources` (webpack-sources). |
|
|
48
|
+
| **entry** | string / array / object / `{import, filename}`; `[name]`. |
|
|
49
|
+
| **output** | `path`, `filename`, `chunkFilename`, `clean`, `publicPath` (asset URLs), `library` (string / `["NS","[name]"]` / `{name,type}`), `libraryTarget` → `var`/`window`/`assign`→iife, `umd`, `commonjs`/`commonjs2`→cjs, `module`→es, `amd`. |
|
|
50
|
+
| **Filename templates** | `[name]`, `[ext]`, `[base]`, `[path]`, `[query]`, `[id]`, `[hash]`, `[contenthash]`, `[chunkhash]` (with `:N`). |
|
|
51
|
+
| **mode** | `production`/`development`/`none` → minify on/off. |
|
|
52
|
+
| **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). |
|
|
53
|
+
| **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`). |
|
|
54
|
+
| **Asset modules** | `asset/resource` (emit + URL), `asset/inline` (data URI), `asset/source`, `asset` (auto by `parser.dataUrlCondition.maxSize`); `generator.filename`/`output.assetModuleFilename`. |
|
|
55
|
+
| **externals** | object map / array / RegExp / sync-callback function (`({request},cb)` & `(ctx,req,cb)`) / `{root}` → external + `output.globals`. |
|
|
56
|
+
| **resolve** | `alias`, `extensions`, `mainFields`, `mainFiles`, `conditionNames`, `modules`, `extensionAlias`, `symlinks`, `fallback` (`false`→empty module), nearest `tsconfig.json` paths. |
|
|
57
|
+
| **devtool (source maps)** | `source-map`, `inline-source-map`, `hidden-source-map`, `nosources-*`, `eval-*` (≈inline). Emits `.map` + `sourceMappingURL`. |
|
|
58
|
+
| **target** | `web` (default) and `node`/`node*` (→ Rolldown `platform:'node'`, node builtins external). |
|
|
59
|
+
| **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. |
|
|
60
|
+
| **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`. |
|
|
61
|
+
| **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.) |
|
|
62
|
+
| **Third-party plugins** | Anything tapping `compilation.hooks.processAssets`/`compiler.hooks.emit`/`done`/etc. works (Copy-style, Html-style, Banner, analyzers). |
|
|
63
|
+
| **performance** | `hints`/`maxAssetSize` enforced (warning/error). |
|
|
64
|
+
| **ignoreWarnings** | RegExp / function filters. |
|
|
65
|
+
| **stats** | `Stats` with `hasErrors`/`hasWarnings`/`toJson`/`toString({colors})`; quiet via `stats:false|'none'|'errors-only'`. |
|
|
66
|
+
| **watch** | `compiler.watch` / `watch:true` with `watchOptions.aggregateTimeout`. |
|
|
67
|
+
| **code-splitting** | dynamic `import()` → chunks for **es/cjs** output (`output.chunkFilename`). |
|
|
68
|
+
| **Namespace** | `webpack.Compiler/Compilation/MultiCompiler/sources/WebpackError/util/ModuleFilenameHelpers/version`, all built-in plugins, `webpack.optimize.*`, `webpack.container.*`, `webpack.ids.*`. |
|
|
69
|
+
|
|
70
|
+
### ➖ Approximated (faithful but not byte-identical to webpack)
|
|
71
|
+
- **`optimization.splitChunks` / cacheGroups / runtimeChunk** — Rolldown does its own chunking; the
|
|
72
|
+
fine-grained cacheGroup controls are not mapped. Single-file output is preserved where required.
|
|
73
|
+
- **`devtool: eval` / `eval-source-map`** — emit a correct **inline source map** (full original
|
|
74
|
+
sources, debuggable in devtools). The literal per-module `eval()` wrapping is a webpack-internal
|
|
75
|
+
rebuild mechanism and isn't reproduced on a whole-bundle engine — the debugging result is equivalent.
|
|
76
|
+
- **HMR** — `module.hot`/`import.meta.hot` exist and run dispose handlers, but updates apply via a
|
|
77
|
+
fast **full reload** (Rolldown emits a whole bundle, not webpack hot-update chunks), so module
|
|
78
|
+
state is not preserved across edits.
|
|
79
|
+
- **`output.environment` / `target` browserslist downleveling** — Oxc emits modern JS; no ES5 downlevel.
|
|
80
|
+
|
|
81
|
+
### ❌ Not supported (Rolldown architecture / out of scope)
|
|
82
|
+
- **Module Federation** (`container.ModuleFederationPlugin` is a no-op), **DllPlugin**.
|
|
83
|
+
- **`experiments.asyncWebAssembly` / lazyCompilation**, persistent **`cache: {type:'filesystem'}`** semantics
|
|
84
|
+
(accepted/ignored — Rolldown has its own caching).
|
|
85
|
+
- Deep `Compilation` graph internals (`moduleGraph`/`chunkGraph`/templates) that some advanced plugins reach into.
|
|
86
|
+
|
|
87
|
+
## Test
|
|
88
|
+
`npm test` runs `test/run.js` — a synthetic feature matrix (36 assertions) covering tree-shaking,
|
|
89
|
+
multi-entry/MultiCompiler, Define/Environment/Banner, the loader system + CSS, custom plugins via
|
|
90
|
+
hooks, externals, source maps, `[contenthash]`, performance/ignoreWarnings, code-splitting, and the
|
|
91
|
+
namespace.
|
|
92
|
+
|
|
93
|
+
## Engine & dependencies
|
|
94
|
+
Rolldown is the bundling engine. The other deps fall into two groups:
|
|
95
|
+
- **Engine / compat layer (actually used by `@rustwrap/webpack`):** `rolldown`, `loader-runner`,
|
|
96
|
+
`webpack-sources`, `tapable`, `mime-types`, `acorn` + `eslint-scope` (ProvidePlugin scope
|
|
97
|
+
analysis), `postcss` + `postcss-modules` (CSS Modules scoping).
|
|
98
|
+
- **webpack drop-in deps (declared so a normal `npm install` of the `webpack` override still
|
|
99
|
+
resolves them, exactly like webpack would provide them transitively):** `terser-webpack-plugin`
|
|
100
|
+
(consumer configs reference it in `optimization.minimizer`; `@rustwrap/webpack` reads its options and never
|
|
101
|
+
calls `.apply()`), `schema-utils` (required by many third-party plugins). These mirror the
|
|
102
|
+
packages webpack itself depends on, so existing `webpack.config.js` files keep working after the
|
|
103
|
+
override. The CLI additionally stubs any *other* unresolved webpack-only plugin require
|
|
104
|
+
(`webpack-bundle-analyzer`, `eslint-webpack-plugin`, …) as a safety net.
|
|
105
|
+
|
|
106
|
+
`@rustwrap/webpack` does not implement its own bundler — the engine is Rolldown.
|
package/bin/rollpack.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/*
|
|
4
|
+
* rollpack CLI — a minimal `webpack`-compatible command. pcf-scripts uses the programmatic API
|
|
5
|
+
* (require("webpack")(config, cb)), so this CLI only covers basic `webpack [--config f] [--mode m]`
|
|
6
|
+
* invocations for direct use.
|
|
7
|
+
*/
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const Module = require("module");
|
|
11
|
+
|
|
12
|
+
// webpack.config.js files often `require()` webpack-ecosystem plugins (TerserPlugin, analyzer,
|
|
13
|
+
// eslint-webpack-plugin, ...) that Rolldown doesn't need. Stub any that aren't resolvable so the
|
|
14
|
+
// config loads; rollpack reads the relevant intent (mode, externals, Define/Banner) from the object.
|
|
15
|
+
const STUB_PKGS = new Set([
|
|
16
|
+
"terser-webpack-plugin", "webpack-bundle-analyzer", "eslint-webpack-plugin",
|
|
17
|
+
"css-minimizer-webpack-plugin", "mini-css-extract-plugin", "fork-ts-checker-webpack-plugin",
|
|
18
|
+
"webpack-cli", "style-loader", "css-loader", "sass-loader", "ts-loader",
|
|
19
|
+
]);
|
|
20
|
+
function makeStub() {
|
|
21
|
+
class StubPlugin { constructor(o) { this.options = o; } apply() {} }
|
|
22
|
+
return new Proxy(StubPlugin, { get(t, p) { if (p in t) return t[p]; if (typeof p === "string" && /^[A-Z]/.test(p)) return StubPlugin; return undefined; } });
|
|
23
|
+
}
|
|
24
|
+
const origLoad = Module._load;
|
|
25
|
+
Module._load = function (request, parent, isMain) {
|
|
26
|
+
try { return origLoad.apply(this, arguments); }
|
|
27
|
+
catch (e) { if (STUB_PKGS.has(request)) return makeStub(); throw e; }
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const webpack = require("../lib/index.js");
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const o = { mode: undefined, config: undefined, watch: false, env: {}, serve: false, port: undefined, host: undefined };
|
|
34
|
+
for (let i = 0; i < argv.length; i++) {
|
|
35
|
+
const a = argv[i];
|
|
36
|
+
if (a === "serve" || a === "dev" || a === "server") o.serve = true;
|
|
37
|
+
else if (a === "build") { /* default */ }
|
|
38
|
+
else if (a === "--config" || a === "-c") o.config = argv[++i];
|
|
39
|
+
else if (a === "--mode") o.mode = argv[++i];
|
|
40
|
+
else if (a === "--watch" || a === "-w") o.watch = true;
|
|
41
|
+
else if (a === "--port") o.port = parseInt(argv[++i], 10);
|
|
42
|
+
else if (a === "--host") o.host = argv[++i];
|
|
43
|
+
else if (a.startsWith("--env")) { const kv = (a.includes("=") ? a.slice(6) : argv[++i] || "").split("="); o.env[kv[0]] = kv.length > 1 ? kv[1] : true; }
|
|
44
|
+
}
|
|
45
|
+
return o;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function findConfig(explicit) {
|
|
49
|
+
if (explicit) return path.resolve(explicit);
|
|
50
|
+
for (const n of ["webpack.config.js", "webpack.config.cjs"]) {
|
|
51
|
+
const p = path.resolve(process.cwd(), n);
|
|
52
|
+
if (fs.existsSync(p)) return p;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
58
|
+
const configPath = findConfig(opts.config);
|
|
59
|
+
let config = {};
|
|
60
|
+
if (configPath) {
|
|
61
|
+
config = require(configPath);
|
|
62
|
+
if (typeof config === "function") config = config(opts.env, { mode: opts.mode }) || {};
|
|
63
|
+
}
|
|
64
|
+
if (opts.mode) config.mode = opts.mode;
|
|
65
|
+
if (opts.watch) config.watch = true;
|
|
66
|
+
|
|
67
|
+
if (opts.serve) {
|
|
68
|
+
// `rollpack serve` / `webpack serve` — start the dev server.
|
|
69
|
+
if (!config.mode) config.mode = "development";
|
|
70
|
+
const devServerOpts = Object.assign({ hot: true }, config.devServer || {});
|
|
71
|
+
if (opts.port) devServerOpts.port = opts.port;
|
|
72
|
+
if (opts.host) devServerOpts.host = opts.host;
|
|
73
|
+
const compiler = webpack(config);
|
|
74
|
+
const server = new webpack.DevServer(devServerOpts, compiler);
|
|
75
|
+
server.start().catch((e) => { console.error(String(e && e.stack || e)); process.exit(1); });
|
|
76
|
+
process.on("SIGINT", () => server.stop().then(() => process.exit(0)));
|
|
77
|
+
} else {
|
|
78
|
+
webpack(config, (err, stats) => {
|
|
79
|
+
if (err) { console.error(String(err && err.stack || err)); process.exit(1); }
|
|
80
|
+
if (stats && stats.hasErrors()) { console.error(stats.toString()); process.exit(2); }
|
|
81
|
+
});
|
|
82
|
+
}
|
package/lib/assets.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Asset modules (webpack 5): type "asset/resource" (emit a file, import resolves to its URL),
|
|
4
|
+
* "asset/inline" (data URI), "asset/source" (raw string), and "asset" (auto: inline under the
|
|
5
|
+
* dataUrlCondition.maxSize threshold, else resource).
|
|
6
|
+
*/
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const crypto = require("crypto");
|
|
10
|
+
|
|
11
|
+
function hash(content, len) {
|
|
12
|
+
const algo = crypto.getHashes().includes("md4") ? "md4" : "sha256";
|
|
13
|
+
return crypto.createHash(algo).update(content).digest("hex").slice(0, len || 20);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function interpolate(template, resource, content, publicPath) {
|
|
17
|
+
const ext = path.extname(resource);
|
|
18
|
+
const name = path.basename(resource, ext);
|
|
19
|
+
const h = hash(content, 20);
|
|
20
|
+
if (typeof template === "function") template = template({ filename: resource });
|
|
21
|
+
return (template || "[hash][ext]")
|
|
22
|
+
.replace(/\[ext\]/gi, ext)
|
|
23
|
+
.replace(/\[name\]/gi, name)
|
|
24
|
+
.replace(/\[base\]/gi, name + ext)
|
|
25
|
+
.replace(/\[(?:content|full)?hash(?::(\d+))?\]/gi, (_m, n) => h.slice(0, n ? parseInt(n, 10) : 20));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Returns a Rolldown load() result: an ESM module exporting the asset's URL / data-uri / source.
|
|
29
|
+
function handleAsset(resource, meta, cfg, compilation, _prod) {
|
|
30
|
+
const out = cfg.output || {};
|
|
31
|
+
const content = fs.readFileSync(resource);
|
|
32
|
+
const type = meta.type;
|
|
33
|
+
const mimeFor = (p) => { try { return require("mime-types").lookup(p) || "application/octet-stream"; } catch (_) { return "application/octet-stream"; } };
|
|
34
|
+
|
|
35
|
+
let resolvedType = type;
|
|
36
|
+
if (type === "asset") {
|
|
37
|
+
const maxSize = (meta.parser && meta.parser.dataUrlCondition && meta.parser.dataUrlCondition.maxSize) || 8192;
|
|
38
|
+
resolvedType = content.length <= maxSize ? "asset/inline" : "asset/resource";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (resolvedType === "asset/source") {
|
|
42
|
+
return { code: `export default ${JSON.stringify(content.toString("utf8"))};`, moduleType: "js" };
|
|
43
|
+
}
|
|
44
|
+
if (resolvedType === "asset/inline") {
|
|
45
|
+
const mime = mimeFor(resource);
|
|
46
|
+
const dataUri = `data:${mime};base64,${content.toString("base64")}`;
|
|
47
|
+
return { code: `export default ${JSON.stringify(dataUri)};`, moduleType: "js" };
|
|
48
|
+
}
|
|
49
|
+
// asset/resource: emit the file and resolve to its public URL.
|
|
50
|
+
const tmpl = (meta.generator && meta.generator.filename) || out.assetModuleFilename || "[hash][ext]";
|
|
51
|
+
const filename = interpolate(tmpl, resource, content, out.publicPath).replace(/^\.?[\\/]/, "");
|
|
52
|
+
if (compilation) compilation.emitAsset(filename, content, { sourceFilename: resource });
|
|
53
|
+
const publicPath = (meta.generator && meta.generator.publicPath) || out.publicPath || "";
|
|
54
|
+
const url = (typeof publicPath === "string" ? publicPath : "") + filename;
|
|
55
|
+
return { code: `export default ${JSON.stringify(url)};`, moduleType: "js" };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { handleAsset, hash, interpolate };
|
package/lib/build.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* build.js — the Rolldown "make" phase. Produces each entry's bundle and writes the result into
|
|
4
|
+
* compilation.assets (the Compiler emits them later, after plugins' processAssets hooks). Integrates
|
|
5
|
+
* the loader system, asset modules, Define/Provide/Ignore/NormalModuleReplacement, source maps,
|
|
6
|
+
* target/platform, externals and library output formats.
|
|
7
|
+
*/
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const { rolldown } = require("rolldown");
|
|
10
|
+
const { normalizeEntries, buildExternals, buildResolve, buildDefine } = require("./config");
|
|
11
|
+
const { loaderPlugin } = require("./loaders");
|
|
12
|
+
const { devtoolToRolldown } = require("./sourcemap");
|
|
13
|
+
|
|
14
|
+
// Non-JS extensions whose content our loader/asset plugin turns into JS — tell Rolldown to treat the
|
|
15
|
+
// load() output as JS (Rolldown otherwise infers type from extension and rejects e.g. CSS).
|
|
16
|
+
const NON_JS_EXT = [
|
|
17
|
+
".css", ".scss", ".sass", ".less", ".styl", ".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp",
|
|
18
|
+
".avif", ".ico", ".bmp", ".woff", ".woff2", ".ttf", ".eot", ".otf", ".txt", ".md", ".mdx",
|
|
19
|
+
".html", ".htm", ".xml", ".csv", ".tsv", ".vue", ".graphql", ".gql", ".pug", ".hbs", ".ejs",
|
|
20
|
+
];
|
|
21
|
+
function nonJsModuleTypes() { const m = {}; for (const e of NON_JS_EXT) m[e] = e === ".svg" ? "jsx" : "js"; return m; }
|
|
22
|
+
|
|
23
|
+
function targetPlatform(target) {
|
|
24
|
+
const t = Array.isArray(target) ? target : [target];
|
|
25
|
+
if (t.includes("node") || t.some((x) => typeof x === "string" && /^node/.test(x))) return "node";
|
|
26
|
+
return "browser";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readMinimize(cfg) {
|
|
30
|
+
if (cfg.optimization && typeof cfg.optimization.minimize === "boolean") return cfg.optimization.minimize;
|
|
31
|
+
return cfg.mode !== "development" && cfg.mode !== "none";
|
|
32
|
+
}
|
|
33
|
+
function readDropConsole(cfg) {
|
|
34
|
+
const min = cfg.optimization && cfg.optimization.minimizer;
|
|
35
|
+
for (const p of Array.isArray(min) ? min : []) {
|
|
36
|
+
const t = p && (p.options || p);
|
|
37
|
+
const c = t && t.terserOptions && t.terserOptions.compress;
|
|
38
|
+
if (c && c.drop_console) return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function esTarget(ecma) {
|
|
44
|
+
if (ecma == null) return undefined;
|
|
45
|
+
const n = String(ecma);
|
|
46
|
+
if (n === "5") return "es5";
|
|
47
|
+
if (/^20\d\d$/.test(n)) return "es" + n;
|
|
48
|
+
if (/^es/i.test(n)) return n.toLowerCase();
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Map a TerserPlugin's `terserOptions` (in optimization.minimizer) to Rolldown's MinifyOptions, so
|
|
53
|
+
// drop_console/drop_debugger, mangle (on/off/toplevel/keep_*), passes, ecma target and comment
|
|
54
|
+
// handling are honored — not just drop_console. Returns false/true/MinifyOptions.
|
|
55
|
+
function buildMinify(cfg, minimize) {
|
|
56
|
+
if (!minimize) return false;
|
|
57
|
+
const min = cfg.optimization && cfg.optimization.minimizer;
|
|
58
|
+
let terser = null;
|
|
59
|
+
for (const p of Array.isArray(min) ? min : []) {
|
|
60
|
+
const opts = (p && p.options) || {};
|
|
61
|
+
if (opts.terserOptions) { terser = opts.terserOptions; break; }
|
|
62
|
+
const cn = p && p.constructor && p.constructor.name;
|
|
63
|
+
if (cn === "TerserPlugin") { terser = opts.terserOptions || {}; break; }
|
|
64
|
+
}
|
|
65
|
+
if (!terser) return true;
|
|
66
|
+
const t = terser;
|
|
67
|
+
const out = {};
|
|
68
|
+
// compress
|
|
69
|
+
if (t.compress === false) {
|
|
70
|
+
out.compress = false;
|
|
71
|
+
} else {
|
|
72
|
+
const c = (t.compress && typeof t.compress === "object") ? t.compress : {};
|
|
73
|
+
const compress = {};
|
|
74
|
+
if (c.drop_console != null) compress.dropConsole = !!c.drop_console;
|
|
75
|
+
if (c.drop_debugger != null) compress.dropDebugger = !!c.drop_debugger;
|
|
76
|
+
if (c.passes != null) compress.maxIterations = c.passes;
|
|
77
|
+
if (c.sequences === false) compress.sequences = false;
|
|
78
|
+
const tgt = esTarget(t.ecma || c.ecma);
|
|
79
|
+
if (tgt) compress.target = tgt;
|
|
80
|
+
if (t.keep_classnames || c.keep_classnames || t.keep_fnames || c.keep_fnames) compress.keepNames = { function: !!(t.keep_fnames || c.keep_fnames), class: !!(t.keep_classnames || c.keep_classnames) };
|
|
81
|
+
if (Object.keys(compress).length) out.compress = compress;
|
|
82
|
+
}
|
|
83
|
+
// mangle
|
|
84
|
+
if (t.mangle === false) {
|
|
85
|
+
out.mangle = false;
|
|
86
|
+
} else {
|
|
87
|
+
const mo = (t.mangle && typeof t.mangle === "object") ? t.mangle : {};
|
|
88
|
+
const m = {};
|
|
89
|
+
if (mo.toplevel != null || t.toplevel != null) m.toplevel = !!(mo.toplevel != null ? mo.toplevel : t.toplevel);
|
|
90
|
+
if (mo.keep_classnames || mo.keep_fnames || t.keep_classnames || t.keep_fnames) m.keepNames = { function: !!(mo.keep_fnames || t.keep_fnames), class: !!(mo.keep_classnames || t.keep_classnames) };
|
|
91
|
+
if (Object.keys(m).length) out.mangle = m;
|
|
92
|
+
}
|
|
93
|
+
// comments (terserOptions.format.comments / output.comments)
|
|
94
|
+
const comments = (t.format && t.format.comments !== undefined) ? t.format.comments : (t.output && t.output.comments);
|
|
95
|
+
if (comments !== undefined) {
|
|
96
|
+
out.codegen = { legalComments: comments === false ? "none" : (comments === "all" || comments === true ? "inline" : "eof") };
|
|
97
|
+
}
|
|
98
|
+
return Object.keys(out).length ? out : true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// PCF and other single-bundle setups force one chunk via LimitChunkCountPlugin({maxChunks:1}).
|
|
102
|
+
function singleChunkForced(cfg) {
|
|
103
|
+
for (const p of cfg.plugins || []) {
|
|
104
|
+
const n = p && p.constructor && p.constructor.name;
|
|
105
|
+
if (n === "LimitChunkCountPlugin" && p.options && typeof p.options.maxChunks === "number" && p.options.maxChunks <= 1) return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Map webpack chunkFilename tokens to Rolldown's ([contenthash]/[chunkhash] -> [hash], [id] -> [name]).
|
|
111
|
+
function convertChunkTemplate(t) {
|
|
112
|
+
return String(t).replace(/\[(content|chunk)hash(?::\d+)?\]/g, "[hash]").replace(/\[id\]/g, "[name]").replace(/^\.?[\\/]/, "");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---- Rolldown plugins for Define / Provide / Ignore / NormalModuleReplacement -------------------
|
|
116
|
+
function definePlugin(define) {
|
|
117
|
+
const keys = Object.keys(define).sort((a, b) => b.length - a.length);
|
|
118
|
+
if (!keys.length) return null;
|
|
119
|
+
const escaped = keys.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
120
|
+
const re = new RegExp(`(?<![\\w$.])(${escaped.join("|")})(?![\\w$])`, "g");
|
|
121
|
+
return {
|
|
122
|
+
name: "rollpack:define",
|
|
123
|
+
transform(code, id) {
|
|
124
|
+
if (/[/\\]node_modules[/\\]/.test(id) && !/process\.env|typeof process|__DEV__/.test(code)) return null;
|
|
125
|
+
re.lastIndex = 0;
|
|
126
|
+
if (!re.test(code)) return null;
|
|
127
|
+
re.lastIndex = 0;
|
|
128
|
+
return { code: code.replace(re, (m) => (define[m] != null ? define[m] : m)), map: null };
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function providePlugin(provide, _context) {
|
|
134
|
+
const entries = Object.entries(provide || {});
|
|
135
|
+
if (!entries.length) return null;
|
|
136
|
+
const acorn = require("acorn");
|
|
137
|
+
const eslintScope = require("eslint-scope");
|
|
138
|
+
const simple = entries.filter(([k]) => !k.includes("."));
|
|
139
|
+
return {
|
|
140
|
+
name: "rollpack:provide",
|
|
141
|
+
transform(code, id) {
|
|
142
|
+
if (/\.(css|scss|sass|less|svg)$/.test(id) || !simple.length) return null;
|
|
143
|
+
if (!simple.some(([k]) => code.includes(k))) return null;
|
|
144
|
+
let ast;
|
|
145
|
+
try {
|
|
146
|
+
ast = acorn.parse(code, { ecmaVersion: "latest", sourceType: "module", allowReturnOutsideFunction: true, allowAwaitOutsideFunction: true, allowHashBang: true });
|
|
147
|
+
} catch (_) { return null; } // unparseable (already-CJS/odd) — skip rather than break the build
|
|
148
|
+
let manager;
|
|
149
|
+
try { manager = eslintScope.analyze(ast, { ecmaVersion: 2022, sourceType: "module", ignoreEval: true, optimistic: true }); }
|
|
150
|
+
catch (_) { return null; }
|
|
151
|
+
// Truly free references = those that never resolved to a binding (bubble to the global scope).
|
|
152
|
+
const free = new Set(manager.globalScope.through.map((r) => r.identifier.name));
|
|
153
|
+
let header = "";
|
|
154
|
+
for (const [name, spec] of simple) {
|
|
155
|
+
if (!free.has(name)) continue;
|
|
156
|
+
const local = `__rp_provide_${name.replace(/[^\w$]/g, "_")}`;
|
|
157
|
+
if (Array.isArray(spec)) {
|
|
158
|
+
if (spec.length > 1) header += `import { ${spec[1]} as ${local} } from ${JSON.stringify(spec[0])};\n`;
|
|
159
|
+
else header += `import ${local} from ${JSON.stringify(spec[0])};\n`;
|
|
160
|
+
} else {
|
|
161
|
+
header += `import ${local} from ${JSON.stringify(spec)};\n`;
|
|
162
|
+
}
|
|
163
|
+
header += `var ${name} = ${local};\n`;
|
|
164
|
+
}
|
|
165
|
+
if (!header) return null;
|
|
166
|
+
return { code: header + code, map: null };
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function ignorePlugin(ignores) {
|
|
172
|
+
if (!ignores || !ignores.length) return null;
|
|
173
|
+
const matches = (request, ctx) => ignores.some((opt) => {
|
|
174
|
+
if (!opt) return false;
|
|
175
|
+
if (opt.resourceRegExp && !opt.resourceRegExp.test(request)) return false;
|
|
176
|
+
if (opt.resourceRegExp && opt.resourceRegExp.test(request)) { if (opt.contextRegExp) return opt.contextRegExp.test(ctx || ""); return true; }
|
|
177
|
+
if (typeof opt.checkResource === "function") return opt.checkResource(request, ctx);
|
|
178
|
+
return false;
|
|
179
|
+
});
|
|
180
|
+
const VID = "\0rollpack-ignored";
|
|
181
|
+
return {
|
|
182
|
+
name: "rollpack:ignore",
|
|
183
|
+
resolveId(source, importer) { if (matches(source, importer && path.dirname(importer))) return VID; return null; },
|
|
184
|
+
load(id) { if (id === VID) return "export default {};"; return null; },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function normalReplacePlugin(replacements) {
|
|
189
|
+
if (!replacements || !replacements.length) return null;
|
|
190
|
+
return {
|
|
191
|
+
name: "rollpack:normal-replace",
|
|
192
|
+
resolveId(source, importer) {
|
|
193
|
+
for (const [re, repl] of replacements) {
|
|
194
|
+
if (re.test(source)) {
|
|
195
|
+
let target = typeof repl === "function" ? (() => { const r = { request: source }; repl(r); return r.request; })() : repl;
|
|
196
|
+
if (target && !path.isAbsolute(target) && importer) target = path.resolve(path.dirname(importer), target);
|
|
197
|
+
return this.resolve ? this.resolve(target, importer, { skipSelf: true }) : target;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function entryRel(e, chunkFileName) {
|
|
206
|
+
const rel = (e.filename || chunkFileName).replace(/^\.[\\/]/, "").replace(/\\/g, "/");
|
|
207
|
+
return rel;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function runMake(compilation, cfg, context) {
|
|
211
|
+
const compiler = compilation.compiler;
|
|
212
|
+
const rp = compiler.$rollpack || {};
|
|
213
|
+
const prod = cfg.mode !== "development" && cfg.mode !== "none";
|
|
214
|
+
const minimize = readMinimize(cfg);
|
|
215
|
+
const entries = normalizeEntries(cfg, context);
|
|
216
|
+
const ext = buildExternals(cfg, context);
|
|
217
|
+
const define = buildDefine(cfg, prod, rp.define);
|
|
218
|
+
// When hot is enabled (dev server or HotModuleReplacementPlugin), make `module.hot` /
|
|
219
|
+
// `import.meta.hot` resolve to the injected client runtime so HMR-guarded code runs.
|
|
220
|
+
if (rp.hot) {
|
|
221
|
+
define["module.hot"] = "(typeof window!=='undefined'&&window.__rollpack_hot__)";
|
|
222
|
+
define["import.meta.hot"] = "(typeof window!=='undefined'&&window.__rollpack_hot__)";
|
|
223
|
+
}
|
|
224
|
+
const resolve = buildResolve(cfg, context);
|
|
225
|
+
const { sourcemap } = devtoolToRolldown(cfg.devtool);
|
|
226
|
+
const platform = targetPlatform(cfg.target);
|
|
227
|
+
const moduleTypes = nonJsModuleTypes();
|
|
228
|
+
const minifyOpt = buildMinify(cfg, minimize);
|
|
229
|
+
const plugins = [
|
|
230
|
+
ignorePlugin(rp.ignore),
|
|
231
|
+
normalReplacePlugin(rp.normalReplace),
|
|
232
|
+
loaderPlugin(cfg, context, compilation),
|
|
233
|
+
providePlugin(rp.provide, context),
|
|
234
|
+
definePlugin(define),
|
|
235
|
+
].filter(Boolean);
|
|
236
|
+
|
|
237
|
+
const t0 = Date.now();
|
|
238
|
+
compilation.__startTime = t0;
|
|
239
|
+
|
|
240
|
+
for (const e of entries) {
|
|
241
|
+
let bundle;
|
|
242
|
+
try {
|
|
243
|
+
bundle = await rolldown({
|
|
244
|
+
input: e.input,
|
|
245
|
+
external: ext.isExternal,
|
|
246
|
+
platform,
|
|
247
|
+
treeshake: cfg.optimization && cfg.optimization.usedExports === false ? false : true,
|
|
248
|
+
resolve,
|
|
249
|
+
moduleTypes,
|
|
250
|
+
plugins,
|
|
251
|
+
onLog: () => {},
|
|
252
|
+
});
|
|
253
|
+
const minifyOptForEntry = minifyOpt;
|
|
254
|
+
const codeSplitting = (e.format === "es" || e.format === "cjs") && !singleChunkForced(cfg) && entries.length === 1;
|
|
255
|
+
const chunkTmpl = convertChunkTemplate((cfg.output && cfg.output.chunkFilename) || "[name].js");
|
|
256
|
+
const gen = await bundle.generate({
|
|
257
|
+
format: e.format,
|
|
258
|
+
name: e.libraryName || undefined,
|
|
259
|
+
extend: e.extend,
|
|
260
|
+
globals: ext.globals,
|
|
261
|
+
minify: minifyOptForEntry,
|
|
262
|
+
codeSplitting,
|
|
263
|
+
chunkFileNames: chunkTmpl,
|
|
264
|
+
sourcemap,
|
|
265
|
+
});
|
|
266
|
+
for (const o of gen.output) {
|
|
267
|
+
if (o.type === "chunk") {
|
|
268
|
+
let code = o.code != null ? o.code : "";
|
|
269
|
+
const rel0 = o.isEntry ? entryRel(e, o.fileName) : o.fileName.replace(/\\/g, "/");
|
|
270
|
+
const rel = require("./template").interpolateName(rel0, { name: o.isEntry ? e.name : path.basename(rel0, path.extname(rel0)), chunkName: e.name, id: e.name, content: code, ext: path.extname(rel0) || ".js" });
|
|
271
|
+
if (o.map && sourcemap && sourcemap !== "inline") {
|
|
272
|
+
const mapName = rel + ".map";
|
|
273
|
+
compilation.emitAsset(mapName, typeof o.map === "string" ? o.map : JSON.stringify(o.map));
|
|
274
|
+
if (sourcemap !== "hidden") code += `\n//# sourceMappingURL=${path.posix.basename(mapName)}`;
|
|
275
|
+
}
|
|
276
|
+
compilation.emitAsset(rel, code);
|
|
277
|
+
} else {
|
|
278
|
+
// Rolldown also emits the sourcemap as a separate asset named after the chunk's default
|
|
279
|
+
// fileName; we already emit the map (renamed to match the entry) from chunk.map above.
|
|
280
|
+
if (/\.map$/.test(o.fileName)) continue;
|
|
281
|
+
compilation.emitAsset(o.fileName.replace(/\\/g, "/"), o.source);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
} catch (err) {
|
|
285
|
+
const detail = (err && (err.message || err.toString())) || String(err);
|
|
286
|
+
const more = err && Array.isArray(err.errors) ? err.errors.map((x) => x.message || String(x)).join(" | ") : "";
|
|
287
|
+
compilation.errors.push({ message: `${e.name}: ${detail}${more ? " :: " + more : ""}` });
|
|
288
|
+
} finally {
|
|
289
|
+
if (bundle) { try { await bundle.close(); } catch (_) {} }
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
compilation.__time = Date.now() - t0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = { runMake, definePlugin, providePlugin, ignorePlugin, normalReplacePlugin };
|