@crossbind/plugin-webpack 2.0.0-beta.50

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/AGENTS.md ADDED
@@ -0,0 +1,89 @@
1
+ # AGENTS.md — @crossbind/plugin-webpack
2
+
3
+ > Webpack **and Rspack** integration plugin. Same package serves both — Rspack reuses the Webpack plugin contract.
4
+
5
+ ## What lives here
6
+
7
+ - `index.js` — exports `class CrossbindWebpackPlugin`. Single class, no separate kernel.
8
+ - Sibling package `@crossbind/plugin-webpack-loader` ships the loader for `.h` files; `getRule()` references it by name.
9
+
10
+ ## Class API surface
11
+
12
+ ```
13
+ new CrossbindWebpackPlugin()
14
+ ↓
15
+ .apply(compiler) ← webpack standard plugin hook
16
+ .getRule() ← module.rules entry for .h
17
+ .getDevServerConfig() ← devServer block w/ COOP+COEP + middleware
18
+ .getLoaderOptions() ← escape hatch: crossbind state passed to other rules
19
+ .setDevServerMiddleware(...) ← internal, called by getDevServerConfig
20
+ ```
21
+
22
+ User config consumes all four; see `docs/playbooks/integration/webpack-rspack.md` for the canonical wiring.
23
+
24
+ ## Hooks the plugin registers
25
+
26
+ | webpack hook | What it does |
27
+ |--------------|--------------|
28
+ | `compiler.hooks.done` | After every compilation: re-run `createLib + createXCFramework + buildWasm` (mtime-aware via `isSourceNewer`) |
29
+ | `compiler.hooks.afterCompile` | Adds `state.config.paths.native` to `compilation.contextDependencies` so webpack watch picks up `.cpp`/`.h` edits |
30
+
31
+ ## DevServer middleware
32
+
33
+ `getDevServerConfig()` returns:
34
+
35
+ ```js
36
+ {
37
+ watchFiles: state.config.paths.native,
38
+ hot: true,
39
+ liveReload: true,
40
+ headers: {
41
+ 'Cross-Origin-Opener-Policy': 'same-origin',
42
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
43
+ },
44
+ setupMiddlewares: (middlewares, devServer) => { ... }
45
+ }
46
+ ```
47
+
48
+ The middleware adds `/crossbind.js` and `/crossbind.wasm` routes that pipe the freshly built artifacts. Both routes also re-emit COOP/COEP per-response (belt-and-suspenders for caching layers that strip top-level headers).
49
+
50
+ ## Rspack compatibility
51
+
52
+ Rspack copies webpack's plugin / loader shape. The plugin works without modification. The Rspack-specific wrinkle: `getRule()` returns a plain object that Rspack's TypeScript-strict config types may complain about — cast to `RspackOptions['module']['rules'][number]` if needed.
53
+
54
+ ## Common pitfalls
55
+
56
+ - **Forgetting `getRule()` while passing the plugin instance.** The `.h` loader rule and the plugin instance are independent registrations; both are required.
57
+ - **Wrapping the plugin's `getDevServerConfig()` and dropping `headers`.** Always spread: `{ ...cppDev, ...custom, headers: { ...cppDev.headers, ...custom.headers } }`.
58
+ - **Trying to inject `getRule()` into `plugins:` array** instead of `module.rules:`. They are distinct.
59
+ - **Babel/SWC running on `.h` first.** Loader chain order matters; `getRule()` test is `/\.h$/` so other rules with broader tests can swallow it. Make sure no rule for `/\.[jt]sx?$/` accidentally matches `.h` (none do by default).
60
+ - **Adding `console.log` for debugging.** Route through `crossbind` exports — `state.config` carries the logger if you need it. Plugin output should be quiet by default.
61
+
62
+ ## Validation
63
+
64
+ Strict gate:
65
+
66
+ ```bash
67
+ pnpm run ci:linux:build && pnpm run e2e:dev && pnpm run e2e:prod
68
+ ```
69
+
70
+ Smoke samples:
71
+
72
+ ```bash
73
+ pnpm --filter=@crossbind/example-web-react-rspack run build
74
+ pnpm --filter=@crossbind/e2e-web-rspack run build
75
+ ```
76
+
77
+ If you touch the dev-server middleware, also test:
78
+
79
+ ```bash
80
+ pnpm --filter=@crossbind/example-web-react-rspack exec rspack serve
81
+ # verify in browser: COOP/COEP headers present, /crossbind.wasm 200
82
+ ```
83
+
84
+ ## Reference
85
+
86
+ - Loader package: `plugins/webpack-loader/`
87
+ - Integration recipe: `docs/playbooks/integration/webpack-rspack.md`
88
+ - Next.js variant (no devServer): `docs/playbooks/integration/nextjs.md`
89
+ - Canonical samples: `examples/web-react-rspack`, `e2e/web-rspack`
package/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # @crossbind/plugin-webpack
2
+
3
+ ## 2.0.0-beta.50
4
+
5
+ ### Patch Changes
6
+
7
+ - A cargo dependency no longer has to be built by hand before the app. The dependency step this
8
+ plugin already awaits builds one whose target is missing, and refuses to link one it could not
9
+ build — on wasm that case used to pass silently and leave a module that died at init.
10
+
11
+ ## 1.0.2
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies
16
+ - crossbind@1.0.4
17
+
18
+ ## 1.0.0
19
+
20
+ ### Major Changes
21
+
22
+ - 🚀 first stable release
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies
27
+ - crossbind@1.0.0
28
+
29
+ ## 1.0.0-beta.10
30
+
31
+ ### Patch Changes
32
+
33
+ - chore: add initial version of CHANGELOGS files
34
+ - Updated dependencies
35
+ - crossbind@1.0.0-beta.33
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Buğra Sarı and crossbind contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # @crossbind/plugin-webpack
2
+ **crossbind Webpack plugin**
3
+ A tool for seamless C++ integration with the Webpack bundler.
4
+
5
+ <a href="https://www.npmjs.com/package/@crossbind/plugin-webpack">
6
+ <img alt="NPM version" src="https://img.shields.io/npm/v/@crossbind/plugin-webpack?style=for-the-badge" />
7
+ </a>
8
+ <a href="https://github.com/crossbind/crossbind/blob/main/LICENSE">
9
+ <img alt="License" src="https://img.shields.io/github/license/crossbind/crossbind?style=for-the-badge" />
10
+ </a>
11
+ <a href="https://crossbind.js.org/docs/guide/integrate-into-existing-project/webpack">
12
+ <img alt="Docs - React Native" src="https://img.shields.io/badge/Docs_-_Webpack-20B2AA?style=for-the-badge" />
13
+ </a>
14
+
15
+ ## Integration
16
+ To integrate crossbind into your project using Webpack as a bundler, you can utilize the @crossbind/plugin-webpack plugin. Start by installing these package with the following command:
17
+
18
+ ```sh
19
+ npm install @crossbind/plugin-webpack @crossbind/plugin-webpack-loader --save-dev
20
+ ```
21
+
22
+ **Webpack**
23
+ To enable the plugin, modify the `webpack.config.js` file as shown below.
24
+
25
+ ```diff
26
+ + const CrossbindWebpackPlugin = require('@crossbind/plugin-webpack');
27
+ + const crossbindWebpackPlugin = new CrossbindWebpackPlugin();
28
+ + const compiler = crossbindWebpackPlugin.getCompiler();
29
+
30
+ module.exports = {
31
+ //...
32
+ plugins: [
33
+ + crossbindWebpackPlugin,
34
+ ],
35
+ module: {
36
+ rules: [
37
+ + {
38
+ + test: /\.h$/,
39
+ + loader: '@crossbind/plugin-webpack-loader',
40
+ + options: { compiler },
41
+ + }
42
+ ],
43
+ },
44
+ };
45
+ ```
46
+
47
+ **Rspack**
48
+ To enable the plugin, modify the `rspack.config.mjs` file as shown below.
49
+
50
+ ```diff
51
+ + import CrossbindWebpackPlugin from '@crossbind/plugin-webpack';
52
+
53
+ + const crossbindWebpackPlugin = new CrossbindWebpackPlugin();
54
+ + const compiler = crossbindWebpackPlugin.getCompiler();
55
+
56
+ export default defineConfig({
57
+ module: {
58
+ rules: [
59
+ + {
60
+ + test: /\.h$/,
61
+ + loader: '@crossbind/plugin-webpack-loader',
62
+ + options: { compiler },
63
+ + }
64
+ ]
65
+ },
66
+ plugins: [
67
+ + crossbindWebpackPlugin,
68
+ ].filter(Boolean),
69
+ + devServer: {
70
+ + watchFiles: compiler.config.paths.native,
71
+ + setupMiddlewares: (middlewares, devServer) => {
72
+ + if (!devServer) {
73
+ + throw new Error('@rspack/dev-server is not defined');
74
+ + }
75
+ +
76
+ + middlewares.unshift({
77
+ + name: '/crossbind.js',
78
+ + path: '/crossbind.js',
79
+ + middleware: (req, res) => {
80
+ + res.sendFile(`${compiler.config.paths.temp}/${compiler.config.general.name}.browser.js`);
81
+ + },
82
+ + });
83
+ + middlewares.unshift({
84
+ + name: '/crossbind.wasm',
85
+ + path: '/crossbind.wasm',
86
+ + middleware: (req, res) => {
87
+ + res.send(fs.readFileSync(`${compiler.config.paths.temp}/${compiler.config.general.name}.wasm`));
88
+ + },
89
+ + });
90
+ +
91
+ + return middlewares;
92
+ + },
93
+ + },
94
+ });
95
+ ```
96
+
97
+ **Craco**
98
+ To enable the plugin, create the `craco.config.js` file as shown below.
99
+
100
+ ```js
101
+ const fs = require('fs');
102
+
103
+ module.exports = async function () {
104
+ const { default: CrossbindWebpackPlugin } = await import('@crossbind/plugin-webpack');
105
+ const crossbindWebpackPlugin = new CrossbindWebpackPlugin();
106
+ const compiler = crossbindWebpackPlugin.getCompiler();
107
+
108
+ return {
109
+ webpack: {
110
+ plugins: {
111
+ add: [crossbindWebpackPlugin],
112
+ },
113
+ configure: (config) => {
114
+ config.module.rules[1].oneOf = [
115
+ {
116
+ test: /\.h$/,
117
+ loader: '@crossbind/plugin-webpack-loader',
118
+ options: { compiler },
119
+ },
120
+ ...config.module.rules[1].oneOf,
121
+ ];
122
+ return config;
123
+ },
124
+ },
125
+ devServer: (devServerConfig) => {
126
+ devServerConfig.watchFiles = compiler.config.paths.native;
127
+ devServerConfig.onBeforeSetupMiddleware = (devServer) => {
128
+ if (!devServer) {
129
+ throw new Error('webpack-dev-server is not defined');
130
+ }
131
+
132
+ devServer.app.get('/crossbind.js', function (req, res) {
133
+ res.sendFile(`${compiler.config.paths.temp}/${compiler.config.general.name}.browser.js`);
134
+ });
135
+
136
+ devServer.app.get('/crossbind.wasm', function (req, res) {
137
+ res.send(fs.readFileSync(`${compiler.config.paths.temp}/${compiler.config.general.name}.wasm`));
138
+ });
139
+ };
140
+
141
+ return devServerConfig;
142
+ },
143
+ };
144
+ };
145
+ ```
package/index.js ADDED
@@ -0,0 +1,176 @@
1
+
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import {
5
+ state, createLib, buildWasm, createBridgeFile, getData, getCrossbindScript, getRustJsScript, getDependFilePath, buildDependencies, getTargetParams, getFilteredBuildTargets, isSourceNewer,
6
+ } from 'crossbind';
7
+
8
+ const targetParams = getTargetParams({ platform: ['wasm'], arch: ['wasm32'], runtime: ['st'], runtimeEnv: ['browser'] }, true);
9
+ let buildTargetRelease = getFilteredBuildTargets(targetParams, { buildType: 'release' })?.[0];
10
+ let buildTargetDebug = getFilteredBuildTargets(targetParams, { buildType: 'debug' })?.[0];
11
+
12
+ if (!buildTargetRelease && !buildTargetDebug) {
13
+ throw new Error('No build targets found');
14
+ }
15
+
16
+ if (!buildTargetDebug) {
17
+ buildTargetDebug = buildTargetRelease;
18
+ } else if (!buildTargetRelease) {
19
+ buildTargetRelease = buildTargetDebug;
20
+ }
21
+
22
+ export default class CrossbindWebpackPlugin {
23
+ static defaultOptions = {};
24
+
25
+ constructor(options = {}) {
26
+ this.options = { ...CrossbindWebpackPlugin.defaultOptions, ...options };
27
+ this.bridges = [];
28
+ }
29
+
30
+ apply(compiler) {
31
+ const pluginName = this.constructor.name;
32
+ compiler.options.resolve = compiler.options.resolve || {};
33
+ compiler.options.resolve.alias = compiler.options.resolve.alias || {};
34
+ // `cargo:` imports use the node:/npm: convention for a non-npm store; webpack's resolver
35
+ // has no such scheme, so the request is rewritten to the generated marker .rs (which the
36
+ // loader then turns into the crate-import bridge). Works on rspack via its compat layer.
37
+ new compiler.webpack.NormalModuleReplacementPlugin(/^cargo:/, (resource) => {
38
+ const marker = getDependFilePath(resource.request, buildTargetRelease);
39
+ if (marker) resource.request = marker;
40
+ }).apply(compiler);
41
+ // Rust packages ship no JS entry at all (their package.json is just a name), so node
42
+ // resolution can never find them. Each known cargo-type dependency gets an exact-match
43
+ // alias onto its crate root; the loader's .rs branch turns that into the proxy module.
44
+ for (const dep of state.config.allDependencies ?? []) {
45
+ if (dep.export?.type !== 'cargo' || !dep.package?.name) continue;
46
+ const libRs = path.resolve(dep.paths.project, dep.export.crate ?? 'crate', 'src/lib.rs');
47
+ if (fs.existsSync(libRs) && !(`${dep.package.name}$` in compiler.options.resolve.alias)) {
48
+ compiler.options.resolve.alias[`${dep.package.name}$`] = libRs;
49
+ }
50
+ }
51
+ // tapPromise (not tap) so webpack awaits the native C++/wasm build and a build
52
+ // failure surfaces as a compilation error instead of an unhandled rejection.
53
+ compiler.hooks.done.tapPromise(pluginName, this.onDone.bind(this));
54
+ compiler.hooks.afterCompile.tapAsync(pluginName, this.afterCompile.bind(this));
55
+ }
56
+
57
+ afterCompile(compilation, callback) {
58
+ state.config.paths.native.map((file) => compilation.contextDependencies.add(file));
59
+ callback();
60
+ }
61
+
62
+ async onDone({ compilation }) {
63
+ const isDev = compilation.options.mode === 'development';
64
+ const buildTarget = isDev ? buildTargetDebug : buildTargetRelease;
65
+ await buildDependencies({ targetParams: { ...targetParams, buildType: [buildTarget.buildType] } });
66
+ const force = isSourceNewer(buildTarget);
67
+ const sourceBuilt = createLib(buildTarget, 'Source', { force, buildSource: true });
68
+ // Bridge cache is keyed on the nativeGlob fingerprint: a changed bridge
69
+ // set rebuilds the lib even without source changes, and a rebuilt lib
70
+ // must force the final link too.
71
+ const bridgeBuilt = createLib(buildTarget, 'Bridge', { force, buildSource: false, nativeGlob: [`${state.config.paths.cli}/assets/cpp-runtime/commonBridges.cpp`, ...this.bridges] });
72
+ await buildWasm(buildTarget, { force: force || Boolean(sourceBuilt) || Boolean(bridgeBuilt) });
73
+ if (!isDev) {
74
+ const output = state.config.paths.output === state.config.paths.build ? compilation.options.output.path : state.config.paths.output;
75
+ // On a first-ever build the output dir may not exist yet when this
76
+ // hook runs; copyFileSync reports that as ENOENT too.
77
+ fs.mkdirSync(output, { recursive: true });
78
+ fs.copyFileSync(`${state.config.paths.build}/${buildTarget.jsName}`, `${output}/crossbind.js`);
79
+ fs.copyFileSync(`${state.config.paths.build}/${buildTarget.wasmName}`, `${output}/crossbind.wasm`);
80
+
81
+ const dataFilePath = `${state.config.paths.build}/${buildTarget.dataTxtName}`;
82
+ if (fs.existsSync(dataFilePath)) {
83
+ fs.copyFileSync(dataFilePath, `${output}/crossbind.data.txt`);
84
+ }
85
+ /* const workerFilePath = `${state.config.paths.build}/${state.config.general.name}.js`;
86
+ if (fs.existsSync(workerFilePath)) {
87
+ fs.copyFileSync(workerFilePath, `${output}/cpp.worker.js`);
88
+ } */
89
+ }
90
+ }
91
+
92
+ getLoaderOptions() {
93
+ return {
94
+ bridges: this.bridges,
95
+ createBridgeFile,
96
+ getData,
97
+ state,
98
+ getCrossbindScript,
99
+ getRustJsScript,
100
+ getTargetParams,
101
+ getFilteredBuildTargets
102
+ };
103
+ }
104
+
105
+ getRule() {
106
+ return {
107
+ // `rs` rides the same rule: the loader branches on the extension.
108
+ test: new RegExp(`\\.(${[...state.config.ext.header, 'rs'].join('|')})$`),
109
+ loader: '@crossbind/plugin-webpack-loader',
110
+ options: { ...this.getLoaderOptions() },
111
+ };
112
+ }
113
+
114
+ setDevServerMiddleware(middlewares, devServer) {
115
+ if (!devServer) {
116
+ throw new Error('devServer is not defined');
117
+ }
118
+
119
+ middlewares.unshift({
120
+ name: '/crossbind.js',
121
+ path: '/crossbind.js',
122
+ middleware: (req, res) => {
123
+ const filePath = `${state.config.paths.build}/${buildTargetDebug.jsName}`;
124
+ res.setHeader('Content-Type', 'application/javascript');
125
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
126
+ res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
127
+ fs.createReadStream(filePath).pipe(res);
128
+ },
129
+ });
130
+
131
+ middlewares.unshift({
132
+ name: '/crossbind.wasm',
133
+ path: '/crossbind.wasm',
134
+ middleware: (req, res) => {
135
+ const filePath = `${state.config.paths.build}/${buildTargetDebug.wasmName}`;
136
+ res.setHeader('Content-Type', 'application/wasm');
137
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
138
+ res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
139
+ fs.createReadStream(filePath).pipe(res);
140
+ },
141
+ });
142
+
143
+ middlewares.unshift({
144
+ name: '/crossbind.data.txt',
145
+ path: '/crossbind.data.txt',
146
+ middleware: (req, res) => {
147
+ const filePath = `${state.config.paths.build}/${buildTargetDebug.dataTxtName}`;
148
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
149
+ res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
150
+ if (!fs.existsSync(filePath)) {
151
+ res.statusCode = 404;
152
+ res.end();
153
+ return;
154
+ }
155
+ fs.createReadStream(filePath).pipe(res);
156
+ },
157
+ });
158
+
159
+ return middlewares;
160
+ }
161
+
162
+ getDevServerConfig() {
163
+ return {
164
+ watchFiles: state.config.paths.native,
165
+ hot: true,
166
+ liveReload: true,
167
+ headers: {
168
+ 'Cross-Origin-Opener-Policy': 'same-origin',
169
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
170
+ },
171
+ setupMiddlewares: (middlewares, devServer) => {
172
+ return this.setDevServerMiddleware(middlewares, devServer);
173
+ },
174
+ };
175
+ }
176
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@crossbind/plugin-webpack",
3
+ "version": "2.0.0-beta.50",
4
+ "description": "crossbind Webpack plugin: A tool for seamless C++ integration with the Webpack bundler.",
5
+ "homepage": "https://github.com/crossbind/crossbind/tree/main/plugins/webpack#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/crossbind/crossbind.git"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "main": "index.js",
13
+ "dependencies": {
14
+ "crossbind": "^2.0.0-beta.50",
15
+ "@crossbind/core-embind-rust": "^2.0.0-beta.50"
16
+ },
17
+ "peerDependencies": {
18
+ "webpack": ">=5.109.2"
19
+ },
20
+ "keywords": [
21
+ "crossbind-plugin",
22
+ "webpack",
23
+ "bind",
24
+ "c++"
25
+ ]
26
+ }