UNPKG

@expo/metro-config

Version:

A Metro config for running React Native projects with the Metro bundler

246 lines 11.4 kB
"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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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 = void 0; /** * 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 countLines_1 = __importDefault(require("metro/src/lib/countLines")); const css_1 = require("./css"); const css_modules_1 = require("./css-modules"); 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 reactServer = options.customTransformOptions?.environment === 'react-server'; 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\/metro-runtime\/rsc\/virtual\.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); } } } const isCss = options.type !== 'asset' && /\.(s?css|sass)$/.test(filename); // If the file is not CSS, then use the default behavior. if (!isCss) { 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?$/))) { // 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); } return worker.transform(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: posixFilename, src: code, options: { reactServer, 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, // @ts-expect-error: Added for testing against virtual file system. resolver: options._test_resolveCss, }); (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, }; // 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, }; } exports.transform = transform; /** * 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