On Zhihu, we often see students asking: How is the front-end engineering of BAT and other large websites organized and managed? This is a really broad Q&A, and I thought if I had to answer it, I’d start with the Webpack configuration.

Today, Webpack has become one of the basic tools necessary for front-end engineering. It is not only widely used for packaging front-end engineering before release, It also acts as local front-end assets Server, hot Module replacement, API Proxy and other roles in development. Combined with code checking tools such as ESLint, it can also achieve strict verification check on source code.

As mentioned above, the front-end is always involved with Webpack from development to pre-deployment, and Webpack has only one default configuration file, webpack.config.js. Should the same Webpack configuration be used during development and pre-deployment? The answer is definitely no, since webpack.config.js is a JS file, we can certainly write JavaScript business logic in the file, NODE_ENV is used to read the environment variable NODE_ENV to determine whether the current environment is dev or production. However, many students are used to mix the configuration of both in the root directory of webpack.config.js, with many fragmented if… With the increase of loaders and plugins, webpack.config.js became longer and longer, and the readability and maintainability of the code decreased greatly.

In this article, I would like to introduce a way to configure Webpack with three JS files, which uses the configuration of many open source projects, as well as the solution to our own problems encountered in development.

The configuration mentioned in this article is based on Webpack 2 or later, and 3.0 or later is recommended


Differences between a development environment and a production environment

The development environment

  • NODE_ENV for development
  • Enable hot Module replacement
  • Additional Webpack-dev-server configuration items, API Proxy configuration items
  • Output Sourcemap

The production environment

  • NODE_ENV for production
  • Configure common libraries such as React and jQuery as External, and use the CDN version directly
  • Style source files (such as CSS, LESS, SCSS, etc.) need to be extracted into CSS files independently through the ExtractTextPlugin
  • To enable the post – CSS
  • Optimize -minimize (uglify, etc.)
  • In the production environment of medium and large commercial websites, console.log() is absolutely not allowed, so configure Remove Console Transform for Babel

What needs to be noted here is that hot Module replacement is enabled in the development environment. In order to make the changes in the style source file also be hot replaced, the ExtractTextPlugin cannot be used and is exported along with the JS Bundle.


You need three profiles

1. webpack.base.config.js

In the base file, you need to centralize the configuration common to both the development and production environments:

const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');
const webpack = require('webpack');

// Configure constants
// The root of the source code (the local physical file path)
const SRC_PATH = path.resolve('./src');
// Packaged resource root directory (local physical file path)
const ASSETS_BUILD_PATH = path.resolve('./build');
// Resource root (absolute or relative path on CDN)
const ASSETS_PUBLIC_PATH = '/assets/';

module.exports = {
  context: SRC_PATH. // Set the default root path for the source code
  resolve: {
    extensions: ['.js'. '.jsx']  // Support both JS and JSX
  },
  entry: {
    // Note that the paths in entry are relative to SRC_PATH
    vendor: './vendor'.
    a: ['./entry-a'].
    b: ['./entry-b'].
    c: ['./entry-c']
  },
  output: {
    path: ASSETS_BUILD_PATH.
    publicPath: ASSETS_PUBLIC_PATH.
    filename: './[name].js'
  },
  module: {
    rules: [
      {
        enforce: 'pre'.  // ESLint has a higher priority than other JS related loaders
        test: /\.jsx? $/.
        exclude: /node_modules/.
        loader: 'eslint-loader'
      },
      {
        test: /\.jsx? $/.
        exclude: /node_modules/.
        // It is recommended to put the runtime configuration of Babel in.babelrc to share the configuration with eslint-loader, etc
        loader: 'babel-loader'
      },
      {
        test: /\.(png|jpg|gif)$/.
        use:
        [
          {
            loader: 'url-loader'.
            options:
            {
              limit: 8192.
              name: 'images/[name].[ext]'
            }
          }
        ]
      },
      {
        test: /\.woff(2)? (\? v=[0-9]\.[0-9]\.[0-9])? $/.
        use:
        [
          {
            loader: 'url-loader'.
            options:
            {
              limit: 8192.
              mimetype: 'application/font-woff'.
              name: 'fonts/[name].[ext]'
            }
          }
        ]
      },
      {
        test: /\.(ttf|eot|svg)(\? v=[0-9]\.[0-9]\.[0-9])? $/.
        use:
        [
          {
            loader: 'file-loader'.
            options:
            {
              limit: 8192.
              mimetype: 'application/font-woff'.
              name: 'fonts/[name].[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    // Before each package, empty the contents of the original directory
    new CleanWebpackPlugin([ASSETS_BUILD_PATH]. { verbose: false }),
    / / enable CommonChunkPlugin
    new webpack.optimize.CommonsChunkPlugin({
      names: 'vendor'.
      minChunks: Infinity
    })
  ]
};
Copy the code

2. webpack.dev.config.js

This is the Webpack configuration for the development environment, inherited from Base:

const webpack = require('webpack');

// Read base config in the same directory
const config = require('./webpack.base.config');

// Add webpack-dev-server configuration items
config.devServer = {
  contentBase: '/'.
  hot: true.
  publicPath: '/assets/'
};
/ / the Webpack API local agent, and the other please refer to https://webpack.github.io/docs/webpack-dev-server.html#proxy

config.module.rules.push(
  {
    test: /\.less$/.
    use: [
      'style-loader'.
      'css-loader'.
      'less-loader'
    ].
    exclude: /node_modules/
  }
);

// In real scenarios, React and jQuery preferentially use the site-wide CDN, so they must be placed in externals
config.externals = {
  react: 'React'.
  'react-dom': 'ReactDOM'
};

// Add Sourcemap support
config.plugins.push(
  new webpack.SourceMapDevToolPlugin({
    filename: '[file].map'.
    exclude: ['vendor.js'] // Vendor usually does not need sourcemap
  })
);

// Hot module replacement
Object.keys(config.entry).forEach((key) = > {
  // There is a private convention. If entry is an array, it needs to be replaced by a hot Module
  if (Array.isArray(config.entry[key])) {
    config.entry[key].unshift(
      'webpack-dev-server/client? http://0.0.0.0:8080 '.
      'webpack/hot/only-dev-server'
    );
  }
});
config.plugins.push(
  new webpack.HotModuleReplacementPlugin(a)
);

module.exports = config;
Copy the code

3. webpack.config.js

This is the WebPack configuration for production, again inherited from Base:

const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

// Read base config in the same directory
const config = require('./webpack.base.config');

config.module.rules.push(
  {
    test: /\.less$/.
    use: ExtractTextPlugin.extract(
      {
        use: [
          'css-loader'.
          'less-loader'
        ].
        fallback: 'style-loader'
      }
    ),
    exclude: /node_modules/
  }
);

config.plugins.push(
  // The official documentation recommends using the following plug-in to ensure NODE_ENV
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
  }),
  / / start minify
  new webpack.LoaderOptionsPlugin({ minimize: true }),
  // Extract the CSS file
  new ExtractTextPlugin({
    filename: '[name].css'.
    allChunks: true.
    ignoreOrder: true
  })
);

module.exports = config;
Copy the code


You should now have three Webpack configuration files in your project folder:

  • webpack.base.config.js
  • webpack.dev.config.js
  • webpack.config.js

Finally, you need to add the corresponding configuration to package.json:

{
  .
  "scripts": {
    "build": "webpack --optimize-minimize".
    "dev": "webpack-dev-server --config webpack.dev.config.js".
    "start": "npm run dev" // Add your own start logic
  },
  .
}
Copy the code

As with many projects, you need to start with NPM Run Dev in development and release with NPM Run Build in production.

As an aside, in a real world scenario, instead of using webpack-dev-server directly, express + Webpack/webpack-dev-Middleware would be configured in exactly the same way as above.


About the column

If you enjoyed this article, check out my front End Zero Stack column, where we talk about front-end technology and front-end engineering.

About the author

Henry works in Alibaba Nanjing R&D Center. He started to learn computer programming at the age of 10 and won the first prize of Jiangsu Youth Information Olympics in the summer vacation of his second year in high school. In 2000, I began to learn JavaScript and webpage making by myself. Since 2006, I have been working on front-end development for more than 10 years. Prior to joining Alibaba, He worked as intelligent transportation Big Data Product Manager at SAP China Research Institute.

Github: MagicCube (Henry Li)