"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(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 });
// @ts-ignore
const webpack_pwa_manifest_plugin_1 = __importDefault(require("@expo/webpack-pwa-manifest-plugin"));
const InterpolateHtmlPlugin_1 = __importDefault(require("react-dev-utils/InterpolateHtmlPlugin"));
const webpack_1 = require("webpack");
const webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer");
// @ts-ignore
const webpack_deep_scope_plugin_1 = __importDefault(require("webpack-deep-scope-plugin"));
const workbox_webpack_plugin_1 = __importDefault(require("workbox-webpack-plugin"));
// @ts-ignore
const clean_webpack_plugin_1 = __importDefault(require("clean-webpack-plugin"));
// @ts-ignore
const ModuleNotFoundPlugin_1 = __importDefault(require("react-dev-utils/ModuleNotFoundPlugin"));
// @ts-ignore
const pnp_webpack_plugin_1 = __importDefault(require("pnp-webpack-plugin"));
const ModuleScopePlugin_1 = __importDefault(require("react-dev-utils/ModuleScopePlugin"));
const webpack_manifest_plugin_1 = __importDefault(require("webpack-manifest-plugin"));
// @ts-ignore
const case_sensitive_paths_webpack_plugin_1 = __importDefault(require("case-sensitive-paths-webpack-plugin"));
const WatchMissingNodeModulesPlugin_1 = __importDefault(require("react-dev-utils/WatchMissingNodeModulesPlugin"));
// @ts-ignore
const mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin"));
// @ts-ignore
const compression_webpack_plugin_1 = __importDefault(require("compression-webpack-plugin"));
// @ts-ignore
const brotli_webpack_plugin_1 = __importDefault(require("brotli-webpack-plugin"));
const copy_webpack_plugin_1 = __importDefault(require("copy-webpack-plugin"));
const paths_1 = require("./utils/paths");
const plugins_1 = require("./plugins");
const utils_1 = require("./utils");
const config_1 = require("./utils/config");
const createFontLoader_1 = __importDefault(require("./loaders/createFontLoader"));
const createBabelLoader_1 = __importDefault(require("./loaders/createBabelLoader"));
const getMode_1 = __importDefault(require("./utils/getMode"));
const getConfig_1 = __importDefault(require("./utils/getConfig"));
const DEFAULT_GZIP = {
test: /\.(js|css)$/,
filename: '[path].gz[query]',
algorithm: 'gzip',
threshold: 1024,
minRatio: 0.8,
};
const DEFAULT_BROTLI = {
asset: '[path].br[query]',
test: /\.(js|css)$/,
threshold: 1024,
minRatio: 0.8,
};
const DEFAULT_SERVICE_WORKER = {};
const DEFAULT_REPORT_CONFIG = {
verbose: false,
path: 'web-report',
statsFilename: 'stats.json',
reportFilename: 'report.html',
};
// This is needed for webpack to import static images in JavaScript files.
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
// TODO: Bacon: Move SVG
const imageLoaderConfiguration = {
test: /\.(gif|jpe?g|png|svg)$/,
use: {
loader: require.resolve('url-loader'),
options: {
// Inline resources as Base64 when there is less reason to parallelize their download. The
// heuristic we use is whether the resource would fit within a TCP/IP packet that we would
// send to request the resource.
//
// An Ethernet MTU is usually 1500. IP headers are 20 (v4) or 40 (v6) bytes and TCP
// headers are 40 bytes. HTTP response headers vary and are around 400 bytes. This leaves
// about 1000 bytes for content to fit in a packet.
limit: 1000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
};
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
const fallbackLoaderConfiguration = {
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
// Excludes: js, jsx, ts, tsx, html, json
exclude: [/\.jsx?$/, /\.tsx?$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
};
function createNoJSComponent(message) {
// from twitter.com
return `" <form action="location.reload()" method="POST" style="background-color:#fff;position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;"><div style="font-size:18px;font-family:Helvetica,sans-serif;line-height:24px;margin:10%;width:80%;"> <p>${message}</p> <p style="margin:20px 0;"> <button type="submit" style="background-color: #4630EB; border-radius: 100px; border: none; box-shadow: none; color: #fff; cursor: pointer; font-weight: bold; line-height: 20px; padding: 6px 16px;">Reload</button> </p> </div> </form> "`;
}
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 'source-map';
}
if (development) {
return 'cheap-module-source-map';
}
return false;
}
function default_1(env) {
return __awaiter(this, void 0, void 0, function* () {
const config = getConfig_1.default(env);
const mode = getMode_1.default(env);
const isDev = mode === 'development';
const isProd = mode === 'production';
const { platform = 'web' } = env;
// Enables deep scope analysis in production mode.
// Remove unused import/exports
// override: `env.removeUnusedImportExports`
const deepScopeAnalysisEnabled = config_1.overrideWithPropertyOrConfig(env.removeUnusedImportExports, false
// isProd
);
const locations = env.locations || (yield paths_1.getPathsAsync(env.projectRoot));
const { publicPath, publicUrl } = paths_1.getPublicPaths(env);
const middlewarePlugins = [];
const { build: buildConfig = {}, lang } = config.web;
const { rootId, babel: babelAppConfig = {} } = buildConfig;
const { noJavaScriptMessage } = config.web.dangerous;
const noJSComponent = createNoJSComponent(noJavaScriptMessage);
if (deepScopeAnalysisEnabled) {
// @ts-ignore
middlewarePlugins.push(new webpack_deep_scope_plugin_1.default());
}
const serviceWorker = config_1.overrideWithPropertyOrConfig(
// Prevent service worker in development mode
buildConfig.serviceWorker, DEFAULT_SERVICE_WORKER);
if (serviceWorker) {
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
middlewarePlugins.push(new workbox_webpack_plugin_1.default.GenerateSW(Object.assign({ exclude: [
/\.LICENSE$/,
/\.map$/,
/asset-manifest\.json$/,
// Exclude all apple touch images as they are cached locally after the PWA is added.
/^\bapple.*\.png$/,
],
/// SINGLE PAGE:
navigateFallback: `${publicUrl}/index.html`, clientsClaim: true, importWorkboxFrom: 'cdn', navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in
// public/ and not a SPA route
new RegExp('/[^/]+\\.[^/]+$'),
] }, (isDev
? {
include: [],
}
: {}), serviceWorker)));
}
/**
* report: {
* verbose: false,
* path: "web-report",
* statsFilename: "stats.json",
* reportFilename: "report.html"
* }
*/
let reportPlugins = [];
const reportConfig = config_1.enableWithPropertyOrConfig(env.report, DEFAULT_REPORT_CONFIG, true);
if (typeof buildConfig.report !== 'undefined') {
throw new Error('expo.web.build.report is deprecated. Please extend webpack.config.js and use env.report instead.');
}
if (reportConfig) {
if (isDev && reportConfig.verbose) {
console.log('Generating a report, this will add noticeably more time to rebuilds.');
}
const reportDir = reportConfig.path;
reportPlugins = [
// Delete the report folder
new clean_webpack_plugin_1.default([locations.absolute(reportDir)], {
root: locations.root,
dry: false,
verbose: reportConfig.verbose,
}),
// Generate the report.html and stats.json
new webpack_bundle_analyzer_1.BundleAnalyzerPlugin(Object.assign({ analyzerMode: 'static', defaultSizes: 'gzip', generateStatsFile: true, openAnalyzer: false }, reportConfig, { logLevel: reportConfig.verbose ? 'info' : 'silent', statsFilename: locations.absolute(reportDir, reportConfig.statsFilename), reportFilename: locations.absolute(reportDir, reportConfig.reportFilename) })),
];
}
const devtool = getDevtool({ production: isProd, development: isDev }, buildConfig);
const babelProjectRoot = babelAppConfig.root || locations.root;
const babelLoader = createBabelLoader_1.default({
mode,
platform,
babelProjectRoot,
verbose: babelAppConfig.verbose,
include: babelAppConfig.include,
use: babelAppConfig.use,
});
const allLoaders = [
{
test: /\.html$/,
use: [require.resolve('html-loader')],
exclude: locations.template.folder,
},
imageLoaderConfiguration,
babelLoader,
createFontLoader_1.default({ locations }),
{
test: /\.(css)$/,
use: [require.resolve('style-loader'), require.resolve('css-loader')],
},
// This needs to be the last loader
fallbackLoaderConfiguration,
].filter(Boolean);
/**
* web: {
* build: {
* verbose: boolean,
* brotli: boolean | {}, // (Brotli Options)
* gzip: boolean | CompressionPlugin.Options<O>,
* }
* }
*/
const gzipConfig = isProd && config_1.overrideWithPropertyOrConfig(buildConfig.gzip, DEFAULT_GZIP);
const brotliConfig = isProd && config_1.enableWithPropertyOrConfig(buildConfig.brotli, DEFAULT_BROTLI);
const appEntry = [];
// In solutions like Gatsby the main entry point doesn't need to be known.
if (locations.appMain) {
appEntry.push(locations.appMain);
}
else {
throw new Error(`The entry point for your project couldn't be found. Please define it in the package.json main field`);
}
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'));
}
return {
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: {
// Build folder (default `web-build`)
path: locations.production.folder,
sourceMapFilename: '[chunkhash].map',
// We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development.
publicPath,
},
plugins: [
// Delete the build folder
isProd &&
new clean_webpack_plugin_1.default([locations.production.folder], {
root: locations.root,
dry: false,
verbose: false,
}),
// Copy the template files over
isProd &&
new copy_webpack_plugin_1.default([
{
from: locations.template.folder,
to: locations.production.folder,
// We generate new versions of these based on the templates
ignore: ['favicon.ico', 'serve.json', 'index.html', 'icon.png'],
},
{
from: locations.template.serveJson,
to: locations.production.serveJson,
},
{
from: locations.template.favicon,
to: locations.production.favicon,
},
]),
// Generate the `index.html`
new plugins_1.ExpoHtmlWebpackPlugin(env),
// Add variables to the `index.html`
new InterpolateHtmlPlugin_1.default(plugins_1.ExpoHtmlWebpackPlugin, {
WEB_PUBLIC_URL: publicPath,
WEB_TITLE: config.web.name,
NO_SCRIPT: noJSComponent,
LANG_ISO_CODE: lang,
ROOT_ID: rootId,
}),
new webpack_pwa_manifest_plugin_1.default(config, {
publicPath,
noResources: isDev || !env.pwa,
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(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isDev && new case_sensitive_paths_webpack_plugin_1.default(),
// 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 a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
new webpack_manifest_plugin_1.default({
fileName: 'asset-manifest.json',
publicPath,
}),
...middlewarePlugins,
gzipConfig && new compression_webpack_plugin_1.default(gzipConfig),
brotliConfig && new brotli_webpack_plugin_1.default(brotliConfig),
new plugins_1.ExpoProgressBarPlugin(),
...reportPlugins,
].filter(Boolean),
module: {
strictExportPresence: false,
rules: [
// Disable require.ensure because it breaks tree shaking.
{ parser: { requireEnsure: false } },
{
oneOf: allLoaders,
},
].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: {
alias: config_1.DEFAULT_ALIAS,
extensions: utils_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,
// Prevents users from importing files from outside of node_modules/.
// This often causes confusion because we only process files within the root folder with babel.
// To fix this, we prevent you from importing files out of the root folder -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin_1.default(babelProjectRoot, [locations.packageJson]),
],
symlinks: false,
},
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
http2: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
};
});
}
exports.default = default_1;
//# sourceMappingURL=webpack.common.js.map