main
js 291 lines 8.41 KB
Raw
1 'use strict';
2
3 const {
4 existsSync,
5 readdirSync,
6 unlinkSync,
7 readFileSync,
8 writeFileSync,
9 } = require('fs');
10 const Bundles = require('./bundles');
11 const {
12 asyncCopyTo,
13 asyncExecuteCommand,
14 asyncExtractTar,
15 asyncRimRaf,
16 } = require('./utils');
17
18 const {
19 NODE_ES2015,
20 ESM_DEV,
21 ESM_PROD,
22 NODE_DEV,
23 NODE_PROD,
24 NODE_PROFILING,
25 BUN_DEV,
26 BUN_PROD,
27 FB_WWW_DEV,
28 FB_WWW_PROD,
29 FB_WWW_PROFILING,
30 RN_OSS_DEV,
31 RN_OSS_PROD,
32 RN_OSS_PROFILING,
33 RN_FB_DEV,
34 RN_FB_PROD,
35 RN_FB_PROFILING,
36 BROWSER_SCRIPT,
37 CJS_DTS,
38 ESM_DTS,
39 } = Bundles.bundleTypes;
40
41 function getPackageName(name) {
42 if (name.indexOf('/') !== -1) {
43 return name.split('/')[0];
44 }
45 return name;
46 }
47
48 function getBundleOutputPath(bundle, bundleType, filename, packageName) {
49 switch (bundleType) {
50 case NODE_ES2015:
51 return `build/node_modules/${packageName}/cjs/${filename}`;
52 case ESM_DEV:
53 case ESM_PROD:
54 case ESM_DTS:
55 return `build/node_modules/${packageName}/esm/${filename}`;
56 case BUN_DEV:
57 case BUN_PROD:
58 return `build/node_modules/${packageName}/cjs/${filename}`;
59 case NODE_DEV:
60 case NODE_PROD:
61 case NODE_PROFILING:
62 case CJS_DTS:
63 return `build/node_modules/${packageName}/cjs/${filename}`;
64 case FB_WWW_DEV:
65 case FB_WWW_PROD:
66 case FB_WWW_PROFILING:
67 return `build/facebook-www/${filename}`;
68 case RN_OSS_DEV:
69 case RN_OSS_PROD:
70 case RN_OSS_PROFILING:
71 switch (packageName) {
72 case 'react-native-renderer':
73 return `build/react-native/implementations/${filename}`;
74 default:
75 throw new Error('Unknown RN package.');
76 }
77 case RN_FB_DEV:
78 case RN_FB_PROD:
79 case RN_FB_PROFILING:
80 switch (packageName) {
81 case 'scheduler':
82 case 'react':
83 case 'react-dom':
84 case 'react-is':
85 case 'react-test-renderer':
86 return `build/facebook-react-native/${packageName}/cjs/${filename}`;
87 case 'react-native-renderer':
88 return `build/react-native/implementations/${filename.replace(
89 /\.js$/,
90 '.fb.js'
91 )}`;
92 default:
93 throw new Error('Unknown RN package.');
94 }
95 case BROWSER_SCRIPT: {
96 // Bundles that are served as browser scripts need to be able to be sent
97 // straight to the browser with any additional bundling. We shouldn't use
98 // a module to re-export. Depending on how they are served, they also may
99 // not go through package.json module resolution, so we shouldn't rely on
100 // that either. We should consider the output path as part of the public
101 // contract, and explicitly specify its location within the package's
102 // directory structure.
103 const outputPath = bundle.outputPath;
104 if (!outputPath) {
105 throw new Error(
106 'Bundles with type BROWSER_SCRIPT must specific an explicit ' +
107 'output path.'
108 );
109 }
110 return `build/node_modules/${packageName}/${outputPath}`;
111 }
112 default:
113 throw new Error('Unknown bundle type.');
114 }
115 }
116
117 async function copyWWWShims() {
118 await asyncCopyTo(
119 `${__dirname}/shims/facebook-www`,
120 'build/facebook-www/shims'
121 );
122 }
123
124 async function copyRNShims() {
125 await asyncCopyTo(
126 `${__dirname}/shims/react-native`,
127 'build/react-native/shims'
128 );
129 await asyncCopyTo(
130 require.resolve('react-native-renderer/src/ReactNativeTypes.js'),
131 'build/react-native/shims/ReactNativeTypes.js'
132 );
133 }
134
135 async function copyAllShims() {
136 await Promise.all([copyWWWShims(), copyRNShims()]);
137 }
138
139 function getTarOptions(tgzName, packageName) {
140 // Files inside the `npm pack`ed archive start
141 // with "package/" in their paths. We'll undo
142 // this during extraction.
143 const CONTENTS_FOLDER = 'package';
144 return {
145 src: tgzName,
146 dest: `build/node_modules/${packageName}`,
147 tar: {
148 entries: [CONTENTS_FOLDER],
149 map(header) {
150 if (header.name.indexOf(CONTENTS_FOLDER + '/') === 0) {
151 header.name = header.name.slice(CONTENTS_FOLDER.length + 1);
152 }
153 },
154 },
155 };
156 }
157
158 let entryPointsToHasBundle = new Map();
159 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
160 for (const bundle of Bundles.bundles) {
161 let hasBundle = entryPointsToHasBundle.get(bundle.entry);
162 if (!hasBundle) {
163 const hasNonFBBundleTypes = bundle.bundleTypes.some(
164 type =>
165 type !== FB_WWW_DEV && type !== FB_WWW_PROD && type !== FB_WWW_PROFILING
166 );
167 entryPointsToHasBundle.set(bundle.entry, hasNonFBBundleTypes);
168 }
169 }
170
171 function filterOutEntrypoints(name) {
172 // Remove entry point files that are not built in this configuration.
173 let jsonPath = `build/node_modules/${name}/package.json`;
174 let packageJSON = JSON.parse(readFileSync(jsonPath));
175 let files = packageJSON.files;
176 let exportsJSON = packageJSON.exports;
177 let browserJSON = packageJSON.browser;
178 if (!Array.isArray(files)) {
179 throw new Error('expected all package.json files to contain a files field');
180 }
181 let changed = false;
182 for (let i = 0; i < files.length; i++) {
183 let filename = files[i];
184 let entry =
185 filename === 'index.js'
186 ? name
187 : name + '/' + filename.replace(/\.js$/, '');
188 let hasBundle = entryPointsToHasBundle.get(entry);
189 if (hasBundle === undefined) {
190 // This entry doesn't exist in the bundles. Check if something similar exists.
191 hasBundle =
192 entryPointsToHasBundle.get(entry + '.node') ||
193 entryPointsToHasBundle.get(entry + '.browser');
194
195 // The .react-server and .rsc suffixes may not have a bundle representation but
196 // should infer their bundle status from the non-suffixed entry point.
197 if (entry.endsWith('.react-server')) {
198 hasBundle = entryPointsToHasBundle.get(
199 entry.slice(0, '.react-server'.length * -1)
200 );
201 } else if (entry.endsWith('.rsc')) {
202 hasBundle = entryPointsToHasBundle.get(
203 entry.slice(0, '.rsc'.length * -1)
204 );
205 }
206 }
207 if (hasBundle === undefined) {
208 // This doesn't exist in the bundles. It's an extra file.
209 } else if (hasBundle === true) {
210 // This is built in this release channel.
211 } else {
212 // This doesn't have any bundleTypes in this release channel.
213 // Let's remove it.
214 files.splice(i, 1);
215 i--;
216 try {
217 unlinkSync(`build/node_modules/${name}/${filename}`);
218 } catch (err) {
219 // If the file doesn't exist we can just move on. Otherwise throw the halt the build
220 if (err.code !== 'ENOENT') {
221 throw err;
222 }
223 }
224 changed = true;
225 // Remove it from the exports field too if it exists.
226 if (exportsJSON) {
227 if (filename === 'index.js') {
228 delete exportsJSON['.'];
229 } else {
230 delete exportsJSON['./' + filename.replace(/\.js$/, '')];
231 }
232 }
233 if (browserJSON) {
234 delete browserJSON['./' + filename];
235 }
236 }
237
238 // We only export the source directory so Jest and Rollup can access them
239 // during local development and at build time. The files don't exist in the
240 // public builds, so we don't need the export entry, either.
241 const sourceWildcardExport = './src/*';
242 if (exportsJSON && exportsJSON[sourceWildcardExport]) {
243 delete exportsJSON[sourceWildcardExport];
244 changed = true;
245 }
246 }
247 if (changed) {
248 let newJSON = JSON.stringify(packageJSON, null, ' ');
249 writeFileSync(jsonPath, newJSON);
250 }
251 }
252
253 async function prepareNpmPackage(name) {
254 await Promise.all([
255 asyncCopyTo('LICENSE', `build/node_modules/${name}/LICENSE`),
256 asyncCopyTo(
257 `packages/${name}/package.json`,
258 `build/node_modules/${name}/package.json`
259 ),
260 asyncCopyTo(
261 `packages/${name}/README.md`,
262 `build/node_modules/${name}/README.md`
263 ),
264 asyncCopyTo(`packages/${name}/npm`, `build/node_modules/${name}`),
265 ]);
266 filterOutEntrypoints(name);
267 const tgzName = (
268 await asyncExecuteCommand(`npm pack build/node_modules/${name}`)
269 ).trim();
270 await asyncRimRaf(`build/node_modules/${name}`);
271 await asyncExtractTar(getTarOptions(tgzName, name));
272 unlinkSync(tgzName);
273 }
274
275 async function prepareNpmPackages() {
276 if (!existsSync('build/node_modules')) {
277 // We didn't build any npm packages.
278 return;
279 }
280 const builtPackageFolders = readdirSync('build/node_modules').filter(
281 dir => dir.charAt(0) !== '.'
282 );
283 await Promise.all(builtPackageFolders.map(prepareNpmPackage));
284 }
285
286 module.exports = {
287 copyAllShims,
288 getPackageName,
289 getBundleOutputPath,
290 prepareNpmPackages,
291 };