This is the 9th day of my participation in the August More Text Challenge. For details, see:August is more challenging

Currently all of our WebPack configuration information is in one configuration file :webpack.config.js

As more and more configurations are made, this file becomes less and less maintainable;

And some configurations are needed in the development environment, some configurations are needed in the build environment, and of course some configurations are needed in both the development and build environments

Therefore, we’d better divide the configuration to facilitate our maintenance and management;

Differentiate the development environment

Environment: In the project root directory, there is a folder called config, which is used to store webpack configuration files

Method 1: Write two different configuration files, and load each configuration file during development and generation

package.json

"scripts": {
  "dev": "webpack --config ./config/config.dev.js",
  "prod": "webpack --config ./config/config.prod.js"
}
Copy the code

Option 2: Use the same entry profile and set parameters to distinguish them

package.json

"scripts": {
  "dev": "webpack --config ./config/config.common.js --env development"."prod": "webpack --config ./config/config.common.js --env production"
}
Copy the code

config.common.js

const path = require('path')

module.exports = env= >  {
  // development ---> { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, development: true }
  // production ---> { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, production: true }
  console.log(env) 

  return {
    // You must write./ You cannot write.. / Otherwise the entry file cannot be found correctly
    entry: './src/index.js'.output: {
      // Since the current directory is under config, the build directory needs to be stored in the directory above
      path: path.resolve(__dirname, '.. /dist'),
      filename: 'bundle.js'}}}Copy the code

context

Our previous rule for writing entry files was :./ SRC /index.js, but if our configuration files are located in the config directory, should we change to.. / SRC/index. Js? Obviously not

That’s because the entry file is actually a context that’s related to another property

The context is used to parse entry points and loaders.

const path = require('path')

module.exports = env= >  {
  return {
    // The value of context is required to be an absolute path
    // The actual entry file path is path.resolve(context, entry).
    context: path.resolve(__dirname, '/'),
    // Because the context is set to the current path, which is the config directory
    entry: '.. /src/index.js'.output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'bundle.js'}}}Copy the code

The default context value is the path where the command line is executed

#So the default path of the context is the root of the project
webpack --config ./config/config.common.js --env development
Copy the code

Because, by default, the entry path should be set to ‘./ SRC /index.js’ instead of ‘.. /src/index.js’

Configuration file separation

When we set mode, Webpack will automatically set the mode value to process.env.node_env

For example, mode: ‘development ===> process.env.node_env is development

path.js

const path = require('path')

// The process.cwd() method in node outputs the directory where the current node process is executed
// The node process is started in the project root directory (started in pacakge.json)
// So the result of process.cwd() is the project root directory
module.exports = (relativePath) = > path.resolve(process.cwd(), relativePath)
Copy the code

config.common.js

const path = require('path')

// The webpack-merge library helps you merge configuration files with each other
const { merge } = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const prodConfig = require('./config.prod')
const devConfig = require('./config.dev')

const resolvePath = require('.. /utils/path')

module.exports = env= >  {
  Process.env.node_env is used to set the global environment value in advance
  // So that Babel can obtain environment variables during initial configuration

  // tips: If one of the attributes of process.env.node_env is set to undefined
  // When we retrieve the value of the process.env.node_env attribute, the result is undefined, but the type is string, not undefined
  process.env.NODE_ENV = env.production ? 'production' : 'development'

  const commonConfig = {
    entry: './src/index.js'.output: {
      path: path.resolve(__dirname, '.. /dist'),
      filename: 'bundle.js'
    },

    devServer: {
      hot: true
    },

    plugins: [
      new HtmlWebpackPlugin({
        template: resolvePath('./public/index.html')]}})return env === 'production' ? merge(commonConfig, prodConfig) : merge(commonConfig, devConfig)
}
Copy the code

config.dev.js

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')

module.exports = {
  mode: 'development'.plugins: [
    new ReactRefreshWebpackPlugin()
  ]
}
Copy the code

config.prod.js

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

module.exports = {
  mode: 'production'.plugins: [
    new CleanWebpackPlugin()
  ]
}
Copy the code

babel.config.js

const presets = [
  '@babel/preset-env'
]

const plugins = []

// Process.env.node_env gets its value because the environment variable is set in common.config.js
// Not because webpack helps us set the mode value
// When Babel is initialized, webpack is not ready to set the environment variables
// So we need to display the global environment variables first
if (process.env.NODE_ENV === 'development') {
  plugins.push('react-refresh/babel')}module.exports = {
  presets,
  plugins
}
Copy the code

package.json

"scripts": {
  "dev": "webpack serve --config ./config/config.common.js --env development"."prod": "webpack --config ./config/config.common.js --env production"
}
Copy the code

The separation

Code Splitting is a very important feature of Webpack:

By default, all JavaScript code (business code, third-party dependencies, temporarily unused modules) is loaded on the home page, which affects the loading speed of the home page.

The main goal is to separate the code into different bundles, which we can then load on demand or load in parallel

Code separation allows for smaller bundles and control over resource load priorities to provide code load performance

There are three common types of code separation in Webpack:

  1. Entry starting point: Manually separate the code using the Entry configuration

  2. Remove duplicate modules: Use Entry Dependencies or SplitChunksPlugin to duplicate and separate code

  3. Dynamic import: code is separated by inline function calls to modules;

Entrance to the starting point

The meaning of the entry starting point is very simple, which is to configure multiple entries

module.exports = {
  entry: {
    main: './src/main.js'.index: './src/index.js'
  },

  output: {
    path: path.resolve(__dirname, '.. /dist'),
    // In this case, the name value is the key value of the entry
    // The package will form main.bundle.js and index.bundle.js
    filename: '[name].bundle.js'}}Copy the code

Remove duplicate modules

Entry Dependencies

Suppose our index.js and main.js both rely on two libraries :lodash and dayjs

If we simply separate the entry, then both bunlde packs will have a copy of Lodash and dayJS

We can actually share them

entry: {
  main: {
    import: './src/main.js'.// Main.js needs to extract the dependency, which is a string value
    dependOn: 'lodash'
  },
  index: {
    import: './src/index.js'.dependOn: 'lodash'
  },
  // Pull out the public module
  lodash: 'lodash'.dayjs: 'dayjs'
}
Copy the code

If there are multiple modules that need to be shared, you can separate the modules into an array and use them together

entry: {
 main: {
   import: './src/main.js'.dependOn: 'shared'
 },
 index: {
   import: './src/index.js'.dependOn: 'shared'
 },
 // An array of modules that need to be shared
 shared: ["lodash"."dayjs"]}Copy the code

SplitChunks

Another subcontracting mode is splitChunk, which is implemented using SplitChunksPlugin:

Since the webPack plug-in is installed and integrated by default, we do not need to install and use the plug-in directly. We only need to provide configuration information related to SplitChunksPlugin

optimization: {
  splitChunks: {
    // Chunks can be set to three values
    // 1. Async Subcontracts packages introduced by async by default
    // 2. Inital subcontracts the synchronously imported packages
    // 3. All Subcontracts all imported packages
    chunks: 'all'}}Copy the code

SplitChunks can also have additional funding options

   optimization: {
      splitChunks: {
        chunks: 'all'.// The default value is 20000bytes, which means that any imported files larger than this size should be extracted
        minSize: 20000.// Indicates how many bytes a packet is larger than or equal to minSize
        // In most cases, if maxSize is set, minSize and maxSize will be the same
        maxSize: 20000.// The number of times a package has been introduced depends on how many times it needs to be removed
        minChunks: 1.// cacheGroups means that all module outputs are stored in a cache and then performed together
        cacheGroups: {
          // The key can be arbitrary. It is just a placeholder here
          // Value is a configuration object
          vender: {
            // Regular to match the corresponding module path
            test: /[\\/]node_modules[\\/]/.// Output file name The output file is output in the form of output file name -hash value.js
            // name: "vender",
            
            // filename specifies the output filename. Unlike name, filename can use placeholder
            filename: 'vender_[id].js'.// The priority is set to a negative value
            priority: -10
          },
          default: {
            minChunks: 2.filename: "common_[id].js".priority: -20}}}}Copy the code

Dynamic import

When we touch a module we want to load it while the code is running (for example, when a condition is true)

We don’t know for sure that the code in this module will be used, so it’s best to split it into a separate JS file

This ensures that the browser does not need to load and process the JS code for the file when the content is not used

At this point we can use dynamic imports

Whenever the code is imported asynchronously, WebPack separates the code regardless of the size of the file

import('./main').then(res= > console.log(res))
Copy the code
Name the file to be dynamically imported

Because dynamic imports are always packaged into separate files, they are not configured in cacheGroups

So the name of it is usually in the output, by the chunkFilename attribute

output: {
  path: path.resolve(__dirname, '.. /dist'),
  // When synchronizing modules, it is usually called bundle
  filename: '[name].bundle.js'.// If it is a dynamic import, it is usually named chunk
  chunkFilename: '[name].chunk.js'
}
Copy the code

You’ll notice that by default we get [name] the same as the name of the ID

If we want to change the value of name, we can do so with magic comments

import(/* webpackChunkName: 'foo' */'./foo').then(res= > console.log(res))
Copy the code

At this point, the packaged module name will be foo.chunk.js

Remove the detach of comments

In production mode, after the packaged code, some comments information will be extracted by default to form the corresponding TXT file

const TerserPlugin = require('terser-webpack-plugin')

optimization: {
  // Compression configuration
  minimizer: [
    new TerserPlugin({
      // Close the detach of comments
      extractComments: false,}})]Copy the code

chunkIds

The optimization. ChunkIds configuration is used to tell WebPack what algorithm is used to generate the id of the module

value instructions
natural Use the ids in numeric order

Such as 2. Bundle. Js, 3. Bundle. Js

Not recommended, because if you remove a module, the natural number of each module will change

Unfavorable cache operation for modules
named Default value under evelopment, a readable name id; (Recommended during development)
deterministic Deterministic, based on internal algorithms that generate short numeric ids that do not change between compilations (recommended during packing)