first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Fork of the upstream transformer, but with modifications made for web production hashing.
* https://github.com/facebook/metro/blob/412771475c540b6f85d75d9dcd5a39a6e0753582/packages/metro-transform-worker/src/utils/assetTransformer.js#L1
*/
import { type ParseResult } from '@babel/core';
import type { BabelTransformerArgs } from '@expo/metro/metro-babel-transformer';
export declare function transform({ filename, options, }: {
filename: string;
options: Pick<BabelTransformerArgs['options'], 'platform' | 'projectRoot' | 'customTransformOptions' | 'publicPath'>;
}, assetRegistryPath: string, assetDataPlugins: readonly string[]): Promise<{
ast: ParseResult;
reactClientReference?: string;
}>;

View File

@@ -0,0 +1,120 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Fork of the upstream transformer, but with modifications made for web production hashing.
* https://github.com/facebook/metro/blob/412771475c540b6f85d75d9dcd5a39a6e0753582/packages/metro-transform-worker/src/utils/assetTransformer.js#L1
*/
const core_1 = require("@babel/core");
const util_1 = require("@expo/metro/metro/Bundler/util");
const node_path_1 = __importDefault(require("node:path"));
const node_url_1 = __importDefault(require("node:url"));
const getAssets_1 = require("./getAssets");
const filePath_1 = require("../utils/filePath");
// Register client components for assets in server component environments.
const buildClientReferenceRequire = core_1.template.statement(`module.exports = require('react-server-dom-webpack/server').createClientModuleProxy(FILE_PATH);`);
const buildStringRef = core_1.template.statement(`module.exports = FILE_PATH;`);
// The React Server Component version cannot have a function otherwise we'd be passing a function to the client component <Image />.
// TODO: Make react-native Image and expo-image server components that can simplify the asset before passing to the client component.
const buildStaticObjectRef = core_1.template.statement(
// Matches the `ImageSource` type from React Native: https://reactnative.dev/docs/image#source
`module.exports = { uri: FILE_PATH, width: WIDTH, height: HEIGHT };`);
const buildStaticObjectClientRef = core_1.template.statement(
// Matches the `ImageSource` type from React Native: https://reactnative.dev/docs/image#source
`module.exports = { uri: FILE_PATH, width: WIDTH, height: HEIGHT, toString() { return this.uri } };`);
async function transform({ filename, options, }, assetRegistryPath, assetDataPlugins) {
options ??= options || {
platform: '',
projectRoot: '',
};
// Is bundling for webview.
const isDomComponent = options.platform === 'web' && options.customTransformOptions?.dom;
const useMd5Filename = options.customTransformOptions?.useMd5Filename;
const isExport = options.publicPath.includes('?export_path=');
const isHosted = options.platform === 'web' || (options.customTransformOptions?.hosted && isExport);
const isReactServer = options.customTransformOptions?.environment === 'react-server';
const isServerEnv = isReactServer || options.customTransformOptions?.environment === 'node';
const absolutePath = node_path_1.default.resolve(options.projectRoot, filename);
const getClientReference = () => isReactServer ? node_url_1.default.pathToFileURL(absolutePath).href : undefined;
if ((options.platform !== 'web' ||
// React Server DOM components should use the client reference in order to local embedded assets.
isDomComponent) &&
// NOTE(EvanBacon): There may be value in simply evaluating assets on the server.
// Here, we're passing the info back to the client so the multi-resolution asset can be evaluated and downloaded.
isReactServer) {
return {
ast: {
comments: null,
...core_1.types.file(core_1.types.program([
buildClientReferenceRequire({
FILE_PATH: JSON.stringify(`./${(0, filePath_1.toPosixPath)(node_path_1.default.relative(options.projectRoot, absolutePath))}`),
}),
])),
errors: [],
},
reactClientReference: getClientReference(),
};
}
const data = await (0, getAssets_1.getUniversalAssetData)(absolutePath, filename, assetDataPlugins, options.platform, isDomComponent && isExport
? // If exporting a dom component, we need to use a public path that doesn't start with `/` to ensure that assets are loaded
// relative to the `DOM_COMPONENTS_BUNDLE_DIR`.
`/assets?export_path=assets`
: options.publicPath, isHosted);
if (isServerEnv || options.platform === 'web') {
const type = !data.type ? '' : `.${data.type}`;
let assetPath;
if (useMd5Filename) {
assetPath = data.hash + type;
}
else if (!isExport) {
assetPath = data.httpServerLocation + '/' + data.name + type;
}
else {
assetPath = data.httpServerLocation.replace(/\.\.\//g, '_') + '/' + data.name + type;
}
// If size data is known then it should be passed back to ensure the correct dimensions are used.
if (data.width != null || data.height != null) {
const options = {
FILE_PATH: JSON.stringify(assetPath),
WIDTH: data.width != null ? core_1.types.numericLiteral(data.width) : core_1.types.buildUndefinedNode(),
HEIGHT: data.height != null ? core_1.types.numericLiteral(data.height) : core_1.types.buildUndefinedNode(),
};
const creatorFunction = isReactServer ? buildStaticObjectRef : buildStaticObjectClientRef;
return {
ast: {
comments: null,
...core_1.types.file(core_1.types.program([creatorFunction(options)])),
errors: [],
},
reactClientReference: getClientReference(),
};
}
// Use single string references outside of client-side React Native.
// module.exports = "/foo/bar.png";
return {
ast: {
comments: null,
...core_1.types.file(core_1.types.program([buildStringRef({ FILE_PATH: JSON.stringify(assetPath) })])),
errors: [],
},
reactClientReference: getClientReference(),
};
}
return {
ast: {
comments: null,
...(0, util_1.generateAssetCodeFileAst)(assetRegistryPath, data),
errors: [],
},
};
}
//# sourceMappingURL=asset-transformer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"asset-transformer.js","sourceRoot":"","sources":["../../src/transform-worker/asset-transformer.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,4 @@
/**
* Copyright © 2025 650 Industries.
*/
export declare function getBrowserslistTargets(projectRoot: string): Promise<import('lightningcss').Targets>;

View File

@@ -0,0 +1,26 @@
"use strict";
/**
* Copyright © 2025 650 Industries.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBrowserslistTargets = getBrowserslistTargets;
const debug = require('debug')('expo:metro:browserslist');
const browserslistCache = {};
// Suppress `browserslist`'s own "data is X months old" warning in transform workers.
process.env.BROWSERSLIST_IGNORE_OLD_DATA = '1';
async function getBrowserslistTargets(projectRoot) {
if (browserslistCache[projectRoot]) {
return browserslistCache[projectRoot];
}
const browserslist = await import('browserslist');
const { browserslistToTargets } = await import('lightningcss');
const targets = browserslistToTargets(browserslist.default(undefined, {
throwOnMissing: false,
ignoreUnknownVersions: true,
path: projectRoot,
}));
debug('Browserslist targets: %O', targets);
browserslistCache[projectRoot] = targets;
return targets;
}
//# sourceMappingURL=browserslist.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"browserslist.js","sourceRoot":"","sources":["../../src/transform-worker/browserslist.ts"],"names":[],"mappings":";AAAA;;GAEG;;AASH,wDAoBC;AA3BD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,yBAAyB,CAAuB,CAAC;AAEhF,MAAM,iBAAiB,GAAmD,EAAE,CAAC;AAE7E,qFAAqF;AACrF,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,GAAG,CAAC;AAExC,KAAK,UAAU,sBAAsB,CAC1C,WAAmB;IAEnB,IAAI,iBAAiB,CAAC,WAAW,CAAC,EAAE,CAAC;QACnC,OAAO,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IAClD,MAAM,EAAE,qBAAqB,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IAE/D,MAAM,OAAO,GAAG,qBAAqB,CACnC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE;QAC9B,cAAc,EAAE,KAAK;QACrB,qBAAqB,EAAE,IAAI;QAC3B,IAAI,EAAE,WAAW;KAClB,CAAC,CACH,CAAC;IAEF,KAAK,CAAC,0BAA0B,EAAE,OAAO,CAAC,CAAC;IAC3C,iBAAiB,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC;IACzC,OAAO,OAAO,CAAC;AACjB,CAAC"}

View File

@@ -0,0 +1,141 @@
/**
* Copyright 2024-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { types as t } from '@babel/core';
import type { NodePath } from '@babel/core';
export type AsyncDependencyType = 'weak' | 'maybeSync' | 'async' | 'prefetch' | 'worker';
type AllowOptionalDependenciesWithOptions = {
exclude: string[];
};
type AllowOptionalDependencies = boolean | AllowOptionalDependenciesWithOptions;
export type Dependency = Readonly<{
data: DependencyData;
name: string;
}>;
type ContextMode = 'sync' | 'eager' | 'lazy' | 'lazy-once';
type ContextFilter = Readonly<{
pattern: string;
flags: string;
}>;
type RequireContextParams = Readonly<{
recursive: boolean;
filter: Readonly<ContextFilter>;
mode: ContextMode;
}>;
type MutableDependencyData = {
/** A locally unique key for this dependency within the current module. */
key: string;
/** If null, then the dependency is synchronous. (ex. `require('foo')`) */
asyncType: AsyncDependencyType | null;
/**
* If true, the dependency is declared using an ESM import, e.g.
* "import x from 'y'" or "await import('z')". A resolver should typically
* use this to assert either "import" or "require" for conditional exports
* and subpath imports.
*/
isESMImport: boolean;
isOptional?: boolean;
locs: readonly t.SourceLocation[];
/** Context for requiring a collection of modules. */
contextParams?: RequireContextParams;
exportNames: string[];
css?: {
url: string;
supports: string | null;
media: string | null;
};
};
export type DependencyData = Readonly<MutableDependencyData>;
export type MutableInternalDependency = MutableDependencyData & {
locs: t.SourceLocation[];
index: number;
name: string;
/** Usage of the dep, number of imports of the dep in other modules, used for tree shaking. */
imports: number;
};
export type InternalDependency = Readonly<MutableInternalDependency>;
export type State = {
asyncRequireModulePathStringLiteral: t.StringLiteral | null;
dependencyCalls: Set<string>;
dependencyRegistry: DependencyRegistry;
dependencyTransformer: DependencyTransformer;
dynamicRequires: DynamicRequiresBehavior;
dependencyMapIdentifier: t.Identifier | null;
keepRequireNames: boolean;
allowOptionalDependencies: AllowOptionalDependencies;
unstable_allowRequireContext: boolean;
unstable_isESMImportAtSource: ((location: t.SourceLocation) => boolean) | null;
/** Indicates that the pass should only collect dependencies and avoid mutating the AST. This is used for tree shaking passes. */
collectOnly?: boolean;
};
export type Options = Readonly<{
asyncRequireModulePath: string;
dependencyMapName?: string | null;
dynamicRequires: DynamicRequiresBehavior;
inlineableCalls: readonly string[];
keepRequireNames: boolean;
allowOptionalDependencies: AllowOptionalDependencies;
dependencyTransformer?: DependencyTransformer;
unstable_allowRequireContext: boolean;
unstable_isESMImportAtSource: ((location: t.SourceLocation) => boolean) | null;
/** Indicates that the pass should only collect dependencies and avoid mutating the AST. This is used for tree shaking passes. */
collectOnly?: boolean;
}>;
export type CollectedDependencies<TAst extends t.File = t.File> = Readonly<{
ast: TAst;
dependencyMapName: string;
dependencies: readonly Dependency[];
}>;
export interface DependencyTransformer {
transformSyncRequire(path: NodePath<t.CallExpression>, dependency: InternalDependency, state: State): void;
transformImportMaybeSyncCall(path: NodePath<any>, dependency: InternalDependency, state: State): void;
transformImportCall(path: NodePath<any>, dependency: InternalDependency, state: State): void;
transformPrefetch(path: NodePath<any>, dependency: InternalDependency, state: State): void;
transformIllegalDynamicRequire(path: NodePath<any>, state: State): void;
}
export type DynamicRequiresBehavior = 'throwAtRuntime' | 'reject' | 'warn';
type ImportQualifier = Readonly<{
name: string;
asyncType: AsyncDependencyType | null;
isESMImport: boolean;
optional: boolean;
contextParams?: RequireContextParams;
exportNames: string[];
}>;
declare function collectDependencies<TAst extends t.File>(ast: TAst, options: Options): CollectedDependencies<TAst>;
export declare function getExportNamesFromPath(path: NodePath<any>): string[];
export declare class InvalidRequireCallError extends Error {
constructor({ node }: NodePath<any>, message?: string);
}
/**
* Given an import qualifier, return a key used to register the dependency.
* Attributes can be appended to distinguish various combinations that would
* otherwise be considered the same dependency edge.
*
* For example, the following dependencies would collapse into a single edge
* if they simply utilized the `name` property:
*
* ```
* require('./foo');
* import foo from './foo'
* await import('./foo')
* require.context('./foo');
* require.context('./foo', true, /something/);
* require.context('./foo', false, /something/);
* require.context('./foo', false, /something/, 'lazy');
* ```
*
* This method should be utilized by `registerDependency`.
*/
export declare function getKeyForDependency(qualifier: Pick<ImportQualifier, 'asyncType' | 'contextParams' | 'isESMImport' | 'name'>): string;
export declare function hashKey(key: string): string;
declare class DependencyRegistry {
private _dependencies;
registerDependency(qualifier: ImportQualifier): InternalDependency;
getDependencies(): InternalDependency[];
}
export default collectDependencies;

View File

@@ -0,0 +1,761 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvalidRequireCallError = void 0;
exports.getExportNamesFromPath = getExportNamesFromPath;
exports.getKeyForDependency = getKeyForDependency;
exports.hashKey = hashKey;
/**
* Copyright 2024-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const core_1 = require("@babel/core");
const generator_1 = __importDefault(require("@babel/generator"));
const node_assert_1 = __importDefault(require("node:assert"));
const crypto = __importStar(require("node:crypto"));
const debug = require('debug')('expo:metro:collect-dependencies');
const MAGIC_IMPORT_COMMENTS = [
'@metro-ignore',
// Add support for Webpack ignore comment which is used in many different React libraries.
'webpackIgnore: true',
];
// asserts non-null
function nullthrows(x, message) {
(0, node_assert_1.default)(x != null, message);
return x;
}
function collectDependencies(ast, options) {
const visited = new WeakSet();
const state = {
asyncRequireModulePathStringLiteral: null,
dependencyCalls: new Set(),
dependencyRegistry: new DependencyRegistry(),
dependencyTransformer: options.dependencyTransformer ?? DefaultDependencyTransformer,
dependencyMapIdentifier: null,
dynamicRequires: options.dynamicRequires,
keepRequireNames: options.keepRequireNames,
allowOptionalDependencies: options.allowOptionalDependencies,
unstable_allowRequireContext: options.unstable_allowRequireContext,
unstable_isESMImportAtSource: options.unstable_isESMImportAtSource ?? null,
collectOnly: options.collectOnly,
};
(0, core_1.traverse)(ast, {
// Match new Worker() patterns
NewExpression(path, state) {
if (path.node.callee.type === 'Identifier' &&
(path.node.callee.name === 'Worker' || path.node.callee.name === 'SharedWorker')) {
const [firstArg] = path.node.arguments;
// Match: new Worker(new URL("../path/to/module", import.meta.url))
if (firstArg &&
firstArg.type === 'NewExpression' &&
firstArg.callee.type === 'Identifier' &&
firstArg.callee.name === 'URL' &&
firstArg.arguments.length > 0 &&
firstArg.arguments[0].type === 'StringLiteral') {
const moduleName = firstArg.arguments[0].value;
// Get the NodePath of the first argument of `new Worker(new URL())`
const urlArgPath = path.get('arguments')[0].get('arguments')[0];
processResolveWorkerCallWithName(moduleName, urlArgPath, state);
// Replace `new Worker` invocation with `asyncRequire.unstable_createWorker` helper call,
// creating an Object URL with a shim script that remaps `importScripts` and `fetch`, while calling the entrypoint with `importScripts`.
// This works around COEP/CORP issues, since worker entrypoint scripts aren't necessarily expected to match these headers
path.get('callee').replaceWith(makeCreateWorkerTemplate({
ASYNC_REQUIRE_MODULE_PATH: nullthrows(state.asyncRequireModulePathStringLiteral),
}));
}
}
},
CallExpression(path, state) {
if (visited.has(path.node)) {
return;
}
const callee = path.node.callee;
const name = callee.type === 'Identifier' ? callee.name : null;
if (core_1.types.isImport(callee)) {
processImportCall(path, state, {
asyncType: 'async',
isESMImport: true,
dynamicRequires: options.dynamicRequires,
});
return;
}
if (name === '__prefetchImport' && !path.scope.getBinding(name)) {
processImportCall(path, state, {
asyncType: 'prefetch',
isESMImport: true,
dynamicRequires: options.dynamicRequires,
});
return;
}
// Match `require.context`
if (
// Feature gate, defaults to `false`.
state.unstable_allowRequireContext &&
callee.type === 'MemberExpression' &&
// `require`
callee.object.type === 'Identifier' &&
callee.object.name === 'require' &&
// `context`
callee.property.type === 'Identifier' &&
callee.property.name === 'context' &&
!callee.computed &&
// Ensure `require` refers to the global and not something else.
!path.scope.getBinding('require')) {
processRequireContextCall(path, state);
visited.add(path.node);
return;
}
// Match `require.resolveWeak`
if (callee.type === 'MemberExpression' &&
// `require`
callee.object.type === 'Identifier' &&
callee.object.name === 'require' &&
// `resolveWeak`
callee.property.type === 'Identifier' &&
callee.property.name === 'resolveWeak' &&
!callee.computed &&
// Ensure `require` refers to the global and not something else.
!path.scope.getBinding('require')) {
processResolveWeakCall(path, state);
visited.add(path.node);
return;
}
// Match `require.unstable_resolveWorker`
if (callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' &&
callee.object.name === 'require' &&
callee.property.type === 'Identifier' &&
callee.property.name === 'unstable_resolveWorker' &&
!callee.computed &&
!path.scope.getBinding('require')) {
processResolveWorkerCall(path, state);
visited.add(path.node);
return;
}
// Match `require.unstable_importMaybeSync`
if (callee.type === 'MemberExpression' &&
// `require`
callee.object.type === 'Identifier' &&
callee.object.name === 'require' &&
// `unstable_importMaybeSync`
callee.property.type === 'Identifier' &&
callee.property.name === 'unstable_importMaybeSync' &&
!callee.computed &&
// Ensure `require` refers to the global and not something else.
!path.scope.getBinding('require')) {
processImportCall(path, state, {
asyncType: 'maybeSync',
// Treat require.unstable_importMaybeSync as an ESM import, like its
// async "await import()" counterpart. Subject to change while
// unstable_.
isESMImport: true,
dynamicRequires: options.dynamicRequires,
});
visited.add(path.node);
return;
}
if (name != null && state.dependencyCalls.has(name) && !path.scope.getBinding(name)) {
processRequireCall(path, state);
visited.add(path.node);
}
},
ImportDeclaration: collectImports,
ExportNamedDeclaration: collectImports,
ExportAllDeclaration: collectImports,
Program(path, state) {
state.asyncRequireModulePathStringLiteral = core_1.types.stringLiteral(options.asyncRequireModulePath);
if (options.dependencyMapName != null) {
state.dependencyMapIdentifier = core_1.types.identifier(options.dependencyMapName);
}
else {
state.dependencyMapIdentifier = path.scope.generateUidIdentifier('dependencyMap');
}
state.dependencyCalls = new Set(['require', ...options.inlineableCalls]);
},
}, undefined, state);
const collectedDependencies = state.dependencyRegistry.getDependencies();
const dependencies = new Array(collectedDependencies.length);
for (const { index, name, ...dependencyData } of collectedDependencies) {
dependencies[index] = {
name,
data: dependencyData,
};
}
return {
ast,
dependencies,
dependencyMapName: nullthrows(state.dependencyMapIdentifier).name,
};
}
/** Extract args passed to the `require.context` method. */
function getRequireContextArgs(path) {
const args = path.get('arguments');
let directory;
if (!Array.isArray(args) || args.length < 1) {
throw new InvalidRequireCallError(path);
}
else {
const result = args[0].evaluate();
if (result.confident && typeof result.value === 'string') {
directory = result.value;
}
else {
throw new InvalidRequireCallError(result.deopt ?? args[0], 'First argument of `require.context` should be a string denoting the directory to require.');
}
}
// Default to requiring through all directories.
let recursive = true;
if (args.length > 1) {
const result = args[1].evaluate();
if (result.confident && typeof result.value === 'boolean') {
recursive = result.value;
}
else if (!(result.confident && typeof result.value === 'undefined')) {
throw new InvalidRequireCallError(result.deopt ?? args[1], 'Second argument of `require.context` should be an optional boolean indicating if files should be imported recursively or not.');
}
}
// Default to all files.
let filter = { pattern: '.*', flags: '' };
if (args.length > 2) {
// evaluate() to check for undefined (because it's technically a scope lookup)
// but check the AST for the regex literal, since evaluate() doesn't do regex.
const result = args[2].evaluate();
const argNode = args[2].node;
if (argNode.type === 'RegExpLiteral') {
// TODO: Handle `new RegExp(...)` -- `argNode.type === 'NewExpression'`
filter = {
pattern: argNode.pattern,
flags: argNode.flags || '',
};
}
else if (!(result.confident && typeof result.value === 'undefined')) {
throw new InvalidRequireCallError(args[2], `Third argument of \`require.context\` should be an optional RegExp pattern matching all of the files to import, instead found node of type: ${argNode.type}.`);
}
}
// Default to `sync`.
let mode = 'sync';
if (args.length > 3) {
const result = args[3].evaluate();
if (result.confident && typeof result.value === 'string') {
mode = getContextMode(args[3], result.value);
}
else if (!(result.confident && typeof result.value === 'undefined')) {
throw new InvalidRequireCallError(result.deopt ?? args[3], 'Fourth argument of `require.context` should be an optional string "mode" denoting how the modules will be resolved.');
}
}
if (args.length > 4) {
throw new InvalidRequireCallError(path, `Too many arguments provided to \`require.context\` call. Expected 4, got: ${args.length}`);
}
return [
directory,
{
recursive,
filter,
mode,
},
];
}
function getContextMode(path, mode) {
if (mode === 'sync' || mode === 'eager' || mode === 'lazy' || mode === 'lazy-once') {
return mode;
}
throw new InvalidRequireCallError(path, `require.context "${mode}" mode is not supported. Expected one of: sync, eager, lazy, lazy-once`);
}
function processRequireContextCall(path, state) {
const [directory, contextParams] = getRequireContextArgs(path);
const transformer = state.dependencyTransformer;
const dep = registerDependency(state, {
// We basically want to "import" every file in a folder and then filter them out with the given `filter` RegExp.
name: directory,
// Capture the matching context
contextParams,
asyncType: null,
isESMImport: false,
optional: isOptionalDependency(directory, path, state),
exportNames: ['*'],
}, path);
// If the pass is only collecting dependencies then we should avoid mutating the AST,
// this enables calling collectDependencies multiple times on the same AST.
if (state.collectOnly !== true) {
// require() the generated module representing this context
path.get('callee').replaceWith(core_1.types.identifier('require'));
}
transformer.transformSyncRequire(path, dep, state);
}
function processResolveWeakCall(path, state) {
const name = getModuleNameFromCallArgs(path);
if (name == null) {
throw new InvalidRequireCallError(path);
}
const dependency = registerDependency(state, {
name,
asyncType: 'weak',
isESMImport: false,
optional: isOptionalDependency(name, path, state),
exportNames: ['*'],
}, path);
if (state.collectOnly !== true) {
path.replaceWith(makeResolveWeakTemplate({
MODULE_ID: createModuleIDExpression(dependency, state),
}));
}
}
function processResolveWorkerCall(path, state) {
const name = getModuleNameFromCallArgs(path);
if (name == null) {
throw new InvalidRequireCallError(path);
}
return processResolveWorkerCallWithName(name, path, state);
}
function processResolveWorkerCallWithName(name, path, state) {
const dependency = registerDependency(state, {
name,
asyncType: 'worker',
isESMImport: false,
optional: isOptionalDependency(name, path, state),
exportNames: ['*'],
}, path);
if (state.collectOnly !== true) {
path.replaceWith(makeResolveTemplate({
ASYNC_REQUIRE_MODULE_PATH: nullthrows(state.asyncRequireModulePathStringLiteral),
DEPENDENCY_MAP: nullthrows(state.dependencyMapIdentifier),
MODULE_ID: createModuleIDExpression(dependency, state),
}));
}
else {
// Inject the async require dependency into the dependency map so it's available in the graph during serialization and splitting.
registerDependency(state, {
name: nullthrows(state.asyncRequireModulePathStringLiteral).value,
asyncType: null,
isESMImport: false,
optional: false,
exportNames: ['*'],
}, path);
}
}
function getExportNamesFromPath(path) {
if (path.node.source) {
if (core_1.types.isExportAllDeclaration(path.node)) {
return ['*'];
}
else if (core_1.types.isExportNamedDeclaration(path.node)) {
return path.node.specifiers.map((specifier) => {
const exportedName = core_1.types.isIdentifier(specifier.exported)
? specifier.exported.name
: specifier.exported.value;
const localName = 'local' in specifier ? specifier.local.name : exportedName;
// `export { default as add } from './add'`
return specifier.type === 'ExportSpecifier' ? localName : exportedName;
});
}
else if (core_1.types.isImportDeclaration(path.node)) {
return path.node.specifiers
.map((specifier) => {
if (specifier.type === 'ImportDefaultSpecifier') {
return 'default';
}
else if (specifier.type === 'ImportNamespaceSpecifier') {
return '*';
}
return core_1.types.isImportSpecifier(specifier) && core_1.types.isIdentifier(specifier.imported)
? specifier.imported.name
: null;
})
.filter(Boolean);
}
}
return [];
}
function collectImports(path, state) {
if (path.node.source) {
registerDependency(state, {
name: path.node.source.value,
asyncType: null,
isESMImport: true,
optional: false,
exportNames: getExportNamesFromPath(path),
}, path);
}
}
/**
* @returns `true` if the import contains the magic comment for opting-out of bundling.
*/
function hasMagicImportComment(path) {
// Get first argument of import()
const [firstArg] = path.node.arguments;
// Check comments before the argument
return !!MAGIC_IMPORT_COMMENTS.some((magicComment) => firstArg?.leadingComments?.some((comment) => comment.value.includes(magicComment)) ||
path.node.leadingComments?.some((comment) => comment.value.includes(magicComment)) ||
// Get the inner comments between import and its argument
path.node.innerComments?.some((comment) => comment.value.includes(magicComment)));
}
function processImportCall(path, state, options) {
// Check both leading and inner comments
if (hasMagicImportComment(path)) {
const line = path.node.loc && path.node.loc.start && path.node.loc.start.line;
debug(`Magic comment at line ${line || '<unknown>'}: Ignoring import: ${(0, generator_1.default)(path.node).code}`);
return;
}
const name = getModuleNameFromCallArgs(path);
if (name == null) {
if (options.dynamicRequires === 'warn') {
warnDynamicRequire(path);
return;
}
throw new InvalidRequireCallError(path);
}
const dep = registerDependency(state, {
name,
asyncType: options.asyncType,
isESMImport: options.isESMImport,
optional: isOptionalDependency(name, path, state),
exportNames: ['*'],
}, path);
const transformer = state.dependencyTransformer;
switch (options.asyncType) {
case 'async':
transformer.transformImportCall(path, dep, state);
break;
case 'maybeSync':
transformer.transformImportMaybeSyncCall(path, dep, state);
break;
case 'prefetch':
transformer.transformPrefetch(path, dep, state);
break;
default:
throw new Error('Unreachable');
}
}
function warnDynamicRequire({ node }, message = '') {
const line = node.loc && node.loc.start && node.loc.start.line;
console.warn(`Dynamic import at line ${line || '<unknown>'}: ${(0, generator_1.default)(node).code}. This module may not work as intended when deployed to a runtime. ${message}`.trim());
}
function processRequireCall(path, state) {
const name = getModuleNameFromCallArgs(path);
const transformer = state.dependencyTransformer;
if (name == null) {
if (state.dynamicRequires === 'reject') {
throw new InvalidRequireCallError(path);
}
else if (state.dynamicRequires === 'warn') {
warnDynamicRequire(path);
return;
}
else {
transformer.transformIllegalDynamicRequire(path, state);
}
return;
}
let isESMImport = false;
if (state.unstable_isESMImportAtSource) {
const isImport = state.unstable_isESMImportAtSource;
const loc = getNearestLocFromPath(path);
if (loc) {
isESMImport = isImport(loc);
}
}
const dep = registerDependency(state, {
name,
asyncType: null,
isESMImport,
optional: isOptionalDependency(name, path, state),
exportNames: ['*'],
}, path);
transformer.transformSyncRequire(path, dep, state);
}
function getNearestLocFromPath(path) {
let current = path;
while (current && !current.node.loc && !current.node.METRO_INLINE_REQUIRES_INIT_LOC) {
current = current.parentPath;
}
// Avoid using the location of the `Program` node,
// to avoid conflating locations of single line code
if (current && core_1.types.isProgram(current.node)) {
current = null;
}
return current?.node.METRO_INLINE_REQUIRES_INIT_LOC ?? current?.node.loc;
}
function registerDependency(state, qualifier, path) {
const dependency = state.dependencyRegistry.registerDependency(qualifier);
const loc = getNearestLocFromPath(path);
if (loc != null) {
dependency.locs.push(loc);
}
dependency.imports += 1;
return dependency;
}
function isOptionalDependency(name, path, state) {
const { allowOptionalDependencies } = state;
// The async require module is a 'built-in'. Resolving should never fail -> treat it as non-optional.
if (name === state.asyncRequireModulePathStringLiteral?.value) {
return false;
}
const isExcluded = () => typeof allowOptionalDependencies !== 'boolean' &&
Array.isArray(allowOptionalDependencies.exclude) &&
allowOptionalDependencies.exclude.includes(name);
if (!allowOptionalDependencies || isExcluded()) {
return false;
}
// Valid statement stack for single-level try-block: expressionStatement -> blockStatement -> tryStatement
let sCount = 0;
let p = path;
while (p && sCount < 3) {
if (p.isStatement()) {
if (p.node.type === 'BlockStatement') {
return (p.parentPath != null && p.parentPath.node.type === 'TryStatement' && p.key === 'block');
}
sCount += 1;
}
p = p.parentPath;
}
return false;
}
function getModuleNameFromCallArgs(path) {
const args = path.get('arguments');
if (!Array.isArray(args) || args.length !== 1) {
throw new InvalidRequireCallError(path);
}
const result = args[0].evaluate();
if (result.confident && typeof result.value === 'string') {
return result.value;
}
return null;
}
class InvalidRequireCallError extends Error {
constructor({ node }, message) {
const line = node.loc && node.loc.start && node.loc.start.line;
super([`Invalid call at line ${line || '<unknown>'}: ${(0, generator_1.default)(node).code}`, message]
.filter(Boolean)
.join('\n'));
}
}
exports.InvalidRequireCallError = InvalidRequireCallError;
/**
* Produces a Babel template that will throw at runtime when the require call
* is reached. This makes dynamic require errors catchable by libraries that
* want to use them.
*/
const dynamicRequireErrorTemplate = core_1.template.expression(`
(function(line) {
throw new Error(
'Dynamic require defined at line ' + line + '; not supported by Metro',
);
})(LINE)
`);
/**
* Produces a Babel template that transforms an "import(...)" call into a
* "require(...)" call to the asyncRequire specified.
*/
const makeAsyncRequireTemplate = core_1.template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeAsyncRequireTemplateWithName = core_1.template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeAsyncPrefetchTemplate = core_1.template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeAsyncPrefetchTemplateWithName = core_1.template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeAsyncImportMaybeSyncTemplate = core_1.template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).unstable_importMaybeSync(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeResolveTemplate = core_1.template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).unstable_resolve(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeCreateWorkerTemplate = core_1.template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).unstable_createWorker
`);
const makeAsyncImportMaybeSyncTemplateWithName = core_1.template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).unstable_importMaybeSync(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeResolveWeakTemplate = core_1.template.expression(`
MODULE_ID
`);
const DefaultDependencyTransformer = {
transformSyncRequire(path, dependency, state) {
const moduleIDExpression = createModuleIDExpression(dependency, state);
path.node.arguments = [moduleIDExpression];
// Always add the debug name argument last
if (state.keepRequireNames || dependency.isOptional) {
path.node.arguments.push(core_1.types.stringLiteral(dependency.name));
}
},
transformImportCall(path, dependency, state) {
const keepRequireNames = state.keepRequireNames || dependency.isOptional;
const makeNode = keepRequireNames ? makeAsyncRequireTemplateWithName : makeAsyncRequireTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: nullthrows(state.asyncRequireModulePathStringLiteral),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: nullthrows(state.dependencyMapIdentifier),
...(keepRequireNames ? { MODULE_NAME: createModuleNameLiteral(dependency) } : null),
};
path.replaceWith(makeNode(opts));
},
transformImportMaybeSyncCall(path, dependency, state) {
const keepRequireNames = state.keepRequireNames || dependency.isOptional;
const makeNode = keepRequireNames
? makeAsyncImportMaybeSyncTemplateWithName
: makeAsyncImportMaybeSyncTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: nullthrows(state.asyncRequireModulePathStringLiteral),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: nullthrows(state.dependencyMapIdentifier),
...(keepRequireNames ? { MODULE_NAME: createModuleNameLiteral(dependency) } : null),
};
path.replaceWith(makeNode(opts));
},
transformPrefetch(path, dependency, state) {
const keepRequireNames = state.keepRequireNames || dependency.isOptional;
const makeNode = keepRequireNames
? makeAsyncPrefetchTemplateWithName
: makeAsyncPrefetchTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: nullthrows(state.asyncRequireModulePathStringLiteral),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: nullthrows(state.dependencyMapIdentifier),
...(keepRequireNames ? { MODULE_NAME: createModuleNameLiteral(dependency) } : null),
};
path.replaceWith(makeNode(opts));
},
transformIllegalDynamicRequire(path, state) {
path.replaceWith(dynamicRequireErrorTemplate({
LINE: core_1.types.numericLiteral(path.node.loc?.start.line ?? 0),
}));
},
};
function createModuleIDExpression(dependency, state) {
return core_1.types.memberExpression(nullthrows(state.dependencyMapIdentifier), core_1.types.numericLiteral(dependency.index), true);
}
function createModuleNameLiteral(dependency) {
return core_1.types.stringLiteral(dependency.name);
}
/**
* Given an import qualifier, return a key used to register the dependency.
* Attributes can be appended to distinguish various combinations that would
* otherwise be considered the same dependency edge.
*
* For example, the following dependencies would collapse into a single edge
* if they simply utilized the `name` property:
*
* ```
* require('./foo');
* import foo from './foo'
* await import('./foo')
* require.context('./foo');
* require.context('./foo', true, /something/);
* require.context('./foo', false, /something/);
* require.context('./foo', false, /something/, 'lazy');
* ```
*
* This method should be utilized by `registerDependency`.
*/
function getKeyForDependency(qualifier) {
const { asyncType, contextParams, isESMImport, name } = qualifier;
let key = [name, isESMImport ? 'import' : 'require'].join('\0');
if (asyncType != null) {
key += '\0' + asyncType;
}
// Add extra qualifiers when using `require.context` to prevent collisions.
if (contextParams) {
// NOTE(EvanBacon): Keep this synchronized with `RequireContextParams`, if any other properties are added
// then this key algorithm should be updated to account for those properties.
// Example: `./directory__true__/foobar/m__lazy`
key += [
'',
'context',
String(contextParams.recursive),
String(contextParams.filter.pattern),
String(contextParams.filter.flags),
contextParams.mode,
].join('\0');
}
return key;
}
function hashKey(key) {
return crypto.createHash('sha1').update(key).digest('base64');
}
class DependencyRegistry {
_dependencies = new Map();
registerDependency(qualifier) {
const key = getKeyForDependency(qualifier);
let dependency = this._dependencies.get(key) ?? null;
if (dependency == null) {
const newDependency = {
name: qualifier.name,
asyncType: qualifier.asyncType,
isESMImport: qualifier.isESMImport,
locs: [],
index: this._dependencies.size,
key: hashKey(key),
exportNames: qualifier.exportNames,
imports: 0,
};
if (qualifier.optional) {
newDependency.isOptional = true;
}
if (qualifier.contextParams) {
newDependency.contextParams = qualifier.contextParams;
}
dependency = newDependency;
}
else {
if (dependency.isOptional && !qualifier.optional) {
dependency = {
...dependency,
isOptional: false,
};
}
dependency = {
...dependency,
exportNames: [...new Set(dependency.exportNames.concat(qualifier.exportNames))],
};
}
this._dependencies.set(key, dependency);
return dependency;
}
getDependencies() {
return Array.from(this._dependencies.values());
}
}
exports.default = collectDependencies;
//# sourceMappingURL=collect-dependencies.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
/**
* Copyright 2024-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* https://github.com/facebook/metro/blob/96c6b893eb77b5929b6050d7189905232ddf6d6d/packages/metro-transform-worker/src/index.js#L679
*/
import type { MetroSourceMapSegmentTuple } from '@expo/metro/metro-source-map';
export declare function countLinesAndTerminateMap(code: string, map: readonly MetroSourceMapSegmentTuple[]): {
lineCount: number;
map: MetroSourceMapSegmentTuple[];
};

View File

@@ -0,0 +1,43 @@
"use strict";
/**
* Copyright 2024-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* https://github.com/facebook/metro/blob/96c6b893eb77b5929b6050d7189905232ddf6d6d/packages/metro-transform-worker/src/index.js#L679
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.countLinesAndTerminateMap = countLinesAndTerminateMap;
function countLinesAndTerminateMap(code, map) {
const NEWLINE = /\r\n?|\n|\u2028|\u2029/g;
let lineCount = 1;
let lastLineStart = 0;
// Count lines and keep track of where the last line starts
for (const match of code.matchAll(NEWLINE)) {
if (match.index == null)
continue;
lineCount++;
lastLineStart = match.index + match[0].length;
}
const lastLineLength = code.length - lastLineStart;
const lastLineIndex1Based = lineCount;
const lastLineNextColumn0Based = lastLineLength;
// If there isn't a mapping at one-past-the-last column of the last line,
// add one that maps to nothing. This ensures out-of-bounds lookups hit the
// null mapping rather than aliasing to whichever mapping happens to be last.
// ASSUMPTION: Mappings are generated in order of increasing line and column.
const lastMapping = map[map.length - 1];
const terminatingMapping = [lastLineIndex1Based, lastLineNextColumn0Based];
if (!lastMapping ||
lastMapping[0] !== terminatingMapping[0] ||
lastMapping[1] !== terminatingMapping[1]) {
return {
lineCount,
map: map.concat([terminatingMapping]),
};
}
return { lineCount, map: [...map] };
}
//# sourceMappingURL=count-lines.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"count-lines.js","sourceRoot":"","sources":["../../src/transform-worker/count-lines.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAIH,8DAsCC;AAtCD,SAAgB,yBAAyB,CACvC,IAAY,EACZ,GAA0C;IAK1C,MAAM,OAAO,GAAG,yBAAyB,CAAC;IAC1C,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,2DAA2D;IAC3D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI;YAAE,SAAS;QAClC,SAAS,EAAE,CAAC;QACZ,aAAa,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAChD,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC;IACnD,MAAM,mBAAmB,GAAG,SAAS,CAAC;IACtC,MAAM,wBAAwB,GAAG,cAAc,CAAC;IAEhD,yEAAyE;IACzE,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxC,MAAM,kBAAkB,GAAqB,CAAC,mBAAmB,EAAE,wBAAwB,CAAC,CAAC;IAC7F,IACE,CAAC,WAAW;QACZ,WAAW,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,CAAC;QACxC,WAAW,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,CAAC,EACxC,CAAC;QACD,OAAO;YACL,SAAS;YACT,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;SACtC,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;AACtC,CAAC"}

View File

@@ -0,0 +1,45 @@
import type { TransformResult, Warning } from 'lightningcss';
export declare function transformCssModuleWeb(props: {
filename: string;
src: string;
options: {
projectRoot: string;
minify: boolean;
dev: boolean;
sourceMap: boolean;
reactServer: boolean;
};
}): Promise<{
externalImports: {
url: string;
supports: string | null;
media: string | null;
}[];
code: string;
dependencies: Readonly<{
data: import("./collect-dependencies").DependencyData;
name: string;
}>[];
output: string;
css: string;
map: void | Uint8Array<ArrayBufferLike>;
}>;
export declare function convertLightningCssToReactNativeWebStyleSheet(input: import('lightningcss').CSSModuleExports): {
styles: Record<string, string>;
reactNativeWeb: Record<string, any>;
variables: Record<string, string>;
};
export declare function matchCssModule(filePath: string): boolean;
export declare function printCssWarnings(filename: string, code: string, warnings?: Warning[]): void;
export declare function collectCssImports(filename: string, originalCode: string, code: string, cssResults: Pick<TransformResult, 'dependencies' | 'exports'>): {
externalImports: {
url: string;
supports: string | null;
media: string | null;
}[];
code: string;
dependencies: Readonly<{
data: import("./collect-dependencies").DependencyData;
name: string;
}>[];
};

View File

@@ -0,0 +1,158 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformCssModuleWeb = transformCssModuleWeb;
exports.convertLightningCssToReactNativeWebStyleSheet = convertLightningCssToReactNativeWebStyleSheet;
exports.matchCssModule = matchCssModule;
exports.printCssWarnings = printCssWarnings;
exports.collectCssImports = collectCssImports;
const code_frame_1 = __importDefault(require("@babel/code-frame"));
const browserslist_1 = require("./browserslist");
const css_1 = require("./css");
const RNW_CSS_CLASS_ID = '_';
async function transformCssModuleWeb(props) {
const { transform } = require('lightningcss');
// TODO: Add bundling to resolve imports
// https://lightningcss.dev/bundling.html#bundling-order
const cssResults = transform({
filename: props.filename,
code: Buffer.from(props.src),
sourceMap: props.options.sourceMap,
cssModules: {
// Prevent renaming CSS variables to ensure
// variables created in global files are available.
dashedIdents: false,
},
errorRecovery: true,
analyzeDependencies: true,
// cssModules: true,
projectRoot: props.options.projectRoot,
minify: props.options.minify,
// @ts-expect-error: Added for testing against virtual file system.
resolver: props.options._test_resolveCss,
targets: await (0, browserslist_1.getBrowserslistTargets)(props.options.projectRoot),
include: 1, // Nesting
});
printCssWarnings(props.filename, props.src, cssResults.warnings);
const { styles, reactNativeWeb, variables } = convertLightningCssToReactNativeWebStyleSheet(cssResults.exports);
let outputModule = `module.exports=Object.assign(${JSON.stringify(styles)},{unstable_styles:${JSON.stringify(reactNativeWeb)}},${JSON.stringify(variables)});`;
const cssImports = collectCssImports(props.filename, props.src, cssResults.code.toString(), cssResults);
if (props.options.dev) {
const runtimeCss = (0, css_1.wrapDevelopmentCSS)({
reactServer: props.options.reactServer,
filename: props.filename,
src: cssImports.code,
});
outputModule += '\n' + runtimeCss;
}
return {
output: outputModule,
css: cssImports.code,
map: cssResults.map,
...cssImports,
};
}
function convertLightningCssToReactNativeWebStyleSheet(input) {
const styles = {};
const reactNativeWeb = {};
const variables = {};
// e.g. { container: { name: 'ahs8IW_container', composes: [], isReferenced: false }, }
Object.entries(input).map(([key, value]) => {
// order matters here
let className = value.name;
if (value.composes.length) {
className += ' ' + value.composes.map((value) => value.name).join(' ');
}
// CSS Variables will be `{string: string}`
if (key.startsWith('--')) {
variables[key] = className;
}
styles[key] = className;
reactNativeWeb[key] = { $$css: true, [RNW_CSS_CLASS_ID]: className };
return {
[key]: { $$css: true, [RNW_CSS_CLASS_ID]: className },
};
});
return { styles, reactNativeWeb, variables };
}
function matchCssModule(filePath) {
return !!/\.module(\.(native|ios|android|web))?\.(css|s[ac]ss)$/.test(filePath);
}
function printCssWarnings(filename, code, warnings) {
if (warnings) {
for (const warning of warnings) {
console.warn(`Warning: ${warning.message} (${filename}:${warning.loc.line}:${warning.loc.column}):\n${(0, code_frame_1.default)(code, warning.loc.line, warning.loc.column)}`);
}
}
}
function isExternalUrl(url) {
return url.match(/^\w+:\/\//);
}
function collectCssImports(filename, originalCode, code, cssResults) {
const externalImports = [];
const cssModuleDeps = [];
if (cssResults.dependencies) {
for (const dep of cssResults.dependencies) {
if (dep.type === 'import') {
// If the URL starts with `http://` or other protocols, we'll treat it like an external import.
if (isExternalUrl(dep.url)) {
externalImports.push({
url: dep.url,
supports: dep.supports,
media: dep.media,
});
}
else {
// If the import is a local file, then add it as a JS dependency so the bundler can resolve it.
cssModuleDeps.push({
name: dep.url,
data: {
asyncType: null,
isESMImport: false,
isOptional: false,
locs: [
{
start: {
line: dep.loc.start.line,
column: dep.loc.start.column,
index: -1,
},
end: {
line: dep.loc.end.line,
column: dep.loc.end.column,
index: -1,
},
filename,
identifierName: undefined,
},
],
css: {
url: dep.url,
media: dep.media,
supports: dep.supports,
},
exportNames: [],
key: dep.url,
},
});
}
}
else if (dep.type === 'url') {
// Put the URL back into the code.
code = code.replaceAll(dep.placeholder, dep.url);
const isSupported = // External URL
isExternalUrl(dep.url) ||
// Data URL, DOM id, or public file.
dep.url.match(/^(data:|[#/])/);
if (!isSupported) {
// Assert that syntax like `background: url('./img.png');` is not supported yet.
console.warn(`Importing local resources in CSS is not supported yet. (${filename}:${dep.loc.start.line}:${dep.loc.start.column}):\n${(0, code_frame_1.default)(originalCode, dep.loc.start.line, dep.loc.start.column)}`);
}
}
}
}
return { externalImports, code, dependencies: cssModuleDeps };
}
//# sourceMappingURL=css-modules.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
export declare function pathToHtmlSafeName(path: string): string;
export declare function getHotReplaceTemplate(id: string): string;
export declare function wrapDevelopmentCSS(props: {
src: string;
filename: string;
reactServer: boolean;
}): string;
export declare function escapeBackticksAndOctals(str: string): string;

View File

@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pathToHtmlSafeName = pathToHtmlSafeName;
exports.getHotReplaceTemplate = getHotReplaceTemplate;
exports.wrapDevelopmentCSS = wrapDevelopmentCSS;
exports.escapeBackticksAndOctals = escapeBackticksAndOctals;
function pathToHtmlSafeName(path) {
return path.replace(/[^a-zA-Z0-9_]/g, '_');
}
function getHotReplaceTemplate(id) {
// In dev mode, we need to replace the style tag instead of appending it
// use the path as the expo-css-hmr attribute to find the style tag
// to replace.
const attr = JSON.stringify(pathToHtmlSafeName(id));
return `style.setAttribute('data-expo-css-hmr', ${attr});
const previousStyle = document.querySelector('[data-expo-css-hmr=${attr}]');
if (previousStyle) {
previousStyle.parentNode.replaceChild(style, previousStyle);
}`;
}
function wrapDevelopmentCSS(props) {
const withBackTicksEscaped = escapeBackticksAndOctals(props.src);
// Ensure we had HMR support to the CSS module in development.
// Why?
// -----
// • Metro recompiles *every* direct dependency of the file you edit. When you
// change a component that imports a CSS-module, Metro re-emits the JS stub
// that represents that `*.module.css` file, marking it "updated".
// • The stub exports a plain object (class-name strings) which React-Refresh
// does **not** recognise as a component -> the stub itself cannot be a refresh
// boundary.
// • If an UPDATED, NON-BOUNDARY module bubbles all the way up the dependency
// graph without meeting another UPDATED boundary, React-Refresh falls
// back to a full reload — wiping React state and breaking fast-refresh.
// • That is exactly what happened: `modal.module.css` changed, its parent
// (ModalStack.web.tsx) *didn't* change, so refresh bailed out.
//
// Solution
// ---------
// We import the CSS here and immediately call `module.hot.accept()` so the
// update is consumed at this level. React-Refresh no longer has to walk past
// the stub, the rest of the graph (including the edited component) hot-swaps
// normally, and local state is preserved.
const injectClientStyle = `const head = document.head || document.getElementsByTagName('head')[0];
const style = document.createElement('style');
${getHotReplaceTemplate(props.filename)}
style.setAttribute('data-expo-loader', 'css');
if (!style.parentNode) head.appendChild(style);
const css = \`${withBackTicksEscaped}\`;
if (style.styleSheet){
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}`;
// When bundling React Server Components, add an iife which will broadcast the client JS script to the root client bundle.
// This will ensure the global CSS is available in the browser in development.
if (props.reactServer) {
const injectStyle = `(()=>{${injectClientStyle}})();`;
return `(() => {
if (typeof __expo_rsc_inject_module === 'function') {
__expo_rsc_inject_module({
id: ${JSON.stringify(props.filename)},
code: ${JSON.stringify(injectStyle)},
});
} else {
throw new Error('RSC SSR CSS injection function is not found (__expo_rsc_inject_module)');
}
})();`;
}
const injectStyle = `(() => {
if (typeof window === 'undefined') {
return
}
${injectClientStyle}
if (module.hot) module.hot.accept();
})();`;
return injectStyle;
}
function escapeBackticksAndOctals(str) {
if (typeof str !== 'string') {
return '';
}
return str
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/[\x00-\x07]/g, (match) => `\\0${match.charCodeAt(0).toString(8)}`);
}
//# sourceMappingURL=css.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/transform-worker/css.ts"],"names":[],"mappings":";;AAAA,gDAEC;AAED,sDAUC;AAED,gDA+DC;AAED,4DASC;AA1FD,SAAgB,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,SAAgB,qBAAqB,CAAC,EAAU;IAC9C,wEAAwE;IACxE,mEAAmE;IACnE,cAAc;IACd,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,2CAA2C,IAAI;qEACa,IAAI;;;IAGrE,CAAC;AACL,CAAC;AAED,SAAgB,kBAAkB,CAAC,KAA8D;IAC/F,MAAM,oBAAoB,GAAG,wBAAwB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEjE,8DAA8D;IAC9D,OAAO;IACP,QAAQ;IACR,8EAA8E;IAC9E,6EAA6E;IAC7E,oEAAoE;IACpE,6EAA6E;IAC7E,iFAAiF;IACjF,cAAc;IACd,6EAA6E;IAC7E,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,iEAAiE;IACjE,EAAE;IACF,WAAW;IACX,YAAY;IACZ,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,0CAA0C;IAE1C,MAAM,iBAAiB,GAAG;;EAE1B,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC;;;gBAGvB,oBAAoB;;;;;EAKlC,CAAC;IAED,0HAA0H;IAC1H,8EAA8E;IAC9E,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACtB,MAAM,WAAW,GAAG,SAAS,iBAAiB,OAAO,CAAC;QACtD,OAAO;;;UAGD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC5B,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;MAKjC,CAAC;IACL,CAAC;IAED,MAAM,WAAW,GAAG;;;;EAIpB,iBAAiB;;;MAGb,CAAC;IAEL,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAgB,wBAAwB,CAAC,GAAW;IAClD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,GAAG;SACP,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACjF,CAAC"}

View File

@@ -0,0 +1,7 @@
/**
* Copyright 2025-present 650 Industries (Expo). All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export declare function parseEnvFile(src: string, isClient: boolean): Record<string, string>;

View File

@@ -0,0 +1,25 @@
"use strict";
/**
* Copyright 2025-present 650 Industries (Expo). All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseEnvFile = parseEnvFile;
const env_1 = require("@expo/env");
function parseEnvFile(src, isClient) {
const output = {};
const env = (0, env_1.parseEnv)(src);
for (const key of Object.keys(env)) {
if (env[key] != null) {
if (isClient && !key.startsWith('EXPO_PUBLIC_')) {
// Don't include non-public variables in the client bundle.
continue;
}
output[key] = env[key];
}
}
return output;
}
//# sourceMappingURL=dot-env-development.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"dot-env-development.js","sourceRoot":"","sources":["../../src/transform-worker/dot-env-development.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAIH,oCAaC;AAfD,mCAAqC;AAErC,SAAgB,YAAY,CAAC,GAAW,EAAE,QAAiB;IACzD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAA,cAAQ,EAAC,GAAG,CAAC,CAAC;IAC1B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,QAAQ,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;gBAChD,2DAA2D;gBAC3D,SAAS;YACX,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}

View File

@@ -0,0 +1,24 @@
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { type AssetData } from '@expo/metro/metro/Assets';
import type { Module, ReadOnlyDependencies } from '@expo/metro/metro/DeltaBundler/types';
type Options = {
processModuleFilter: (modules: Module) => boolean;
assetPlugins: readonly string[];
platform?: string | null;
projectRoot: string;
publicPath: string;
isHosted?: boolean;
};
export declare function getUniversalAssetData(assetPath: string, localPath: string, assetDataPlugins: readonly string[], platform: string | null | undefined, publicPath: string, isHosted?: boolean): Promise<HashedAssetData>;
export type HashedAssetData = AssetData & {
fileHashes: string[];
_name?: string;
};
export default function getAssets(dependencies: ReadOnlyDependencies, options: Options): Promise<HashedAssetData[]>;
export {};

View File

@@ -0,0 +1,98 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getUniversalAssetData = getUniversalAssetData;
exports.default = getAssets;
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const Assets_1 = require("@expo/metro/metro/Assets");
// NOTE(@kitten): jest-resolver -> resolve.exports bug (https://github.com/lukeed/resolve.exports/issues/40)
const js_js_1 = require("@expo/metro/metro/DeltaBundler/Serializers/helpers/js.js");
const node_crypto_1 = __importDefault(require("node:crypto"));
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const debug = require('debug')('expo:metro-config:assets');
function getMD5ForData(data) {
if (data.length === 1)
return data[0];
const hash = node_crypto_1.default.createHash('md5');
hash.update(data.join(''));
return hash.digest('hex');
}
function getMD5ForFilePathAsync(path) {
return new Promise((resolve, reject) => {
const output = node_crypto_1.default.createHash('md5');
const input = node_fs_1.default.createReadStream(path);
input.on('error', (err) => reject(err));
output.on('error', (err) => reject(err));
output.once('readable', () => resolve(output.read().toString('hex')));
input.pipe(output);
});
}
function isHashedAssetData(asset) {
if ('fileHashes' in asset && Array.isArray(asset.fileHashes)) {
return true;
}
return false;
}
async function ensureOtaAssetHashesAsync(asset) {
// Legacy cases where people have the `expo-asset/tools/hashAssetFiles` set still.
if (isHashedAssetData(asset)) {
debug('fileHashes already added, skipping injection for: ' + asset.name);
return asset;
}
const hashes = await Promise.all(asset.files.map(getMD5ForFilePathAsync));
// New version where we run the asset plugin every time.
asset.fileHashes = hashes;
// Convert the `../` segments of the server URL to `_` to support monorepos.
// This same transformation takes place in `AssetSourceResolver.web` (expo-assets, expo-image) and `persistMetroAssets` of Expo CLI,
// this originally came from the Metro opinion https://github.com/react-native-community/cli/blob/2204d357379e2067cebe2791e90388f7e97fc5f5/packages/cli-plugin-metro/src/commands/bundle/getAssetDestPathIOS.ts#L19C5-L19C10
if (asset.httpServerLocation.includes('?export_path=')) {
// @ts-expect-error: marked as read-only
asset.httpServerLocation = asset.httpServerLocation
.match(/\?export_path=(.*)/)[1]
.replace(/\.\.\//g, '_');
}
// URL encode asset paths defined as `?export_path` or `?unstable_path` query parameters.
// Decoding should be done automatically when parsing the URL through Node or the browser.
const assetPathQueryParameter = asset.httpServerLocation.match(/\?(export_path|unstable_path)=(.*)/);
if (assetPathQueryParameter && assetPathQueryParameter[2]) {
const assetPath = assetPathQueryParameter[2];
// @ts-expect-error: marked as read-only
asset.httpServerLocation = asset.httpServerLocation.replace(assetPath, encodeURIComponent(assetPath));
}
return asset;
}
async function getUniversalAssetData(assetPath, localPath, assetDataPlugins, platform, publicPath, isHosted = false) {
const metroAssetData = await (0, Assets_1.getAssetData)(assetPath, localPath, assetDataPlugins, platform, publicPath);
const data = await ensureOtaAssetHashesAsync(metroAssetData);
// NOTE(EvanBacon): This is where we modify the asset to include a hash in the name for web cache invalidation.
if ((isHosted || platform === 'web') && publicPath.includes('?export_path=')) {
// `local-image.[contenthash]`. Using `.` but this won't work if we ever apply to Android because Android res files cannot contain `.`.
// TODO: Prevent one multi-res image from updating the hash in all images.
// @ts-expect-error: name is typed as readonly.
data.name = `${data.name}.${getMD5ForData(data.fileHashes)}`;
}
return data;
}
async function getAssets(dependencies, options) {
const promises = [];
const { processModuleFilter } = options;
for (const module of dependencies.values()) {
if ((0, js_js_1.isJsModule)(module) &&
processModuleFilter(module) &&
(0, js_js_1.getJsOutput)(module).type === 'js/module/asset' &&
node_path_1.default.relative(options.projectRoot, module.path) !== 'package.json') {
promises.push(getUniversalAssetData(module.path, node_path_1.default.relative(options.projectRoot, module.path), options.assetPlugins, options.platform, options.publicPath, options.isHosted));
}
}
return await Promise.all(promises);
}
//# sourceMappingURL=getAssets.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getAssets.js","sourceRoot":"","sources":["../../src/transform-worker/getAssets.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,50 @@
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Fork of the Metro transformer worker, but with additional transforms moved to `babel-preset-expo` and modifications made for web support.
* https://github.com/facebook/metro/blob/412771475c540b6f85d75d9dcd5a39a6e0753582/packages/metro-transform-worker/src/index.js#L1
*/
import { types as t } from '@babel/core';
import type { MetroSourceMapSegmentTuple } from '@expo/metro/metro-source-map';
import type { JsTransformerConfig, JsTransformOptions } from '@expo/metro/metro-transform-worker';
import { InvalidRequireCallError as InternalInvalidRequireCallError, CollectedDependencies, Options as CollectDependenciesOptions } from './collect-dependencies';
import { ExpoJsOutput } from '../serializer/jsOutput';
export { JsTransformOptions };
interface TransformResponse {
readonly dependencies: CollectedDependencies['dependencies'];
readonly output: readonly ExpoJsOutput[];
}
export declare class InvalidRequireCallError extends Error {
innerError: InternalInvalidRequireCallError;
filename: string;
constructor(innerError: InternalInvalidRequireCallError, filename: string);
}
export declare const minifyCode: (config: Pick<JsTransformerConfig, "minifierPath" | "minifierConfig">, filename: string, code: string, source: string, map: MetroSourceMapSegmentTuple[], reserved?: string[]) => Promise<{
code: string;
map: MetroSourceMapSegmentTuple[];
}>;
export declare function applyImportSupport<TFile extends t.File>(ast: TFile, { filename, options, importDefault, importAll, collectLocations, performConstantFolding, }: {
filename: string;
options: Pick<JsTransformOptions, 'experimentalImportSupport' | 'inlineRequires' | 'nonInlinedRequires' | 'customTransformOptions'>;
importDefault: string;
importAll: string;
collectLocations?: boolean;
performConstantFolding?: boolean;
}): {
ast: TFile;
metadata?: any;
};
export declare function transform(config: JsTransformerConfig, projectRoot: string, filename: string, data: Buffer, options: JsTransformOptions): Promise<TransformResponse>;
export declare function getCacheKey(config: JsTransformerConfig): string;
export declare function collectDependenciesForShaking(ast: t.File, options: CollectDependenciesOptions): Readonly<{
ast: t.File;
dependencyMapName: string;
dependencies: readonly Readonly<{
data: import("./collect-dependencies").DependencyData;
name: string;
}>[];
}>;

View File

@@ -0,0 +1,661 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.minifyCode = exports.InvalidRequireCallError = void 0;
exports.applyImportSupport = applyImportSupport;
exports.transform = transform;
exports.getCacheKey = getCacheKey;
exports.collectDependenciesForShaking = collectDependenciesForShaking;
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Fork of the Metro transformer worker, but with additional transforms moved to `babel-preset-expo` and modifications made for web support.
* https://github.com/facebook/metro/blob/412771475c540b6f85d75d9dcd5a39a6e0753582/packages/metro-transform-worker/src/index.js#L1
*/
const core_1 = require("@babel/core");
const generator_1 = __importDefault(require("@babel/generator"));
const JsFileWrapping = __importStar(require("@expo/metro/metro/ModuleGraph/worker/JsFileWrapping"));
const generateImportNames_1 = __importDefault(require("@expo/metro/metro/ModuleGraph/worker/generateImportNames"));
const importLocationsPlugin_1 = require("@expo/metro/metro/ModuleGraph/worker/importLocationsPlugin");
const metro_cache_1 = require("@expo/metro/metro-cache");
const metro_cache_key_1 = require("@expo/metro/metro-cache-key");
const metro_source_map_1 = require("@expo/metro/metro-source-map");
const metroTransformPlugins = __importStar(require("@expo/metro/metro-transform-plugins"));
const node_assert_1 = __importDefault(require("node:assert"));
const assetTransformer = __importStar(require("./asset-transformer"));
const collect_dependencies_1 = __importStar(require("./collect-dependencies"));
const count_lines_1 = require("./count-lines");
const resolveOptions_1 = require("./resolveOptions");
const transform_plugins_1 = require("../transform-plugins");
const getMinifier_1 = require("./utils/getMinifier");
class InvalidRequireCallError extends Error {
innerError;
filename;
constructor(innerError, filename) {
super(`${filename}:${innerError.message}`);
this.innerError = innerError;
this.filename = filename;
}
}
exports.InvalidRequireCallError = InvalidRequireCallError;
// asserts non-null
function nullthrows(x, message) {
(0, node_assert_1.default)(x != null, message);
return x;
}
function getDynamicDepsBehavior(inPackages, filename) {
switch (inPackages) {
case 'reject':
return 'reject';
case 'throwAtRuntime':
return /(?:^|[/\\])node_modules[/\\]/.test(filename) ? inPackages : 'reject';
default:
throw new Error(`invalid value for dynamic deps behavior: \`${inPackages}\``);
}
}
const minifyCode = async (config, filename, code, source, map, reserved = []) => {
const sourceMap = (0, metro_source_map_1.fromRawMappings)([
{
code,
source,
map,
// functionMap is overridden by the serializer
functionMap: null,
path: filename,
// isIgnored is overriden by the serializer
isIgnored: false,
},
]).toMap(undefined, {});
const minify = (0, getMinifier_1.getMinifier)(config.minifierPath);
try {
const minified = await minify({
code,
map: sourceMap,
filename,
reserved,
config: config.minifierConfig,
});
return {
code: minified.code,
map: minified.map ? (0, metro_source_map_1.toBabelSegments)(minified.map).map(metro_source_map_1.toSegmentTuple) : [],
};
}
catch (error) {
if (error.constructor.name === 'JS_Parse_Error') {
throw new Error(`${error.message} in file ${filename} at ${error.line}:${error.col}`);
}
throw error;
}
};
exports.minifyCode = minifyCode;
function applyUseStrictDirective(ast) {
// Add "use strict" if the file was parsed as a module, and the directive did
// not exist yet.
const { directives } = ast.program;
if (ast.program.sourceType === 'module' &&
directives != null &&
directives.findIndex((d) => d.value.value === 'use strict') === -1) {
directives.push(core_1.types.directive(core_1.types.directiveLiteral('use strict')));
}
}
function applyImportSupport(ast, { filename, options, importDefault, importAll, collectLocations, performConstantFolding, }) {
// Perform the import-export transform (in case it's still needed), then
// fold requires and perform constant folding (if in dev).
const plugins = [];
const babelPluginOpts = {
...options,
inlineableCalls: [importDefault, importAll],
importDefault,
importAll,
};
if (collectLocations) {
plugins.push(
// TODO: Only enable this during reconciling.
importLocationsPlugin_1.importLocationsPlugin);
}
// NOTE(EvanBacon): This is effectively a replacement for the `@babel/plugin-transform-modules-commonjs`
// plugin that's running in `@react-native/babel-preset`, but with shared names for inlining requires.
if (options.experimentalImportSupport === true) {
const liveBindings = options.customTransformOptions?.liveBindings !== 'false';
plugins.push([
liveBindings ? transform_plugins_1.importExportLiveBindingsPlugin : transform_plugins_1.importExportPlugin,
{ ...babelPluginOpts, performConstantFolding },
]);
}
// NOTE(EvanBacon): This can basically never be safely enabled because it doesn't respect side-effects and
// has no ability to respect side-effects because the transformer hasn't collected all dependencies yet.
if (options.inlineRequires) {
plugins.push([
metroTransformPlugins.inlineRequiresPlugin,
{
...babelPluginOpts,
ignoredRequires: options.nonInlinedRequires,
},
]);
}
// NOTE(EvanBacon): We apply this conditionally in `babel-preset-expo` with other AST transforms.
// plugins.push([metroTransformPlugins.inlinePlugin, babelPluginOpts]);
// TODO: This MUST be run even though no plugins are added, otherwise the babel runtime generators are broken.
if (plugins.length) {
return nullthrows(
// @ts-expect-error
(0, core_1.transformFromAstSync)(ast, '', {
ast: true,
babelrc: false,
code: false,
configFile: false,
comments: true,
filename,
plugins,
sourceMaps: false,
// NOTE(kitten): This was done to wipe the paths/scope caches, which the `constantFoldingPlugin` needs to work,
// but has been replaced with `programPath.scope.crawl()`.
// Old Note from Metro:
// > Not-Cloning the input AST here should be safe because other code paths above this call
// > are mutating the AST as well and no code is depending on the original AST.
// > However, switching the flag to false caused issues with ES Modules if `experimentalImportSupport` isn't used https://github.com/facebook/metro/issues/641
// > either because one of the plugins is doing something funky or Babel messes up some caches.
// > Make sure to test the above mentioned case before flipping the flag back to false.
cloneInputAst: false,
}));
}
return { ast };
}
function performConstantFolding(ast, { filename }) {
// NOTE(kitten): Any Babel helpers that have been added (`path.hub.addHelper(...)`) will usually not have any
// references, and hence the `constantFoldingPlugin` below will remove them.
// To fix the references we add an explicit `programPath.scope.crawl()`. Alternatively, we could also wipe the
// Babel traversal cache (`traverse.cache.clear()`)
const clearProgramScopePlugin = {
visitor: {
Program: {
enter(path) {
path.scope.crawl();
},
},
},
};
// Run the constant folding plugin in its own pass, avoiding race conditions
// with other plugins that have exit() visitors on Program (e.g. the ESM
// transform).
ast = nullthrows(
// @ts-expect-error
(0, core_1.transformFromAstSync)(ast, '', {
ast: true,
babelrc: false,
code: false,
configFile: false,
comments: true,
filename,
plugins: [clearProgramScopePlugin, metroTransformPlugins.constantFoldingPlugin],
sourceMaps: false,
// NOTE(kitten): In Metro, this is also false, but only works because the prior run of `transformFromAstSync` was always
// running with `cloneInputAst: true`.
// This isn't needed anymore since `clearProgramScopePlugin` re-crawls the ASTs scope instead.
cloneInputAst: false,
}).ast);
return ast;
}
async function transformJS(file, { config, options }) {
const targetEnv = options.customTransformOptions?.environment;
const isServerEnv = targetEnv === 'node' || targetEnv === 'react-server';
const optimize =
// Ensure we don't enable tree shaking for scripts or assets.
file.type === 'js/module' &&
String(options.customTransformOptions?.optimize) === 'true' &&
// Disable tree shaking on JSON files.
!file.filename.match(/\.(json|s?css|sass)$/);
const unstable_disableModuleWrapping = optimize || config.unstable_disableModuleWrapping;
if (optimize && !options.experimentalImportSupport) {
// Add a warning so devs can incrementally migrate since experimentalImportSupport may cause other issues in their app.
throw new Error('Experimental graph optimizations only work with experimentalImportSupport enabled.');
}
// Transformers can output null ASTs (if they ignore the file). In that case
// we need to parse the module source code to get their AST.
let ast = file.ast ?? nullthrows((0, core_1.parse)(file.code, { sourceType: 'unambiguous' }));
// NOTE(EvanBacon): This can be really expensive on larger files. We should replace it with a cheaper alternative that just iterates and matches.
const { importDefault, importAll } = (0, generateImportNames_1.default)(ast);
// Add "use strict" if the file was parsed as a module, and the directive did
// not exist yet.
applyUseStrictDirective(ast);
const unstable_renameRequire = config.unstable_renameRequire;
// NOTE(@hassankhan): Constant folding can be an expensive/slow operation, so we limit it to
// production builds, or files that have specifically seen a change in their exports
if (!options.dev || file.performConstantFolding) {
ast = performConstantFolding(ast, { filename: file.filename });
}
// Disable all Metro single-file optimizations when full-graph optimization will be used.
if (!optimize) {
ast = applyImportSupport(ast, {
filename: file.filename,
performConstantFolding: Boolean(file.performConstantFolding),
options,
importDefault,
importAll,
}).ast;
}
let dependencyMapName = '';
let dependencies;
let wrappedAst;
// If the module to transform is a script (meaning that is not part of the
// dependency graph and it code will just be prepended to the bundle modules),
// we need to wrap it differently than a commonJS module (also, scripts do
// not have dependencies).
let collectDependenciesOptions;
if (file.type === 'js/script') {
dependencies = [];
wrappedAst = JsFileWrapping.wrapPolyfill(ast);
}
else {
try {
const importDeclarationLocs = file.unstable_importDeclarationLocs ?? null;
// These values must be serializable to JSON as they're stored in the transform cache when tree shaking is enabled.
collectDependenciesOptions = {
asyncRequireModulePath: config.asyncRequireModulePath,
dependencyTransformer: config.unstable_disableModuleWrapping === true
? disabledDependencyTransformer
: undefined,
dynamicRequires: isServerEnv
? // NOTE(EvanBacon): Allow arbitrary imports in server environments.
// This requires a patch to Metro collectDeps.
'warn'
: getDynamicDepsBehavior(config.dynamicDepsInPackages, file.filename),
inlineableCalls: [importDefault, importAll],
keepRequireNames: options.dev,
allowOptionalDependencies: config.allowOptionalDependencies,
dependencyMapName: config.unstable_dependencyMapReservedName,
unstable_allowRequireContext: config.unstable_allowRequireContext,
unstable_isESMImportAtSource: null,
// If tree shaking is enabled, then preserve the original require calls.
// This ensures require.context calls are not broken.
collectOnly: optimize === true,
};
({ ast, dependencies, dependencyMapName } = (0, collect_dependencies_1.default)(ast, {
...collectDependenciesOptions,
// This setting shouldn't be shared with the tree shaking transformer.
dependencyTransformer: unstable_disableModuleWrapping === true ? disabledDependencyTransformer : undefined,
unstable_isESMImportAtSource: importDeclarationLocs != null
? (loc) => importDeclarationLocs.has((0, importLocationsPlugin_1.locToKey)(loc))
: null,
}));
// Ensure we use the same name for the second pass of the dependency collection in the serializer.
collectDependenciesOptions = {
...collectDependenciesOptions,
dependencyMapName,
};
}
catch (error) {
if (error instanceof collect_dependencies_1.InvalidRequireCallError) {
throw new InvalidRequireCallError(error, file.filename);
}
throw error;
}
if (unstable_disableModuleWrapping === true) {
wrappedAst = ast;
}
else {
// TODO: Replace this with a cheaper transform that doesn't require AST.
({ ast: wrappedAst } = JsFileWrapping.wrapModule(ast, importDefault, importAll, dependencyMapName, config.globalPrefix,
// TODO: This config is optional to allow its introduction in a minor
// release. It should be made non-optional in ConfigT or removed in
// future.
unstable_renameRequire === false));
}
}
const minify = (0, resolveOptions_1.shouldMinify)(options);
const shouldNormalizePseudoGlobals = minify &&
file.inputFileSize <= config.optimizationSizeLimit &&
!config.unstable_disableNormalizePseudoGlobals;
const reserved = [];
if (config.unstable_dependencyMapReservedName != null) {
reserved.push(config.unstable_dependencyMapReservedName);
}
if (shouldNormalizePseudoGlobals &&
// TODO: If the module wrapping is disabled then the normalize function needs to change to account for not being in a body.
!unstable_disableModuleWrapping) {
// NOTE(EvanBacon): Simply pushing this function will mutate the AST, so it must run before the `generate` step!!
reserved.push(...metroTransformPlugins.normalizePseudoGlobals(wrappedAst, {
reservedNames: reserved,
}));
}
const result = (0, generator_1.default)(wrappedAst, {
comments: true,
compact: config.unstable_compactOutput,
filename: file.filename,
retainLines: false,
sourceFileName: file.filename,
sourceMaps: true,
}, file.code);
// @ts-expect-error: incorrectly typed upstream
let map = result.rawMappings ? result.rawMappings.map(metro_source_map_1.toSegmentTuple) : [];
let code = result.code;
// NOTE: We might want to enable this on native + hermes when tree shaking is enabled.
if (minify) {
({ map, code } = await (0, exports.minifyCode)(config, file.filename, result.code, file.code, map, reserved));
}
const possibleReconcile = optimize && collectDependenciesOptions
? {
inlineRequires: options.inlineRequires,
importDefault,
importAll,
normalizePseudoGlobals: shouldNormalizePseudoGlobals,
globalPrefix: config.globalPrefix,
unstable_compactOutput: config.unstable_compactOutput,
collectDependenciesOptions,
minify: minify
? {
minifierPath: config.minifierPath,
minifierConfig: config.minifierConfig,
}
: undefined,
unstable_dependencyMapReservedName: config.unstable_dependencyMapReservedName,
optimizationSizeLimit: config.optimizationSizeLimit,
unstable_disableNormalizePseudoGlobals: config.unstable_disableNormalizePseudoGlobals,
unstable_renameRequire,
}
: undefined;
let lineCount;
({ lineCount, map } = (0, count_lines_1.countLinesAndTerminateMap)(code, map));
// Clean the AST for tree shaking by stripping non-serializable values (Symbols, functions, etc.)
// that React Compiler and other Babel plugins may add.
const output = [
{
data: {
code,
lineCount,
map,
functionMap: file.functionMap,
hasCjsExports: file.hasCjsExports,
reactServerReference: file.reactServerReference,
reactClientReference: file.reactClientReference,
expoDomComponentReference: file.expoDomComponentReference,
loaderReference: file.loaderReference,
...(possibleReconcile
? {
ast: wrappedAst,
// Store settings for the module that will be used to finish transformation after graph-based optimizations
// have finished.
reconcile: possibleReconcile,
}
: {}),
},
type: file.type,
},
];
if (possibleReconcile) {
const reactCompilerFlag = options.customTransformOptions?.reactCompiler;
if (reactCompilerFlag === true || reactCompilerFlag === 'true') {
try {
return {
dependencies,
// React compiler adds symbols to the AST which break threading. This will ensure the
// AST and other properties are fully serialized before being sent to the worker.
output: JSON.parse(JSON.stringify(output)),
};
}
catch (error) {
throw new Error(`Failed to serialize output for file ${file.filename}: ${error}`);
}
}
}
return {
dependencies,
output,
};
}
/** Transforms an asset file. */
async function transformAsset(file, context) {
const { assetRegistryPath, assetPlugins } = context.config;
// TODO: Add web asset hashing in production.
const result = await assetTransformer.transform(getBabelTransformArgs(file, context), assetRegistryPath, assetPlugins);
const jsFile = {
...file,
type: 'js/module/asset',
ast: result.ast,
functionMap: null,
hasCjsExports: true,
reactClientReference: result.reactClientReference,
};
return transformJS(jsFile, context);
}
/**
* Transforms a JavaScript file with Babel before processing the file with
* the generic JavaScript transformation.
*/
async function transformJSWithBabel(file, context) {
const { babelTransformerPath } = context.config;
const transformer = require(babelTransformerPath);
// HACK: React Compiler injects import statements and exits the Babel process which leaves the code in
// a malformed state. For now, we'll enable the experimental import support which compiles import statements
// outside of the standard Babel process.
if (!context.options.experimentalImportSupport) {
const reactCompilerFlag = context.options.customTransformOptions?.reactCompiler;
if (reactCompilerFlag === true || reactCompilerFlag === 'true') {
// @ts-expect-error: readonly.
context.options.experimentalImportSupport = true;
}
}
// TODO: Add a babel plugin which returns if the module has commonjs, and if so, disable all tree shaking optimizations early.
const transformResult = await transformer.transform(getBabelTransformArgs(file, context, [
// functionMapBabelPlugin populates metadata.metro.functionMap
metro_source_map_1.functionMapBabelPlugin,
// importLocationsPlugin populates metadata.metro.unstable_importDeclarationLocs
importLocationsPlugin_1.importLocationsPlugin,
]));
const jsFile = {
...file,
ast: transformResult.ast,
functionMap: transformResult.metadata?.metro?.functionMap ??
// Fallback to deprecated explicitly-generated `functionMap`
transformResult.functionMap ??
null,
unstable_importDeclarationLocs: transformResult?.metadata?.metro?.unstable_importDeclarationLocs,
hasCjsExports: transformResult.metadata?.hasCjsExports,
reactServerReference: transformResult.metadata?.reactServerReference,
reactClientReference: transformResult.metadata?.reactClientReference,
expoDomComponentReference: transformResult.metadata?.expoDomComponentReference,
loaderReference: transformResult.metadata?.loaderReference,
performConstantFolding: transformResult.metadata?.performConstantFolding,
};
return await transformJS(jsFile, context);
}
async function transformJSON(file, { options, config }) {
let code = config.unstable_disableModuleWrapping === true
? JsFileWrapping.jsonToCommonJS(file.code)
: JsFileWrapping.wrapJson(file.code, config.globalPrefix);
let map = [];
const minify = (0, resolveOptions_1.shouldMinify)(options);
if (minify) {
({ map, code } = await (0, exports.minifyCode)(config, file.filename, code, file.code, map));
}
let jsType;
if (file.type === 'asset') {
jsType = 'js/module/asset';
}
else if (file.type === 'script') {
jsType = 'js/script';
}
else {
jsType = 'js/module';
}
let lineCount;
({ lineCount, map } = (0, count_lines_1.countLinesAndTerminateMap)(code, map));
const output = [
{
data: { code, lineCount, map, functionMap: null },
type: jsType,
},
];
return {
dependencies: [],
output,
};
}
function getBabelTransformArgs(file, { options, config, projectRoot }, plugins = []) {
const { inlineRequires: _, ...babelTransformerOptions } = options;
return {
filename: file.filename,
options: {
...babelTransformerOptions,
enableBabelRCLookup: config.enableBabelRCLookup,
// NOTE(@kitten): This shouldn't be relevant via this code path. However, in case it does,
// this prevents us from adding imports/requires to @babel/runtime when we're transforming a script
enableBabelRuntime: options.type === 'script' ? false : config.enableBabelRuntime,
hermesParser: config.hermesParser,
projectRoot,
publicPath: config.publicPath,
globalPrefix: config.globalPrefix,
platform: babelTransformerOptions.platform ?? null,
},
plugins,
src: file.code,
};
}
async function transform(config, projectRoot, filename, data, options) {
const context = {
config,
projectRoot,
options,
};
const sourceCode = data.toString('utf8');
const { unstable_dependencyMapReservedName } = config;
if (unstable_dependencyMapReservedName != null) {
const position = sourceCode.indexOf(unstable_dependencyMapReservedName);
if (position > -1) {
throw new SyntaxError('Source code contains the reserved string `' +
unstable_dependencyMapReservedName +
'` at character offset ' +
position);
}
}
if (filename.endsWith('.json')) {
const jsonFile = {
filename,
inputFileSize: data.length,
code: sourceCode,
type: options.type,
};
return transformJSON(jsonFile, context);
}
if (options.type === 'asset') {
const file = {
filename,
inputFileSize: data.length,
code: sourceCode,
type: options.type,
};
return transformAsset(file, context);
}
const file = {
filename,
inputFileSize: data.length,
code: sourceCode,
type: options.type === 'script' ? 'js/script' : 'js/module',
functionMap: null,
};
return transformJSWithBabel(file, context);
}
function getCacheKey(config) {
const {
// The `expo_customTransformerPath` from `./supervising-transform-worker` should not participate be part of the cache key
expo_customTransformerPath: _customTransformerPath, babelTransformerPath, minifierPath, ...remainingConfig } = config;
// TODO(@kitten): We can now tie this into `@expo/metro`, which could also simply export a static version export
const filesKey = (0, metro_cache_key_1.getCacheKey)([
require.resolve(babelTransformerPath),
(0, getMinifier_1.resolveMinifier)(minifierPath),
require.resolve('@expo/metro/metro-transform-worker/utils/getMinifier'),
require.resolve('./collect-dependencies'),
require.resolve('./asset-transformer'),
require.resolve('./resolveOptions'),
require.resolve('@expo/metro/metro/ModuleGraph/worker/generateImportNames'),
require.resolve('@expo/metro/metro/ModuleGraph/worker/JsFileWrapping'),
...metroTransformPlugins.getTransformPluginCacheKeyFiles(),
]);
const babelTransformer = require(babelTransformerPath);
return [
filesKey,
(0, metro_cache_1.stableHash)(remainingConfig).toString('hex'),
babelTransformer.getCacheKey ? babelTransformer.getCacheKey() : '',
].join('$');
}
/**
* Produces a Babel template that transforms an "import(...)" call into a
* "require(...)" call to the asyncRequire specified.
*/
const makeShimAsyncRequireTemplate = core_1.template.expression(`require(ASYNC_REQUIRE_MODULE_PATH)`);
const disabledDependencyTransformer = {
transformSyncRequire: (path) => { },
transformImportMaybeSyncCall: () => { },
transformImportCall: (path, dependency, state) => {
// TODO: Prevent extraneous includes of the async require for normal imports.
// HACK: Ensure the async import code is included in the bundle when an import() call is found.
let topParent = path;
while (topParent.parentPath) {
topParent = topParent.parentPath;
}
// @ts-expect-error
if (topParent._handled) {
return;
}
path.insertAfter(makeShimAsyncRequireTemplate({
ASYNC_REQUIRE_MODULE_PATH: nullthrows(state.asyncRequireModulePathStringLiteral),
}));
// @ts-expect-error: Prevent recursive loop
topParent._handled = true;
},
transformPrefetch: () => { },
transformIllegalDynamicRequire: () => { },
};
function collectDependenciesForShaking(ast, options) {
const collectDependenciesOptions = {
...options,
// If tree shaking is enabled, then preserve the original require calls.
// This ensures require.context calls are not broken.
collectOnly: true,
};
return (0, collect_dependencies_1.default)(ast, {
...collectDependenciesOptions,
// This setting shouldn't be shared with the tree shaking transformer.
dependencyTransformer: disabledDependencyTransformer,
});
}
//# sourceMappingURL=metro-transform-worker.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
type PostCSSInputConfig = {
plugins?: any[];
from?: string;
to?: string;
syntax?: string;
map?: boolean;
parser?: string;
stringifier?: string;
};
export declare function transformPostCssModule(projectRoot: string, { src, filename }: {
src: string;
filename: string;
}): Promise<{
src: string;
hasPostcss: boolean;
}>;
export declare function pluginFactory(): (plugins?: any) => Map<string, any>;
export declare function resolvePostcssConfig(projectRoot: string): Promise<PostCSSInputConfig | null>;
export declare function getPostcssConfigHash(projectRoot: string): string | null;
export {};

View File

@@ -0,0 +1,210 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformPostCssModule = transformPostCssModule;
exports.pluginFactory = pluginFactory;
exports.resolvePostcssConfig = resolvePostcssConfig;
exports.getPostcssConfigHash = getPostcssConfigHash;
/**
* Copyright © 2023 650 Industries.
* Copyright JS Foundation and other contributors
*
* https://github.com/webpack-contrib/postcss-loader/
*/
const json_file_1 = __importDefault(require("@expo/json-file"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const resolve_from_1 = __importDefault(require("resolve-from"));
const require_1 = require("./utils/require");
const CONFIG_FILE_NAME = 'postcss.config';
const debug = require('debug')('expo:metro:transformer:postcss');
async function transformPostCssModule(projectRoot, { src, filename }) {
const inputConfig = await resolvePostcssConfig(projectRoot);
if (!inputConfig) {
return { src, hasPostcss: false };
}
return {
src: await processWithPostcssInputConfigAsync(projectRoot, {
inputConfig,
src,
filename,
}),
hasPostcss: true,
};
}
async function processWithPostcssInputConfigAsync(projectRoot, { src, filename, inputConfig }) {
const { plugins, processOptions } = await parsePostcssConfigAsync(projectRoot, {
config: inputConfig,
resourcePath: filename,
});
debug('options:', processOptions);
debug('plugins:', plugins);
// TODO: Surely this can be cached...
const postcss = require('postcss');
const processor = postcss.default(plugins);
const { content } = await processor.process(src, processOptions);
return content;
}
async function parsePostcssConfigAsync(projectRoot, { resourcePath: file, config: { plugins: inputPlugins, map, parser, stringifier, syntax, ...config } = {}, }) {
const factory = pluginFactory();
factory(inputPlugins);
// delete config.plugins;
const plugins = [...factory()].map((item) => {
const [plugin, options] = item;
if (typeof plugin === 'string') {
return loadPlugin(projectRoot, plugin, options, file);
}
return plugin;
});
if (config.from) {
config.from = path_1.default.resolve(projectRoot, config.from);
}
if (config.to) {
config.to = path_1.default.resolve(projectRoot, config.to);
}
const processOptions = {
from: file,
to: file,
map: false,
};
if (typeof parser === 'string') {
try {
processOptions.parser = await (0, require_1.tryRequireThenImport)(resolve_from_1.default.silent(projectRoot, parser) ?? parser);
}
catch (error) {
if (error instanceof Error) {
throw new Error(`Loading PostCSS "${parser}" parser failed: ${error.message}\n\n(@${file})`);
}
throw error;
}
}
if (typeof stringifier === 'string') {
try {
processOptions.stringifier = await (0, require_1.tryRequireThenImport)(resolve_from_1.default.silent(projectRoot, stringifier) ?? stringifier);
}
catch (error) {
if (error instanceof Error) {
throw new Error(`Loading PostCSS "${stringifier}" stringifier failed: ${error.message}\n\n(@${file})`);
}
throw error;
}
}
if (typeof syntax === 'string') {
try {
processOptions.syntax = await (0, require_1.tryRequireThenImport)(resolve_from_1.default.silent(projectRoot, syntax) ?? syntax);
}
catch (error) {
throw new Error(`Loading PostCSS "${syntax}" syntax failed: ${error.message}\n\n(@${file})`);
}
}
if (map === true) {
// https://github.com/postcss/postcss/blob/master/docs/source-maps.md
processOptions.map = { inline: true };
}
return { plugins, processOptions };
}
function loadPlugin(projectRoot, plugin, options, file) {
try {
debug('load plugin:', plugin);
// e.g. `tailwindcss`
let loadedPlugin = require((0, resolve_from_1.default)(projectRoot, plugin));
if (loadedPlugin.default) {
loadedPlugin = loadedPlugin.default;
}
if (!options || !Object.keys(options).length) {
return loadedPlugin;
}
return loadedPlugin(options);
}
catch (error) {
if (error instanceof Error) {
throw new Error(`Loading PostCSS "${plugin}" plugin failed: ${error.message}\n\n(@${file})`);
}
throw error;
}
}
function pluginFactory() {
const listOfPlugins = new Map();
return (plugins) => {
if (typeof plugins === 'undefined') {
return listOfPlugins;
}
if (Array.isArray(plugins)) {
for (const plugin of plugins) {
if (Array.isArray(plugin)) {
const [name, options] = plugin;
if (typeof name !== 'string') {
throw new Error(`PostCSS plugin must be a string, but "${name}" was found. Verify the configuration is correct.`);
}
listOfPlugins.set(name, options);
}
else if (plugin && typeof plugin === 'function') {
listOfPlugins.set(plugin, undefined);
}
else if (plugin &&
Object.keys(plugin).length === 1 &&
(typeof plugin[Object.keys(plugin)[0]] === 'object' ||
typeof plugin[Object.keys(plugin)[0]] === 'boolean') &&
plugin[Object.keys(plugin)[0]] !== null) {
const [name] = Object.keys(plugin);
const options = plugin[name];
if (options === false) {
listOfPlugins.delete(name);
}
else {
listOfPlugins.set(name, options);
}
}
else if (plugin) {
listOfPlugins.set(plugin, undefined);
}
}
}
else {
const objectPlugins = Object.entries(plugins);
for (const [name, options] of objectPlugins) {
if (options === false) {
listOfPlugins.delete(name);
}
else {
listOfPlugins.set(name, options);
}
}
}
return listOfPlugins;
};
}
async function resolvePostcssConfig(projectRoot) {
for (const ext of ['.mjs', '.js']) {
const configPath = path_1.default.join(projectRoot, CONFIG_FILE_NAME + ext);
if (fs_1.default.existsSync(configPath)) {
debug('load file:', configPath);
const config = await (0, require_1.tryRequireThenImport)(configPath);
return 'default' in config ? config.default : config;
}
}
const jsonConfigPath = path_1.default.join(projectRoot, CONFIG_FILE_NAME + '.json');
if (fs_1.default.existsSync(jsonConfigPath)) {
debug('load file:', jsonConfigPath);
return json_file_1.default.read(jsonConfigPath, { json5: true });
}
return null;
}
function getPostcssConfigHash(projectRoot) {
// TODO: Maybe recurse plugins and add versions to the hash in the future.
const { stableHash, } = require('@expo/metro/metro-cache');
for (const ext of ['.mjs', '.js']) {
const configPath = path_1.default.join(projectRoot, CONFIG_FILE_NAME + ext);
if (fs_1.default.existsSync(configPath)) {
return stableHash(fs_1.default.readFileSync(configPath, 'utf8')).toString('hex');
}
}
const jsonConfigPath = path_1.default.join(projectRoot, CONFIG_FILE_NAME + '.json');
if (fs_1.default.existsSync(jsonConfigPath)) {
return stableHash(fs_1.default.readFileSync(jsonConfigPath, 'utf8')).toString('hex');
}
return null;
}
//# sourceMappingURL=postcss.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
import type { JsTransformOptions } from '@expo/metro/metro-transform-worker';
export declare function shouldMinify(options: Pick<JsTransformOptions, 'unstable_transformProfile' | 'customTransformOptions' | 'minify'>): boolean;

View File

@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.shouldMinify = shouldMinify;
function isHermesEngine(options) {
// NOTE: This has multiple inputs since we also use the `customTransformOptions.engine` option to indicate the Hermes engine.
return (options.unstable_transformProfile === 'hermes-canary' ||
options.unstable_transformProfile === 'hermes-stable');
}
function isBytecodeEnabled(options) {
return options.customTransformOptions?.bytecode === '1';
}
function shouldMinify(options) {
// If using Hermes + bytecode, then skip minification because the Hermes compiler will minify the code.
if (isHermesEngine(options) && isBytecodeEnabled(options)) {
return false;
}
return options.minify;
}
//# sourceMappingURL=resolveOptions.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resolveOptions.js","sourceRoot":"","sources":["../../src/transform-worker/resolveOptions.ts"],"names":[],"mappings":";;AAcA,oCAYC;AAxBD,SAAS,cAAc,CAAC,OAA8D;IACpF,6HAA6H;IAC7H,OAAO,CACL,OAAO,CAAC,yBAAyB,KAAK,eAAe;QACrD,OAAO,CAAC,yBAAyB,KAAK,eAAe,CACtD,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,OAA2D;IACpF,OAAO,OAAO,CAAC,sBAAsB,EAAE,QAAQ,KAAK,GAAG,CAAC;AAC1D,CAAC;AAED,SAAgB,YAAY,CAC1B,OAGC;IAED,uGAAuG;IACvG,IAAI,cAAc,CAAC,OAAO,CAAC,IAAI,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC;AACxB,CAAC"}

View File

@@ -0,0 +1,8 @@
export declare function matchSass(filename: string): import('sass').Syntax | null;
export declare function compileSass(projectRoot: string, { src }: {
filename: string;
src: string;
}, options?: Record<string, any>): {
src: string;
map: any;
};

View File

@@ -0,0 +1,39 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchSass = matchSass;
exports.compileSass = compileSass;
const resolve_from_1 = __importDefault(require("resolve-from"));
let sassInstance = null;
// TODO(@kitten): Add optional peer for `sass` instead
function getSassInstance(projectRoot) {
if (!sassInstance) {
const sassPath = resolve_from_1.default.silent(projectRoot, 'sass');
if (!sassPath) {
throw new Error(`Cannot parse Sass files without the module 'sass' installed. Run 'yarn add sass' and try again.`);
}
sassInstance = require(sassPath);
}
return sassInstance;
}
function matchSass(filename) {
if (filename.endsWith('.sass')) {
return 'indented';
}
else if (filename.endsWith('.scss')) {
return 'scss';
}
return null;
}
function compileSass(projectRoot, { src }, options) {
const sass = getSassInstance(projectRoot);
const result = sass.compileString(src, options);
return {
src: result.css,
// NOTE(@kitten): Types won't match up, but we're aware of the format from SASS matching
map: result.sourceMap,
};
}
//# sourceMappingURL=sass.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sass.js","sourceRoot":"","sources":["../../src/transform-worker/sass.ts"],"names":[],"mappings":";;;;;AAqBA,8BAOC;AAED,kCAYC;AA1CD,gEAAuC;AAEvC,IAAI,YAAY,GAAiC,IAAI,CAAC;AAEtD,sDAAsD;AACtD,SAAS,eAAe,CAAC,WAAmB;IAC1C,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,QAAQ,GAAG,sBAAW,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAEzD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CACb,iGAAiG,CAClG,CAAC;QACJ,CAAC;QAED,YAAY,GAAG,OAAO,CAAC,QAAQ,CAA0B,CAAC;IAC5D,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAgB,SAAS,CAAC,QAAgB;IACxC,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/B,OAAO,UAAU,CAAC;IACpB,CAAC;SAAM,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,WAAW,CACzB,WAAmB,EACnB,EAAE,GAAG,EAAqC,EAC1C,OAA6B;IAE7B,MAAM,IAAI,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAChD,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,wFAAwF;QACxF,GAAG,EAAE,MAAM,CAAC,SAAgB;KAC7B,CAAC;AACJ,CAAC"}

View File

@@ -0,0 +1,8 @@
import type { JsTransformerConfig, JsTransformOptions } from '@expo/metro/metro-transform-worker';
import type { TransformResponse } from './transform-worker';
declare module '@expo/metro/metro-transform-worker' {
interface JsTransformerConfig {
expo_customTransformerPath?: string;
}
}
export declare function transform(config: JsTransformerConfig, projectRoot: string, filename: string, data: Buffer, options: JsTransformOptions): Promise<TransformResponse>;

View File

@@ -0,0 +1,106 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
const path_1 = __importDefault(require("path"));
const worker = __importStar(require("./metro-transform-worker"));
const moduleMapper_1 = require("./utils/moduleMapper");
const defaultTransformer = require('./transform-worker');
const defaultTransformerPath = require.resolve('./transform-worker');
const debug = require('debug')('expo:metro-config:supervising-transform-worker');
const getCustomTransform = (() => {
let _transformerPath;
let _transformer;
return (config, projectRoot) => {
// The user's original `transformerPath` is stored on `config.transformer.expo_customTransformerPath`
// by @expo/cli in `withMetroSupervisingTransformWorker()`
if (_transformer == null && _transformerPath == null) {
_transformerPath = config.expo_customTransformerPath;
}
else if (config.expo_customTransformerPath != null &&
_transformerPath !== config.expo_customTransformerPath) {
throw new Error('expo_customTransformerPath must not be modified after initialization');
}
// We override require calls in the user transformer to use *our* version
// of Metro and this version of @expo/metro-config forcefully.
// Versions of Metro must be aligned to the ones that Expo is using
// This is done by patching Node.js' module resolution function
(0, moduleMapper_1.patchNodeModuleResolver)();
if (_transformer == null &&
_transformerPath != null &&
_transformerPath !== defaultTransformerPath) {
// We only load the user transformer once and cache it
// If the user didn't add a custom transformer, we don't load it,
// but the user maybe has a custom Babel transformer
debug(`Loading custom transformer at "${_transformerPath}"`);
try {
_transformer = require.call(null, _transformerPath);
}
catch (error) {
// If the user's transformer throws and fails initialization, we customize the
// error and output a path to the user to clarify that it's the transformer that
// failed to initialize
const relativeTransformerPath = path_1.default.relative(projectRoot, _transformerPath);
throw new Error(`Your custom Metro transformer has failed to initialize. Check: "${relativeTransformerPath}"\n` +
(typeof error.message === 'string' ? error.message : `${error}`));
}
}
return _transformer;
};
})();
function transform(config, projectRoot, filename, data, options) {
const customWorker = getCustomTransform(config, projectRoot) ?? defaultTransformer;
// Delete this custom property before we call the custom transform worker
// The supervising-transform-worker should be transparent, and the user's transformer
// shouldn't know it's been called by it
if (config.expo_customTransformerPath != null) {
config.expo_customTransformerPath = undefined;
}
return customWorker.transform(config, projectRoot, filename, data, options);
}
module.exports = {
// Use defaults for everything that's not custom.
...worker,
// We ensure that core utilities are never replaced
applyImportSupport: worker.applyImportSupport,
getCacheKey: worker.getCacheKey,
collectDependenciesForShaking: worker.collectDependenciesForShaking,
minifyCode: worker.minifyCode,
transform,
};
//# sourceMappingURL=supervising-transform-worker.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"supervising-transform-worker.js","sourceRoot":"","sources":["../../src/transform-worker/supervising-transform-worker.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,8BAeC;AAjFD,gDAAwB;AAExB,iEAAmD;AAEnD,uDAA+D;AAE/D,MAAM,kBAAkB,GAAwC,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAC9F,MAAM,sBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAQrE,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAC5B,gDAAgD,CAC3B,CAAC;AAExB,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE;IAC/B,IAAI,gBAAoC,CAAC;IACzC,IAAI,YAA6D,CAAC;IAClE,OAAO,CAAC,MAA2B,EAAE,WAAmB,EAAE,EAAE;QAC1D,qGAAqG;QACrG,0DAA0D;QAC1D,IAAI,YAAY,IAAI,IAAI,IAAI,gBAAgB,IAAI,IAAI,EAAE,CAAC;YACrD,gBAAgB,GAAG,MAAM,CAAC,0BAA0B,CAAC;QACvD,CAAC;aAAM,IACL,MAAM,CAAC,0BAA0B,IAAI,IAAI;YACzC,gBAAgB,KAAK,MAAM,CAAC,0BAA0B,EACtD,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC1F,CAAC;QAED,yEAAyE;QACzE,8DAA8D;QAC9D,mEAAmE;QACnE,+DAA+D;QAC/D,IAAA,sCAAuB,GAAE,CAAC;QAE1B,IACE,YAAY,IAAI,IAAI;YACpB,gBAAgB,IAAI,IAAI;YACxB,gBAAgB,KAAK,sBAAsB,EAC3C,CAAC;YACD,sDAAsD;YACtD,iEAAiE;YACjE,oDAAoD;YACpD,KAAK,CAAC,kCAAkC,gBAAgB,GAAG,CAAC,CAAC;YAC7D,IAAI,CAAC;gBACH,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;YACtD,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,8EAA8E;gBAC9E,gFAAgF;gBAChF,uBAAuB;gBACvB,MAAM,uBAAuB,GAAG,cAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;gBAC7E,MAAM,IAAI,KAAK,CACb,mEAAmE,uBAAuB,KAAK;oBAC7F,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CACnE,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC,CAAC;AACJ,CAAC,CAAC,EAAE,CAAC;AAEL,SAAgB,SAAS,CACvB,MAA2B,EAC3B,WAAmB,EACnB,QAAgB,EAChB,IAAY,EACZ,OAA2B;IAE3B,MAAM,YAAY,GAAG,kBAAkB,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,kBAAkB,CAAC;IACnF,yEAAyE;IACzE,qFAAqF;IACrF,wCAAwC;IACxC,IAAI,MAAM,CAAC,0BAA0B,IAAI,IAAI,EAAE,CAAC;QAC9C,MAAM,CAAC,0BAA0B,GAAG,SAAS,CAAC;IAChD,CAAC;IACD,OAAO,YAAY,CAAC,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,OAAO,GAAG;IACf,iDAAiD;IACjD,GAAG,MAAM;IAET,mDAAmD;IACnD,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;IAC7C,WAAW,EAAE,MAAM,CAAC,WAAW;IAC/B,6BAA6B,EAAE,MAAM,CAAC,6BAA6B;IACnE,UAAU,EAAE,MAAM,CAAC,UAAU;IAE7B,SAAS;CACV,CAAC"}

View File

@@ -0,0 +1,14 @@
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { TransformResultDependency } from '@expo/metro/metro/DeltaBundler';
import type { JsTransformerConfig, JsTransformOptions, JsOutput } from '@expo/metro/metro-transform-worker';
export interface TransformResponse {
readonly dependencies: readonly TransformResultDependency[];
readonly output: readonly JsOutput[];
}
export declare function transform(config: JsTransformerConfig, projectRoot: string, filename: string, data: Buffer, options: JsTransformOptions): Promise<TransformResponse>;

View File

@@ -0,0 +1,315 @@
"use strict";
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = transform;
const countLines_1 = __importDefault(require("@expo/metro/metro/lib/countLines"));
const node_path_1 = require("node:path");
const browserslist_1 = require("./browserslist");
const css_1 = require("./css");
const css_modules_1 = require("./css-modules");
const dot_env_development_1 = require("./dot-env-development");
const worker = __importStar(require("./metro-transform-worker"));
const postcss_1 = require("./postcss");
const sass_1 = require("./sass");
const filePath_1 = require("../utils/filePath");
const debug = require('debug')('expo:metro-config:transform-worker');
function getStringArray(value) {
if (!value)
return undefined;
if (typeof value === 'string') {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) {
return parsed;
}
throw new Error('Expected an array of strings for the `clientBoundaries` option.');
}
if (Array.isArray(value)) {
return value;
}
throw new Error('Expected an array of strings for the `clientBoundaries` option.');
}
async function transform(config, projectRoot, filename, data, options) {
const posixFilename = (0, filePath_1.toPosixPath)(filename);
if (typeof options.customTransformOptions?.dom === 'string' &&
posixFilename.match(/expo\/dom\/entry\.js/)) {
// TODO: Find some method to do this without invalidating the cache between different DOM components.
// Inject source for DOM component entry.
const relativeDomComponentEntry = JSON.stringify(decodeURI(options.customTransformOptions.dom));
const src = `require('expo/dom/internal').registerDOMComponent(require(${relativeDomComponentEntry}).default);`;
return worker.transform(config, projectRoot, filename, Buffer.from(src), options);
}
if (posixFilename.match(/(^|\/)expo\/virtual\/rsc\.js/)) {
const environment = options.customTransformOptions?.environment;
const isServer = environment === 'node' || environment === 'react-server';
if (!isServer) {
const clientBoundaries = getStringArray(options.customTransformOptions?.clientBoundaries);
// Inject client boundaries into the root client bundle for production bundling.
if (clientBoundaries) {
debug('Parsed client boundaries:', clientBoundaries);
// Inject source
const src = 'module.exports = {\n' +
clientBoundaries
.map((boundary) => {
const serializedBoundary = JSON.stringify(boundary);
return `[\`$\{require.resolveWeak(${serializedBoundary})}\`]: /* ${boundary} */ () => import(${serializedBoundary}),`;
})
.join('\n') +
'\n};';
return worker.transform(config, projectRoot, filename, Buffer.from('/* RSC client boundaries */\n' + src), options);
}
}
}
if (options.type !== 'asset' && /\.(s?css|sass)$/.test(filename)) {
return transformCss(config, projectRoot, filename, data, options);
}
// If the file is not CSS, then use the default behavior.
const environment = options.customTransformOptions?.environment;
const isClientEnvironment = environment !== 'node' && environment !== 'react-server';
if (isClientEnvironment &&
// TODO: Ensure this works with windows.
(filename.match(new RegExp(`^app/\\+html(\\.${options.platform})?\\.([tj]sx?|[cm]js)?$`)) ||
// Strip +api files.
filename.match(/\+api(\.(native|ios|android|web))?\.[tj]sx?$/) ||
// Strip +middleware files.
filename.match(/\+middleware\.[tj]sx?$/))) {
// Remove the server-only +html file and API Routes from the bundle when bundling for a client environment.
return worker.transform(config, projectRoot, filename, !options.minify
? Buffer.from(
// Use a string so this notice is visible in the bundle if the user is
// looking for it.
'"> The server-only file was removed from the client JS bundle by Expo CLI."')
: Buffer.from(''), options);
}
if (isClientEnvironment &&
!filename.match(/\/node_modules\//) &&
filename.match(/\+api(\.(native|ios|android|web))?\.[tj]sx?$/)) {
// Clear the contents of +api files when bundling for the client.
// This ensures that the client doesn't accidentally use the server-only +api files.
return worker.transform(config, projectRoot, filename, Buffer.from(''), options);
}
// Add support for parsing env files to JavaScript objects. Stripping the non-public variables in client environments.
if (filename.match(/(^|\/)\.env(\.(local|(development|production)(\.local)?))?$/)) {
const envFileParsed = (0, dot_env_development_1.parseEnvFile)(data.toString('utf-8'), isClientEnvironment);
return worker.transform(config, projectRoot, filename, Buffer.from(`export default ${JSON.stringify(envFileParsed)};`), options);
}
if (
// Noop the streams polyfill in the server environment.
!isClientEnvironment &&
filename.match(/\/expo\/virtual\/streams\.js$/)) {
return worker.transform(config, projectRoot, filename, Buffer.from(''), options);
}
if (
// Parsing the virtual env is client-only, on the server we use `process.env` directly.
isClientEnvironment &&
// Finally match the virtual env file.
filename.match(/\/expo\/virtual\/env\.js$/)) {
if (
// Variables should be inlined in production. We only use this JS object to ensure HMR in development.
options.dev) {
const relativePath = (0, node_path_1.relative)((0, node_path_1.dirname)(filename), projectRoot);
const posixPath = (0, filePath_1.toPosixPath)(relativePath);
// This virtual module uses a context module to conditionally observe and load all of the possible .env files in development.
// We then merge them in the expected order.
// This module still depends on the `process.env` polyfill in the serializer to include EXPO_PUBLIC_ variables that are
// defined in the script or bash, essentially all places where HMR is not possible.
// Finally, we export with `env` to align with the babel plugin that transforms static process.env usage to the virtual module.
// The .env regex depends `watcher.additionalExts` being set correctly (`'env', 'local', 'development'`) so that .env files aren't resolved as platform extensions.
const contents = `const dotEnvModules = require.context(${JSON.stringify(posixPath)},false,/^\\.\\/\\.env/);
export const env = !dotEnvModules.keys().length ? process.env : { ...process.env, ...['.env', '.env.development', '.env.local', '.env.development.local'].reduce((acc, file) => {
return { ...acc, ...(dotEnvModules(file)?.default ?? {}) };
}, {}) };`;
return worker.transform(config, projectRoot, filename, Buffer.from(contents), options);
}
else {
// Add a fallback in production for sanity and better errors if something goes wrong or the user manually imports the virtual module somehow.
// Create a proxy module where a helpful error is thrown whenever a key from `process.env` is accessed.
const contents = `
export const env = new Proxy({}, {
get(target, key) {
throw new Error(\`Attempting to access internal environment variable "\${key}" is not supported in production bundles. Environment variables should be inlined in production by Babel.\`);
},
});`;
return worker.transform(config, projectRoot, filename, Buffer.from(contents), options);
}
}
return worker.transform(config, projectRoot, filename, data, options);
}
function isReactServerEnvironment(options) {
return options.customTransformOptions?.environment === 'react-server';
}
async function transformCss(config, projectRoot, filename, data, options) {
// If the platform is not web, then return an empty module.
if (options.platform !== 'web') {
const code = (0, css_modules_1.matchCssModule)(filename) ? 'module.exports={ unstable_styles: {} };' : '';
return worker.transform(config, projectRoot, filename,
// TODO: Native CSS Modules
Buffer.from(code), options);
}
let code = data.toString('utf8');
// Apply postcss transforms
const postcssResults = await (0, postcss_1.transformPostCssModule)(projectRoot, {
src: code,
filename,
});
if (postcssResults.hasPostcss) {
code = postcssResults.src;
}
// TODO: When native has CSS support, this will need to move higher up.
const syntax = (0, sass_1.matchSass)(filename);
if (syntax) {
code = (0, sass_1.compileSass)(projectRoot, { filename, src: code }, { syntax }).src;
}
// If the file is a CSS Module, then transform it to a JS module
// in development and a static CSS file in production.
if ((0, css_modules_1.matchCssModule)(filename)) {
const results = await (0, css_modules_1.transformCssModuleWeb)({
// NOTE(cedric): use POSIX-formatted filename fo rconsistent CSS module class names.
// This affects the content hashes, which should be stable across platforms.
filename: (0, filePath_1.toPosixPath)(filename),
src: code,
options: {
reactServer: isReactServerEnvironment(options),
projectRoot,
dev: options.dev,
minify: options.minify,
sourceMap: false,
},
});
const jsModuleResults = await worker.transform(config, projectRoot, filename, Buffer.from(results.output), options);
const cssCode = results.css.toString();
const output = [
{
type: 'js/module',
data: {
...jsModuleResults.output[0]?.data,
// Append additional css metadata for static extraction.
css: {
code: cssCode,
lineCount: (0, countLines_1.default)(cssCode),
map: [],
functionMap: null,
// Disable caching for CSS files when postcss is enabled and has been run on the file.
// This ensures that things like tailwind can update on every change.
skipCache: postcssResults.hasPostcss,
externalImports: results.externalImports,
},
},
},
];
return {
dependencies: jsModuleResults.dependencies.concat(results.dependencies),
output,
};
}
// Global CSS:
const { transform } = require('lightningcss');
// Here we delegate bundling to lightningcss to resolve all CSS imports together.
// TODO: Add full CSS bundling support to Metro.
const cssResults = transform({
filename,
code: Buffer.from(code),
errorRecovery: true,
sourceMap: false,
cssModules: false,
projectRoot,
minify: options.minify,
analyzeDependencies: true,
targets: await (0, browserslist_1.getBrowserslistTargets)(projectRoot),
// targets: pkg?.browserslist,
// @ts-expect-error: Added for testing against virtual file system.
resolver: options._test_resolveCss,
// https://lightningcss.dev/transpilation.html
include: 1, // Nesting
});
(0, css_modules_1.printCssWarnings)(filename, code, cssResults.warnings);
const cssImports = (0, css_modules_1.collectCssImports)(filename, code, cssResults.code.toString(), cssResults);
const cssCode = cssImports.code;
// Append additional css metadata for static extraction.
const cssOutput = {
code: cssCode,
lineCount: (0, countLines_1.default)(cssCode),
map: [],
functionMap: null,
// Disable caching for CSS files when postcss is enabled and has been run on the file.
// This ensures that things like tailwind can update on every change.
skipCache: postcssResults.hasPostcss,
externalImports: cssImports.externalImports,
};
const reactServer = isReactServerEnvironment(options);
// Create a mock JS module that exports an empty object,
// this ensures Metro dependency graph is correct.
const jsModuleResults = await worker.transform(config, projectRoot, filename, options.dev
? Buffer.from((0, css_1.wrapDevelopmentCSS)({ src: cssCode, filename, reactServer }))
: Buffer.from(''), options);
// In production, we export the CSS as a string and use a special type to prevent
// it from being included in the JS bundle. We'll extract the CSS like an asset later
// and append it to the HTML bundle.
const output = [
{
type: 'js/module',
data: {
...jsModuleResults.output[0].data,
css: cssOutput,
},
},
];
return {
dependencies: jsModuleResults.dependencies.concat(cssImports.dependencies),
output,
};
}
/**
* A custom Metro transformer that adds support for processing Expo-specific bundler features.
* - Global CSS files on web.
* - CSS Modules on web.
* - TODO: Tailwind CSS on web.
*/
module.exports = {
// Use defaults for everything that's not custom.
...worker,
transform,
};
//# sourceMappingURL=transform-worker.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
export { default as getMinifier } from '@expo/metro/metro-transform-worker/utils/getMinifier';
export declare function resolveMinifier(request: string): string;

View File

@@ -0,0 +1,32 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMinifier = void 0;
exports.resolveMinifier = resolveMinifier;
const path_1 = __importDefault(require("path"));
const moduleMapper_1 = require("./moduleMapper");
var getMinifier_1 = require("@expo/metro/metro-transform-worker/utils/getMinifier");
Object.defineProperty(exports, "getMinifier", { enumerable: true, get: function () { return __importDefault(getMinifier_1).default; } });
function resolveMinifier(request) {
// We have to imitate how `getMinifier` resolves the minifier
// It does so by requiring the `minifierPath` from the `metro-transform-worker/utils/getMinifier` base path
// This means that when we're trying to resolve the minifier, we need to redirect the resolution to `metro-transform-worker`
const moduleMapper = (0, moduleMapper_1.createModuleMapper)();
const metroTransformWorkerPath = moduleMapper('metro-transform-worker/package.json');
if (metroTransformWorkerPath) {
try {
return require.resolve(request, {
paths: [path_1.default.dirname(metroTransformWorkerPath)],
});
}
catch (error) {
if (error.code !== 'MODULE_NOT_FOUND') {
throw error;
}
}
}
return require.resolve(request);
}
//# sourceMappingURL=getMinifier.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getMinifier.js","sourceRoot":"","sources":["../../../src/transform-worker/utils/getMinifier.ts"],"names":[],"mappings":";;;;;;AAMA,0CAmBC;AAzBD,gDAAwB;AAExB,iDAAoD;AAEpD,oFAA8F;AAArF,2HAAA,OAAO,OAAe;AAE/B,SAAgB,eAAe,CAAC,OAAe;IAC7C,6DAA6D;IAC7D,2GAA2G;IAC3G,4HAA4H;IAC5H,MAAM,YAAY,GAAG,IAAA,iCAAkB,GAAE,CAAC;IAC1C,MAAM,wBAAwB,GAAG,YAAY,CAAC,qCAAqC,CAAC,CAAC;IACrF,IAAI,wBAAwB,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE;gBAC9B,KAAK,EAAE,CAAC,cAAI,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;aAChD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBACtC,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAClC,CAAC"}

View File

@@ -0,0 +1,29 @@
declare module 'module' {
namespace Module {
function _resolveFilename(request: string, parent: {
id: string;
filename: string;
paths: string[];
} | string | null, isMain?: boolean, options?: {
paths?: string[];
}): string;
}
}
/** Returns a resolver function that given a request to a module returns that module's remapped path. */
export declare const createModuleMapper: () => (request: string) => string | null;
/** Patches `Module._resolveFilename` (usually just does Node resolution) to override some requires and imports
* @remarks
* The user's transform worker (or their babel transformer, which is called inside the transform-worker) can
* import/require any version of metro, metro-*, or @expo/metro-config in theory. But Expo CLI uses a specific
* version of Metro.
* It's unsupported to use one version of Metro in Expo CLI but another in the transform worker or babel transformer,
* and while this *can work* sometimes, it's never correct.
*
* When called, this function modifies this Node.js thread's module resolution to redirect all imports for Metro
* packages or @expo/metro-config to the version that we know is correct.
*
* We know the versions we have are correct since we're inside @expo/metro-config in this file.
*
* NOTE: Bun also supports overriding `Module._resolveFilename`
*/
export declare const patchNodeModuleResolver: () => void;

View File

@@ -0,0 +1,132 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.patchNodeModuleResolver = exports.createModuleMapper = void 0;
const module_1 = __importDefault(require("module"));
const path_1 = __importDefault(require("path"));
const debug = require('debug')('expo:metro-config:supervising-transform-worker:module-mapper');
const requireResolveBasepath = (request, params) => path_1.default.dirname(require.resolve(`${request}/package.json`, params));
const expoMetroBasepath = requireResolveBasepath('@expo/metro');
/** Modules that should be mapped to a different resolution path.
* @remarks
* This defines a list of packages we want to remap and their destinations.
* For each entry, the key is the module resolution to redirect, and the
* value is the path to resolve from.
* `createModuleMapper()` resolves these modules as if we were requiring
* them from the paths below.
*
* For example, for `expoMetroBasepath`, we're
* requiring this module as if we were inside `@expo/metro`.
*
* This means we'll always get that path's dependency.
*/
const MODULE_RESOLUTIONS = {
metro: expoMetroBasepath,
'metro-babel-transformer': expoMetroBasepath,
'metro-cache': expoMetroBasepath,
'metro-cache-key': expoMetroBasepath,
'metro-config': expoMetroBasepath,
'metro-core': expoMetroBasepath,
'metro-file-map': expoMetroBasepath,
'metro-resolver': expoMetroBasepath,
'metro-runtime': expoMetroBasepath,
'metro-source-map': expoMetroBasepath,
'metro-transform-plugins': expoMetroBasepath,
'metro-transform-worker': expoMetroBasepath,
'@expo/metro-config': requireResolveBasepath('expo'),
};
const escapeDependencyName = (dependency) => dependency.replace(/[*.?()[\]]/g, (x) => `\\${x}`);
const dependenciesToRegex = (dependencies) => new RegExp(`^(${dependencies.map(escapeDependencyName).join('|')})($|/.*)`);
/** Returns a resolver function that given a request to a module returns that module's remapped path. */
const createModuleMapper = () => {
// Matches only module names, inside `MODULE_RESOLUTIONS`
const moduleTestRe = dependenciesToRegex(Object.keys(MODULE_RESOLUTIONS));
return (request) => {
const moduleMatch = moduleTestRe.exec(request);
if (moduleMatch) {
// If the request is for a package in `MODULE_RESOLUTIONS`, we use
// the value in `MODULE_RESOLUTIONS` as a require path
const moduleSearchPath = MODULE_RESOLUTIONS[moduleMatch[1]];
if (moduleSearchPath) {
// Resolve the dependency request from `moduleSearchPath` instead of
// the transformer's own path
return require.resolve(request, { paths: [moduleSearchPath] });
}
}
return null;
};
};
exports.createModuleMapper = createModuleMapper;
/** Checks if we're either in a worker thread or a child process */
const isInForkedProcess = () => !require('worker_threads').isMainThread || typeof process.send === 'function';
let hasPatchedNodeModuleResolver = false;
/** Patches `Module._resolveFilename` (usually just does Node resolution) to override some requires and imports
* @remarks
* The user's transform worker (or their babel transformer, which is called inside the transform-worker) can
* import/require any version of metro, metro-*, or @expo/metro-config in theory. But Expo CLI uses a specific
* version of Metro.
* It's unsupported to use one version of Metro in Expo CLI but another in the transform worker or babel transformer,
* and while this *can work* sometimes, it's never correct.
*
* When called, this function modifies this Node.js thread's module resolution to redirect all imports for Metro
* packages or @expo/metro-config to the version that we know is correct.
*
* We know the versions we have are correct since we're inside @expo/metro-config in this file.
*
* NOTE: Bun also supports overriding `Module._resolveFilename`
*/
const patchNodeModuleResolver = () => {
if (hasPatchedNodeModuleResolver) {
return;
}
else if (!isInForkedProcess()) {
// If max-workers=0 is set for Metro, we will be transforming in the
// main thread (same thread as @expo/cli and Metro).
// We should not patch Module._resolveFilename if we're not in a
// separate Node.js thread to prevent `@expo/cli`'s imports from
// being manipulated. This is dangerous and it'd get hard to
// predict what would happen
debug('Module interception disabled: Not in a child process!');
}
hasPatchedNodeModuleResolver = true;
const moduleMapper = (0, exports.createModuleMapper)();
// NOTE: Guard against mocks, see: https://github.com/danez/pirates/blob/5a81f70/lib/index.js#L8-L10
const Module = module.constructor.length > 1 ? module.constructor : module_1.default;
const originalResolveFilename = Module._resolveFilename;
let isInCustomResolver = false;
Module._resolveFilename = function (request, parent, isMain, options) {
if (!isInCustomResolver) {
try {
isInCustomResolver = true;
const parentId = typeof parent === 'string' ? parent : parent?.id;
if (parentId) {
// If the `transform-worker` requests a module in `MODULE_RESOLUTIONS`,
// we redirect the request to a different resolution path
// This path is based on requiring as if we're in a different module
// For example,
// 1. the user's transform-worker imports `metro-transform-worker`
// 2. this matches in `moduleMapper` and we get a replacement path
// 3. we return this redirect path here
// 4. the user's transform-worker now imports `metro-transform-worker` from `@expo/metro`'s dependencies instead
const redirectedRequest = moduleMapper(request);
if (redirectedRequest) {
debug(`Redirected request "${request}" -> "${redirectedRequest}"`);
return redirectedRequest;
}
}
}
catch (error) {
debug(`Could not redirect request "${request}": ${error}`);
}
finally {
// This guards against infinite recursion
isInCustomResolver = false;
}
}
return originalResolveFilename.call(this, request, parent, isMain, options);
};
};
exports.patchNodeModuleResolver = patchNodeModuleResolver;
//# sourceMappingURL=moduleMapper.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"moduleMapper.js","sourceRoot":"","sources":["../../../src/transform-worker/utils/moduleMapper.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export declare function tryRequireThenImport<TModule>(moduleId: string): Promise<TModule>;
export declare function requireUncachedFile(moduleId: string): any;

View File

@@ -0,0 +1,76 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.tryRequireThenImport = tryRequireThenImport;
exports.requireUncachedFile = requireUncachedFile;
const path = __importStar(require("node:path"));
const url = __importStar(require("node:url"));
async function tryRequireThenImport(moduleId) {
try {
return require(moduleId);
}
catch (requireError) {
let importESM;
try {
// eslint-disable-next-line no-new-func
importESM = new Function('id', 'return import(id);');
}
catch {
importESM = null;
}
if (requireError?.code === 'ERR_REQUIRE_ESM' && importESM) {
const moduleIdOrUrl = path.isAbsolute(moduleId) ? url.pathToFileURL(moduleId).href : moduleId;
const m = await importESM(moduleIdOrUrl);
return m.default ?? m;
}
throw requireError;
}
}
function requireUncachedFile(moduleId) {
try {
// delete require.cache[require.resolve(moduleId)];
}
catch { }
try {
return require(moduleId);
}
catch (error) {
if (error instanceof Error) {
error.message = `Cannot load file ${moduleId}: ${error.message}`;
}
throw error;
}
}
//# sourceMappingURL=require.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"require.js","sourceRoot":"","sources":["../../../src/transform-worker/utils/require.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,oDAqBC;AAED,kDAYC;AAtCD,gDAAkC;AAClC,8CAAgC;AAEzB,KAAK,UAAU,oBAAoB,CAAU,QAAgB;IAClE,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,YAAiB,EAAE,CAAC;QAC3B,IAAI,SAAS,CAAC;QACd,IAAI,CAAC;YACH,uCAAuC;YACvC,SAAS,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;QAED,IAAI,YAAY,EAAE,IAAI,KAAK,iBAAiB,IAAI,SAAS,EAAE,CAAC;YAC1D,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;YAE9F,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,aAAa,CAAC,CAAC;YACzC,OAAO,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;QACxB,CAAC;QAED,MAAM,YAAY,CAAC;IACrB,CAAC;AACH,CAAC;AAED,SAAgB,mBAAmB,CAAC,QAAgB;IAClD,IAAI,CAAC;QACH,mDAAmD;IACrD,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACV,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,KAAK,CAAC,OAAO,GAAG,oBAAoB,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACnE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}