Skip to content

Commit bc71c0c

Browse files
authored
feat(browser): introduce BrowserRequirePlugin and remove nonWebpackRequire (#11470)
* BrowserRequirePlugin * Update docs * Fix * Update api
1 parent 80600b4 commit bc71c0c

File tree

9 files changed

+113
-89
lines changed

9 files changed

+113
-89
lines changed

packages/rspack/etc/core.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,8 @@ export class Compiler {
11141114
get __internal__ruleSet(): RuleSetCompiler;
11151115
// @internal
11161116
__internal__takeModuleExecutionResult(id: number): any;
1117+
// @internal
1118+
__internal_browser_require: (id: string) => unknown;
11171119
// (undocumented)
11181120
cache: Cache_2;
11191121
// (undocumented)

packages/rspack/src/Compiler.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ class Compiler {
162162
compilerPath: string;
163163
options: RspackOptionsNormalized;
164164

165+
/**
166+
* Note: This is not a webpack public API, maybe removed in future.
167+
* @internal
168+
*/
169+
__internal_browser_require: (id: string) => unknown;
170+
165171
constructor(context: string, options: RspackOptionsNormalized) {
166172
this.#initial = true;
167173

@@ -241,6 +247,12 @@ class Compiler {
241247
this.idle = false;
242248

243249
this.watchMode = false;
250+
251+
this.__internal_browser_require = () => {
252+
throw new Error(
253+
"Cannot execute user defined code in browser without `BrowserRequirePlugin`"
254+
);
255+
};
244256
// this is a bit tricky since applyDefaultOptions is executed later, so we don't get the `resolve.pnp` default value
245257
// we need to call pnp defaultValue early here
246258
this.resolverFactory = new ResolverFactory(
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import fs from "node:fs";
2+
import { sync as resolveSync, transformSync } from "@rspack/binding";
3+
import type { Compiler } from "../Compiler";
4+
5+
type BrowserRequire = typeof Compiler.prototype.__internal_browser_require;
6+
7+
/**
8+
* Represents the runtime context for CommonJS modules in a browser environment.
9+
*/
10+
interface CommonJsRuntime {
11+
module: any;
12+
exports: any;
13+
require: BrowserRequire;
14+
}
15+
16+
interface BrowserRequirePluginOptions {
17+
/**
18+
* This function defines how to execute CommonJS code.
19+
*/
20+
execute: (code: string, runtime: CommonJsRuntime) => void;
21+
}
22+
23+
const unsafeExecute: BrowserRequirePluginOptions["execute"] = (
24+
code,
25+
runtime
26+
) => {
27+
const wrapper = new Function("module", "exports", "require", code);
28+
wrapper(runtime.module, runtime.exports, runtime.require);
29+
};
30+
31+
/**
32+
* This plugin inject browser-compatible `require` function to the `Compiler`.
33+
* 1. It resolves the JavaScript in the memfs with Node.js resolution algorithm rather than in the host filesystem.
34+
* 2. It transform ESM to CommonJS which will be executed with a user-defined `execute` function.
35+
*/
36+
export class BrowserRequirePlugin {
37+
/**
38+
* This is an unsafe way to execute code in the browser using `new Function`.
39+
* It is your responsibility to ensure that your application is not vulnerable to attacks due to this function.
40+
*/
41+
static unsafeExecute = unsafeExecute;
42+
43+
constructor(private options: BrowserRequirePluginOptions) {}
44+
45+
apply(compiler: Compiler) {
46+
const execute = this.options.execute;
47+
compiler.__internal_browser_require = function browserRequire(id: string) {
48+
const { path: loaderPath } = resolveSync("", id);
49+
if (!loaderPath) {
50+
throw new Error(`Cannot find loader of ${id}`);
51+
}
52+
53+
const data = fs.readFileSync(loaderPath);
54+
const code = data?.toString() || "";
55+
56+
const module: any = { exports: {} };
57+
const exports = module.exports;
58+
59+
const cjs = transformSync(
60+
code,
61+
JSON.stringify({
62+
module: { type: "commonjs" }
63+
})
64+
);
65+
66+
execute(cjs.code, { exports, module, require: browserRequire });
67+
return exports.default ?? module.exports;
68+
};
69+
}
70+
}

packages/rspack/src/browser/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "../index";
22
export { BrowserHttpImportEsmPlugin } from "./BrowserHttpImportEsmPlugin";
3+
export { BrowserRequirePlugin } from "./BrowserRequire";
34

45
import { fs, volume } from "./fs";
56
export const builtinMemFs = {

packages/rspack/src/builtin-plugin/SubresourceIntegrityPlugin.ts

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import type { Compiler } from "../Compiler";
1313
import type { CrossOriginLoading } from "../config/types";
1414
import { getSRIPluginOptionsSchema } from "../schema/plugins";
1515
import { validate } from "../schema/validate";
16-
import { nonWebpackRequire } from "../util/require";
1716
import { create } from "./base";
1817

1918
const PLUGIN_NAME = "SubresourceIntegrityPlugin";
@@ -297,27 +296,17 @@ export class SubresourceIntegrityPlugin extends NativeSubresourceIntegrityPlugin
297296
}
298297
}
299298

300-
if (IS_BROWSER) {
301-
nonWebpackRequire()(this.options.htmlPlugin)
302-
.then(bindingHtmlHooks)
303-
.catch(e => {
304-
if (
305-
!isErrorWithCode(e as Error) ||
306-
(e as Error & { code: string }).code !== "MODULE_NOT_FOUND"
307-
) {
308-
throw e;
309-
}
310-
});
311-
} else {
312-
try {
313-
bindingHtmlHooks(require(this.options.htmlPlugin));
314-
} catch (e) {
315-
if (
316-
!isErrorWithCode(e as Error) ||
317-
(e as Error & { code: string }).code !== "MODULE_NOT_FOUND"
318-
) {
319-
throw e;
320-
}
299+
try {
300+
const htmlPlugin = IS_BROWSER
301+
? compiler.__internal_browser_require(this.options.htmlPlugin)
302+
: require(this.options.htmlPlugin);
303+
bindingHtmlHooks(htmlPlugin);
304+
} catch (e) {
305+
if (
306+
!isErrorWithCode(e as Error) ||
307+
(e as Error & { code: string }).code !== "MODULE_NOT_FOUND"
308+
) {
309+
throw e;
321310
}
322311
}
323312
}

packages/rspack/src/builtin-plugin/html-plugin/plugin.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88

99
import type { Compilation } from "../../Compilation";
1010
import type { Compiler } from "../../Compiler";
11-
import { nonWebpackRequire } from "../../util/require";
1211
import { create } from "../base";
1312
import {
1413
cleanPluginHooks,
@@ -137,9 +136,11 @@ const HtmlRspackPluginImpl = create(
137136
);
138137
}
139138
try {
140-
const renderer = (await nonWebpackRequire()(templateFilePath)) as (
141-
data: Record<string, unknown>
142-
) => Promise<string> | string;
139+
const renderer = (
140+
IS_BROWSER
141+
? this.__internal_browser_require(templateFilePath)
142+
: require(templateFilePath)
143+
) as (data: Record<string, unknown>) => Promise<string> | string;
143144
if (c.templateParameters === false) {
144145
return await renderer({});
145146
}

packages/rspack/src/container/ModuleFederationPlugin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,11 @@ function getDefaultEntryRuntime(
191191
options.shareStrategy ?? "version-first"
192192
)}`,
193193
compiler.webpack.Template.getFunctionContent(
194-
require("./moduleFederationDefaultRuntime.js")
194+
IS_BROWSER
195+
? compiler.__internal_browser_require(
196+
"@rspack/browser/moduleFederationDefaultRuntime.js"
197+
)
198+
: require("./moduleFederationDefaultRuntime.js")
195199
)
196200
].join(";");
197201
return `@module-federation/runtime/rspack.js!=!data:text/javascript,${content}`;

packages/rspack/src/loader-runner/loadLoader.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type Url from "node:url";
1212
import type { LoaderDefinitionFunction } from "../config";
1313
import type { PitchLoaderDefinitionFunction } from "../config/adapterRuleUse";
1414
import type { Compiler } from "../exports";
15-
import { nonWebpackRequire } from "../util/require";
1615
import type { LoaderObject } from ".";
1716
import LoaderLoadingError from "./LoaderLoadingError";
1817

@@ -31,12 +30,13 @@ export default function loadLoader(
3130
callback: (err: unknown) => void
3231
): void {
3332
if (IS_BROWSER) {
34-
// Why is IS_BROWSER used here:
35-
// @see [../utils/require.ts]
36-
nonWebpackRequire()(loader.path).then(module => {
37-
handleResult(loader, module, callback);
38-
}, callback);
39-
return;
33+
let module: any;
34+
try {
35+
module = compiler.__internal_browser_require(loader.path);
36+
} catch (e) {
37+
return callback(e);
38+
}
39+
return handleResult(loader, module, callback);
4040
}
4141

4242
if (loader.type === "module") {

packages/rspack/src/util/require.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)