157eba0f9SEvan Bacon/** 257eba0f9SEvan Bacon * Copyright © 2023 650 Industries. 357eba0f9SEvan Bacon * 457eba0f9SEvan Bacon * This source code is licensed under the MIT license found in the 557eba0f9SEvan Bacon * LICENSE file in the root directory of this source tree. 657eba0f9SEvan Bacon */ 757eba0f9SEvan Baconimport fs from 'fs'; 8*8be58d21SEvan Baconimport { builtinModules } from 'module'; 957eba0f9SEvan Baconimport path from 'path'; 1057eba0f9SEvan Bacon 11*8be58d21SEvan Bacon// A list of the Node.js standard library modules that are currently 12*8be58d21SEvan Bacon// available, 13*8be58d21SEvan Baconexport const NODE_STDLIB_MODULES: string[] = [ 1457eba0f9SEvan Bacon 'fs/promises', 15*8be58d21SEvan Bacon ...( 16*8be58d21SEvan Bacon builtinModules || 17*8be58d21SEvan Bacon // @ts-expect-error 18*8be58d21SEvan Bacon (process.binding ? Object.keys(process.binding('natives')) : []) || 19*8be58d21SEvan Bacon [] 20*8be58d21SEvan Bacon ).filter((x) => !/^_|^(internal|v8|node-inspect)\/|\//.test(x) && !['sys'].includes(x)), 21*8be58d21SEvan Bacon].sort(); 2257eba0f9SEvan Bacon 2357eba0f9SEvan Baconexport const EXTERNAL_REQUIRE_POLYFILL = '.expo/metro/polyfill.js'; 2457eba0f9SEvan Baconexport const EXTERNAL_REQUIRE_NATIVE_POLYFILL = '.expo/metro/polyfill.native.js'; 2557eba0f9SEvan Baconexport const METRO_EXTERNALS_FOLDER = '.expo/metro/externals'; 2657eba0f9SEvan Bacon 2757eba0f9SEvan Baconexport function getNodeExternalModuleId(fromModule: string, moduleId: string) { 2857eba0f9SEvan Bacon return path.relative( 2957eba0f9SEvan Bacon path.dirname(fromModule), 3057eba0f9SEvan Bacon path.join(METRO_EXTERNALS_FOLDER, moduleId, 'index.js') 3157eba0f9SEvan Bacon ); 3257eba0f9SEvan Bacon} 3357eba0f9SEvan Bacon 3457eba0f9SEvan Baconexport async function setupNodeExternals(projectRoot: string) { 3557eba0f9SEvan Bacon await tapExternalRequirePolyfill(projectRoot); 3657eba0f9SEvan Bacon await tapNodeShims(projectRoot); 3757eba0f9SEvan Bacon} 3857eba0f9SEvan Bacon 3957eba0f9SEvan Baconasync function tapExternalRequirePolyfill(projectRoot: string) { 4057eba0f9SEvan Bacon await fs.promises.mkdir(path.join(projectRoot, path.dirname(EXTERNAL_REQUIRE_POLYFILL)), { 4157eba0f9SEvan Bacon recursive: true, 4257eba0f9SEvan Bacon }); 4357eba0f9SEvan Bacon await fs.promises.writeFile( 4457eba0f9SEvan Bacon path.join(projectRoot, EXTERNAL_REQUIRE_POLYFILL), 4557eba0f9SEvan Bacon 'global.$$require_external = typeof window === "undefined" ? require : () => null;' 4657eba0f9SEvan Bacon ); 4757eba0f9SEvan Bacon await fs.promises.writeFile( 4857eba0f9SEvan Bacon path.join(projectRoot, EXTERNAL_REQUIRE_NATIVE_POLYFILL), 4957eba0f9SEvan Bacon 'global.$$require_external = (moduleId) => {throw new Error(`Node.js standard library module ${moduleId} is not available in this JavaScript environment`);}' 5057eba0f9SEvan Bacon ); 5157eba0f9SEvan Bacon} 5257eba0f9SEvan Bacon 5357eba0f9SEvan Baconexport function isNodeExternal(moduleName: string): string | null { 5457eba0f9SEvan Bacon const moduleId = moduleName.replace(/^node:/, ''); 5557eba0f9SEvan Bacon if (NODE_STDLIB_MODULES.includes(moduleId)) { 5657eba0f9SEvan Bacon return moduleId; 5757eba0f9SEvan Bacon } 5857eba0f9SEvan Bacon return null; 5957eba0f9SEvan Bacon} 6057eba0f9SEvan Bacon 6157eba0f9SEvan Baconfunction tapNodeShimContents(moduleId: string): string { 6257eba0f9SEvan Bacon return `module.exports = $$require_external('node:${moduleId}');`; 6357eba0f9SEvan Bacon} 6457eba0f9SEvan Bacon 6557eba0f9SEvan Bacon// Ensure Node.js shims which require using `$$require_external` are available inside the project. 6657eba0f9SEvan Baconasync function tapNodeShims(projectRoot: string) { 6757eba0f9SEvan Bacon const externals: Record<string, string> = {}; 6857eba0f9SEvan Bacon for (const moduleId of NODE_STDLIB_MODULES) { 6957eba0f9SEvan Bacon const shimDir = path.join(projectRoot, METRO_EXTERNALS_FOLDER, moduleId); 7057eba0f9SEvan Bacon const shimPath = path.join(shimDir, 'index.js'); 7157eba0f9SEvan Bacon externals[moduleId] = shimPath; 7257eba0f9SEvan Bacon 7357eba0f9SEvan Bacon if (!fs.existsSync(shimPath)) { 7457eba0f9SEvan Bacon await fs.promises.mkdir(shimDir, { recursive: true }); 7557eba0f9SEvan Bacon await fs.promises.writeFile(shimPath, tapNodeShimContents(moduleId)); 7657eba0f9SEvan Bacon } 7757eba0f9SEvan Bacon } 7857eba0f9SEvan Bacon} 79