"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.withExpoSerializers = withExpoSerializers; exports.withSerializerPlugins = withSerializerPlugins; exports.createDefaultExportCustomSerializer = createDefaultExportCustomSerializer; exports.createSerializerFromSerialProcessors = createSerializerFromSerialProcessors; const bundleToString_1 = __importDefault(require("@expo/metro/metro/lib/bundleToString")); const jsc_safe_url_1 = require("jsc-safe-url"); const debugId_1 = require("./debugId"); const environmentVariableSerializerPlugin_1 = require("./environmentVariableSerializerPlugin"); const serializeChunks_1 = require("./serializeChunks"); const env_1 = require("../env"); // Lazy-loaded to avoid pulling in metro-source-map and @babel/traverse at startup (~100ms savings) let _sourceMapString; function getSourceMapString() { if (!_sourceMapString) { _sourceMapString = require('@expo/metro/metro/DeltaBundler/Serializers/sourceMapString').sourceMapString; } return _sourceMapString; } // Lazy-loaded to avoid pulling in @babel/generator, @babel/core at startup let _reconcileTransformSerializerPlugin; function getReconcileTransformSerializerPlugin() { if (!_reconcileTransformSerializerPlugin) { _reconcileTransformSerializerPlugin = require('./reconcileTransformSerializerPlugin').reconcileTransformSerializerPlugin; } return _reconcileTransformSerializerPlugin; } let _treeShakeSerializer; function getTreeShakeSerializer() { if (!_treeShakeSerializer) { _treeShakeSerializer = require('./treeShakeSerializerPlugin').treeShakeSerializer; } return _treeShakeSerializer; } // Lazy-loaded to avoid pulling in metro's getAppendScripts -> sourceMapString chain at startup let _baseJSBundle; function getBaseJSBundle() { if (!_baseJSBundle) { _baseJSBundle = require('./fork/baseJSBundle').baseJSBundle; } return _baseJSBundle; } function withExpoSerializers(config, options = {}) { const processors = []; processors.push(environmentVariableSerializerPlugin_1.serverPreludeSerializerPlugin); if (!env_1.env.EXPO_NO_CLIENT_ENV_VARS) { processors.push(environmentVariableSerializerPlugin_1.environmentVariableSerializerPlugin); } // Then tree-shake the modules. processors.push(getTreeShakeSerializer()); // Then finish transforming the modules from AST to JS. processors.push(getReconcileTransformSerializerPlugin()); return withSerializerPlugins(config, processors, options); } // There can only be one custom serializer as the input doesn't match the output. // Here we simply run function withSerializerPlugins(config, processors, options = {}) { const expoSerializer = createSerializerFromSerialProcessors(config, processors, config.serializer?.customSerializer ?? null, options); // We can't object-spread the config, it loses the reference to the original config // Meaning that any user-provided changes are not propagated to the serializer config // @ts-expect-error TODO(cedric): it's a read only property, but we can actually write it config.serializer ??= {}; // @ts-expect-error TODO(cedric): it's a read only property, but we can actually write it config.serializer.customSerializer = expoSerializer; return config; } function createDefaultExportCustomSerializer(config, configOptions = {}) { return async (entryPoint, preModules, graph, inputOptions) => { // NOTE(@kitten): My guess is that this was supposed to always be disabled for `node` since we set `hot: true` manually for it const isPossiblyDev = graph.transformOptions.dev || graph.transformOptions.customTransformOptions?.environment === 'node'; // TODO: This is a temporary solution until we've converged on using the new serializer everywhere. const enableDebugId = inputOptions.inlineSourceMap !== true && !isPossiblyDev; const context = { platform: graph.transformOptions?.platform, environment: graph.transformOptions?.customTransformOptions?.environment ?? 'client', }; const options = { ...inputOptions, createModuleId: (moduleId, ...props) => { if (props.length > 0) { return inputOptions.createModuleId(moduleId, ...props); } return inputOptions.createModuleId(moduleId, // @ts-expect-error: context is added by Expo and not part of the upstream Metro implementation. context); }, }; let debugId; const loadDebugId = () => { if (!enableDebugId || debugId) { return debugId; } // TODO: Perform this cheaper. const bundle = getBaseJSBundle()(entryPoint, preModules, graph, { ...options, debugId: undefined, }); const outputCode = (0, bundleToString_1.default)(bundle).code; debugId = (0, debugId_1.stringToUUID)(outputCode); return debugId; }; let premodulesToBundle = [...preModules]; let bundleCode = null; let bundleMap = null; // Only invoke the custom serializer if it's not our serializer // We write the Expo serializer back to the original config object, possibly falling into recursive loops const originalCustomSerializer = unwrapOriginalSerializer(config.serializer?.customSerializer); if (originalCustomSerializer) { const bundle = await originalCustomSerializer(entryPoint, premodulesToBundle, graph, options); if (typeof bundle === 'string') { bundleCode = bundle; } else { bundleCode = bundle.code; bundleMap = bundle.map; } } else { const debugId = loadDebugId(); if (configOptions.unstable_beforeAssetSerializationPlugins) { for (const plugin of configOptions.unstable_beforeAssetSerializationPlugins) { premodulesToBundle = plugin({ graph, premodules: [...premodulesToBundle], debugId }); } } bundleCode = (0, bundleToString_1.default)(getBaseJSBundle()(entryPoint, premodulesToBundle, graph, { ...options, debugId, })).code; } const getEnsuredMaps = () => { bundleMap ??= getSourceMapString()([...premodulesToBundle, ...(0, serializeChunks_1.getSortedModules)([...graph.dependencies.values()], options)], { // TODO: Surface this somehow. excludeSource: false, // excludeSource: options.serializerOptions?.excludeSource, processModuleFilter: options.processModuleFilter, shouldAddToIgnoreList: options.shouldAddToIgnoreList, }); return bundleMap; }; if (!bundleMap && options.sourceUrl) { const url = (0, jsc_safe_url_1.isJscSafeUrl)(options.sourceUrl) ? (0, jsc_safe_url_1.toNormalUrl)(options.sourceUrl) : options.sourceUrl; const parsed = new URL(url, 'http://expo.dev'); // Is dev server request for source maps... if (parsed.pathname.endsWith('.map')) { return { code: bundleCode, map: getEnsuredMaps(), }; } } if (isPossiblyDev) { if (bundleMap == null) { return bundleCode; } return { code: bundleCode, map: bundleMap, }; } // Exports.... bundleMap ??= getEnsuredMaps(); if (enableDebugId) { const mutateSourceMapWithDebugId = (sourceMap) => { // NOTE: debugId isn't required for inline source maps because the source map is included in the same file, therefore // we don't need to disambiguate between multiple source maps. const sourceMapObject = JSON.parse(sourceMap); sourceMapObject.debugId = loadDebugId(); // NOTE: Sentry does this, but bun does not. // sourceMapObject.debug_id = debugId; return JSON.stringify(sourceMapObject); }; return { code: bundleCode, map: mutateSourceMapWithDebugId(bundleMap), }; } return { code: bundleCode, map: bundleMap, }; }; } function getDefaultSerializer(config, fallbackSerializer, configOptions = {}) { const defaultSerializer = fallbackSerializer ?? createDefaultExportCustomSerializer(config, configOptions); const expoSerializer = async (entryPoint, preModules, graph, inputOptions) => { const context = { platform: graph.transformOptions?.platform, environment: graph.transformOptions?.customTransformOptions?.environment ?? 'client', }; const isLoaderBundle = graph.transformOptions?.customTransformOptions?.isLoaderBundle === 'true'; const loaderPaths = isLoaderBundle ? getLoaderPaths(graph.dependencies) : new Set(); const options = { ...inputOptions, createModuleId: (moduleId, ...props) => { // For loader bundles, append `+loader` to modules with `loaderReference`. // This creates different module IDs from `render.js` for the same source file, // avoiding module ID collisions when both bundles are loaded in the same runtime. let pathToHash = moduleId; if (isLoaderBundle && loaderPaths.has(moduleId)) { pathToHash = `${moduleId}+loader`; } if (props.length > 0) { return inputOptions.createModuleId(pathToHash, ...props); } return inputOptions.createModuleId(pathToHash, // @ts-expect-error: context is added by Expo and not part of the upstream Metro implementation. context); }, }; const customSerializerOptions = inputOptions.serializerOptions; // Custom options can only be passed outside of the dev server, meaning // we don't need to stringify the results at the end, i.e. this is `npx expo export` or `npx expo export:embed`. const supportsNonSerialReturn = !!customSerializerOptions?.output; const serializerOptions = (() => { if (customSerializerOptions) { return { outputMode: customSerializerOptions.output, splitChunks: customSerializerOptions.splitChunks, usedExports: customSerializerOptions.usedExports, includeSourceMaps: customSerializerOptions.includeSourceMaps, }; } if (options.sourceUrl) { const sourceUrl = (0, jsc_safe_url_1.isJscSafeUrl)(options.sourceUrl) ? (0, jsc_safe_url_1.toNormalUrl)(options.sourceUrl) : options.sourceUrl; const url = new URL(sourceUrl, 'https://expo.dev'); return { outputMode: url.searchParams.get('serializer.output'), usedExports: url.searchParams.get('serializer.usedExports') === 'true', splitChunks: url.searchParams.get('serializer.splitChunks') === 'true', includeSourceMaps: url.searchParams.get('serializer.map') === 'true', }; } return null; })(); if (serializerOptions?.outputMode !== 'static') { return defaultSerializer(entryPoint, preModules, graph, options); } // Mutate the serializer options with the parsed options. options.serializerOptions = { ...options.serializerOptions, ...serializerOptions, }; const assets = await (0, serializeChunks_1.graphToSerialAssetsAsync)(config, { includeSourceMaps: !!serializerOptions.includeSourceMaps, splitChunks: !!serializerOptions.splitChunks, ...configOptions, }, entryPoint, preModules, graph, options); if (supportsNonSerialReturn) { // @ts-expect-error: this is future proofing for adding assets to the output as well. return assets; } return JSON.stringify(assets); }; return Object.assign(expoSerializer, { __expoSerializer: true }); } function createSerializerFromSerialProcessors(config, processors, originalSerializer, options = {}) { const finalSerializer = getDefaultSerializer(config, originalSerializer, options); return wrapSerializerWithOriginal(originalSerializer, async (...props) => { for (const processor of processors) { if (processor) { props = await processor(...props); } } return finalSerializer(...props); }); } function wrapSerializerWithOriginal(original, expo) { return Object.assign(expo, { __originalSerializer: original }); } function unwrapOriginalSerializer(serializer) { if (!serializer || !('__originalSerializer' in serializer)) return null; return serializer.__originalSerializer; } /** * Collect paths of modules that have `loaderReference` metadata. * In loader bundles, these modules need different IDs to avoid collisions with `render.js`. */ function getLoaderPaths(dependencies) { const loaderPaths = new Set(); for (const module of dependencies.values()) { for (const output of module.output) { if ('loaderReference' in output.data && typeof output.data.loaderReference === 'string') { loaderPaths.add(module.path); } } } return loaderPaths; } //# sourceMappingURL=withExpoSerializers.js.map