Menu
Menu Sheet Overlay
Search
Search Sheet

Migration guide for Webpack 4 upgrade

    This guide is meant for developers upgrading a project generated on version 1.1.0 of the Mobify platform. However, most of these steps will also apply for projects generated on prior versions of the Mobify platform.

    Follow this guide to upgrade to webpack 4. Upgrading to webpack 4 can significantly improve a project’s build time. After upgrading the Merlin’s Potions example project, we found that the time it took to run npm start dropped by 33%.

    The Progressive Web App (PWA) and Accelerated Mobile Pages (AMP) parts of a project have separate webpack configurations. These configurations can be updated independently.

    Table of contents #

    Upgrading webpack for the PWA #

    Establish a baseline #

    Before making any changes, it is important to record the size and performance of your PWA. After upgrading your PWA, you should record these values again and compare them. This will allow you to verify that the upgrade has improved the build time and performance of your project.

    Run npm run analyze-bundle and record the size of each file generated by webpack.

    Run npm run test:lighthouse and record the Lighthouse score of your PWA.

    Update project dependencies #

    You will need to upgrade any webpack plug-ins used on your project, as webpack 4 introduced breaking changes to the plug-in API.

    Most PWA projects will need to upgrade the following dependencies. In web/package.json, change the version of each of these dependencies to the recommended version. Then, run npm install to install these new versions.

    Dependency Recommended Version
    babel-loader 7.1.4
    copy-webpack-plugin 4.5.1
    extract-text-webpack-plugin 4.0.0-beta.0
    html-webpack-plugin 3.2.0
    postcss-loader 2.1.2
    uglifyjs-webpack-plugin 1.2.5
    webpack 4.6.0
    webpack-dev-server 1.16.2

    Additionally, there are new dependencies that must be added. See the table below.

    New Dependency Recommended Version
    webpack-cli 2.0.14
    mini-css-extract-plugin 0.4.1

    You can install this by running npm install --save-dev webpack-cli@2.0.14 and npm install --save mini-css-extract-plugin@0.4.1

    The webpack-hot-middleware dependency should be removed. Remove this plug-in from web/package.json.

    Most of these plug-ins can be upgraded without additional changes to your project. Only the postcss-loader plug-in and mini-css-extract-plugin require additional changes.

    Replace Webpack Plugin

    As of Webpack 4, mini-css-extract-plugin should be used for CSS in place of extract-text-webpack-plugin. To comply, make these changes to these four files:

    1. In web/webpack/base.main.js and web/webpack/base.non-pwa.js, replace extract-text-webpack-plugin with mini-css-extract-plugin

       - const ExtractTextPlugin = require('extract-text-webpack-plugin')
       + const MiniCssExtractPlugin = require('mini-css-extract-plugin')
         // ...
       - new ExtractTextPlugin({
       + new MiniCssExtractPlugin({
      
    2. In web/webpack/base.dev.js and web/webpack/production.js, replace extract-text-webpack-plugin with mini-css-extract-plugin

       - const ExtractTextPlugin = require('extract-text-webpack-plugin')
       + const MiniCssExtractPlugin = require('mini-css-extract-plugin')
         // ...
       - const cssLoader = ExtractTextPlugin.extract([
       + const cssLoader = [
       +     MiniCssExtractPlugin.loader,
         // ...
       - ])
       + ]
      

    Update postcss-loader configuration #

    1. Remove the postcss-loader plug-in from webpack/base.common.js

       - postcss: () => {
       -     return [
       -         // Supported browser list is provided within package.json
       -         autoprefixer()
       -     ]
       - }
      
    2. Add the postcss-loader plug-in to webpack/base.loader.js

       {
           test: /\.css?$/,
           exclude: /node_modules/,
           use: {
               loader: 'postcss-loader',
               options: {
                   config: {
                       path: '../web/webpack/postcss.config.js'
                   }
               }
           }
       }
      
    3. Remove the LoaderOptionsPlugin from webpack/base.loader.js and webpack/base.main.js

       - new webpack.LoaderOptionsPlugin({
       -     options: {
       -         postcss: baseCommon.postcss
       -     }
       - })
      
    4. Add the postcss-loader configuration file to the webpack directory

       /* eslint-disable import/no-commonjs */
       /* eslint-env node */
       const autoprefixer = require('autoprefixer')
      
       module.exports = {
           plugins: [
               autoprefixer({
                   // Tell Autoprefixer not to remove outdated prefixes
                   // We don't include any by default, so this just speeds up build time
                   remove: false
               })
           ]
       }
      
    5. Create a cssLoader plug-in for loading CSS in webpack/dev.js and webpack/production.js. This plug-in should be applied to the mainConfig, productionMainConfig, and nonPWAConfig webpack configurations.

       // webpack/production.js
      
       const cssLoader = [
           // Note that this includes the change described above: replacing
           // ExtractTextPlugin with MiniCssExtractPlugin
           MiniCssExtractPlugin.loader,
           {
               loader: 'css-loader?-autoprefixer',
               options: {
                   // Don't use css-loader's automatic URL transforms
                   url: false,
                   minimize: true // in webpack/dev.js, set this to false
               }
           },
           // Manually specify the path to the postcss config
           // so that we can use one single file for all webpack configs that use it
           {
               loader: 'postcss-loader',
               options: {
                   config: {
                       path: '../web/webpack/postcss.config.js'
                   }
               }
           },
           'sass-loader'
       ]
      
       // Handle loading and transforming PWA scss files
       productionMainConfig.module.rules = productionMainConfig.module.rules.concat({
           test: /\.scss$/,
           loader: cssLoader,
           include: [
               /progressive-web-sdk/,
               /app/
           ]
       })
      
       // Handle loading and transforming non-PWA scss files
       nonPWAConfig.module.rules = nonPWAConfig.module.rules.concat({
           test: /\.scss$/,
           loader: cssLoader,
           include: [
               /node_modules\/progressive-web-sdk/,
               /app/,
               /non-pwa/
           ]
       })
      

    Update the webpack config #

    Set the mode #

    All webpack 4 configurations should include a mode attribute set to either development or production. This attribute controls which optimizations, such as minification, are applied to the build.

    In webpack/dev.js and webpack/production.js, you can set this value for each of the config objects.

    // webpack/dev.js
    const configs = [
        mainConfig,
        baseLoaderConfig,
        workerConfig,
        nonPWAConfig,
        translationsConfig
    ]
    
    // Apply shared settings to all configs
    configs.forEach((config) => {
        config.mode = 'development'
        config.plugins = config.plugins.concat([
            new webpack.DefinePlugin({
                DEBUG: true
            })
        ])
    })
    
    // webpack/production.js
    const configs = [
        productionMainConfig,
        baseLoaderConfig,
        workerConfig,
        nonPWAConfig,
        translationsConfig
    ]
    
    // Apply shared settings to all configs
    configs.forEach((config) => {
        config.mode = 'production'
        config.plugins = config.plugins.concat([
            new webpack.DefinePlugin({
                DEBUG: false
            })
        ])
    })
    

    Replace CommonChunksPlugin #

    The CommonChunksPlugin was removed in webpack 4. Previously, we were using CommonChunksPlugin to manually split out our node_modules into a separate vendor.js file. Now, we need to implement this using the optimization option.

    Remove both instances of the CommonChunksPlugin from webpack/base.main.js. Add the following optimization object to the root of the webpack config object.

    // Manually split all of our node_modules into the file vendor.js
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendor',
                    chunks: 'initial'
                }
            }
        }
    }
    

    Remove json-loader #

    In webpack 4, JSON loading is handled automatically and does not require an additional loader. Remove the json-loader plug-in from webpack/base.main.js

    - {
    -     test: /\.json$/,
    -     use: 'json-loader'
    - }
    

    Remove UglifyJS #

    When the mode attribute is set to production, uglification is automatically applied to the build.

    If you need custom uglification rules in development, you can add UglifyJsPlugin to your project and override the default settings. However, we do not recommend doing this for production. In our testing, we found that the default minimization provided by webpack 4 reduced the file size more than our custom uglification rules.

    const uglifyPluginOptions = {
        uglifyOptions: { ... },
        // This is necessary to get maps with the Uglify plug-in
        sourceMap: true
    }
    const uglifyJSPlugin = new UglifyJsPlugin(uglifyPluginOptions)
    
    // ...
    
    // Apply shared settings to all configs
    configs.forEach((config) => {
        if (config.optimization) {
            config.optimization.minimizer = [uglifyJSPlugin]
        } else {
            config.optimization = {
                minimizer: [uglifyJSPlugin]
            }
        }
    })
    

    Update the dev server logging #

    The server.listen function has changed in the new version of webpack-dev-server. It now accepts 3 parameters: a port, hostname, and callback. Update dev-server/index.js to include this new parameter.

    server.listen(port, 'localhost', (err) => { // eslint-disable-line consistent-return
        if (err) {
            return logger.error(err.message)
        }
    
        logger.appStarted(port)
        logger.waitForBuild()
    })
    

    You may also wish to modify the information that the dev server logs by default. Use the stats documentation to determine what you need the dev server to log.

    Test changes #

    Run npm start and preview your project to ensure that everything works as expected.

    Run npm run analyze-bundle and npm run test:lighthouse and compare the data with the data that you recorded in Establish a baseline.

    Upgrading webpack for AMP #

    Update project dependencies #

    You will need to upgrade any webpack plug-ins used on your project, as webpack 4 introduced breaking changes to the plug-in API.

    Most AMP projects need to upgrade the following dependencies. In amp/package.json, change the version of each of these dependencies to the recommended version. Then, run npm install to install these new versions.

    Dependency Recommended Version
    babel-loader 7.1.4
    postcss-loader 2.1.2
    webpack 4.6.0

    Additionally, the webpack-cli dependency should be added. We recommend version 2.0.14. You can install this by running npm install --save-dev webpack-cli@2.0.14.

    Update postcss-loader configuration #

    1. Remove the LoaderOptionsPlugin from webpack.config.js

       - new webpack.LoaderOptionsPlugin({
       -     options: {
       -         postcss: () => [
       -             autoprefixer({
       -                 browsers: [
       -                     'iOS >= 9.0',
       -                     'Android >= 4.4.4',
       -                     'last 4 ChromeAndroid versions'
       -                 ]
       -             })
       -         ]
       -     }
       - })
      
    2. Add the postcss-loader configuration file

       /* eslint-disable import/no-commonjs */
       /* eslint-env node */
       const autoprefixer = require('autoprefixer')
      
       module.exports = {
           plugins: [
               autoprefixer({
                   // Tell Autoprefixer not to remove outdated prefixes
                   // We don't include any by default, so this just speeds up build time
                   remove: false
               })
           ]
       }
      
    3. Add browsersList to amp/package.json. Autoprefixer will use these settings to determine which prefixes to add to your CSS.

       "browserslist": [
           "iOS >= 9.0",
           "Android >= 4.4.4",
           "last 4 ChromeAndroid versions"
       ]
      

    Update the webpack config #

    Set the mode #

    All webpack 4 config objects should include a mode attribute set to either development or production. This attribute controls which optimizations, such as minification, are applied to the build.

    In webpack.config.js, you can set this value based on the NODE_ENV variable.

    const isProduction = process.env.NODE_ENV === 'production'
    
    module.exports = {
        mode: isProduction ? 'production' : 'development',
        devtool: isProduction ? false : 'sourcemap',
        // ...
    }
    

    Test changes #

    Run npm start and test your project to ensure that everything works as expected.