in fusion-cli/build/get-webpack-config.js [119:639]
function getWebpackConfig(opts /*: WebpackConfigOpts */) {
const {
id,
dev,
dir,
hmr,
watch,
state,
fusionConfig,
zopfli, // TODO: Remove redundant zopfli option
gzip,
brotli,
minify,
skipSourceMaps,
legacyPkgConfig = {},
worker,
} = opts;
const main = 'src/main.js';
if (!fs.existsSync(path.join(dir, main))) {
throw new Error(`Project directory must contain a ${main} file`);
}
const runtime = COMPILATIONS[id];
const env = dev ? 'development' : 'production';
const shouldMinify = !dev && minify;
// Both options default to true, but if `--zopfli=false`
// it should be respected for backwards compatibility
const shouldGzip = zopfli && gzip;
const babelConfigData = {
target: runtime === 'server' ? 'node-bundled' : 'browser-modern',
specOnly: true,
plugins:
fusionConfig.babel && fusionConfig.babel.plugins
? fusionConfig.babel.plugins
: [],
presets:
fusionConfig.babel && fusionConfig.babel.presets
? fusionConfig.babel.presets
: [],
};
const babelOverridesData = {
dev: dev,
fusionTransforms: true,
assumeNoImportSideEffects: fusionConfig.assumeNoImportSideEffects,
target: runtime === 'server' ? 'node-bundled' : 'browser-modern',
specOnly: false,
};
const legacyBabelOverridesData = {
dev: dev,
fusionTransforms: true,
assumeNoImportSideEffects: fusionConfig.assumeNoImportSideEffects,
target: runtime === 'server' ? 'node-bundled' : 'browser-legacy',
specOnly: false,
};
const {experimentalBundleTest, experimentalTransformTest} = fusionConfig;
const babelTester = experimentalTransformTest
? modulePath => {
if (!JS_EXT_PATTERN.test(modulePath)) {
return false;
}
const transform = experimentalTransformTest(
modulePath,
getTransformDefault(modulePath, dir)
);
if (transform === 'none') {
return false;
} else if (transform === 'all' || transform === 'spec') {
return true;
} else {
throw new Error(
`Unexpected value from experimentalTransformTest ${transform}. Expected 'spec' | 'all' | 'none'`
);
}
}
: JS_EXT_PATTERN;
return {
name: runtime,
target: {server: 'node', client: 'web', sw: 'webworker'}[runtime],
entry: {
main: [
runtime === 'client' &&
path.join(__dirname, '../entries/client-public-path.js'),
runtime === 'server' &&
path.join(__dirname, '../entries/server-public-path.js'),
dev &&
hmr &&
watch &&
runtime !== 'server' &&
`${require.resolve('webpack-hot-middleware/client')}?name=client`,
// TODO(#46): use 'webpack/hot/signal' instead
dev &&
hmr &&
watch &&
runtime === 'server' &&
`${require.resolve('webpack/hot/poll')}?1000`,
runtime === 'server' &&
path.join(__dirname, `../entries/${id}-entry.js`), // server-entry or serverless-entry
runtime === 'client' &&
path.join(__dirname, '../entries/client-entry.js'),
].filter(Boolean),
},
mode: dev ? 'development' : 'production',
// TODO(#47): Do we need to do something different here for production?
stats: 'minimal',
/**
* `cheap-module-source-map` is best supported by Chrome DevTools
* See: https://github.com/webpack/webpack/issues/2145#issuecomment-294361203
*
* We use `source-map` in production but effectively create a
* `hidden-source-map` using SourceMapPlugin to strip the comment.
*
* Chrome DevTools support doesn't matter in these case.
* We only use it for generating nice stack traces
*/
// TODO(#6): what about node v8 inspector?
devtool: skipSourceMaps
? false
: runtime === 'client' && !dev
? 'source-map'
: runtime === 'sw'
? 'hidden-source-map'
: 'cheap-module-source-map',
output: {
path: path.join(dir, `.fusion/dist/${env}/${runtime}`),
filename:
runtime === 'server'
? 'server-main.js'
: dev
? 'client-[name].js'
: 'client-[name]-[chunkhash].js',
libraryTarget: runtime === 'server' ? 'commonjs2' : 'var',
// This is the recommended default.
// See https://webpack.js.org/configuration/output/#output-sourcemapfilename
sourceMapFilename: `[file].map`,
// We will set __webpack_public_path__ at runtime, so this should be set to undefined
publicPath: void 0,
crossOriginLoading: 'anonymous',
devtoolModuleFilenameTemplate: (info /*: Object */) => {
// always return absolute paths in order to get sensible source map explorer visualization
return path.isAbsolute(info.absoluteResourcePath)
? info.absoluteResourcePath
: path.join(dir, info.absoluteResourcePath);
},
},
performance: {
hints: false,
},
context: dir,
node: Object.assign(
getNodeConfig(runtime),
legacyPkgConfig.node,
fusionConfig.nodeBuiltins
),
module: {
/**
* Compile-time error for importing a non-existent export
* https://github.com/facebookincubator/create-react-app/issues/1559
*/
strictExportPresence: true,
rules: [
/**
* Global transforms (including ES2017+ transpilations)
*/
runtime === 'server' && {
compiler: id => id === 'server',
test: babelTester,
exclude: EXCLUDE_TRANSPILATION_PATTERNS,
use: [
{
loader: babelLoader.path,
options: {
dir,
configCacheKey: 'server-config',
overrideCacheKey: 'server-override',
babelConfigData: {...babelConfigData},
/**
* Fusion-specific transforms (not applied to node_modules)
*/
overrides: [
{
...babelOverridesData,
},
],
},
},
],
},
/**
* Global transforms (including ES2017+ transpilations)
*/
(runtime === 'client' || runtime === 'sw') && {
compiler: id => id === 'client' || id === 'sw',
test: babelTester,
exclude: EXCLUDE_TRANSPILATION_PATTERNS,
use: [
{
loader: babelLoader.path,
options: {
dir,
configCacheKey: 'client-config',
overrideCacheKey: 'client-override',
babelConfigData: {...babelConfigData},
/**
* Fusion-specific transforms (not applied to node_modules)
*/
overrides: [
{
...babelOverridesData,
},
],
},
},
],
},
/**
* Global transforms (including ES2017+ transpilations)
*/
runtime === 'client' && {
compiler: id => id === 'client-legacy',
test: babelTester,
exclude: EXCLUDE_TRANSPILATION_PATTERNS,
use: [
{
loader: babelLoader.path,
options: {
dir,
configCacheKey: 'legacy-config',
overrideCacheKey: 'legacy-override',
babelConfigData: {
target:
runtime === 'server' ? 'node-bundled' : 'browser-legacy',
specOnly: true,
plugins:
fusionConfig.babel && fusionConfig.babel.plugins
? fusionConfig.babel.plugins
: [],
presets:
fusionConfig.babel && fusionConfig.babel.presets
? fusionConfig.babel.presets
: [],
},
/**
* Fusion-specific transforms (not applied to node_modules)
*/
overrides: [
{
...legacyBabelOverridesData,
},
],
},
},
],
},
{
test: /\.json$/,
type: 'javascript/auto',
loader: require.resolve('./loaders/json-loader.js'),
},
{
test: /\.ya?ml$/,
type: 'json',
loader: require.resolve('yaml-loader'),
},
{
test: /\.graphql$|.gql$/,
loader: require.resolve('graphql-tag/loader'),
},
fusionConfig.assumeNoImportSideEffects && {
sideEffects: false,
test: modulePath => {
if (
modulePath.includes('core-js/modules') ||
modulePath.includes('regenerator-runtime/runtime')
) {
return false;
}
return true;
},
},
].filter(Boolean),
},
externals: [
runtime === 'server' &&
((context, request, callback) => {
if (/^[@a-z\-0-9]+/.test(request)) {
const absolutePath = resolveFrom.silent(context, request);
// do not bundle external packages and those not whitelisted
if (typeof absolutePath !== 'string') {
// if module is missing, skip rewriting to absolute path
return callback(null, request);
}
if (experimentalBundleTest) {
const bundle = experimentalBundleTest(
absolutePath,
'browser-only'
);
if (bundle === 'browser-only') {
// don't bundle on the server
return callback(null, 'commonjs ' + absolutePath);
} else if (bundle === 'universal') {
// bundle on the server
return callback();
} else {
throw new Error(
`Unexpected value: ${bundle} from experimentalBundleTest. Expected 'browser-only' | 'universal'.`
);
}
}
return callback(null, 'commonjs ' + absolutePath);
}
// bundle everything else (local files, __*)
return callback();
}),
].filter(Boolean),
resolve: {
symlinks: process.env.NODE_PRESERVE_SYMLINKS ? false : true,
aliasFields: [
(runtime === 'client' || runtime === 'sw') && 'browser',
'es2015',
'es2017',
].filter(Boolean),
alias: {
// we replace need to set the path to user application at build-time
__FUSION_ENTRY_PATH__: path.join(dir, main),
__ENV__: env,
...(process.env.ENABLE_REACT_PROFILER === 'true'
? {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}
: {}),
},
plugins: [PnpWebpackPlugin],
},
resolveLoader: {
symlinks: process.env.NODE_PRESERVE_SYMLINKS ? false : true,
alias: {
[fileLoader.alias]: fileLoader.path,
[chunkIdsLoader.alias]: chunkIdsLoader.path,
[syncChunkIdsLoader.alias]: syncChunkIdsLoader.path,
[syncChunkPathsLoader.alias]: syncChunkPathsLoader.path,
[chunkUrlMapLoader.alias]: chunkUrlMapLoader.path,
[i18nManifestLoader.alias]: i18nManifestLoader.path,
[swLoader.alias]: swLoader.path,
[workerLoader.alias]: workerLoader.path,
},
plugins: [PnpWebpackPlugin.moduleLoader(module)],
},
plugins: [
runtime === 'client' && !dev && new SourceMapPlugin(),
runtime === 'client' &&
new webpack.optimize.RuntimeChunkPlugin({
name: 'runtime',
}),
(fusionConfig.defaultImportSideEffects === false ||
Array.isArray(fusionConfig.defaultImportSideEffects)) &&
new DefaultNoImportSideEffectsPlugin(
Array.isArray(fusionConfig.defaultImportSideEffects)
? {ignoredPackages: fusionConfig.defaultImportSideEffects}
: {}
),
new webpack.optimize.SideEffectsFlagPlugin(),
runtime === 'server' &&
new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}),
new ProgressBarPlugin(),
runtime === 'server' &&
new LoaderContextProviderPlugin('optsContext', opts),
new LoaderContextProviderPlugin(devContextKey, dev),
runtime === 'server' &&
new LoaderContextProviderPlugin(
clientChunkMetadataContextKey,
state.mergedClientChunkMetadata
),
runtime === 'client'
? new I18nDiscoveryPlugin(
state.i18nDeferredManifest,
state.i18nManifest
)
: new LoaderContextProviderPlugin(
translationsManifestContextKey,
state.i18nDeferredManifest
),
new LoaderContextProviderPlugin(workerKey, worker),
!dev && shouldGzip && gzipWebpackPlugin,
!dev && brotli && brotliWebpackPlugin,
!dev && svgoWebpackPlugin,
// In development, skip the emitting phase on errors to ensure there are
// no assets emitted that include errors. This fixes an issue with hot reloading
// server side code and recovering from errors correctly. We only want to do this
// in dev because the CLI will not exit with an error code if the option is enabled,
// so failed builds would look like successful ones.
watch && new webpack.NoEmitOnErrorsPlugin(),
runtime === 'server'
? // Server
new InstrumentedImportDependencyTemplatePlugin({
compilation: 'server',
clientChunkMetadata: state.mergedClientChunkMetadata,
})
: /**
* Client
* Don't wait for the client manifest on the client.
* The underlying plugin is able determine client chunk metadata on its own.
*/
new InstrumentedImportDependencyTemplatePlugin({
compilation: 'client',
i18nManifest: state.i18nManifest,
}),
dev && hmr && watch && new webpack.HotModuleReplacementPlugin(),
!dev && runtime === 'client' && new webpack.HashedModuleIdsPlugin(),
runtime === 'client' &&
// case-insensitive paths can cause problems
new CaseSensitivePathsPlugin(),
runtime === 'server' &&
new webpack.BannerPlugin({
raw: true,
entryOnly: true,
// Enforce NODE_ENV at runtime
banner: getEnvBanner(env),
}),
new webpack.EnvironmentPlugin({NODE_ENV: env}),
id === 'client-modern' &&
new ClientChunkMetadataStateHydratorPlugin(state.clientChunkMetadata),
id === 'client-modern' &&
new ChildCompilationPlugin({
name: 'client-legacy',
entry: [
path.resolve(__dirname, '../entries/client-public-path.js'),
path.resolve(__dirname, '../entries/client-entry.js'),
// EVENTUALLY HAVE HMR
],
enabledState: opts.state.legacyBuildEnabled,
outputOptions: {
filename: opts.dev
? 'client-legacy-[name].js'
: 'client-legacy-[name]-[chunkhash].js',
chunkFilename: opts.dev
? 'client-legacy-[name].js'
: 'client-legacy-[name]-[chunkhash].js',
},
plugins: options => [
new webpack.optimize.RuntimeChunkPlugin(
options.optimization.runtimeChunk
),
new webpack.optimize.SplitChunksPlugin(
options.optimization.splitChunks
),
// need to re-apply template
new InstrumentedImportDependencyTemplatePlugin({
compilation: 'client',
i18nManifest: state.i18nManifest,
}),
new ClientChunkMetadataStateHydratorPlugin(
state.legacyClientChunkMetadata
),
new ChunkIdPrefixPlugin(),
],
}),
].filter(Boolean),
optimization: {
runtimeChunk: runtime === 'client' && {name: 'runtime'},
splitChunks:
runtime !== 'client'
? void 0
: fusionConfig.splitChunks
? // Tilde character in filenames is not well supported
// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
{...fusionConfig.splitChunks, automaticNameDelimiter: '-'}
: {
chunks: 'async',
automaticNameDelimiter: '-',
cacheGroups: {
default: {
minChunks: 2,
reuseExistingChunk: true,
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'initial',
enforce: true,
},
},
},
minimize: shouldMinify,
minimizer: shouldMinify
? [
new TerserPlugin({
sourceMap: skipSourceMaps ? false : true, // default from webpack (see https://github.com/webpack/webpack/blob/aab3554cad2ebc5d5e9645e74fb61842e266da34/lib/WebpackOptionsDefaulter.js#L290-L297)
cache: true, // default from webpack
parallel: true, // default from webpack
extractComments: false,
terserOptions: {
compress: {
// typeofs: true (default) transforms typeof foo == "undefined" into foo === void 0.
// This mangles mapbox-gl creating an error when used alongside with window global mangling:
// https://github.com/webpack-contrib/uglifyjs-webpack-plugin/issues/189
typeofs: false,
// inline=2 can cause const reassignment
// https://github.com/mishoo/UglifyJS2/issues/2842
inline: 1,
},
keep_fnames: opts.preserveNames,
keep_classnames: opts.preserveNames,
},
}),
]
: undefined,
},
};
}