295 lines
8.6 KiB
JavaScript
295 lines
8.6 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
existsSync,
|
|
readdirSync,
|
|
unlinkSync,
|
|
readFileSync,
|
|
writeFileSync,
|
|
} = require('fs');
|
|
const path = require('path');
|
|
const Bundles = require('./bundles');
|
|
const {
|
|
asyncCopyTo,
|
|
asyncExecuteCommand,
|
|
asyncExtractTar,
|
|
asyncRimRaf,
|
|
} = require('./utils');
|
|
const {getSigningToken, signFile} = require('signedsource');
|
|
|
|
const {
|
|
NODE_ES2015,
|
|
ESM_DEV,
|
|
ESM_PROD,
|
|
UMD_DEV,
|
|
UMD_PROD,
|
|
UMD_PROFILING,
|
|
NODE_DEV,
|
|
NODE_PROD,
|
|
NODE_PROFILING,
|
|
BUN_DEV,
|
|
BUN_PROD,
|
|
FB_WWW_DEV,
|
|
FB_WWW_PROD,
|
|
FB_WWW_PROFILING,
|
|
RN_OSS_DEV,
|
|
RN_OSS_PROD,
|
|
RN_OSS_PROFILING,
|
|
RN_FB_DEV,
|
|
RN_FB_PROD,
|
|
RN_FB_PROFILING,
|
|
BROWSER_SCRIPT,
|
|
} = Bundles.bundleTypes;
|
|
|
|
function getPackageName(name) {
|
|
if (name.indexOf('/') !== -1) {
|
|
return name.split('/')[0];
|
|
}
|
|
return name;
|
|
}
|
|
|
|
function getBundleOutputPath(bundle, bundleType, filename, packageName) {
|
|
switch (bundleType) {
|
|
case NODE_ES2015:
|
|
return `build/node_modules/${packageName}/cjs/${filename}`;
|
|
case ESM_DEV:
|
|
case ESM_PROD:
|
|
return `build/node_modules/${packageName}/esm/${filename}`;
|
|
case BUN_DEV:
|
|
case BUN_PROD:
|
|
return `build/node_modules/${packageName}/cjs/${filename}`;
|
|
case NODE_DEV:
|
|
case NODE_PROD:
|
|
case NODE_PROFILING:
|
|
return `build/node_modules/${packageName}/cjs/${filename}`;
|
|
case UMD_DEV:
|
|
case UMD_PROD:
|
|
case UMD_PROFILING:
|
|
return `build/node_modules/${packageName}/umd/${filename}`;
|
|
case FB_WWW_DEV:
|
|
case FB_WWW_PROD:
|
|
case FB_WWW_PROFILING:
|
|
return `build/facebook-www/${filename}`;
|
|
case RN_OSS_DEV:
|
|
case RN_OSS_PROD:
|
|
case RN_OSS_PROFILING:
|
|
switch (packageName) {
|
|
case 'react-native-renderer':
|
|
return `build/react-native/implementations/${filename}`;
|
|
default:
|
|
throw new Error('Unknown RN package.');
|
|
}
|
|
case RN_FB_DEV:
|
|
case RN_FB_PROD:
|
|
case RN_FB_PROFILING:
|
|
switch (packageName) {
|
|
case 'scheduler':
|
|
case 'react':
|
|
case 'react-is':
|
|
case 'react-test-renderer':
|
|
return `build/facebook-react-native/${packageName}/cjs/${filename}`;
|
|
case 'react-native-renderer':
|
|
return `build/react-native/implementations/${filename.replace(
|
|
/\.js$/,
|
|
'.fb.js'
|
|
)}`;
|
|
default:
|
|
throw new Error('Unknown RN package.');
|
|
}
|
|
case BROWSER_SCRIPT: {
|
|
// Bundles that are served as browser scripts need to be able to be sent
|
|
// straight to the browser with any additional bundling. We shouldn't use
|
|
// a module to re-export. Depending on how they are served, they also may
|
|
// not go through package.json module resolution, so we shouldn't rely on
|
|
// that either. We should consider the output path as part of the public
|
|
// contract, and explicitly specify its location within the package's
|
|
// directory structure.
|
|
const outputPath = bundle.outputPath;
|
|
if (!outputPath) {
|
|
throw new Error(
|
|
'Bundles with type BROWSER_SCRIPT must specific an explicit ' +
|
|
'output path.'
|
|
);
|
|
}
|
|
return `build/node_modules/${packageName}/${outputPath}`;
|
|
}
|
|
default:
|
|
throw new Error('Unknown bundle type.');
|
|
}
|
|
}
|
|
|
|
async function copyWWWShims() {
|
|
await asyncCopyTo(
|
|
`${__dirname}/shims/facebook-www`,
|
|
'build/facebook-www/shims'
|
|
);
|
|
}
|
|
|
|
async function copyRNShims() {
|
|
await asyncCopyTo(
|
|
`${__dirname}/shims/react-native`,
|
|
'build/react-native/shims'
|
|
);
|
|
await asyncCopyTo(
|
|
require.resolve('react-native-renderer/src/ReactNativeTypes.js'),
|
|
'build/react-native/shims/ReactNativeTypes.js'
|
|
);
|
|
processGenerated('build/react-native/shims');
|
|
}
|
|
|
|
function processGenerated(directory) {
|
|
const files = readdirSync(directory)
|
|
.filter(dir => dir.endsWith('.js'))
|
|
.map(file => path.join(directory, file));
|
|
|
|
files.forEach(file => {
|
|
const originalContents = readFileSync(file, 'utf8');
|
|
const contents = originalContents
|
|
// Replace {@}format with {@}noformat
|
|
.replace(/(\r?\n\s*\*\s*)@format\b.*(\n)/, '$1@noformat$2')
|
|
// Add {@}nolint and {@}generated
|
|
.replace(/(\r?\n\s*\*)\//, `$1 @nolint$1 ${getSigningToken()}$1/`);
|
|
const signedContents = signFile(contents);
|
|
writeFileSync(file, signedContents, 'utf8');
|
|
});
|
|
}
|
|
|
|
async function copyAllShims() {
|
|
await Promise.all([copyWWWShims(), copyRNShims()]);
|
|
}
|
|
|
|
function getTarOptions(tgzName, packageName) {
|
|
// Files inside the `npm pack`ed archive start
|
|
// with "package/" in their paths. We'll undo
|
|
// this during extraction.
|
|
const CONTENTS_FOLDER = 'package';
|
|
return {
|
|
src: tgzName,
|
|
dest: `build/node_modules/${packageName}`,
|
|
tar: {
|
|
entries: [CONTENTS_FOLDER],
|
|
map(header) {
|
|
if (header.name.indexOf(CONTENTS_FOLDER + '/') === 0) {
|
|
header.name = header.name.slice(CONTENTS_FOLDER.length + 1);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
let entryPointsToHasBundle = new Map();
|
|
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
|
for (const bundle of Bundles.bundles) {
|
|
let hasBundle = entryPointsToHasBundle.get(bundle.entry);
|
|
if (!hasBundle) {
|
|
const hasNonFBBundleTypes = bundle.bundleTypes.some(
|
|
type =>
|
|
type !== FB_WWW_DEV && type !== FB_WWW_PROD && type !== FB_WWW_PROFILING
|
|
);
|
|
entryPointsToHasBundle.set(bundle.entry, hasNonFBBundleTypes);
|
|
}
|
|
}
|
|
|
|
function filterOutEntrypoints(name) {
|
|
// Remove entry point files that are not built in this configuration.
|
|
let jsonPath = `build/node_modules/${name}/package.json`;
|
|
let packageJSON = JSON.parse(readFileSync(jsonPath));
|
|
let files = packageJSON.files;
|
|
let exportsJSON = packageJSON.exports;
|
|
let browserJSON = packageJSON.browser;
|
|
if (!Array.isArray(files)) {
|
|
throw new Error('expected all package.json files to contain a files field');
|
|
}
|
|
let changed = false;
|
|
for (let i = 0; i < files.length; i++) {
|
|
let filename = files[i];
|
|
let entry =
|
|
filename === 'index.js'
|
|
? name
|
|
: name + '/' + filename.replace(/\.js$/, '');
|
|
let hasBundle = entryPointsToHasBundle.get(entry);
|
|
if (hasBundle === undefined) {
|
|
// This entry doesn't exist in the bundles. Check if something similar exists.
|
|
hasBundle =
|
|
entryPointsToHasBundle.get(entry + '.node') ||
|
|
entryPointsToHasBundle.get(entry + '.browser');
|
|
}
|
|
if (hasBundle === undefined) {
|
|
// This doesn't exist in the bundles. It's an extra file.
|
|
} else if (hasBundle === true) {
|
|
// This is built in this release channel.
|
|
} else {
|
|
// This doesn't have any bundleTypes in this release channel.
|
|
// Let's remove it.
|
|
files.splice(i, 1);
|
|
i--;
|
|
unlinkSync(`build/node_modules/${name}/${filename}`);
|
|
changed = true;
|
|
// Remove it from the exports field too if it exists.
|
|
if (exportsJSON) {
|
|
if (filename === 'index.js') {
|
|
delete exportsJSON['.'];
|
|
} else {
|
|
delete exportsJSON['./' + filename.replace(/\.js$/, '')];
|
|
}
|
|
}
|
|
if (browserJSON) {
|
|
delete browserJSON['./' + filename];
|
|
}
|
|
}
|
|
|
|
// We only export the source directory so Jest and Rollup can access them
|
|
// during local development and at build time. The files don't exist in the
|
|
// public builds, so we don't need the export entry, either.
|
|
const sourceWildcardExport = './src/*';
|
|
if (exportsJSON && exportsJSON[sourceWildcardExport]) {
|
|
delete exportsJSON[sourceWildcardExport];
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed) {
|
|
let newJSON = JSON.stringify(packageJSON, null, ' ');
|
|
writeFileSync(jsonPath, newJSON);
|
|
}
|
|
}
|
|
|
|
async function prepareNpmPackage(name) {
|
|
await Promise.all([
|
|
asyncCopyTo('LICENSE', `build/node_modules/${name}/LICENSE`),
|
|
asyncCopyTo(
|
|
`packages/${name}/package.json`,
|
|
`build/node_modules/${name}/package.json`
|
|
),
|
|
asyncCopyTo(
|
|
`packages/${name}/README.md`,
|
|
`build/node_modules/${name}/README.md`
|
|
),
|
|
asyncCopyTo(`packages/${name}/npm`, `build/node_modules/${name}`),
|
|
]);
|
|
filterOutEntrypoints(name);
|
|
const tgzName = (
|
|
await asyncExecuteCommand(`npm pack build/node_modules/${name}`)
|
|
).trim();
|
|
await asyncRimRaf(`build/node_modules/${name}`);
|
|
await asyncExtractTar(getTarOptions(tgzName, name));
|
|
unlinkSync(tgzName);
|
|
}
|
|
|
|
async function prepareNpmPackages() {
|
|
if (!existsSync('build/node_modules')) {
|
|
// We didn't build any npm packages.
|
|
return;
|
|
}
|
|
const builtPackageFolders = readdirSync('build/node_modules').filter(
|
|
dir => dir.charAt(0) !== '.'
|
|
);
|
|
await Promise.all(builtPackageFolders.map(prepareNpmPackage));
|
|
}
|
|
|
|
module.exports = {
|
|
copyAllShims,
|
|
getPackageName,
|
|
getBundleOutputPath,
|
|
prepareNpmPackages,
|
|
};
|