@expo/webpack-config
Version:
The default Webpack configuration used to build Expo apps targeting the web.
288 lines • 14.9 kB
JavaScript
"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