1/** 2 * Copyright © 2023 650 Industries. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7import fs from 'fs'; 8import { builtinModules } from 'module'; 9import path from 'path'; 10 11// A list of the Node.js standard library modules that are currently 12// available, 13export const NODE_STDLIB_MODULES: string[] = [ 14 'fs/promises', 15 ...( 16 builtinModules || 17 // @ts-expect-error 18 (process.binding ? Object.keys(process.binding('natives')) : []) || 19 [] 20 ).filter((x) => !/^_|^(internal|v8|node-inspect)\/|\//.test(x) && !['sys'].includes(x)), 21].sort(); 22 23export const EXTERNAL_REQUIRE_POLYFILL = '.expo/metro/polyfill.js'; 24export const EXTERNAL_REQUIRE_NATIVE_POLYFILL = '.expo/metro/polyfill.native.js'; 25export const METRO_EXTERNALS_FOLDER = '.expo/metro/externals'; 26 27export function getNodeExternalModuleId(fromModule: string, moduleId: string) { 28 return path.relative( 29 path.dirname(fromModule), 30 path.join(METRO_EXTERNALS_FOLDER, moduleId, 'index.js') 31 ); 32} 33 34export async function setupNodeExternals(projectRoot: string) { 35 await tapExternalRequirePolyfill(projectRoot); 36 await tapNodeShims(projectRoot); 37} 38 39async function tapExternalRequirePolyfill(projectRoot: string) { 40 await fs.promises.mkdir(path.join(projectRoot, path.dirname(EXTERNAL_REQUIRE_POLYFILL)), { 41 recursive: true, 42 }); 43 await fs.promises.writeFile( 44 path.join(projectRoot, EXTERNAL_REQUIRE_POLYFILL), 45 'global.$$require_external = typeof window === "undefined" ? require : () => null;' 46 ); 47 await fs.promises.writeFile( 48 path.join(projectRoot, EXTERNAL_REQUIRE_NATIVE_POLYFILL), 49 'global.$$require_external = (moduleId) => {throw new Error(`Node.js standard library module ${moduleId} is not available in this JavaScript environment`);}' 50 ); 51} 52 53export function isNodeExternal(moduleName: string): string | null { 54 const moduleId = moduleName.replace(/^node:/, ''); 55 if (NODE_STDLIB_MODULES.includes(moduleId)) { 56 return moduleId; 57 } 58 return null; 59} 60 61function tapNodeShimContents(moduleId: string): string { 62 return `module.exports = $$require_external('node:${moduleId}');`; 63} 64 65// Ensure Node.js shims which require using `$$require_external` are available inside the project. 66async function tapNodeShims(projectRoot: string) { 67 const externals: Record<string, string> = {}; 68 for (const moduleId of NODE_STDLIB_MODULES) { 69 const shimDir = path.join(projectRoot, METRO_EXTERNALS_FOLDER, moduleId); 70 const shimPath = path.join(shimDir, 'index.js'); 71 externals[moduleId] = shimPath; 72 73 if (!fs.existsSync(shimPath)) { 74 await fs.promises.mkdir(shimDir, { recursive: true }); 75 await fs.promises.writeFile(shimPath, tapNodeShimContents(moduleId)); 76 } 77 } 78} 79