Webpack (including 4) configuration details

foreword

source code

Familiar with webpack and webpack4 configuration.

The main difference between webpack4 and 3 is so-called 零配置, but in order to meet our project needs, we still have to configure it ourselves, but we can use some webpack presets. At the same time, webpack is also split into two parts, webpack and webpack-cli, both of which need to be installed locally.

We have an experience by implementing a vue development template (vue init webpack template, which has little to do with vue). In the configuration process, we will try to use the relevant content of webpack4.

This article does not give a complete introduction to webpack configuration , but focuses on the points that need to be paid attention to in the configuration process. Viewing code comments is better for reading, and the full configuration and detailed comments can be seen in the source code. The configuration is located under the build folder.

Sections related to version 4 have the symbol ④ added .

One thing to note is that our webpack code runs in the node environment, and this part of the code can use the node api, but our business code (under src) cannot use the node api.

Basic common configuration

Since the configuration of webpack configuration such as context, entry (chunk entry), output (output) and loaders in module.rules are basically common in development mode and production mode, we extract them into webpack.base.jsfiles for reuse. The output part is as follows:

output: {
  
  path: path.resolve(__dirname, '../dist/'), // 资源文件输出时写入的路径
  filename: 'static/js/[name].[chunkhash].js', // 使用 chunkhash 加入文件名做文件更新和缓存处理
  chunkFilename: 'static/js/[name].[chunkhash].js'
}

Note:

filename hash

The hash is used in the name of the file output, for example [name].[hash].js, in general, webpack provides three hashes:

  1. [hash]: The hash of all the contents packaged this time.
  2. [chunkhash]: Each chunk is calculated based on its own content.
  3. [contenthash]: Provided by the css extraction plugin, calculated from its own content.

The use of three hashes, we will talk about it in the optimization part, and use it first [chunkhash].

loader priority

The loader priority needs to pay attention to two points,

  1. Priority within the same test configuration: multiple loaders are configured under the same test, and the priority loader is placed after the configuration array . If less is processed, then:
    {
      test: /\.less$/,
      use: [
        'style-loader', 
        'css-loader', 
        'postcss-loader', 
        'less-loader'
      ]
    }
    
  2. Priority in different tests: For example, the processing of js files requires two tests to be configured separately, using eslint-loaderand babel-loader, but they cannot be configured in one configuration object, you can use enforce: 'pre' to emphasize the eslint-loaderpriority will be processed first.
    {
      test: /\.(js|vue)$/,
      loader: 'eslint-loader',
      enforce: 'pre',
    },
    {
      test: /\.js$/,
      loader: 'babel-loader'
    }
    

css preprocessor configuration

Let's take the loader configuration of the less file as an example['vue-style-loader', 'css-loader', 'postcss-loader', 'less-loader'] :@import url(demo.less)

  1. less-loader processes less syntax first
  2. postcss-loader performs other processing such as prefix addition
  3. css-loader imports content into the css file where @import is located
  4. vue-style-loader will generate style tags to insert css content into HTML

vue-style-loader functions like style-loader

However, due to the single-file component in vue, it is divided into two cases:

  • The style in the .vue file:
    vue-loaderIt will process the .vue single-file component. For various lang="type" in the .vue single-file component, we vue-loadercan configure different loaders in the options . Because of the vue-loaderbuilt- in postcssprocessing of css, So we don't need to configure it herepostcss-loader

    {
      test: /\.vue$/,
      loader: 'vue-loader',
      options: {
        loaders: {
          less: ['// xxx-loaders'],
          scss: ['// xxx-loaders'],
        }
      }
    }
    
  • The style file is imported in the direct import of js:
    For example, in main.js import 'demo.less', the style file imported in this way is outside the vue-loaderprocessing scope, so it still needs to be configured postcss-loader.

Due to this difference, we will encapsulate the configuration of the css preprocessor file as a function, generate the corresponding configuration from the usePostCssparameter , put the file in utils.jsthe file, and put the vue-loaderconfiguration in the vue-loader.jsfile.

That is, for the configuration of the css preprocessor, we vue-loaderneed webpackto configure it twice within and within.

During the writing of this README.md, vue-loader released the v15 version, which needs to be used with the plug-in, so there is no need to configure it twice.

postcss-loader

postcss-loader is a powerful css processing tool. We split the configuration of postcss and create a new postcss.config.jsconfiguration file

module.exports = {
  plugins: {
    // 处理 @import
    'postcss-import': {},
    // 处理 css 中 url
    'postcss-url': {},
    // 自动前缀
    'autoprefixer': {
      "browsers": [
        "> 1%",
        "last 2 versions"
      ]
    }
  }
}

In addition to the required functional plug-ins listed in the comments, we may also use nextcss(the processing of the new CSS syntax), px2rem/px-to-viewportmobile terminal adaptation related plug-ins.

babel-loader

We use babel to compile js and js-like syntax that browsers cannot recognize, such as escaping ES6+, JSX, etc. Also split the configuration of babel-loader , you need to create .babelrcand configure:

{
  "presets": [
    [
      /* *
       *  babel-preset-env
       *  可以根据配置的目标运行环境自动启用需要的 babel 插件。
       */
      "env", {
        "modules": false, // 关闭 babel 对 es module 的处理
        "targets": { // 目标运行环境
          "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
        }
      }
    ]
  ],
  "plugins": [
    "syntax-dynamic-import" // 异步加载语法编译插件
  ]
}

media resource loader

We also need to configure the loader for pictures, videos, fonts and other files. Taking font files as an example, we mainly use url-loader :

{
  /**
   * 末尾 \?.* 匹配带 ? 资源路径
   * 我们引入的第三方 css 字体样式对字体的引用路径中可能带查询字符串的版本信息
   */
  test: /\.(woff2|woff|eot|ttf|otf)(\?.*)?$/,
  /**
   * url-loader
   * 会配合 webpack 对资源引入路径进行复写,如将 css 提取成独立文件,可能出现 404 错误可查看 提取 js 中的 css 部分解决
   * 会以 webpack 的输出路径为基本路径,以 name 配置进行具体输出
   * limit 单位为 byte,小于这个大小的文件会编译为 base64 写进 js 或 html
   */
  loader: 'url-loader',
  options: {
    limit: 10000,
    name: 'static/fonts/[name].[hash:7].[ext]',
  }
}

static file copy

The direct reference (absolute path) and the resource path determined when the code is executed should exist as static files. These resource files will not be compiled and processed by webpack, so we put them in a separate folder (such as static), and in the The code is packaged and copied to our output directory. We use copy-webpack-plugin to automate this work:

const CopyWebpackPlugin = require('copy-webpack-plugin')

// 在开发模式下,会将文件写入内存
new CopyWebpackPlugin([
  {
    from: path.resolve(__dirname, '../static'),
    to: 'static',
    ignore: ['.*']
  }
])

This plugin will crash when too many files are copied. I don't know if it has been solved.

production mode

We first configure the production mode.

Add script script command

Add under package.json

"scripts": {
  "build": "node build/build.js"`
}

Then use the npm run buildcommand to execute node build/build.js. We do not directly use the webpack webpack.prod.config.jscommand to execute the configuration file, but in build.js, do some file deletion processing, and then start webpack.

Create build.js logic

There are two main tasks, importing the rimrafmodule delete the specified files generated under webpack, starting webpack, and giving different prompts at different stages.

// 在第一行设置当前为 生产环境
process.env.NODE_ENV = 'production'

const webpack = require('webpack')
const rm = require('rimraf')
const webpackConfig = require('./webpack.prod')
// 删除 webpack 输出目录下的内容,也可只删除子文件如 static 等
rm(webpackConfig.output.path, err => {
  // webpack 按照生产模式配置启动
  webpack(webpackConfig, (err, stats) => {
    // 输出一些状态信息
  })
}

See the source code comments for more details .

Production Mode Profile

Create a new webpack.prod.jsfile using

const merge = require('webpack-merge') // 专用合并 webpack 配置的包
const webpackBaseConfig = require('./webpack.base')
module.exports = merge(webpackBaseConfig, {
  // 生产模式配置
})

Combine the basic configuration and the unique configuration of production mode, and then we start to fill in the configuration information of webpack in production mode.

④ mode preset

This is the new api of webpack4, there are three default values: development, production, none, we choose in production mode , webpack4 is enabled by defaultmode: 'production' under this configuration :

  • plugin
    • FlagDependencyUsagePlugin: It should delete useless code, other plugins depend on
    • FlagIncludedChunksPlugin: It should delete useless code, other plugins depend on
    • ModuleConcatenationPlugin: Scope boosts webpack3's scope hosting
    • NoEmitOnErrorsPlugin: Do not jump out when encountering error codes
    • OccurrenceOrderPlugin
    • SideEffectsFlagPlugin
    • UglifyJsPlugin: js code compression
    • The value of process.env.NODE_ENV is set to production

So we don't need to configure these default enabled contents.

The last bit of setup process.env.NODE_ENV 的值设为 productionis actually to use the DefinePlugin plugin:

new webpack.DefinePlugin({
  "process.env.NODE_ENV": JSON.stringify("production") 
})

So we can pass in the business code process.env.NODE_ENV, such as to judge, whether to use the development interface or the online interface. If we need to determine the current environment in webpack, we also need a separate setting process.env.NODE_ENV = 'production', which is what we do in build.jsthe first line of .

Add the bundles generated by webpack to the HTML file

  • When we use webpack to configure the entry, we can only configure the js file as the entry, and the bundles produced by webpack cannot be automatically associated with the HTML file of our project.
  • We need to manually add <script src="./bundles.js"></script>(and possibly include the css file extracted later) to the HTML file.
  • We can automate this using the html-webpack-plugin plugin .
  • This step is not required when only js is packaged with webpack and no HTML files are required.
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
  new HtmlWebpackPlugin({
    filename: path.join(__dirname, '../dist/index.html'),// 文件写入路径
    template: path.join(__dirname, '../src/index.html'),// 模板文件路径
    inject: true // js 等 bundles 插入 html 的位置 head/body等
  })
]

If the HtmlWebpackPlugin is not configured, it will create an HTML file, which is still important filenamein development mode.

④ Extract the css part of js to a separate file

Students who have used webpack3 should be familiar with the extract-text-webpack-plugin plug-in (referred to as the old plug-in). In order to try webpack4, I don’t want to use the @nextversion , so I chose a new replacement plug-in mini-css-extract- plugin (referred to as the new plugin).
Same as the old plug-in, it also needs to be configured in the loader and plugin parts of webpack. The difference is that the new plug-in provides a separate loader, and the configuration in the loader part is not the same as that of the old plug-in. The configuration is as follows:

  • loader section

    ```js
    const MiniCssExtractPlugin = require("mini-css-extract-plugin")
    // ...
    [
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
        /*
        * 复写 css 文件中资源路径
        * webpack3.x 配置在 extract-text-webpack-plugin 插件中
        * 因为 css 文件中的外链是相对与 css 的,
        * 我们抽离的 css 文件在可能会单独放在 css 文件夹内
        * 引用其他如 img/a.png 会寻址错误
        * 这种情况下所以单独需要配置 ../,复写其中资源的路径
        */
        publicPath: '../' 
      },
      {
        loader: 'css-loader',
        options: {}
      },
      {
        loader: 'less-loader',
        options: {}
      }
    ]
    ```
    
  • plugin section

    ```js
    new MiniCssExtractPlugin({
      // 输出到单独的 css 文件夹下
      filename: "static/css/[name].[chunkhash].css"
    })
    ```
    

It can be seen that this loader is also configured in the css preprocessor part. We have extracted the css preprocessor configuration to the function of the utils.js file, so here too, we use the extractparameter to decide whether to extract.

Recall from the previous use of style-loaderor vue-style-loaderthat they create tags to insert the css content directly into the HTML. After extracting into a separate css file, the work of inserting into HTML is done by the html-webpack-pluginplugin . This part of the responsibilities of the two is duplicated, so we need to use the extractparameter to do something like the following:

if (options.extract) {
  return [MiniCssExtractPlugin.loader, ...otherLoaders]
} else {
  return ['vue-style-loader', ...otherLoaders]
}

④ Split js code

This is a very important part of webpack configuration, which affects the rationality of our use of browser caching and the loading speed of page resources. Reasonable splitting of js can effectively reduce the scope of files affected by each code update. .
Students who have used webpack3 must know that we usually extract several files manifest.js(when webpack is running, that is, webpack parses the code of other bundles, etc.), vendor.js(libraries in node_modules), app.js (real project business code). In webpack3, we use webpack.optimize.CommonsChunkPluginplugins for extraction, and in webpack4 we can directly use the optimization configuration item for configuration (of course, plugin configuration can still be used):

/**
 * 优化部分包括代码拆分
 * 且运行时(manifest)的代码拆分提取为了独立的 runtimeChunk 配置 
 */
optimization: {
  splitChunks: {
    chunks: "all",
    cacheGroups: {
      // 提取 node_modules 中代码
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        name: "vendors",
        chunks: "all"
      },
      commons: {
        // async 设置提取异步代码中的公用代码
        chunks: "async"
        name: 'commons-async',
        /**
         * minSize 默认为 30000
         * 想要使代码拆分真的按照我们的设置来
         * 需要减小 minSize
         */
        minSize: 0,
        // 至少为两个 chunks 的公用代码
        minChunks: 2
      }
    }
  },
  /**
   * 对应原来的 minchunks: Infinity
   * 提取 webpack 运行时代码
   * 直接置为 true 或设置 name
   */
  runtimeChunk: {
    name: 'manifest'
  }
}

You can also configure unchanged development dependencies into separate entries, such as:

entry: {
  app: 'index.js',
  vendor2: ['vue', 'vue-router', 'axios']
}

development mode

The difference between development mode and production mode is that code is frequently run during development, so many things are not recommended to be configured in development mode, such as css file extraction, code compression, etc. Therefore, for some functions that are written into public configuration files, but not required in development mode, we need to make similar modifications: process.env.NODE_ENV === 'production' ? true : false, such as whether to configure the extraction loader in css preprocessing MiniCssExtractPlugin.loader. In addition, some are only configured in production mode, such as MiniCssExtractPluginand js code splitting optimization.

In the development mode, we need a development service to help us complete functions such as real-time updates and interface agents. we use webpack-dev-server. Requires npm installation.

Add script script command

Likewise, add under package.json

"scripts": {
  "dev": "webpack-dev-server --config ./build/webpack.dev.js"
}

Use the --configspecified configuration file. Since the command directly calls webpack-dev-server to run, we can just write the configuration directly, and we can write the calling logic unlike the production mode.

Development mode configuration file

Create a new webpack.dev.jsfile , also use:

// 在第一行设置当前环境为开发环境
process.env.NODE_ENV = 'development'
const merge = require('webpack-merge') // 专用合并webpack配置的包
const webpackBaseConfig = require('./webpack.base')
module.exports = merge(webpackBaseConfig, {
  // 开发模式配置
})

④ mode preset

Similarly, in development mode we can modeconfigure to development, also by default, enable some features:

  • plugin
    • NamedChunksPlugin: Use entry name as chunk identifier
    • NamedModulesPlugin: Use the relative path of the module to not increment the id as the module identifier
  • The value of process.env.NODE_ENV is set to development

Development service configuration devServer

Documentation

devServer: {
  clientLogLevel: 'warning',
  inline: true,
  // 启动热更新
  hot: true,
  // 在页面上全屏输出报错信息
  overlay: {
    warnings: true,
    errors: true
  },
  // 显示 webpack 构建进度
  progress: true,
  // dev-server 服务路径
  contentBase: false,
  compress: true,
  host: 'localhost',
  port: '8080',
  // 自动打开浏览器
  open: true,
  // 可以进行接口代理配置
  proxy: xxx,
  // 跟 friendly-errors-webpack-plugin 插件配合
  quiet: true,
  publicPath: '/'
}

other plugins

A plugin is required when devServer uses hot update hot:

plugins: [
  new webpack.HotModuleReplacementPlugin()
]

To optimize webpack output information, you need to configure:

const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
plugins: [
  new FriendlyErrorsPlugin()
]

Precautions

  • Hot update: When using hot update, we cannot use [hash]as an , and file name changes cannot be hot updated, so we need to write the file name configuration originally configured in the output of the public configuration into the production and development mode configurations respectively. , the development mode is removed[hash]
    filename: 'static/[name].js', 
    chunkFilename: 'static/[id].js'
    
  • HtmlWebpackPlugin: In production mode, we write html files to dist, but in development mode, there is no actual writing process, and the content of the service after devServerstartup is contentBaserelated, both need to be consistent, so we will configure HtmlWebpackPluginthe It is also divided into production and development mode. In development mode, use:
    new HtmlWebpackPlugin({
      filename: 'index.html', // 文件写入路径,前面的路径与 devServer 中 contentBase 对应
      template: path.resolve(__dirname, '../src/index.html'),// 模板文件路径
      inject: true
    })
    

optimization

Configuration extraction

  • Some features of development mode and production mode are enabled, such as whether css is extracted or not.
  • Path configuration, such as file output path and file name, publicPath in output (only path is configured in the code output , no publicPath is configured , and the static of this part of the path is written to the output name of each resource. For details, please refer to the publicPath in Webpack ) , Service configuration such as port, etc.

We can extract into a separate config file (not done in this code).

split js code

In the production mode 拆分 js 代码part we have already talked about how to split, so in order to better analyze whether our split is reasonable, we can configure a bundle composition analysis plugin.

const BundleAnalyzer = require('webpack-bundle-analyzer')
plugins: [
  new BundleAnalyzer.BundleAnalyzerPlugin()
]

hash curing

We use the hash change in the file name to update the resource file, so when using the cache reasonably, we are required to split the file reasonably, and minimally affect the hash in the file name when the content is updated. It is used here [hash], [chunkhash], [contenthash]. However, the default processing of hash by webpack is not satisfactory. For the optimization of this part, please refer to the persistent caching scheme based on webpack

multiple pages

The multi-page configuration code is in the muilt-pages branch . We only need to make a few changes, take the entry page and the index page as an example.

entry changes

Configure webpackthe entryin :

entry: {
  /**
    * 入口,chunkname: 路径
    * 多入口可配置多个
    */
  main: './src/main.js',
  entry: './src/entry.js'
}

You can also set the project structure yourself, and use the node api to dynamically read the current multi-page entry.

HtmlWebpackPlugin changes

You need to configure multiple pages according to the number of pages HtmlWebpackPlugin:

new HtmlWebpackPlugin({
  filename: path.join(__dirname, '../dist/main.html'),// 文件写入路径
  template: path.join(__dirname, '../src/index.html'),// 模板文件路径
  inject: true, // 插入位置
  chunks: ['manifest', 'vendors', 'common', 'main']
}),
new HtmlWebpackPlugin({
  filename: path.join(__dirname, '../dist/entry.html'),// 文件写入路径
  template: path.join(__dirname, '../src/index.html'),// 模板文件路径
  inject: true, // 插入位置
  chunks: ['manifest', 'vendors', 'common', 'entry']
}),

Among them, you need to manually specify the inserted chunks (synchronized) of each page, otherwise the files of other pages will also be inserted into the current page.

④ Public js extraction

Under a single page, there is generally no problem of extracting the common code (non-node_modules) of non-asynchronous js files. Under multiple pages, our pages may share common api, configuration and other files. At this time, you can add:

'common': {
  // initial 设置提取同步代码中的公用代码
  chunks: 'initial',
  // test: 'xxxx', 也可使用 test 选择提取哪些 chunks 里的代码
  name: 'common',
  minSize: 0,
  minChunks: 2
}

Extract common code in synchronous code

refer to

  1. Persistent caching scheme based on webpack
  2. webpack issues
  3. vuejs-templates/webpack/issues

Author: toBeTheLight
Link: https://juejin.im/post/5ae925cff265da0ba76f89b7
Source: Nuggets The
copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325854695&siteId=291194637