@expo/cli
Version:
408 lines (407 loc) • 20 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
instantiateMetroAsync: function() {
return instantiateMetroAsync;
},
isWatchEnabled: function() {
return isWatchEnabled;
},
loadMetroConfigAsync: function() {
return loadMetroConfigAsync;
}
});
function _config() {
const data = require("@expo/config");
_config = function() {
return data;
};
return data;
}
function _paths() {
const data = require("@expo/config/paths");
_paths = function() {
return data;
};
return data;
}
function _metroconfig() {
const data = require("@expo/metro-config");
_metroconfig = function() {
return data;
};
return data;
}
function _chalk() {
const data = /*#__PURE__*/ _interop_require_default(require("chalk"));
_chalk = function() {
return data;
};
return data;
}
function _RevisionNotFoundError() {
const data = /*#__PURE__*/ _interop_require_default(require("metro/src/IncrementalBundler/RevisionNotFoundError"));
_RevisionNotFoundError = function() {
return data;
};
return data;
}
function _formatBundlingError() {
const data = /*#__PURE__*/ _interop_require_default(require("metro/src/lib/formatBundlingError"));
_formatBundlingError = function() {
return data;
};
return data;
}
function _metroconfig1() {
const data = require("metro-config");
_metroconfig1 = function() {
return data;
};
return data;
}
function _metrocore() {
const data = require("metro-core");
_metrocore = function() {
return data;
};
return data;
}
function _nodeutil() {
const data = /*#__PURE__*/ _interop_require_default(require("node:util"));
_nodeutil = function() {
return data;
};
return data;
}
function _path() {
const data = /*#__PURE__*/ _interop_require_default(require("path"));
_path = function() {
return data;
};
return data;
}
const _DevToolsPluginWebsocketEndpoint = require("./DevToolsPluginWebsocketEndpoint");
const _MetroTerminalReporter = require("./MetroTerminalReporter");
const _attachAtlas = require("./debugging/attachAtlas");
const _createDebugMiddleware = require("./debugging/createDebugMiddleware");
const _createMetroMiddleware = require("./dev-server/createMetroMiddleware");
const _runServerfork = require("./runServer-fork");
const _withMetroMultiPlatform = require("./withMetroMultiPlatform");
const _log = require("../../../log");
const _env = require("../../../utils/env");
const _errors = require("../../../utils/errors");
const _CorsMiddleware = require("../middleware/CorsMiddleware");
const _createJsInspectorMiddleware = require("../middleware/inspector/createJsInspectorMiddleware");
const _mutations = require("../middleware/mutations");
const _platformBundlers = require("../platformBundlers");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
// Wrap terminal and polyfill console.log so we can log during bundling without breaking the indicator.
class LogRespectingTerminal extends _metrocore().Terminal {
constructor(stream){
super(stream);
const sendLog = (...args)=>{
this._logLines.push(// format args like console.log
_nodeutil().default.format(...args));
this._scheduleUpdate();
// Flush the logs to the terminal immediately so logs at the end of the process are not lost.
this.flush();
};
console.log = sendLog;
console.info = sendLog;
}
}
// Share one instance of Terminal for all instances of Metro.
const terminal = new LogRespectingTerminal(process.stdout);
async function loadMetroConfigAsync(projectRoot, options, { exp, isExporting, getMetroBundler }) {
var _exp_experiments, _exp_experiments1, _exp_experiments2, _exp_experiments3, _config_resolver, _exp_experiments4, _exp_experiments5, _exp_experiments6;
let reportEvent;
const serverActionsEnabled = ((_exp_experiments = exp.experiments) == null ? void 0 : _exp_experiments.reactServerFunctions) ?? _env.env.EXPO_UNSTABLE_SERVER_FUNCTIONS;
if (serverActionsEnabled) {
process.env.EXPO_UNSTABLE_SERVER_FUNCTIONS = '1';
}
// NOTE: Enable all the experimental Metro flags when RSC is enabled.
if (((_exp_experiments1 = exp.experiments) == null ? void 0 : _exp_experiments1.reactServerComponentRoutes) || serverActionsEnabled) {
process.env.EXPO_USE_METRO_REQUIRE = '1';
process.env.EXPO_USE_FAST_RESOLVER = '1';
}
const isReactCanaryEnabled = (((_exp_experiments2 = exp.experiments) == null ? void 0 : _exp_experiments2.reactServerComponentRoutes) || serverActionsEnabled || ((_exp_experiments3 = exp.experiments) == null ? void 0 : _exp_experiments3.reactCanary)) ?? false;
if (isReactCanaryEnabled) {
// The fast resolver is required for React canary to work as it can switch the node_modules location for react imports.
process.env.EXPO_USE_FAST_RESOLVER = '1';
}
const serverRoot = (0, _paths().getMetroServerRoot)(projectRoot);
const terminalReporter = new _MetroTerminalReporter.MetroTerminalReporter(serverRoot, terminal);
const hasConfig = await (0, _metroconfig1().resolveConfig)(options.config, projectRoot);
let config = {
...await (0, _metroconfig1().loadConfig)({
cwd: projectRoot,
projectRoot,
...options
}, // If the project does not have a metro.config.js, then we use the default config.
hasConfig.isEmpty ? (0, _metroconfig().getDefaultConfig)(projectRoot) : undefined),
reporter: {
update (event) {
terminalReporter.update(event);
if (reportEvent) {
reportEvent(event);
}
}
}
};
// @ts-expect-error: Set the global require cycle ignore patterns for SSR bundles. This won't work with custom global prefixes, but we don't use those.
globalThis.__requireCycleIgnorePatterns = (_config_resolver = config.resolver) == null ? void 0 : _config_resolver.requireCycleIgnorePatterns;
if (isExporting) {
var _exp_experiments7;
// This token will be used in the asset plugin to ensure the path is correct for writing locally.
// @ts-expect-error: typed as readonly.
config.transformer.publicPath = `/assets?export_path=${(((_exp_experiments7 = exp.experiments) == null ? void 0 : _exp_experiments7.baseUrl) ?? '') + '/assets'}`;
} else {
// @ts-expect-error: typed as readonly
config.transformer.publicPath = '/assets/?unstable_path=.';
}
const platformBundlers = (0, _platformBundlers.getPlatformBundlers)(projectRoot, exp);
if ((_exp_experiments4 = exp.experiments) == null ? void 0 : _exp_experiments4.reactCompiler) {
_log.Log.warn(`Experimental React Compiler is enabled.`);
}
if (_env.env.EXPO_UNSTABLE_TREE_SHAKING && !_env.env.EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH) {
throw new _errors.CommandError('EXPO_UNSTABLE_TREE_SHAKING requires EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH to be enabled.');
}
if (_env.env.EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH) {
_log.Log.warn(`Experimental bundle optimization is enabled.`);
}
if (_env.env.EXPO_UNSTABLE_TREE_SHAKING) {
_log.Log.warn(`Experimental tree shaking is enabled.`);
}
if (serverActionsEnabled) {
var _exp_experiments8;
_log.Log.warn(`React Server Functions (beta) are enabled. Route rendering mode: ${((_exp_experiments8 = exp.experiments) == null ? void 0 : _exp_experiments8.reactServerComponentRoutes) ? 'server' : 'client'}`);
}
config = await (0, _withMetroMultiPlatform.withMetroMultiPlatformAsync)(projectRoot, {
config,
exp,
platformBundlers,
isTsconfigPathsEnabled: ((_exp_experiments5 = exp.experiments) == null ? void 0 : _exp_experiments5.tsconfigPaths) ?? true,
isFastResolverEnabled: _env.env.EXPO_USE_FAST_RESOLVER,
isExporting,
isReactCanaryEnabled,
isNamedRequiresEnabled: _env.env.EXPO_USE_METRO_REQUIRE,
isReactServerComponentsEnabled: !!((_exp_experiments6 = exp.experiments) == null ? void 0 : _exp_experiments6.reactServerComponentRoutes),
getMetroBundler
});
return {
config,
setEventReporter: (logger)=>reportEvent = logger,
reporter: terminalReporter
};
}
async function instantiateMetroAsync(metroBundler, options, { isExporting, exp = (0, _config().getConfig)(metroBundler.projectRoot, {
skipSDKVersionRequirement: true
}).exp }) {
const projectRoot = metroBundler.projectRoot;
const { config: metroConfig, setEventReporter, reporter } = await loadMetroConfigAsync(projectRoot, options, {
exp,
isExporting,
getMetroBundler () {
return metro.getBundler().getBundler();
}
});
// Create the core middleware stack for Metro, including websocket listeners
const { middleware, messagesSocket, eventsSocket, websocketEndpoints } = (0, _createMetroMiddleware.createMetroMiddleware)(metroConfig);
if (!isExporting) {
// Enable correct CORS headers for Expo Router features
(0, _mutations.prependMiddleware)(middleware, (0, _CorsMiddleware.createCorsMiddleware)(exp));
// Enable debug middleware for CDP-related debugging
const { debugMiddleware, debugWebsocketEndpoints } = (0, _createDebugMiddleware.createDebugMiddleware)(metroBundler, reporter);
Object.assign(websocketEndpoints, debugWebsocketEndpoints);
middleware.use(debugMiddleware);
middleware.use('/_expo/debugger', (0, _createJsInspectorMiddleware.createJsInspectorMiddleware)());
// TODO(cedric): `enhanceMiddleware` is deprecated, but is currently used to unify the middleware stacks
// See: https://github.com/facebook/metro/commit/22e85fde85ec454792a1b70eba4253747a2587a9
// See: https://github.com/facebook/metro/commit/d0d554381f119bb80ab09dbd6a1d310b54737e52
const customEnhanceMiddleware = metroConfig.server.enhanceMiddleware;
// @ts-expect-error: can't mutate readonly config
metroConfig.server.enhanceMiddleware = (metroMiddleware, server)=>{
if (customEnhanceMiddleware) {
metroMiddleware = customEnhanceMiddleware(metroMiddleware, server);
}
return middleware.use(metroMiddleware);
};
}
// Attach Expo Atlas if enabled
await (0, _attachAtlas.attachAtlasAsync)({
isExporting,
exp,
projectRoot,
middleware,
metroConfig,
// NOTE(cedric): reset the Atlas file once, and reuse it for static exports
resetAtlasFile: isExporting
});
const { server, hmrServer, metro } = await (0, _runServerfork.runServer)(metroBundler, metroConfig, {
websocketEndpoints: {
...websocketEndpoints,
...(0, _DevToolsPluginWebsocketEndpoint.createDevToolsPluginWebsocketEndpoint)()
},
watch: !isExporting && isWatchEnabled()
}, {
mockServer: isExporting
});
// Patch transform file to remove inconvenient customTransformOptions which are only used in single well-known files.
const originalTransformFile = metro.getBundler().getBundler().transformFile.bind(metro.getBundler().getBundler());
metro.getBundler().getBundler().transformFile = async function(filePath, transformOptions, fileBuffer) {
return originalTransformFile(filePath, pruneCustomTransformOptions(filePath, // Clone the options so we don't mutate the original.
{
...transformOptions,
customTransformOptions: {
__proto__: null,
...transformOptions.customTransformOptions
}
}), fileBuffer);
};
setEventReporter(eventsSocket.reportMetroEvent);
// This function ensures that modules in source maps are sorted in the same
// order as in a plain JS bundle.
metro._getSortedModules = function(graph) {
var _graph_transformOptions_customTransformOptions;
const modules = [
...graph.dependencies.values()
];
const ctx = {
platform: graph.transformOptions.platform,
environment: (_graph_transformOptions_customTransformOptions = graph.transformOptions.customTransformOptions) == null ? void 0 : _graph_transformOptions_customTransformOptions.environment
};
// Assign IDs to modules in a consistent order
for (const module of modules){
// @ts-expect-error
this._createModuleId(module.path, ctx);
}
// Sort by IDs
return modules.sort(// @ts-expect-error
(a, b)=>this._createModuleId(a.path, ctx) - this._createModuleId(b.path, ctx));
};
if (hmrServer) {
let hmrJSBundle;
try {
hmrJSBundle = require('@expo/metro-config/build/serializer/fork/hmrJSBundle').default;
} catch {
// Add fallback for monorepo tests up until the fork is merged.
_log.Log.warn('Failed to load HMR serializer from @expo/metro-config, using fallback version.');
hmrJSBundle = require('metro/src/DeltaBundler/Serializers/hmrJSBundle');
}
// Patch HMR Server to send more info to the `_createModuleId` function for deterministic module IDs and add support for serializing HMR updates the same as all other bundles.
hmrServer._prepareMessage = async function(group, options, changeEvent) {
// Fork of https://github.com/facebook/metro/blob/3b3e0aaf725cfa6907bf2c8b5fbc0da352d29efe/packages/metro/src/HmrServer.js#L327-L393
// with patch for `_createModuleId`.
const logger = !options.isInitialUpdate ? changeEvent == null ? void 0 : changeEvent.logger : null;
try {
var _revision_graph_transformOptions_customTransformOptions;
const revPromise = this._bundler.getRevision(group.revisionId);
if (!revPromise) {
return {
type: 'error',
body: (0, _formatBundlingError().default)(new (_RevisionNotFoundError()).default(group.revisionId))
};
}
logger == null ? void 0 : logger.point('updateGraph_start');
const { revision, delta } = await this._bundler.updateGraph(await revPromise, false);
logger == null ? void 0 : logger.point('updateGraph_end');
this._clientGroups.delete(group.revisionId);
group.revisionId = revision.id;
for (const client of group.clients){
client.revisionIds = client.revisionIds.filter((revisionId)=>revisionId !== group.revisionId);
client.revisionIds.push(revision.id);
}
this._clientGroups.set(group.revisionId, group);
logger == null ? void 0 : logger.point('serialize_start');
// NOTE(EvanBacon): This is the patch
const moduleIdContext = {
platform: revision.graph.transformOptions.platform,
environment: (_revision_graph_transformOptions_customTransformOptions = revision.graph.transformOptions.customTransformOptions) == null ? void 0 : _revision_graph_transformOptions_customTransformOptions.environment
};
const hmrUpdate = hmrJSBundle(delta, revision.graph, {
clientUrl: group.clientUrl,
// NOTE(EvanBacon): This is also the patch
createModuleId: (moduleId)=>{
// @ts-expect-error
return this._createModuleId(moduleId, moduleIdContext);
},
includeAsyncPaths: group.graphOptions.lazy,
projectRoot: this._config.projectRoot,
serverRoot: this._config.server.unstable_serverRoot ?? this._config.projectRoot
});
logger == null ? void 0 : logger.point('serialize_end');
return {
type: 'update',
body: {
revisionId: revision.id,
isInitialUpdate: options.isInitialUpdate,
...hmrUpdate
}
};
} catch (error) {
const formattedError = (0, _formatBundlingError().default)(error);
this._config.reporter.update({
type: 'bundling_error',
error
});
return {
type: 'error',
body: formattedError
};
}
};
}
return {
metro,
hmrServer,
server,
middleware,
messageSocket: messagesSocket
};
}
// TODO: Fork the entire transform function so we can simply regex the file contents for keywords instead.
function pruneCustomTransformOptions(filePath, transformOptions) {
var _transformOptions_customTransformOptions, _transformOptions_customTransformOptions1, _transformOptions_customTransformOptions2, _transformOptions_customTransformOptions3;
// Normalize the filepath for cross platform checking.
filePath = filePath.split(_path().default.sep).join('/');
if (((_transformOptions_customTransformOptions = transformOptions.customTransformOptions) == null ? void 0 : _transformOptions_customTransformOptions.dom) && // The only generated file that needs the dom root is `expo/dom/entry.js`
!filePath.match(/expo\/dom\/entry\.js$/)) {
// Clear the dom root option if we aren't transforming the magic entry file, this ensures
// that cached artifacts from other DOM component bundles can be reused.
transformOptions.customTransformOptions.dom = 'true';
}
if (((_transformOptions_customTransformOptions1 = transformOptions.customTransformOptions) == null ? void 0 : _transformOptions_customTransformOptions1.routerRoot) && // The router root is used all over expo-router (`process.env.EXPO_ROUTER_ABS_APP_ROOT`, `process.env.EXPO_ROUTER_APP_ROOT`) so we'll just ignore the entire package.
!(filePath.match(/\/expo-router\/_ctx/) || filePath.match(/\/expo-router\/build\//))) {
// Set to the default value.
transformOptions.customTransformOptions.routerRoot = 'app';
}
if (((_transformOptions_customTransformOptions2 = transformOptions.customTransformOptions) == null ? void 0 : _transformOptions_customTransformOptions2.asyncRoutes) && // The async routes settings are also used in `expo-router/_ctx.ios.js` (and other platform variants) via `process.env.EXPO_ROUTER_IMPORT_MODE`
!(filePath.match(/\/expo-router\/_ctx/) || filePath.match(/\/expo-router\/build\//))) {
delete transformOptions.customTransformOptions.asyncRoutes;
}
if (((_transformOptions_customTransformOptions3 = transformOptions.customTransformOptions) == null ? void 0 : _transformOptions_customTransformOptions3.clientBoundaries) && // The client boundaries are only used in `expo/virtual/rsc.js` for production RSC exports.
!filePath.match(/\/expo\/virtual\/rsc\.js$/)) {
delete transformOptions.customTransformOptions.clientBoundaries;
}
return transformOptions;
}
function isWatchEnabled() {
if (_env.env.CI) {
_log.Log.log((0, _chalk().default)`Metro is running in CI mode, reloads are disabled. Remove {bold CI=true} to enable watch mode.`);
}
return !_env.env.CI;
}
//# sourceMappingURL=instantiateMetro.js.map
;