webpack.config.js
'use strict';
const
CleanPlugin = require( 'clean-webpack-plugin' ),
path = require( 'path' ),
// The output directory for all build artifacts. Only absolute paths are accepted by
// output.path.
distDir = path.resolve( __dirname, 'resources/dist' ),
// The extension used for source map files.
srcMapExt = '.map.json',
ENTRIES = {
startup: 'mobile.startup',
editor: 'mobile.editor.overlay',
languages: 'mobile.languages.structured',
mediaViewer: 'mobile.mediaViewer',
mobileInit: 'mobile.init',
mobileOptions: 'mobile.special.mobileoptions.scripts',
userLogin: 'mobile.special.userlogin.scripts',
watchlist: 'mobile.special.watchlist.scripts'
};
module.exports = ( env, argv ) => ( {
// Apply the rule of silence: https://wikipedia.org/wiki/Unix_philosophy.
stats: {
all: false,
// Output a timestamp when a build completes. Useful when watching files.
builtAt: true,
errors: true,
warnings: true
},
// Fail on the first build error instead of tolerating it for prod builds. This seems to
// correspond to optimization.emitOnErrors.
bail: argv.mode === 'production',
// Specify that all paths are relative the Webpack configuration directory not the current
// working directory.
context: __dirname,
// A map of ResourceLoader module / entry chunk names to JavaScript files to pack. E.g.,
// "mobile.startup" maps to src/mobile.startup/mobile.startup.js. The JavaScript entry could be
// named simply "index.js" but the redundancy of "[name].js" improves presentation and search-
// ability in some tools. Entry names are tightly coupled to output.filename and extension.json.
entry: {
// mobile.startup.runtime: reserved entry for the Webpack bootloader
// optimization.runtimeChunk. Without a distinct runtime chunk, it's instead bundled into
// each entry which is inefficient. This chunk should only change when Webpack or this
// configuration changes.
[ENTRIES.startup]: './src/mobile.startup/mobile.startup.js',
// Make some chunks which will be lazy loaded by resource loader.
// If we utilize webpack lazy loading instead of resource loader lazy
// loading, we won't be required to explicitly create this new chunk and
// this can be removed.
[ENTRIES.editor]: './src/mobile.editor.overlay/mobile.editor.overlay.js',
[ENTRIES.languages]: './src/mobile.languages.structured/mobile.languages.structured.js',
[ENTRIES.mediaViewer]: './src/mobile.mediaViewer/mobile.mediaViewer.js',
// all mobile skins,
[ENTRIES.mobileInit]: './src/mobile.init/mobile.init.js',
// T212823 Make a chunk for each mobile special page
[ENTRIES.mobileOptions]: './src/mobile.special.mobileoptions.scripts.js',
[ENTRIES.userLogin]: './src/mobile.special.userlogin.scripts.js',
[ENTRIES.watchlist]: './src/mobile.special.watchlist.scripts/mobile.special.watchlist.scripts.js'
},
resolve: {
alias: {
// This avoids leaking unnecessary code into the webpack test build
'./mockMediaWiki': path.resolve( __dirname, 'tests/node-qunit/utils/blank.js' )
}
},
module: {
rules: [ {
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// Beware of https://github.com/babel/babel-loader/issues/690. Changes to browsers require
// manual invalidation.
cacheDirectory: true
}
}
} ]
},
optimization: {
// Don't produce production output when a build error occurs.
emitOnErrors: argv.mode !== 'production',
// Use filenames instead of unstable numerical identifiers for file references. This
// increases the gzipped bundle size some but makes the build products easier to debug and
// appear deterministic. I.e., code changes will only alter the bundle they're packed in
// instead of shifting the identifiers in other bundles.
// https://webpack.js.org/guides/caching/#deterministic-hashes (namedModules replaces NamedModulesPlugin.)
moduleIds: 'named',
// Generate a single Webpack bootstrap chunk for ResourceLoader modules to share. This will
// be packaged inside the mobile.startup module which should be a dependency for
// all modules. The inefficient alternative is for each module to bundle its own runtime.
// The terms bootloader and runtime are used interchangeably.
runtimeChunk: { name: 'mobile.startup.runtime' },
splitChunks: {
cacheGroups: {
// Turn off webpack's default 'default' cache group.
// https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks
default: false,
// Turn off webpack's default 'vendors' cache group. If this is desired
// later on, we can explicitly turn this on for clarity.
// https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks
vendors: false,
// TT210210: This was undesirably added when trying to get lazy loaded
// modules to work (e.g. ENTRIES.languages). It will excise modules
// shared between the chunks listed in the whitelist entry array into a
// new 'mobile.common' chunk. Ideally, the common chunk would be merged
// into the mobile.startup chunk and would not exist. However, there was
// difficulty in making webpack cleanly do this. When we overcome
// webpack lazy loading hurdles (or figure out a way to make webpack use
// mobile.startup as the common chunk), we won't be required to do this
// for lazy loaded chunks although it might still be valuable for
// special page chunks.
common: {
name: 'mobile.common',
// Minimum num of chunks module must share before excising into common
// chunk
minChunks: 2,
// Do no reuse existing chunks when splitting (i.e. we do not want
// webpack excising startup modules into an async chunk)
// https://github.com/webpack/webpack.js.org/issues/2122#issuecomment-388609306
reuseExistingChunk: false,
// ignore webpack's default minSize option (and other splitChunks
// defaults) and always create chunks based on criteria specified for
// this cacheGroup
enforce: true,
// Only consider splitting chunks off of these whitelisted entry names
chunks: ( chunk ) => [
ENTRIES.startup,
ENTRIES.categories,
ENTRIES.editor,
ENTRIES.languages,
ENTRIES.mediaViewer,
ENTRIES.mobileInit,
ENTRIES.mobileDiff,
ENTRIES.mobileOptions,
ENTRIES.userLogin,
ENTRIES.watchlist
].includes( chunk.name )
}
}
}
},
output: {
// Specify the destination of all build products.
path: distDir,
// Store outputs per module in files named after the modules. For the JavaScript entry
// itself, append .js to each ResourceLoader module entry name. This value is tightly
// coupled to sourceMapFilename.
filename: '[name].js',
// Rename source map extensions. Per T173491 files with a .map extension cannot be served
// from prod.
sourceMapFilename: `[file]${ srcMapExt }`,
// Expose the module.exports of each module entry chunk through the global
// mfModules[name].
// This is useful for debugging. E.g., mfModules['mobile.startup'] is set by the
// module.exports of mobile.startup.js.
library: [ 'mfModules', '[name]' ],
libraryTarget: 'this'
},
// Accurate source maps at the expense of build time. The source map is intentionally exposed
// to users via sourceMapFilename for prod debugging. This goes against convention as source
// code is publicly distributed.
devtool: 'source-map',
plugins: [
// Delete the output directory on each build.
new CleanPlugin( distDir, { verbose: false } )
],
performance: {
// Size violations for prod builds fail; development builds are unchecked.
hints: argv.mode === 'production' ? 'error' : false,
// Minified uncompressed size limits for chunks / assets and entrypoints. Keep these numbers
// up-to-date and rounded to the nearest 10th of a kilobyte so that code sizing costs are
// well understood. Related to bundlesize minified, gzipped compressed file size tests.
// Note: entrypoint size implicitly includes the mobile.startup.runtime and mobile.common
// chunks.
maxAssetSize: 48.1 * 1024,
maxEntrypointSize: 85.4 * 1024,
// The default filter excludes map files but we rename ours.
assetFilter: ( filename ) => !filename.endsWith( srcMapExt )
}
} );