@forgebook/react-native-web 0.0.0-stage → 0.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 +21 -0
- package/README.md +190 -2
- package/dist/src/mountFrame.d.ts +15 -0
- package/dist/src/mountFrame.js +16 -0
- package/dist/src/nativeFrameRenderer.d.ts +5 -0
- package/dist/src/nativeFrameRenderer.js +54 -0
- package/dist/src/nativeRenderer.d.ts +3 -0
- package/dist/src/nativeRenderer.js +16 -0
- package/dist/src/stories.d.ts +26 -0
- package/dist/src/stories.js +0 -0
- package/dist/src/vite/assetRequires.d.ts +24 -0
- package/dist/src/vite/assetRequires.js +141 -0
- package/dist/src/vite/babelPlugins.d.ts +24 -0
- package/dist/src/vite/babelPlugins.js +35 -0
- package/dist/src/vite/extractDocs.d.ts +7 -0
- package/dist/src/vite/extractDocs.js +17 -0
- package/dist/src/vite/forgebookFiles.d.ts +6 -0
- package/dist/src/vite/forgebookFiles.js +51 -0
- package/dist/src/vite/nativePlugin.d.ts +49 -0
- package/dist/src/vite/nativePlugin.js +213 -0
- package/dist/src/vite/nativeSources.d.ts +75 -0
- package/dist/src/vite/nativeSources.js +150 -0
- package/dist/src/vite/projectPostcss.d.ts +19 -0
- package/dist/src/vite/projectPostcss.js +122 -0
- package/dist/src/vite/reactNativeWeb.d.ts +53 -0
- package/dist/src/vite/reactNativeWeb.js +65 -0
- package/package.json +107 -3
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a file is Forgebook's own: a module of the catalogue (its shell, its frame, this package) that a React Native
|
|
3
|
+
* project's compiling must leave alone, wherever it is installed.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* A file outside `node_modules` is taken for the project's own, so React Native's compiling (JSX in `.js`, the JSX
|
|
7
|
+
* import source, `require` of assets) reaches a monorepo's linked packages. Forgebook's own packages can be linked the
|
|
8
|
+
* same way (a monorepo that develops a catalogue beside it, or this repository): they are told by the name of the
|
|
9
|
+
* package that holds them, `forgebook` or `@forgebook/…`, read once per folder.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
/** The name of the package each folder is in, by folder; none for a folder outside any package. */
|
|
16
|
+
const packageNames = new Map();
|
|
17
|
+
/**
|
|
18
|
+
* The name of the package a folder is in: its own `package.json`'s, or its parent's.
|
|
19
|
+
*
|
|
20
|
+
* @param dir - the folder, absolute.
|
|
21
|
+
*/
|
|
22
|
+
const packageNameOf = (dir) => {
|
|
23
|
+
if (packageNames.has(dir))
|
|
24
|
+
return packageNames.get(dir);
|
|
25
|
+
let name;
|
|
26
|
+
try {
|
|
27
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
28
|
+
name =
|
|
29
|
+
typeof manifest === "object" &&
|
|
30
|
+
manifest !== null &&
|
|
31
|
+
"name" in manifest &&
|
|
32
|
+
typeof manifest.name === "string"
|
|
33
|
+
? manifest.name
|
|
34
|
+
: "";
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
const parent = dirname(dir);
|
|
38
|
+
name = parent === dir ? undefined : packageNameOf(parent);
|
|
39
|
+
}
|
|
40
|
+
packageNames.set(dir, name);
|
|
41
|
+
return name;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Whether a file is in one of Forgebook's packages.
|
|
45
|
+
*
|
|
46
|
+
* @param file - the file's path, absolute, its query left out.
|
|
47
|
+
*/
|
|
48
|
+
export const isForgebookFile = (file) => {
|
|
49
|
+
const name = packageNameOf(dirname(file));
|
|
50
|
+
return name !== undefined && (name === "forgebook" || name.startsWith("@forgebook/"));
|
|
51
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Vite plugins that let a React Native project's components run in the browser: `react-native` resolved to
|
|
3
|
+
* `react-native-web`, the web's files first, React Native's globals, JSX in `.js` files and Flow types compiled, and
|
|
4
|
+
* `require` of an image or a font made its address.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Metro, React Native's bundler, does all of it for a phone; here Vite does it for the catalogue's frames. Three
|
|
8
|
+
* places read the project's code, and each compiles the same files (`isNativeSource`), so what draws on the dev
|
|
9
|
+
* server draws in a built catalogue and reads the same on the Node side:
|
|
10
|
+
*
|
|
11
|
+
* - the dev server's dependency pre-bundling (Rolldown, given a plugin of its own here);
|
|
12
|
+
* - Vite's own pipeline: the project's files on the dev server, everything in a build;
|
|
13
|
+
* - the dev server's Node side, where the agents' answers load the stories. Node itself would load a package Vite
|
|
14
|
+
* leaves external, with the real `react-native` (Flow, native modules), so the packages written for React Native go
|
|
15
|
+
* through Vite there too, pre-bundled as in the browser: they mix CommonJS files into ES modules, which only a bundle
|
|
16
|
+
* runs on that side.
|
|
17
|
+
*
|
|
18
|
+
* A deep import of React Native's own files (`react-native/Libraries/…`) has no web version: it fails with a message
|
|
19
|
+
* naming the module, instead of a Flow syntax error deep inside React Native.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { type DepOptimizationOptions, type Plugin, type Rolldown, type UserConfig } from "vite";
|
|
24
|
+
import type { ReactNativeWebOptions } from "./reactNativeWeb.ts";
|
|
25
|
+
/**
|
|
26
|
+
* The plugin the dependency pre-bundling runs: the dependencies written for React Native compiled as Metro compiles
|
|
27
|
+
* them, their `.js` files read as JSX once their Flow types are gone, and their `require` of an asset made its
|
|
28
|
+
* address, as in a build (the pre-bundling would make it a module object, which `Image` cannot draw).
|
|
29
|
+
*
|
|
30
|
+
* @param nativePackages - the other packages written for React Native, by name.
|
|
31
|
+
*/
|
|
32
|
+
export declare const prebundlePlugin: (nativePackages: readonly string[]) => Rolldown.Plugin;
|
|
33
|
+
/**
|
|
34
|
+
* What the plugin adds to Vite's configuration.
|
|
35
|
+
*
|
|
36
|
+
* @param isDevelopment - whether the catalogue runs on the dev server.
|
|
37
|
+
* @param options - the adapter's options: the other packages written for React Native, the JSX import source.
|
|
38
|
+
* @param entries - the files the dev server scans for the dependencies to pre-bundle (the story files, as Forgebook's
|
|
39
|
+
* plugin names them for the browser), scanned on the Node side too.
|
|
40
|
+
*/
|
|
41
|
+
export declare const nativeConfig: (isDevelopment: boolean, options: ReactNativeWebOptions, entries: DepOptimizationOptions["entries"]) => UserConfig;
|
|
42
|
+
/**
|
|
43
|
+
* The plugins.
|
|
44
|
+
*
|
|
45
|
+
* @param options - the packages written for React Native beyond React Native's and Expo's families.
|
|
46
|
+
* @returns the plugin that sets Vite up and compiles React Native's code, and the one that writes `require` of an
|
|
47
|
+
* asset as its import (after Vite's own compiling, on plain JavaScript).
|
|
48
|
+
*/
|
|
49
|
+
export declare const nativePlugins: (options: ReactNativeWebOptions) => Plugin[];
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Vite plugins that let a React Native project's components run in the browser: `react-native` resolved to
|
|
3
|
+
* `react-native-web`, the web's files first, React Native's globals, JSX in `.js` files and Flow types compiled, and
|
|
4
|
+
* `require` of an image or a font made its address.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Metro, React Native's bundler, does all of it for a phone; here Vite does it for the catalogue's frames. Three
|
|
8
|
+
* places read the project's code, and each compiles the same files (`isNativeSource`), so what draws on the dev
|
|
9
|
+
* server draws in a built catalogue and reads the same on the Node side:
|
|
10
|
+
*
|
|
11
|
+
* - the dev server's dependency pre-bundling (Rolldown, given a plugin of its own here);
|
|
12
|
+
* - Vite's own pipeline: the project's files on the dev server, everything in a build;
|
|
13
|
+
* - the dev server's Node side, where the agents' answers load the stories. Node itself would load a package Vite
|
|
14
|
+
* leaves external, with the real `react-native` (Flow, native modules), so the packages written for React Native go
|
|
15
|
+
* through Vite there too, pre-bundled as in the browser: they mix CommonJS files into ES modules, which only a bundle
|
|
16
|
+
* runs on that side.
|
|
17
|
+
*
|
|
18
|
+
* A deep import of React Native's own files (`react-native/Libraries/…`) has no web version: it fails with a message
|
|
19
|
+
* naming the module, instead of a Flow syntax error deep inside React Native.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { parseAst, transformWithOxc } from "vite";
|
|
24
|
+
import { importedAssets } from "./assetRequires.js";
|
|
25
|
+
import { isForgebookFile } from "./forgebookFiles.js";
|
|
26
|
+
import { isNativeSource, NATIVE_SCRIPT, nativeDefines, nativePackagePattern, serverDefines, WEB_EXTENSIONS, withoutFlow } from "./nativeSources.js";
|
|
27
|
+
/** React Native's own files, imported by path: Metro's internals, which have no web version. */
|
|
28
|
+
const DEEP_NATIVE_IMPORT = /^react-native\/(.+)$/;
|
|
29
|
+
/** A script that may hold JSX besides `.js`: the JSX import source is named in it. */
|
|
30
|
+
const JSX_SCRIPT = /\.[jt]sx(?:\?|$)/;
|
|
31
|
+
/** A pragma that names a file's JSX import source already. */
|
|
32
|
+
const JSX_PRAGMA = /@jsxImportSource\s/;
|
|
33
|
+
/** The scripts a `require` of an asset is looked for in: every kind Vite compiles. */
|
|
34
|
+
const SCRIPT_FILE = /\.(?:[cm]?[jt]sx?)(?:\?|$)/;
|
|
35
|
+
/**
|
|
36
|
+
* A module's path without its query (`?v=…`, `?import`).
|
|
37
|
+
*
|
|
38
|
+
* @param id - the module's id.
|
|
39
|
+
*/
|
|
40
|
+
const withoutQuery = (id) => id.split("?", 1)[0] ?? id;
|
|
41
|
+
/**
|
|
42
|
+
* The plugin the dependency pre-bundling runs: the dependencies written for React Native compiled as Metro compiles
|
|
43
|
+
* them, their `.js` files read as JSX once their Flow types are gone, and their `require` of an asset made its
|
|
44
|
+
* address, as in a build (the pre-bundling would make it a module object, which `Image` cannot draw).
|
|
45
|
+
*
|
|
46
|
+
* @param nativePackages - the other packages written for React Native, by name.
|
|
47
|
+
*/
|
|
48
|
+
export const prebundlePlugin = (nativePackages) => ({
|
|
49
|
+
name: "forgebook:react-native-web-prebundle",
|
|
50
|
+
transform: {
|
|
51
|
+
filter: { id: NATIVE_SCRIPT },
|
|
52
|
+
handler: async (code, id) => {
|
|
53
|
+
if (!isNativeSource(withoutQuery(id), nativePackages))
|
|
54
|
+
return null;
|
|
55
|
+
const plain = await withoutFlow(code);
|
|
56
|
+
const withAssets = plain.includes("require")
|
|
57
|
+
? importedAssets(plain, parseAst(plain, { lang: "jsx" }))
|
|
58
|
+
: undefined;
|
|
59
|
+
return { code: withAssets ?? plain, moduleType: "jsx" };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
/**
|
|
64
|
+
* The packages written for React Native that a catalogue names, with its JSX import source's: NativeWind's runtime
|
|
65
|
+
* imports `react-native` as the other packages do.
|
|
66
|
+
*
|
|
67
|
+
* @param options - the adapter's options.
|
|
68
|
+
*/
|
|
69
|
+
const nativePackagesOf = (options) => [
|
|
70
|
+
...(options.nativePackages ?? []),
|
|
71
|
+
...(options.jsxImportSource === undefined || options.jsxImportSource === "react"
|
|
72
|
+
? []
|
|
73
|
+
: [options.jsxImportSource])
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* How JSX is compiled: React's automatic runtime, from the catalogue's import source (NativeWind's) when it names one.
|
|
77
|
+
*
|
|
78
|
+
* @param options - the adapter's options.
|
|
79
|
+
*/
|
|
80
|
+
const jsxOptions = (options) => ({
|
|
81
|
+
runtime: "automatic",
|
|
82
|
+
...(options.jsxImportSource === undefined ? {} : { importSource: options.jsxImportSource })
|
|
83
|
+
});
|
|
84
|
+
/**
|
|
85
|
+
* What the plugin adds to Vite's configuration.
|
|
86
|
+
*
|
|
87
|
+
* @param isDevelopment - whether the catalogue runs on the dev server.
|
|
88
|
+
* @param options - the adapter's options: the other packages written for React Native, the JSX import source.
|
|
89
|
+
* @param entries - the files the dev server scans for the dependencies to pre-bundle (the story files, as Forgebook's
|
|
90
|
+
* plugin names them for the browser), scanned on the Node side too.
|
|
91
|
+
*/
|
|
92
|
+
export const nativeConfig = (isDevelopment, options, entries) => {
|
|
93
|
+
const nativePackages = nativePackagesOf(options);
|
|
94
|
+
const define = nativeDefines(isDevelopment);
|
|
95
|
+
/**
|
|
96
|
+
* The pre-bundling's options, with the globals it defines.
|
|
97
|
+
*
|
|
98
|
+
* @param defines - the globals.
|
|
99
|
+
*/
|
|
100
|
+
const prebundling = (defines) => ({
|
|
101
|
+
rolldownOptions: {
|
|
102
|
+
// Expo's packages are compiled from TypeScript one file at a time, so an import of a type is left as an import
|
|
103
|
+
// of a module that exports nothing (`expo-modules-core`'s declarations): Metro lets it be `undefined`, as here
|
|
104
|
+
shimMissingExports: true,
|
|
105
|
+
resolve: { extensions: [...WEB_EXTENSIONS] },
|
|
106
|
+
transform: { define: { ...defines }, jsx: jsxOptions(options) },
|
|
107
|
+
plugins: [prebundlePlugin(nativePackages)]
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
resolve: {
|
|
112
|
+
// exactly `react-native`: `react-native-web`, `react-native-svg` and the others keep their own name
|
|
113
|
+
alias: [{ find: /^react-native$/, replacement: "react-native-web" }],
|
|
114
|
+
extensions: [...WEB_EXTENSIONS]
|
|
115
|
+
},
|
|
116
|
+
define,
|
|
117
|
+
build: { rolldownOptions: { shimMissingExports: true } },
|
|
118
|
+
optimizeDeps: { ...prebundling(define), include: ["react-native-web"] },
|
|
119
|
+
environments: {
|
|
120
|
+
ssr: {
|
|
121
|
+
resolve: { noExternal: [nativePackagePattern(nativePackages), "react-native-web"] },
|
|
122
|
+
// scanned up front as the browser's are, so no answer meets a dependency the bundle lacks and fails while the
|
|
123
|
+
// bundle is made again
|
|
124
|
+
optimizeDeps: {
|
|
125
|
+
...prebundling(serverDefines(isDevelopment)),
|
|
126
|
+
noDiscovery: false,
|
|
127
|
+
include: ["react-native-web"],
|
|
128
|
+
...(entries === undefined ? {} : { entries })
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* The plugins.
|
|
136
|
+
*
|
|
137
|
+
* @param options - the packages written for React Native beyond React Native's and Expo's families.
|
|
138
|
+
* @returns the plugin that sets Vite up and compiles React Native's code, and the one that writes `require` of an
|
|
139
|
+
* asset as its import (after Vite's own compiling, on plain JavaScript).
|
|
140
|
+
*/
|
|
141
|
+
export const nativePlugins = (options) => {
|
|
142
|
+
const nativePackages = nativePackagesOf(options);
|
|
143
|
+
// the project's own files and the packages written for React Native, never Forgebook's own modules
|
|
144
|
+
const isProjectSource = (id) => {
|
|
145
|
+
const file = withoutQuery(id);
|
|
146
|
+
return isNativeSource(file, nativePackages) && !isForgebookFile(file);
|
|
147
|
+
};
|
|
148
|
+
const { jsxImportSource } = options;
|
|
149
|
+
let isDevelopment = true;
|
|
150
|
+
return [
|
|
151
|
+
{
|
|
152
|
+
name: "forgebook:react-native-web",
|
|
153
|
+
enforce: "pre",
|
|
154
|
+
config: (config, environment) => {
|
|
155
|
+
isDevelopment = environment.command === "serve";
|
|
156
|
+
return nativeConfig(isDevelopment, options, config.optimizeDeps?.entries);
|
|
157
|
+
},
|
|
158
|
+
resolveId: {
|
|
159
|
+
filter: { id: DEEP_NATIVE_IMPORT },
|
|
160
|
+
handler(source, importer) {
|
|
161
|
+
const from = importer === undefined ? "" : ` (imported by ${withoutQuery(importer)})`;
|
|
162
|
+
this.error(`${source}${from} is one of React Native's own files, which have no web version: ` +
|
|
163
|
+
"only `react-native` itself runs in the browser, as react-native-web");
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
// React Native writes JSX in `.js` files, which Vite reads as plain JavaScript: compiled before Vite reads them
|
|
167
|
+
transform: {
|
|
168
|
+
filter: { id: NATIVE_SCRIPT },
|
|
169
|
+
handler: async (code, id) => {
|
|
170
|
+
const file = withoutQuery(id);
|
|
171
|
+
if (!isProjectSource(file))
|
|
172
|
+
return null;
|
|
173
|
+
const compiled = await transformWithOxc(await withoutFlow(code), file, {
|
|
174
|
+
lang: "jsx",
|
|
175
|
+
jsx: { ...jsxOptions(options), development: isDevelopment },
|
|
176
|
+
sourcemap: true
|
|
177
|
+
});
|
|
178
|
+
return { code: compiled.code, map: compiled.map ?? null };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
// the JSX import source (NativeWind's) is the project's, not Forgebook's own pages': named in each of its files,
|
|
184
|
+
// on their first line so the others keep their place
|
|
185
|
+
name: "forgebook:react-native-web-jsx-source",
|
|
186
|
+
enforce: "pre",
|
|
187
|
+
transform: {
|
|
188
|
+
filter: { id: JSX_SCRIPT },
|
|
189
|
+
handler: (code, id) => {
|
|
190
|
+
// a file run as a program starts with its interpreter's line, before which nothing may go
|
|
191
|
+
if (jsxImportSource === undefined || code.startsWith("#!") || JSX_PRAGMA.test(code))
|
|
192
|
+
return null;
|
|
193
|
+
if (!isProjectSource(id))
|
|
194
|
+
return null;
|
|
195
|
+
return { code: `/** @jsxImportSource ${jsxImportSource} */ ${code}`, map: null };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "forgebook:react-native-web-assets",
|
|
201
|
+
transform: {
|
|
202
|
+
filter: { id: SCRIPT_FILE, code: "require" },
|
|
203
|
+
handler(code, id) {
|
|
204
|
+
if (!isProjectSource(id))
|
|
205
|
+
return null;
|
|
206
|
+
const rewritten = importedAssets(code, this.parse(code));
|
|
207
|
+
// the module keeps its lines and columns, so its source map stays as it is
|
|
208
|
+
return rewritten === undefined ? null : { code: rewritten, map: null };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
];
|
|
213
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a React Native project's code needs before the browser can run it: which files are written as React Native
|
|
3
|
+
* writes them (JSX in `.js`, Flow types), the extensions a web build resolves first, the globals React Native's code
|
|
4
|
+
* reads. Pure: the plugin (`nativePlugin.ts`) applies it, the tests read it.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Metro, React Native's bundler, compiles every file with Babel, so React Native code and many of its libraries ship
|
|
8
|
+
* JSX in `.js` files and Flow types: a web bundler reads neither. Those files are compiled here: the project's own
|
|
9
|
+
* `.js` files, and the packages written for React Native: those of React Native's and Expo's families
|
|
10
|
+
* (`react-native-*`, `@react-native/*`, `expo-*`, `@expo/*`) and any the catalogue names. Every other package in
|
|
11
|
+
* `node_modules` is taken as the plain JavaScript its publisher ships, as Vite takes it.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The extensions a module is looked for with, the web's first: `Button.web.tsx` wins over `Button.tsx` and over
|
|
17
|
+
* `Button.native.tsx`, which is never read, as Metro does for the web platform.
|
|
18
|
+
*/
|
|
19
|
+
export declare const WEB_EXTENSIONS: readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* The packages written for React Native, as a pattern that matches their names and the modules inside them
|
|
22
|
+
* (`@expo/vector-icons/Ionicons`): the families, and the packages the catalogue names.
|
|
23
|
+
*
|
|
24
|
+
* @param nativePackages - the other packages written for React Native, by name.
|
|
25
|
+
*/
|
|
26
|
+
export declare const nativePackagePattern: (nativePackages: readonly string[]) => RegExp;
|
|
27
|
+
/** A script React Native writes JSX in: the `.js` and `.mjs` files a web bundler reads as plain JavaScript. */
|
|
28
|
+
export declare const NATIVE_SCRIPT: RegExp;
|
|
29
|
+
/**
|
|
30
|
+
* The package a file belongs to, from its path: the name after its last `node_modules`, so a package pnpm stores in
|
|
31
|
+
* `node_modules/.pnpm/…/node_modules/<name>` is named too.
|
|
32
|
+
*
|
|
33
|
+
* @param file - the file's path, with `/` separators.
|
|
34
|
+
* @returns the package's name, or none for a file of the project's own.
|
|
35
|
+
*/
|
|
36
|
+
export declare const packageOf: (file: string) => string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a package is written for React Native: one of React Native's or Expo's families, or one the catalogue names.
|
|
39
|
+
*
|
|
40
|
+
* @param name - the package's name.
|
|
41
|
+
* @param nativePackages - the other packages written for React Native, by name.
|
|
42
|
+
*/
|
|
43
|
+
export declare const isNativePackage: (name: string, nativePackages: readonly string[]) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Whether a file is compiled as React Native's code: one of the project's own (a linked package of a monorepo among
|
|
46
|
+
* them), or one of a package written for React Native.
|
|
47
|
+
*
|
|
48
|
+
* @param file - the file's path, its query left out.
|
|
49
|
+
* @param nativePackages - the other packages written for React Native, by name.
|
|
50
|
+
*/
|
|
51
|
+
export declare const isNativeSource: (file: string, nativePackages: readonly string[]) => boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The globals React Native's code reads, as the web build gives them: Metro defines `__DEV__`, Expo's Babel preset
|
|
54
|
+
* `process.env.EXPO_OS`, and React Native's runtime names the global object `global`.
|
|
55
|
+
*
|
|
56
|
+
* @param isDevelopment - whether the catalogue runs in development (the dev server) or is built.
|
|
57
|
+
* @returns the definitions, each value a JavaScript expression.
|
|
58
|
+
*/
|
|
59
|
+
export declare const nativeDefines: (isDevelopment: boolean) => Readonly<Record<string, string>>;
|
|
60
|
+
/**
|
|
61
|
+
* The globals React Native's code reads on the dev server's Node side, where the agents' answers load the stories:
|
|
62
|
+
* the browser's, and the animation frames Node has none of. Reanimated asks for a frame as it loads; without one, the
|
|
63
|
+
* request throws outside any story and stops the dev server. A frame there is a timer's turn: nothing is drawn.
|
|
64
|
+
*
|
|
65
|
+
* @param isDevelopment - whether the catalogue runs in development.
|
|
66
|
+
*/
|
|
67
|
+
export declare const serverDefines: (isDevelopment: boolean) => Readonly<Record<string, string>>;
|
|
68
|
+
/**
|
|
69
|
+
* A file's code without its Flow types: blanked out in place, so its lines and columns, and the source map of what
|
|
70
|
+
* compiles it next, stay right. A file without a Flow pragma is returned as it is, and `flow-remove-types` is loaded
|
|
71
|
+
* the first time a file needs it: most projects never do.
|
|
72
|
+
*
|
|
73
|
+
* @param code - the file's code.
|
|
74
|
+
*/
|
|
75
|
+
export declare const withoutFlow: (code: string) => Promise<string>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a React Native project's code needs before the browser can run it: which files are written as React Native
|
|
3
|
+
* writes them (JSX in `.js`, Flow types), the extensions a web build resolves first, the globals React Native's code
|
|
4
|
+
* reads. Pure: the plugin (`nativePlugin.ts`) applies it, the tests read it.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Metro, React Native's bundler, compiles every file with Babel, so React Native code and many of its libraries ship
|
|
8
|
+
* JSX in `.js` files and Flow types: a web bundler reads neither. Those files are compiled here: the project's own
|
|
9
|
+
* `.js` files, and the packages written for React Native: those of React Native's and Expo's families
|
|
10
|
+
* (`react-native-*`, `@react-native/*`, `expo-*`, `@expo/*`) and any the catalogue names. Every other package in
|
|
11
|
+
* `node_modules` is taken as the plain JavaScript its publisher ships, as Vite takes it.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
16
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
17
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
18
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return path;
|
|
22
|
+
};
|
|
23
|
+
import { codeLiteral } from "forgebook/code-literal";
|
|
24
|
+
/**
|
|
25
|
+
* The extensions a module is looked for with, the web's first: `Button.web.tsx` wins over `Button.tsx` and over
|
|
26
|
+
* `Button.native.tsx`, which is never read, as Metro does for the web platform.
|
|
27
|
+
*/
|
|
28
|
+
export const WEB_EXTENSIONS = [
|
|
29
|
+
".web.tsx",
|
|
30
|
+
".web.ts",
|
|
31
|
+
".web.jsx",
|
|
32
|
+
".web.js",
|
|
33
|
+
".web.mjs",
|
|
34
|
+
".tsx",
|
|
35
|
+
".ts",
|
|
36
|
+
".jsx",
|
|
37
|
+
".js",
|
|
38
|
+
".mjs",
|
|
39
|
+
".mts",
|
|
40
|
+
".cjs",
|
|
41
|
+
".json"
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* The packages written for React Native, as a pattern of their names: React Native's family (`react-native-*`,
|
|
45
|
+
* `@react-native/*`) and Expo's (`expo`, `expo-*`, `@expo/*`). `react-native` itself is not among them, since it is
|
|
46
|
+
* never loaded (react-native-web stands for it), nor react-native-web, which ships plain JavaScript.
|
|
47
|
+
*/
|
|
48
|
+
const NATIVE_FAMILIES = "(?!react-native-web(?:/|$))(?:react-native-|@react-native/|expo(?:-|/|$)|@expo/)";
|
|
49
|
+
/**
|
|
50
|
+
* A text matched as it is in a regular expression.
|
|
51
|
+
*
|
|
52
|
+
* @param text - the text.
|
|
53
|
+
*/
|
|
54
|
+
const escapedPattern = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
55
|
+
/**
|
|
56
|
+
* The packages written for React Native, as a pattern that matches their names and the modules inside them
|
|
57
|
+
* (`@expo/vector-icons/Ionicons`): the families, and the packages the catalogue names.
|
|
58
|
+
*
|
|
59
|
+
* @param nativePackages - the other packages written for React Native, by name.
|
|
60
|
+
*/
|
|
61
|
+
export const nativePackagePattern = (nativePackages) => {
|
|
62
|
+
const named = nativePackages.map((name) => `|${escapedPattern(name)}(?:/|$)`).join("");
|
|
63
|
+
return new RegExp(`^(?:${NATIVE_FAMILIES}${named})`);
|
|
64
|
+
};
|
|
65
|
+
/** A script React Native writes JSX in: the `.js` and `.mjs` files a web bundler reads as plain JavaScript. */
|
|
66
|
+
export const NATIVE_SCRIPT = /\.m?js(?:\?|$)/;
|
|
67
|
+
/** The last `node_modules` of a path, and what follows it: the package's name and the file. */
|
|
68
|
+
const IN_PACKAGE = /\/node_modules\/((?:@[^/]+\/)?[^/]+)\//g;
|
|
69
|
+
/**
|
|
70
|
+
* The package a file belongs to, from its path: the name after its last `node_modules`, so a package pnpm stores in
|
|
71
|
+
* `node_modules/.pnpm/…/node_modules/<name>` is named too.
|
|
72
|
+
*
|
|
73
|
+
* @param file - the file's path, with `/` separators.
|
|
74
|
+
* @returns the package's name, or none for a file of the project's own.
|
|
75
|
+
*/
|
|
76
|
+
export const packageOf = (file) => {
|
|
77
|
+
const matches = [...file.split("\\").join("/").matchAll(IN_PACKAGE)];
|
|
78
|
+
return matches.at(-1)?.[1];
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Whether a package is written for React Native: one of React Native's or Expo's families, or one the catalogue names.
|
|
82
|
+
*
|
|
83
|
+
* @param name - the package's name.
|
|
84
|
+
* @param nativePackages - the other packages written for React Native, by name.
|
|
85
|
+
*/
|
|
86
|
+
export const isNativePackage = (name, nativePackages) => nativePackagePattern(nativePackages).test(name);
|
|
87
|
+
/**
|
|
88
|
+
* Whether a file is compiled as React Native's code: one of the project's own (a linked package of a monorepo among
|
|
89
|
+
* them), or one of a package written for React Native.
|
|
90
|
+
*
|
|
91
|
+
* @param file - the file's path, its query left out.
|
|
92
|
+
* @param nativePackages - the other packages written for React Native, by name.
|
|
93
|
+
*/
|
|
94
|
+
export const isNativeSource = (file, nativePackages) => {
|
|
95
|
+
const name = packageOf(file);
|
|
96
|
+
return name === undefined || isNativePackage(name, nativePackages);
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* The globals React Native's code reads, as the web build gives them: Metro defines `__DEV__`, Expo's Babel preset
|
|
100
|
+
* `process.env.EXPO_OS`, and React Native's runtime names the global object `global`.
|
|
101
|
+
*
|
|
102
|
+
* @param isDevelopment - whether the catalogue runs in development (the dev server) or is built.
|
|
103
|
+
* @returns the definitions, each value a JavaScript expression.
|
|
104
|
+
*/
|
|
105
|
+
export const nativeDefines = (isDevelopment) => ({
|
|
106
|
+
// biome-ignore lint/style/useNamingConvention: the global's name, as React Native's code reads it
|
|
107
|
+
__DEV__: codeLiteral(isDevelopment),
|
|
108
|
+
"process.env.EXPO_OS": codeLiteral("web"),
|
|
109
|
+
global: "globalThis"
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* The globals React Native's code reads on the dev server's Node side, where the agents' answers load the stories:
|
|
113
|
+
* the browser's, and the animation frames Node has none of. Reanimated asks for a frame as it loads; without one, the
|
|
114
|
+
* request throws outside any story and stops the dev server. A frame there is a timer's turn: nothing is drawn.
|
|
115
|
+
*
|
|
116
|
+
* @param isDevelopment - whether the catalogue runs in development.
|
|
117
|
+
*/
|
|
118
|
+
export const serverDefines = (isDevelopment) => ({
|
|
119
|
+
...nativeDefines(isDevelopment),
|
|
120
|
+
requestAnimationFrame: "globalThis.setTimeout",
|
|
121
|
+
cancelAnimationFrame: "globalThis.clearTimeout"
|
|
122
|
+
});
|
|
123
|
+
/** A pragma that marks a file as Flow's: `@flow`, or `@noflow`, which may still hold types. */
|
|
124
|
+
const FLOW_PRAGMA = /@(?:no)?flow\b/;
|
|
125
|
+
/** The package that strips Flow types: named by a variable, since it ships no types and is checked once loaded. */
|
|
126
|
+
const FLOW_REMOVE_TYPES = "flow-remove-types";
|
|
127
|
+
/**
|
|
128
|
+
* Whether a loaded module is `flow-remove-types`: its default export is a function.
|
|
129
|
+
*
|
|
130
|
+
* @param module - the module, as `import()` gave it.
|
|
131
|
+
*/
|
|
132
|
+
const isFlowRemoveTypes = (module) => typeof module === "object" &&
|
|
133
|
+
module !== null &&
|
|
134
|
+
"default" in module &&
|
|
135
|
+
typeof module.default === "function";
|
|
136
|
+
/**
|
|
137
|
+
* A file's code without its Flow types: blanked out in place, so its lines and columns, and the source map of what
|
|
138
|
+
* compiles it next, stay right. A file without a Flow pragma is returned as it is, and `flow-remove-types` is loaded
|
|
139
|
+
* the first time a file needs it: most projects never do.
|
|
140
|
+
*
|
|
141
|
+
* @param code - the file's code.
|
|
142
|
+
*/
|
|
143
|
+
export const withoutFlow = async (code) => {
|
|
144
|
+
if (!FLOW_PRAGMA.test(code))
|
|
145
|
+
return code;
|
|
146
|
+
const loaded = await import(__rewriteRelativeImportExtension(FLOW_REMOVE_TYPES));
|
|
147
|
+
if (!isFlowRemoveTypes(loaded))
|
|
148
|
+
throw new Error(`${FLOW_REMOVE_TYPES} has no default export to strip Flow types with`);
|
|
149
|
+
return loaded.default(code).toString();
|
|
150
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
/** A folder a style sheet's result depends on (Tailwind's content), with the files in it that count. */
|
|
3
|
+
type DirDependency = {
|
|
4
|
+
/** The folder, absolute. */
|
|
5
|
+
readonly dir: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The folders a PostCSS result depends on, from its messages.
|
|
9
|
+
*
|
|
10
|
+
* @param messages - the result's messages.
|
|
11
|
+
* @returns each folder, and each file, a result names.
|
|
12
|
+
*/
|
|
13
|
+
export declare const dependenciesOf: (messages: readonly unknown[]) => {
|
|
14
|
+
readonly dirs: DirDependency[];
|
|
15
|
+
readonly files: string[];
|
|
16
|
+
};
|
|
17
|
+
/** The plugin. */
|
|
18
|
+
export declare const projectPostcss: () => Plugin;
|
|
19
|
+
export {};
|