wikimedia/mediawiki-extensions-MobileFrontend

View on GitHub
webpack.config.js

Summary

Maintainability
A
0 mins
Test Coverage
'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 )
    }
} );