preface

Webpack, as the most well-known front-end packaging tool, can package scattered modules into a complete application. Most well-known frameworks CLI are written based on WebPack. These CLIS preconfigure various processing configurations for the user, and when you use them too much, you take them for granted, regardless of how they are configured internally. Without CLI development, you might not be able to start.

In this article, I will share the configuration that was used when I recently built a project from scratch when I was developing a few one-page projects.

The preparatory work

Quickly generate package.json:

npm init -y
Copy the code

Essential Webpack and Webpack – CLI:

npm i webpack webpack-cli -D
Copy the code

Entrance, exit

The configuration of webpack will be managed in the configuration file. Create a new file named webpack.config.js in the root directory:

const path = require('path')

module.exports = {
  entry: {
    main: './src/js/main.js'
  },
  output: {
    // filename defines the name of the packaged file
    // [name] The name of the entry file in the corresponding entry configuration (main as above)
    // [hash] A random string generated based on the file content
    filename: '[name].[hash].js'.// path defines the path of the entire packaged folder named dist
    path: path.join(__dirname, 'dist')}}Copy the code

Entry Configuration entry. You can configure multiple entries. Webpack looks for dependencies, parses, and packages them from the entry file.

Output configures the export, multi-entry corresponds to multi-exit, that is, how many files are configured in the entrance, and the corresponding files are packaged out.

Modify script configuration of package.json:

"scripts": {
  "build": "webpack --config webpack.config.js"
}
Copy the code

This completes the simplest configuration, which can be packaged by typing NPM run build from the command line.

Intelligent notification of configuration items

The configuration items of Webpack are quite complicated. For students who are not familiar with it, the efficiency and accuracy of development will be greatly improved if they can provide intelligent hints when entering configuration items.

By default VSCode does not know the type of the webpack Configuration object. After importing the Configuration type in the webpack module, there will be an intelligent prompt before writing the Configuration item.

/ * *@type {import('webpack').Configuration} * /
module.exports = {

}
Copy the code

The environment variable

There are generally two environments for development and production, and some configurations of WebPack vary depending on the environment. Environment variables are therefore an important feature, and you can use the cross-env module to inject environment variables into configuration files.

Install cross-env:

npm i cross-env -D
Copy the code

The command passed to modify package.json variables:

"scripts": {
  "build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
}
Copy the code

In the configuration file, you can use the variables passed in as follows:

module.exports = {
  devtool: process.env.NODE_ENV === 'production' ? 'none' : 'source-map'
}
Copy the code

Similarly, this variable can be used within the js of a project.

Set the source – the map

This option allows you to set different types of source-map. The source map is like a map that corresponds to the location of the source code. This option helps developers enhance the debugging process and locate errors accurately.

To see how it works, I deliberately output a non-existent variable in the source code, simulating a line error:

When previewing, an error is triggered:

It is obvious that the number of lines in error does not correspond, so let’s set devTool to output source-map file after webPack is packaged to locate the error.

module.exports = {
  devtool: 'source-map'
}
Copy the code

The error is triggered again, and the source-map file is used to locate exactly the number of lines of code in error.

Source-map is usually only used for debugging in the development environment. You must never go online with source-Map files as this will expose the source code. Let’s set the devtool option correctly using the environment variable.

module.exports = {
  devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none'
}
Copy the code

Devtool has many options, and its quality is related to build speed, such as locating to a file only, or locating to a row or column, which is faster or slower. You can choose according to your needs, see the Webpack documentation for more options

The loader and the plugin

Loader and Plugin are the soul of Webpack. If webpack is compared to a food processing plant, Loader is like many assembly lines to process food raw materials. Plugins are things that add functionality to existing functionality.

Basic usage of loader

Loader is configured on the module.rules property.

module.exports = {
  module: {
    rules: []}}Copy the code

The following is a brief introduction to loader rules. The test and use attributes are commonly used:

  1. testProperty that identifies the files or files that should be converted by the corresponding loader.
  2. useProperty indicating which loader should be used for conversion.
rules: [
  {
    test: /\.css$/,
    use: ['css-loader']}]// Can also be written as
rules: [{test: /\.css$/,
    loader: 'css-loader'}]Copy the code

The above example matches files ending in.css and uses CSS-loader to parse.

When some loaders can pass in configuration, they can change to object form:

rules: [
  {
    test: /\.css$/,
    user: [
      {
        loader: 'css-loader'.options: {}}]}]Copy the code

Finally, remember that multiple Loaders are executed in strict order, from right to left and from bottom to top.

Plugin basic Usage

Plugin is configured on the plugins property.

module.exports = {
  plugins: []}Copy the code

A plugin would normally expose a constructor and use it as new. The plugin argument is passed in when the function is called. Plugin naming is usually changed to a big hump.

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

module.exports = {
  plugin: [
    new CleanWebpackPlugin()
  ]
}
Copy the code

Generating HTML files

Webpack without any configuration is packaged with only JS files. Html-webpack-plugin can customize an HTML file as a template, and eventually HTML will be packaged into Dist, and JS will also be introduced into it.

Install HTML – webpack – the plugin:

npm i html-webpack-plugin -D
Copy the code

Configure plugins:

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    // Use the HTML template
    new HtmlWebpackPlugin({
      // Configure the HTML title
      title: 'home'.// Template path
      template: './src/index.html'./ / compression
      minify: true}})]Copy the code

Interpolate the title tag in HTML for the title tag to take effect:

<title><%= htmlWebpackPlugin.options.title %></title>
Copy the code

In addition to title, you can also configure things like meta tags.

Multiple page

Using the above method, only one HTML will be present after packaging. The demand for multiple pages is actually very simple, how many pages on the new several times htmlWebpackPlugin.

However, it is important to note that the entry configuration js is fully imported into the HTML. If you want a JS to correspond to an HTML, you can configure the chunks option for the plug-in. You also need to configure the filename attribute, because the filename attribute defaults to index.html and the name of the attribute is overwritten.

module.exports = {
  entry: {
    main: './src/js/main.js'.news: './src/js/news.js'
  },
  output: {
    filename: '[name].[hash].js'.path: path.join(__dirname, 'dist')},plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'.filename: 'index.html'.minify: true.chunks: ['main']}),new HtmlWebpackPlugin({
      template: './src/news.html'.filename: 'news.html'.minify: true.chunks: ['news']})]}Copy the code

Turn es6 es5

Install the related modules of Babel:

npm i babel-loader @babel/core @babel/preset-env -D
Copy the code

@babel/core is the core module of Babel, and the @babel/preset-env is a syntax conversion plug-in for preset in different environments, which defaults ES6 to ES5.

Create.babelrc file in root directory:

{
  "presets": ["@babel/preset-env"]
}
Copy the code

Loader configuration:

rules: [
  {
    test: /\.js$/,
    exclude: /node_modules/, 
    loader: 'babel-loader'}]Copy the code

Parsing the CSS

Install the loader:

npm i css-loader style-loader -D
Copy the code

Css-loader is only responsible for parsing CSS files. It usually needs to work with style-loader to insert the parsed content into the page for the style to take effect. The order is parse first and then insert, so CSS-Loader is placed on the far right and executed first.

rules: [
  {
    test: /\.css$/,
    use: ['style-loader'.'css-loader']}]Copy the code

Note, however, that the final package is not a CSS file, but js. It inserts styles by creating the style tag.

The separation of CSS

After the CSS parsing above, the packaged styles will be mixed in with the JS. For some scenarios where we want to package CSS separately, we can use the mini-CSs-extract-plugin to separate the CSS.

Install the mini – CSS – extract – the plugin:

npm i mini-css-extract-plugin -D
Copy the code

Configure plugins:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash].css'.chunkFilename: 'css/[id].[hash].css'}})]Copy the code

Loader configuration:

rules: [
  {
    test: /\.css$/,
    use: [
      // Insert into the page
      'style-loader',
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          // Specify custom public paths for external resources (such as images, files, etc.)
          publicPath: '.. / ',}},'css-loader',]},]Copy the code

After the above configuration, the CSS will be separated out after packaging.

However, if the CSS file is not very large, it can be counterproductive to separate the CSS file, because it will require one more file request. Generally speaking, if the CSS file is larger than 200KB, separate the CSS file.

CSS browser compatible prefixes

Installation-related dependencies:

npm i postcss postcss-loader autoprefixer -D
Copy the code

Create postcss.config.js in the root directory of your project:

module.exports = {
  plugins: [
    require('autoprefixer') (the)]}Copy the code

Package. json added browserslist configuration:

{
  "browserslist": [
    "defaults"."not ie < 11"."last 2 versions"."1%" >."iOS 7"."last 3 iOS versions"]}Copy the code

Postcss-loader:

rules: [
  {
    test: /\.css$/,
    use: [
      'style-loader'.'css-loader'.'postcss-loader']}]Copy the code

Comparison before and after treatment:

Compress CSS

By default, webpack compresses only JS files. CSS compression can be accomplished with optimize- CSS -assets-webpack-plugin.

Install the optimize – CSS – assets – webpack – the plugin:

npm i optimize-css-assets-webpack-plugin -D
Copy the code

Configure plugins:

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
  plugins: [
    new OptimizeCssAssetsWebpackPlugin()
  ]
}
Copy the code

Compression results:

Resolution images

Images are definitely part of the project, and WebPack also has a urL-loader for image resources. Url-loader not only parses images, but also converts small images to Base64 to reduce online requests for images.

Install the url – loader:

npm i url-loader -D
Copy the code

Loader configuration:

rules: [
  {
    test: /\.(jpg|png|jpeg|gif)$/,
      use: [{
        loader: 'url-loader'.// Images less than 50K are converted to base64, and those beyond are packed into the images folder
        options: {
          limit: 1024 * 50.outputPath: './images/'.pulbicPath: './images/'}}}]]Copy the code

Once the configuration is complete, just import the image in the entry file and WebPack will help you pack the image.

Sometimes, however, image links are written directly to HTML, in which case urL-loader cannot parse. Don’t panic, use htMl-loader to do this.

rules: [
  {
    test: /\.(html)$/,
    use: [{
      loader: 'html-loader'.options: {
        // Compress HTML template Spaces
        minimize: true.attributes: {
          // Configure attributes and tags that need to be resolved
          list: [{
            tag: 'img'.attribute: 'src'.type: 'src',}]},}}]},]Copy the code

Note that html-loader is used for parsing only and can be used together with urL-loader or file-loader. In other words, after the image link of the template is parsed, the process of urL-loader configured above is still carried out.

Also, with html-loader, the htML-webpack-plugin doesn’t interpolate in HTML.

Other types of resource resolution

Parsing other resources is similar, but file-loader is used here. File-loader and url-loader mainly parse resources imported by import/require() on files into urls and send the resources to the output directory. The difference is that url-loader can convert resources into Base64.

The installation file – loader:

npm i file-loader -D
Copy the code

Loader configuration:

rules: [
  {
    test:/\.(mp3)$/,
    use: [{
      loader: 'file-loader'.options: {
        outputPath: './music/'.pulbicPath: './music/'}}}, {test:/\.(mp4)$/,
    use: [{
      loader: 'file-loader'.options: {
        outputPath: './video/'.pulbicPath: './video/'}}}]]Copy the code

The above is only a partial list. If you need to resolve other types of resources, add configurations according to the above format.

The same goes for parsing other types of resources in HTML. Use html-loader to configure the tags and attributes of the object.

DevServer improves development efficiency

Every time you want to run a project, you have to build it before you preview it, which is inefficient.

The plugin webpack-dev-server can be used to preview a project by visiting a local server. When the project file changes, it will be hot updated without manual refreshes, thus improving the development efficiency.

Install webpack dev – server:

npm i webpack-dev-server -D
Copy the code

Configuration webpack. Config. Js:

const path = require('path')

module.exports = {
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    / / the default 8080
    port: 8000.compress: true.open: true,}}Copy the code

The usage of webpack-dev-server differs from other plug-ins in that it does not add to plugins, but simply adds the configuration to the devServer property.

Add the startup command package.json:

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack serve"}},Copy the code

Run NPM run dev from the command line to get a flying experience.

Copy files to dist

For files that do not need to be parsed, but still want to put them in dist after packaging, you can use the copy-webpack-plugin.

Install the copy – webpack – the plugin:

npm i copy-webpack-plugin -D
Copy the code

Configure plugins:

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

module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [{// Resource path
          from: 'src/json'.// Target folder
          to: 'json',}]}),]}Copy the code

Remove old Dist before packing

Packaged files typically have hash values that are generated based on the contents of the file. Because of the different names, it may be possible to have a residual DIST from the last packaged file, and it may not be smart to remove it manually each time. The clean-Webpack-plugin can help us clean up the last dist and ensure no redundant files.

Install the clean – webpack – the plugin:

npm i clean-webpack-plugin -D
Copy the code

Configure plugins:

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

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

The plug-in clears the output.path folder by default.

Custom compression options

The compression plugin built into Webpack from V4.26.0 has become terser-webpack-plugin. If there is no other requirement, the custom compression plug-in will try to be consistent with the official.

Install terser webpack – the plugin:

npm i terser-webpack-plugin -D
Copy the code

Configure plugins:

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

module.exports = {
  optimization: {
    // Defaults to true
    minimize: true.minimizer: [
        new TerserPlugin()
    ]
  },
}
Copy the code

Plug-in compression configuration can be customized at call time by referring to the Terser-webpack-plugin documentation.

Add the difference between Minimizer and plugins

CSS compression plug-ins like the one above can also be added to optimization.minimizer. Unlike configuration-to-plugins, configuration-to-plugins execute in any case, configuration-to-minimizer only works when the minimize property is on.

The purpose of this is to facilitate uniform control of compression through the minimize property.

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = reuqire('terser-webpack-plugin')

module.exports = {
  optimization: {
    // Defaults to true
    minimize: true.minimizer: [
        new TerserPlugin(),
        new OptimizeCssAssetsWebpackPlugin()
    ]
  },
}
Copy the code

Note that if you provide the minimizer option and do not use the JS compression plugin, the packaged JS will not be compressed, even though js compression is built into Webpack. Because the WebPack compression configuration is overwritten by Minimizer.

Tips for troubleshooting errors

While working with WebPack, it occasionally gets some weird errors.

Here are some of the mistakes I encountered and how they worked out (fYI, not a panacea) :

  1. Some loaders and plugins rely on webpack versions when used. If errors occur, check for version incompatibilities and try lowering a version.
  2. Reinstall dependencies. It is possible that some dependencies may not be installed during the download process.
  3. Looking at the usage documentation, the option properties passed in for different versions may be different (gapped).

Also pay attention to the console prompt, you can usually guess what the problem is based on the error message.

Version dependent and full configuration

Project Structure:

Dependent version:

{
    "@babel/core": "^ 7.12.3." "."@babel/preset-env": "^ 7.12.1"."autoprefixer": "^ 10.0.1." "."babel-loader": "^ 8.1.0"."clean-webpack-plugin": "^ 3.0.0"."copy-webpack-plugin": "^ 6.3.0"."cross-env": "^ 7.0.2"."css-loader": "^ 5.0.0"."file-loader": "^ 6.2.0"."html-loader": "^ 1.3.2." "."html-webpack-plugin": "^ 4.5.0." "."mini-css-extract-plugin": "^ 0.9.0"."postcss": "^ 8.1.6"."postcss-loader": "^ 4.0.4." "."style-loader": "^ 2.0.0." "."url-loader": "^ 4.4.1"."webpack": "^ 4.44.2"."webpack-cli": "^ 4.2.0"."webpack-dev-server": "^ 3.11.0"
}
Copy the code

webpack.config.js:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

/ * *@type {import('webpack').Configuration} * /
module.exports = {
  devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none'.entry: {
    main: './src/js/main.js'
  },
  output: {
    filename: '[name].[hash].js'.path: path.join(__dirname, 'dist')},devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 8000.compress: true.open: true,},plugins: [
    // Clear the last package
    new CleanWebpackPlugin(),
    // Copy files to dist
    new CopyPlugin({
      patterns: [{from: 'src/music'.to: 'music',}}]),// Use the HTML template
    new HtmlWebpackPlugin({
      template: './src/index.html'.minify: true
    }),
    CSS / / separation
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash].css'.chunkFilename: 'css/[id].[hash].css'
    }),
    / / compress CSS
    new OptimizeCssAssetsWebpackPlugin()
  ],
  module: {
    rules: [
      // Parse js (es6 to ES5)
      {
        test: /\.js$/,
        exclude: /node_modules/, 
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '.. / ',}},'css-loader'.'postcss-loader'] {},test: /\.(html)$/,
        use: [{
          // The img image path in HTML needs to be used with url-loader or file-loader
          loader: 'html-loader'.options: {
            attributes: {
              list: [{
                tag: 'img'.attribute: 'src'.type: 'src'}, {tag: 'source'.attribute: 'src'.type: 'src',}},minimize: true}}},// Parse the image
      {
        test: /\.(jpg|png|gif|svg)$/,
        use: [{
          loader: 'url-loader'.// Images less than 50K are converted to base64, and those beyond are packed into the images folder
          options: {
            limit: 1024 * 50.outputPath: './images/'.pulbicPath: './images/'}}},// Parse other types of files (e.g. fonts)
      {
        test: /\.(eot|ttf)$/,
        use: ['file-loader'] {},test: /\.(mp3)$/,
        use: [{
          loader: 'file-loader'.options: {
            outputPath: './music/'.pulbicPath: './music/'}}]}},}Copy the code

The last

Because of the simplicity of the one-page project and the simplicity of the configuration items, this article focuses on the basics. But the routine is similar, according to the needs of the project to choose loader and plugin. It is more important to understand the functions and usage of these plug-ins, as well as other commonly used plug-ins.