UNPKG

@expo/webpack-config

Version:

The default Webpack configuration used to build Expo apps targeting the web.

288 lines 14.9 kB
"use strict"; /** @internal */ /** */ /* eslint-env node */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const webpack_pwa_manifest_plugin_1 = __importDefault(require("@expo/webpack-pwa-manifest-plugin")); const webpack_1 = require("webpack"); const webpack_deep_scope_plugin_1 = __importDefault(require("webpack-deep-scope-plugin")); const ModuleNotFoundPlugin_1 = __importDefault(require("react-dev-utils/ModuleNotFoundPlugin")); const pnp_webpack_plugin_1 = __importDefault(require("pnp-webpack-plugin")); const webpack_manifest_plugin_1 = __importDefault(require("webpack-manifest-plugin")); const WatchMissingNodeModulesPlugin_1 = __importDefault(require("react-dev-utils/WatchMissingNodeModulesPlugin")); const mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin")); const copy_webpack_plugin_1 = __importDefault(require("copy-webpack-plugin")); const getenv_1 = require("getenv"); const path_1 = __importDefault(require("path")); const clean_webpack_plugin_1 = require("clean-webpack-plugin"); const config_1 = require("@expo/config"); const env_1 = require("./env"); const loaders_1 = require("./loaders"); const plugins_1 = require("./plugins"); const addons_1 = require("./addons"); const utils_1 = require("./utils"); // Source maps are resource heavy and can cause out of memory issue for large source files. const shouldUseSourceMap = getenv_1.boolish('GENERATE_SOURCEMAP', true); function getDevtool({ production, development }, { devtool }) { if (production) { // string or false if (devtool !== undefined) { // When big assets are involved sources maps can become expensive and cause your process to run out of memory. return devtool; } return shouldUseSourceMap ? 'source-map' : false; } if (development) { return 'cheap-module-source-map'; } return false; } function getOutput(locations, mode, publicPath) { const commonOutput = { // We inferred the "public path" (such as / or /my-project) from homepage. // We use "/" in development. publicPath, // Build folder (default `web-build`) path: locations.production.folder, // this defaults to 'window', but by setting it to 'this' then // module chunks which are built will work in web workers as well. globalObject: 'this', }; if (mode === 'production') { commonOutput.filename = 'static/js/[name].[contenthash:8].js'; // There are also additional JS chunk files if you use code splitting. commonOutput.chunkFilename = 'static/js/[name].[contenthash:8].chunk.js'; // Point sourcemap entries to original disk location (format as URL on Windows) commonOutput.devtoolModuleFilenameTemplate = (info) => path_1.default.relative(locations.root, info.absoluteResourcePath).replace(/\\/g, '/'); } else { // Add comments that describe the file import/exports. // This will make it easier to debug. commonOutput.pathinfo = true; // Give the output bundle a constant name to prevent caching. // Also there are no actual files generated in dev. commonOutput.filename = 'static/js/bundle.js'; // There are also additional JS chunk files if you use code splitting. commonOutput.chunkFilename = 'static/js/[name].chunk.js'; // Point sourcemap entries to original disk location (format as URL on Windows) commonOutput.devtoolModuleFilenameTemplate = (info) => path_1.default.resolve(info.absoluteResourcePath).replace(/\\/g, '/'); } return commonOutput; } function default_1(env, argv = {}) { return __awaiter(this, void 0, void 0, function* () { const config = env_1.getConfig(env); const mode = env_1.getMode(env); const isDev = mode === 'development'; const isProd = mode === 'production'; // Enables deep scope analysis in production mode. // Remove unused import/exports // override: `env.removeUnusedImportExports` const deepScopeAnalysisEnabled = utils_1.overrideWithPropertyOrConfig(env.removeUnusedImportExports, false // isProd ); const locations = env.locations || (yield env_1.getPathsAsync(env.projectRoot)); const { publicPath, publicUrl } = env_1.getPublicPaths(env); const { build: buildConfig = {} } = config.web || {}; const devtool = getDevtool({ production: isProd, development: isDev }, buildConfig); const appEntry = []; // In solutions like Gatsby the main entry point doesn't need to be known. if (locations.appMain) { appEntry.push(locations.appMain); } // Add a loose requirement on the ResizeObserver polyfill if it's installed... // Avoid `withEntry` as we don't need so much complexity with this config. const resizeObserverPolyfill = config_1.projectHasModule('resize-observer-polyfill/dist/ResizeObserver.global', env.projectRoot, config); if (resizeObserverPolyfill) { appEntry.unshift(resizeObserverPolyfill); } if (isDev) { // https://github.com/facebook/create-react-app/blob/e59e0920f3bef0c2ac47bbf6b4ff3092c8ff08fb/packages/react-scripts/config/webpack.config.js#L144 // Include an alternative client for WebpackDevServer. A client's job is to // connect to WebpackDevServer by a socket and get notified about changes. // When you save a file, the client will either apply hot updates (in case // of CSS changes), or refresh the page (in case of JS changes). When you // make a syntax error, this client will display a syntax error overlay. // Note: instead of the default WebpackDevServer client, we use a custom one // to bring better experience for Create React App users. You can replace // the line below with these two lines if you prefer the stock client: // require.resolve('webpack-dev-server/client') + '?/', // require.resolve('webpack/hot/dev-server'), appEntry.unshift(require.resolve('react-dev-utils/webpackHotDevClient')); } let generatePWAImageAssets = !isDev; if (!isDev && typeof env.pwa !== 'undefined') { generatePWAImageAssets = env.pwa; } const filesToCopy = [ { from: locations.template.folder, to: locations.production.folder, // We generate new versions of these based on the templates ignore: [ 'expo-service-worker.js', 'favicon.ico', 'serve.json', 'index.html', 'icon.png', // We copy this over in `withWorkbox` as it must be part of the Webpack `entry` and have templates replaced. 'register-service-worker.js', ], }, { from: locations.template.serveJson, to: locations.production.serveJson, }, { from: locations.template.favicon, to: locations.production.favicon, }, ]; if (env.offline !== false) { filesToCopy.push({ from: locations.template.serviceWorker, to: locations.production.serviceWorker, }); } let webpackConfig = { mode, entry: { app: appEntry, }, // https://webpack.js.org/configuration/other-options/#bail // Fail out on the first error instead of tolerating it. bail: isProd, devtool, context: __dirname, // configures where the build ends up output: getOutput(locations, mode, publicPath), plugins: [ // Delete the build folder isProd && new clean_webpack_plugin_1.CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: [locations.production.folder], dry: false, verbose: false, }), // Copy the template files over isProd && new copy_webpack_plugin_1.default(filesToCopy), // Generate the `index.html` new plugins_1.ExpoHtmlWebpackPlugin(env), plugins_1.ExpoInterpolateHtmlPlugin.fromEnv(env, plugins_1.ExpoHtmlWebpackPlugin), new webpack_pwa_manifest_plugin_1.default(config, { publicPath, projectRoot: env.projectRoot, noResources: !generatePWAImageAssets, filename: locations.production.manifest, HtmlWebpackPlugin: plugins_1.ExpoHtmlWebpackPlugin, }), // This gives some necessary context to module not found errors, such as // the requesting resource. new ModuleNotFoundPlugin_1.default(locations.root), new plugins_1.ExpoDefinePlugin({ mode, publicUrl, config, productionManifestPath: locations.production.manifest, }), // This is necessary to emit hot updates (currently CSS only): isDev && new webpack_1.HotModuleReplacementPlugin(), // If you require a missing module and then `npm install` it, you still have // to restart the development server for Webpack to discover it. This plugin // makes the discovery automatic so you don't have to restart. // See https://github.com/facebook/create-react-app/issues/186 isDev && new WatchMissingNodeModulesPlugin_1.default(locations.modules), isProd && new mini_css_extract_plugin_1.default({ // Options similar to the same options in webpackOptions.output // both options are optional filename: 'static/css/[name].[contenthash:8].css', chunkFilename: 'static/css/[name].[contenthash:8].chunk.css', }), // Generate an asset manifest file with the following content: // - "files" key: Mapping of all asset filenames to their corresponding // output file so that tools can pick it up without having to parse // `index.html` // - "entrypoints" key: Array of files which are included in `index.html`, // can be used to reconstruct the HTML if necessary new webpack_manifest_plugin_1.default({ fileName: 'asset-manifest.json', publicPath, filter: ({ path }) => { // Remove compressed versions and service workers return !(path.endsWith('.gz') || path.endsWith('worker.js')); }, generate: (seed, files, entrypoints) => { const manifestFiles = files.reduce((manifest, file) => { if (file.name) { manifest[file.name] = file.path; } return manifest; }, seed); const entrypointFiles = entrypoints.app.filter(fileName => !fileName.endsWith('.map')); return { files: manifestFiles, entrypoints: entrypointFiles, }; }, }), deepScopeAnalysisEnabled && new webpack_deep_scope_plugin_1.default(), new plugins_1.ExpoProgressBarPlugin(), ].filter(Boolean), module: { strictExportPresence: false, rules: [ // Disable require.ensure because it breaks tree shaking. { parser: { requireEnsure: false } }, { oneOf: loaders_1.createAllLoaders(env), }, ].filter(Boolean), }, resolveLoader: { plugins: [ // Also related to Plug'n'Play, but this time it tells Webpack to load its loaders // from the current package. pnp_webpack_plugin_1.default.moduleLoader(module), ], }, resolve: { mainFields: ['browser', 'module', 'main'], extensions: env_1.getModuleFileExtensions('web'), plugins: [ // Adds support for installing with Plug'n'Play, leading to faster installs and adding // guards against forgotten dependencies and such. pnp_webpack_plugin_1.default, ], symlinks: false, }, // Turn off performance processing because we utilize // our own (CRA) hints via the FileSizeReporter // TODO: Bacon: Remove this higher value performance: getenv_1.boolish('CI', false) ? false : { maxAssetSize: 600000, maxEntrypointSize: 600000 }, }; if (isProd) { webpackConfig = addons_1.withCompression(addons_1.withOptimizations(webpackConfig), env); } else { webpackConfig = addons_1.withDevServer(webpackConfig, env, { allowedHost: argv.allowedHost, proxy: argv.proxy, }); } return addons_1.withReporting(addons_1.withNodeMocks(addons_1.withAlias(webpackConfig)), env); }); } exports.default = default_1; //# sourceMappingURL=webpack.config.js.map