rsbuild-plugin-react-native-web 3.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Rspack Contrib
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,49 @@
1
+ # rsbuild-plugin-react-native-web
2
+
3
+ Rsbuild plugin for React Native Web support. This plugin enables you to use React Native components in web applications built with Rsbuild.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install rsbuild-plugin-react-native-web react-native-web
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ // rsbuild.config.ts
15
+ import { defineConfig } from '@rsbuild/core'
16
+ import { pluginReactNativeWeb } from 'rsbuild-plugin-react-native-web'
17
+
18
+ export default defineConfig({
19
+ plugins: [pluginReactNativeWeb()],
20
+ })
21
+ ```
22
+
23
+ ## Options
24
+
25
+ ### modulesToTranspile
26
+
27
+ An array of additional node_modules that need to be transpiled. By default, packages starting with `react-native`, `@react-native`, `expo`, and `@expo` are already included.
28
+
29
+ ```ts
30
+ pluginReactNativeWeb({
31
+ modulesToTranspile: ['my-react-native-library'],
32
+ })
33
+ ```
34
+
35
+ ## Features
36
+
37
+ - Aliases `react-native` to `react-native-web`
38
+ - Adds `.web.*` file extensions with higher priority
39
+ - Configures global variables required by React Native (`__DEV__`, `EXPO_OS`, etc.)
40
+ - Handles Flow syntax in React Native packages
41
+ - Transforms problematic code patterns for web compatibility
42
+
43
+ ## Credits
44
+
45
+ This plugin is inspired by [vite-plugin-rnw](https://github.com/dannyhw/vite-plugin-rnw) by [@dannyhw](https://github.com/dannyhw). Many of the core transformation techniques and React Native Web compatibility patterns were adapted from that project. Thanks for the great work.
46
+
47
+ ## License
48
+
49
+ MIT
@@ -0,0 +1,62 @@
1
+ import { RsbuildPlugin } from '@rsbuild/core';
2
+ import MagicString from 'magic-string';
3
+
4
+ /**
5
+ * Code transformation utilities for fixing React Native package compatibility issues
6
+ * Ported from vite-plugin-rnw (https://github.com/dannyhw/vite-plugin-rnw)
7
+ */
8
+
9
+ interface TransformResult {
10
+ code: string;
11
+ map: ReturnType<MagicString['generateMap']> | null;
12
+ changed: boolean;
13
+ }
14
+
15
+ interface PluginReactNativeWebOptions {
16
+ /**
17
+ * Additional node_modules that need to be transpiled.
18
+ * By default, packages starting with `react-native`, `@react-native`, `expo`, and `@expo`
19
+ * are already included.
20
+ *
21
+ * @example ['my-react-native-library']
22
+ */
23
+ modulesToTranspile?: string[];
24
+ /**
25
+ * The JSX runtime to use.
26
+ * - 'automatic': Uses the new JSX transform (React 17+)
27
+ * - 'classic': Uses React.createElement
28
+ * @default 'automatic'
29
+ */
30
+ jsxRuntime?: 'automatic' | 'classic';
31
+ /**
32
+ * The source for JSX imports when using the automatic runtime.
33
+ * @default 'react'
34
+ * @example 'nativewind' for NativeWind v4+
35
+ */
36
+ jsxImportSource?: string;
37
+ /**
38
+ * Modules that should not be tree-shaken.
39
+ * Some React Native packages have side effects that may be incorrectly removed.
40
+ * @default ['react-native-css-interop', 'expo-modules-core']
41
+ */
42
+ noTreeshakeModules?: string[];
43
+ /**
44
+ * Absolute path to the react-native-web package directory.
45
+ * This is needed for pnpm/monorepo setups where react-native-reanimated
46
+ * cannot resolve react-native-web internal modules.
47
+ *
48
+ * If provided, the plugin will rewrite import paths in transformed code
49
+ * to use absolute paths, ensuring proper module resolution.
50
+ *
51
+ * @example '/path/to/node_modules/react-native-web'
52
+ */
53
+ reactNativeWebPath?: string;
54
+ }
55
+ /**
56
+ * Rsbuild plugin for React Native Web support.
57
+ * This plugin enables you to use React Native components in web applications.
58
+ */
59
+ declare function pluginReactNativeWeb(options?: PluginReactNativeWebOptions): RsbuildPlugin;
60
+
61
+ export { pluginReactNativeWeb };
62
+ export type { PluginReactNativeWebOptions, TransformResult };
package/dist/index.js ADDED
@@ -0,0 +1,228 @@
1
+ import CJS_COMPAT_NODE_URL_62a820a7ed2baf7c from 'node:url';
2
+ import CJS_COMPAT_NODE_PATH_62a820a7ed2baf7c from 'node:path';
3
+ import CJS_COMPAT_NODE_MODULE_62a820a7ed2baf7c from "node:module";
4
+
5
+ var __filename = CJS_COMPAT_NODE_URL_62a820a7ed2baf7c.fileURLToPath(import.meta.url);
6
+ var __dirname = CJS_COMPAT_NODE_PATH_62a820a7ed2baf7c.dirname(__filename);
7
+ var require = CJS_COMPAT_NODE_MODULE_62a820a7ed2baf7c.createRequire(import.meta.url);
8
+
9
+ // ------------------------------------------------------------
10
+ // end of CJS compatibility banner, injected by Storybook's esbuild configuration
11
+ // ------------------------------------------------------------
12
+
13
+ // src/index.ts
14
+ import { normalize as normalize2 } from "pathe";
15
+
16
+ // src/transforms.ts
17
+ import MagicString from "magic-string";
18
+ import { normalize } from "pathe";
19
+ function analyzeRequireStatements(originalCode, exportedVars) {
20
+ let importMap = /* @__PURE__ */ new Map();
21
+ for (let varName of exportedVars) {
22
+ let requireMatch = new RegExp(
23
+ `${varName}\\s*=\\s*[\\s\\S]*?require\\(['"]([^'"]+)['"]\\)(?:\\.([\\w]+))?`,
24
+ "g"
25
+ ).exec(originalCode);
26
+ if (requireMatch) {
27
+ let [, modulePath, prop] = requireMatch, key = `${modulePath}:${prop || "default"}`;
28
+ importMap.has(key) || importMap.set(key, []), importMap.get(key).push(varName);
29
+ }
30
+ }
31
+ return importMap;
32
+ }
33
+ function generateDirectExports(importMap, resolveModule) {
34
+ let exports = "";
35
+ for (let [key, vars] of importMap) {
36
+ let [modulePath, prop] = key.split(":"), resolvedPath = resolveModule ? resolveModule(modulePath) : modulePath;
37
+ if (prop === "default")
38
+ for (let varName of vars)
39
+ exports += `export { default as ${varName} } from '${resolvedPath}';
40
+ `;
41
+ else
42
+ for (let varName of vars)
43
+ exports += `export { ${prop} as ${varName} } from '${resolvedPath}';
44
+ `;
45
+ }
46
+ return exports;
47
+ }
48
+ function transformReanimatedWebUtils(code, id, _isProduction, opts) {
49
+ let normalizedId = normalize(id);
50
+ if (!(normalizedId.includes("react-native-reanimated") && normalizedId.includes("node_modules")) || !normalizedId.includes("ReanimatedModule/js-reanimated/webUtils") || !code.includes("export let") || !code.includes("try") || !code.includes("require"))
51
+ return { code, map: null, changed: !1 };
52
+ let exportedVars = Array.from(code.matchAll(/export let (\w+);/g)).map((match) => match[1]);
53
+ if (exportedVars.length === 0)
54
+ return { code, map: null, changed: !1 };
55
+ let importMap = analyzeRequireStatements(code, exportedVars), ms = new MagicString(code), changed = !1;
56
+ for (let m of code.matchAll(
57
+ /try\s*\{[^{}]*?require\([^)]+\)[^{}]*?\}\s*catch[^{}]*?\{[^{}]*?\}/gs
58
+ )) {
59
+ let start = m.index, end = start + m[0].length;
60
+ ms.remove(start, end), changed = !0;
61
+ }
62
+ for (let m of code.matchAll(/export let \w+;/g)) {
63
+ let start = m.index, end = start + m[0].length;
64
+ ms.remove(start, end), changed = !0;
65
+ }
66
+ let exports = generateDirectExports(importMap, opts?.resolveModule);
67
+ if (exports && (ms.append(`
68
+ ${exports}`), changed = !0), !changed)
69
+ return { code, map: null, changed: !1 };
70
+ let resultCode = ms.toString(), map = ms.generateMap({
71
+ source: opts?.source ?? id.split("?")[0],
72
+ includeContent: !0,
73
+ hires: !0
74
+ });
75
+ return { code: resultCode, map, changed: !0 };
76
+ }
77
+ function transformCssInteropDoctorCheck(code, id, opts) {
78
+ if (!normalize(id).includes(
79
+ "node_modules/react-native-css-interop/dist/doctor.js"
80
+ ))
81
+ return { code, map: null, changed: !1 };
82
+ let pattern = /return\s*<react-native-css-interop-jsx-pragma-check\s*\/>\s*===\s*true\s*;/g;
83
+ if (!pattern.test(code))
84
+ return { code, map: null, changed: !1 };
85
+ let ms = new MagicString(code), match;
86
+ for (pattern.lastIndex = 0; (match = pattern.exec(code)) !== null; ) {
87
+ let start = match.index, end = start + match[0].length;
88
+ ms.overwrite(start, end, "return true;");
89
+ }
90
+ let resultCode = ms.toString(), map = ms.generateMap({
91
+ source: opts?.source ?? id.split("?")[0],
92
+ includeContent: !0,
93
+ hires: !0
94
+ });
95
+ return { code: resultCode, map, changed: !0 };
96
+ }
97
+
98
+ // src/index.ts
99
+ var webExtensions = [
100
+ ".web.mjs",
101
+ ".web.js",
102
+ ".web.jsx",
103
+ ".web.ts",
104
+ ".web.tsx",
105
+ ".mjs",
106
+ ".js",
107
+ ".jsx",
108
+ ".ts",
109
+ ".tsx",
110
+ ".json"
111
+ ], DEFAULT_MODULES_TO_TRANSPILE = [
112
+ "react-native",
113
+ "@react-native",
114
+ "expo",
115
+ "@expo"
116
+ ], DEFAULT_NO_TREESHAKE_MODULES = [
117
+ "react-native-css-interop",
118
+ "react-native-css",
119
+ "expo-modules-core"
120
+ ];
121
+ function createTranspileIncludePattern(modules) {
122
+ let escapedModules = modules.map(
123
+ (m) => m.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
124
+ );
125
+ return new RegExp(`node_modules[\\\\/](${escapedModules.join("|")})`);
126
+ }
127
+ function pluginReactNativeWeb(options = {}) {
128
+ let modulesToTranspile = [
129
+ .../* @__PURE__ */ new Set([
130
+ ...DEFAULT_MODULES_TO_TRANSPILE,
131
+ ...options.modulesToTranspile || []
132
+ ])
133
+ ], includePattern = createTranspileIncludePattern(modulesToTranspile), jsxRuntime = options.jsxRuntime ?? "automatic", jsxImportSource = options.jsxImportSource, noTreeshakeModules = [
134
+ .../* @__PURE__ */ new Set([
135
+ ...DEFAULT_NO_TREESHAKE_MODULES,
136
+ ...options.noTreeshakeModules || []
137
+ ])
138
+ ], noTreeshakePattern = createTranspileIncludePattern(noTreeshakeModules), reactNativeWebPath = options.reactNativeWebPath;
139
+ function createModuleResolver() {
140
+ if (!reactNativeWebPath)
141
+ return;
142
+ let normalizedReactNativeWebPath = normalize2(reactNativeWebPath);
143
+ return (modulePath) => {
144
+ if (modulePath.startsWith("react-native-web/")) {
145
+ let relativePart = modulePath.slice(17);
146
+ return `${normalizedReactNativeWebPath}/${relativePart}`;
147
+ }
148
+ return modulePath;
149
+ };
150
+ }
151
+ return {
152
+ name: "rsbuild:react-native-web",
153
+ setup(api) {
154
+ let isProduction = !1;
155
+ api.modifyRsbuildConfig((config) => (config.source ??= {}, config.source.define = {
156
+ ...config.source.define,
157
+ __DEV__: JSON.stringify(process.env.NODE_ENV !== "production"),
158
+ "process.env.NODE_ENV": JSON.stringify(
159
+ process.env.NODE_ENV || "development"
160
+ ),
161
+ EXPO_OS: JSON.stringify("web"),
162
+ "process.env.EXPO_OS": JSON.stringify("web"),
163
+ _WORKLET: "false",
164
+ _frameTimestamp: "undefined",
165
+ "global.Error": "Error",
166
+ "global.__x": "{}"
167
+ }, config.resolve ??= {}, config.resolve.extensions = webExtensions, config.source.include = [
168
+ ...config.source.include || [],
169
+ includePattern
170
+ ], config)), api.onBeforeCreateCompiler(() => {
171
+ isProduction = api.context.bundlerType === "rspack" ? process.env.NODE_ENV === "production" : !1;
172
+ }), api.modifyBundlerChain((chain) => {
173
+ let jsRule = chain.module.rule("js");
174
+ jsRule && jsRule.include.add(includePattern);
175
+ let reactConfig = {
176
+ runtime: jsxRuntime
177
+ };
178
+ jsxImportSource && jsxRuntime === "automatic" && (reactConfig.importSource = jsxImportSource), chain.module.rule("react-native-jsx").test(/\.(js|mjs|jsx)$/).include.add(includePattern).end().use("swc").loader("builtin:swc-loader").options({
179
+ jsc: {
180
+ parser: {
181
+ syntax: "ecmascript",
182
+ jsx: !0
183
+ },
184
+ transform: {
185
+ react: reactConfig
186
+ }
187
+ }
188
+ }), chain.module.rule("react-native-no-treeshake").test(/\.(js|mjs|jsx|ts|tsx)$/).include.add(noTreeshakePattern).end().set("sideEffects", !0);
189
+ });
190
+ let moduleResolver = createModuleResolver();
191
+ api.transform(
192
+ {
193
+ test: /\.(js|mjs|jsx|ts|tsx)$/
194
+ },
195
+ ({ code, resource }) => {
196
+ let cssInteropResult = transformCssInteropDoctorCheck(
197
+ code,
198
+ resource,
199
+ {
200
+ source: resource
201
+ }
202
+ );
203
+ if (cssInteropResult.changed)
204
+ return {
205
+ code: cssInteropResult.code,
206
+ map: cssInteropResult.map ?? void 0
207
+ };
208
+ let reanimatedResult = transformReanimatedWebUtils(
209
+ code,
210
+ resource,
211
+ isProduction,
212
+ {
213
+ source: resource,
214
+ resolveModule: moduleResolver
215
+ }
216
+ );
217
+ return reanimatedResult.changed ? {
218
+ code: reanimatedResult.code,
219
+ map: reanimatedResult.map ?? void 0
220
+ } : { code };
221
+ }
222
+ );
223
+ }
224
+ };
225
+ }
226
+ export {
227
+ pluginReactNativeWeb
228
+ };
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "rsbuild-plugin-react-native-web",
3
+ "version": "3.2.0",
4
+ "description": "Rsbuild plugin for React Native Web support",
5
+ "keywords": [
6
+ "rsbuild",
7
+ "rspack",
8
+ "react-native",
9
+ "react-native-web",
10
+ "expo"
11
+ ],
12
+ "homepage": "https://github.com/rspack-contrib/storybook-rsbuild",
13
+ "bugs": {
14
+ "url": "https://github.com/rspack-contrib/storybook-rsbuild/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/rspack-contrib/storybook-rsbuild",
19
+ "directory": "packages/rsbuild-plugin-react-native-web"
20
+ },
21
+ "license": "MIT",
22
+ "type": "module",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "dist/**/*",
32
+ "README.md",
33
+ "*.js",
34
+ "*.d.ts",
35
+ "!src/**/*"
36
+ ],
37
+ "scripts": {
38
+ "build": "pnpm run prep --production",
39
+ "check": "jiti ../../scripts/check/check-package.ts",
40
+ "prep": "jiti ../../scripts/build/build-package.ts",
41
+ "prepare": "pnpm run build"
42
+ },
43
+ "dependencies": {
44
+ "magic-string": "^0.30.21",
45
+ "pathe": "^2.0.3"
46
+ },
47
+ "devDependencies": {
48
+ "@rsbuild/core": "^1.6.13",
49
+ "@types/node": "^22.0.0",
50
+ "typescript": "^5.9.3"
51
+ },
52
+ "peerDependencies": {
53
+ "@rsbuild/core": "^1.5.0"
54
+ },
55
+ "engines": {
56
+ "node": ">=18.0.0"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ },
61
+ "bundler": {
62
+ "entries": [
63
+ "./src/index.ts"
64
+ ],
65
+ "platform": "node"
66
+ }
67
+ }