“This is the second day of my participation in the November Gwen Challenge. See details of the event: The last Gwen Challenge 2021”.

preface

There are probably some students like me, after graduation work for a period of time to the project configuration file resistance psychology, every day only dare to NPM run serve/dev, for fear of moving something, it will not run…

This kind of behavior lasted about half a year. As long as the project configuration file was involved, I left it to my supervisor. This irresponsible behavior was criticized for being insufficiently motivated for many times. 😢

The subsequent transformation may be due to the growth of experience or the desire to become a better engineer, so I gradually began to learn and practice in the project.

Nonsense said, preface also gather together finished, but the main purpose or do not want everyone to repeat my mistakes ~

This article is a practical summary of webPack configuration. Hopefully, after reading it, you will feel that WebPack is not that difficult.

What is the webpack

Webpack is a static module packaging tool for JavaScript that internally builds dependency diagrams from one or more entry points and then combines each module needed in a project into one or more bundles.

From the definition, webPack only supports JavaScript file types out of the box, but also includes JSON file types. Other static resources need to be supported by the Loader, which will be explained later.

concept

Initialize the project

  1. Create a folder.
  2. npm init -yGenerate package.json file.
  3. npm webpack webpack-cli --save-devInstall webPack dependencies.

Version of webpack installed, different versions may cause errors in subsequent steps (please contact us in the comments section ~).

Webpack: 5.61.0 WebPack – CLI: 4.9.1 node: 14.15.0

Create a SRC folder in the project root directory, and then create index.js and util.js under the folder.

// util.js
export function helloWebpack {
  return 'hello webpack'
}

// index.js
import { helloWebpack } from './utils'

document.write(helloworld())
Copy the code

To build a webpack. Config. Js

const path = require('path')

module.exports = {
  entry: './src/index.js'.// Import file
  output: {  // Export file
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  mode: 'production' // Current build environment
}
Copy the code

Add the package command to scripts in package.json and run NPM run build.

"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "webpack"
},
Copy the code

When packing is complete, there will be an additional dist folder ~ in the root directory

Json files can read commands in the node_modules/.bin directory, and commands are soft links created when the module is partially installed.

Entry

The entry field in the initial project configuration specifies the webPack entry. Note that the entry file only supports JavaScript files (see dependency diagrams on the official website).

There are two types of Entry configuration: single page and multiple pages.

In a single page configuration, entry is a string.

module.exports = {
  entry: './src/index.js'
}
Copy the code

In multi-page configuration, Entry is an object.

module.exports = {entry: {app: './src/app.js'.app2: './src/app2.js'}}Copy the code

Output

Initialize the Output field in the project configuration to specify the webPack file export and name the export file.

The egress configuration and the inlet configuration have two configurations: single page and multiple pages.

This section describes the usage of two common apis in the PATH library before configuration.

Path. resolve: The sequence is from right to left. If a character starts with a slash (/), the preceding paths are not collated. If the.. /, concatenates the previous path, and does not include the path of the last section; If it begins with a./ or has no symbol, the preceding path is concatenated.

Path. join: Joins the path fragments from right to left.

In the single page configuration, the output configuration is as follows.

const path = require('path')

module.exports = {
  entry: './src/index.js'.output: {
    filename: 'bundle.js'.path: path.join(__dirname, 'dist')}}Copy the code

In multi-page configuration, the name variable in [name] corresponds to the key of the entry object.

const path = require('path')

module.exports = {
  entry: {
    app: './src/app.js'
    app2: './src/app2.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: [name].js
  }
}
Copy the code

loader

Since WebPack only supports JavaScript and JSON file types, loaders are provided to help WebPack handle unsupported file types and convert them into valid modules for use by applications and to be added to dependency diagrams.

The following describes common Loaders.

The name of the describe
babel-loader Handles es6+ syntax and compiles it into browser-executable JS syntax
vue-loader Support.vue file loading and parsing
style-loader Insert THE CSS into the HTML file with the style tag
css-loader Supports loading and parsing of. CSS files
sass-loader/less-loader Convert sass/ LESS files to CSS
file-loader Static resources such as images and fonts are packaged
url-loader Similar to file-loader, when the file is below the limit value, go to base64
ts-loader Convert Ts to Js
raw-loader Import the file as a string

Loader has two attributes:

Test: indicates the matching file type. Use: loader used for file conversion.

The basic usage is as follows.

const path = require('path')

module.exports = {
  module: {
    rules: [{
      test: /\.txt$/.// Match the TXT file type
      use: 'raw-loader' / / use raw - loader}}}]Copy the code

Plugins

Plug-ins are used to extend the functionality of WebPack and can be used for bundle optimization, resource management, and environment variable injection, running throughout the packaging cycle.

Common plugins are shown below.

The name of the describe
SplitChunksPlugin Starting with V4, the CommonsChunkPlugin was removed and replaced withoptimization.splitChunks. The purpose is to extract common modules, reduce the bundle size, and optimize the first screen rendering
CleanWebpackPlugin Cleaning up the build directory
CopyWebpackPlugin Copy files or folders to the output directory of the build
MiniCSSExtractPlugin Starting with V4, the ExtractTextWebpackPlugin was removed and replaced withMiniCSSExtractPlugin. The CSS is extracted from the bundle into a separate CSS file and injected into the HTML as a link tag
CssMinimizerWebpackPlugin Compressed CSS code
HotModuleReplacementPlugin Module hot update
HtmlWebpackPlugin Create an HTML file and insert the static file into the HTML file
UglifyjsPlugin Compressed JS, starting with V4, is already built in.
TerserWebpackPlugin Compressed JS, starting with V5, is already built in.

Plugins are relatively simple to use. Take HTMLWebpackPlugin as an example

const path = require('path')

module.exports = {
  plugins: [
    new HTMLWebpackPlugin({
      template: './src/index.html'}})]Copy the code

Mode

Distinguish between production and development in the current build environment and the default value is Production.

Values include production, development, and None.

practice

Now that you’ve got the basics of Webpack, let’s get started

Creating an HTML file

Create an HTML template file in the SRC directory. The following examples will focus on Vue, so the template content is as follows.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
Copy the code

Subsequent packaging to create HTML, and inserting static resource files into that HTML, all benefit from the HtmlWebpackPlugin.

Install NPM I html-webpack-plugin -d.

Simple configuration…

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

module.exports = {
  entry: './src/index.js'.output: { 
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  mode: 'production'.plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'.// Template HTML file path
      filename: 'index.html'.// Specify the package file name
      inject: true.// Inject all static resources into the HTML template}})]Copy the code

For details, refer to the HTMLWebpackPlugin official configuration document.

Support for ES6 +

Convert ES6+ code to ES5 with babel-related libraries for compatibility with more browser environments.

Install the library functions associated with Babel

npm i @babel/core @babel/preset-env babel-loader --save-dev

Add parsing to js files in webpack.config.js.

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

module.exports = {
  entry: './src/index.js'.output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [{test: /\.js$/,
        exclude: /node_modules/.// Remove dependencies
        use: 'babel-loader'}},mode: 'production'.plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'.filename: 'index.html'.inject: true,}})]Copy the code

Create the.babelrc configuration file in the root directory and install the dependency: NPM I core-js@3 –save-dev.

{"presets": [["@babel/preset-env", {"useBuiltIns": "usage", // Introduce "corejs" separately as needed in the file where the new feature is applied: 3, // targets: "> 0.25%, not dead" // browser support range}]]}Copy the code

UseBuiltIns configuration helps us deal with new features like Promise, Map, Set, Symbol, etc. Core-js (deprecated by @babel/ Polyfill in 7.4.0) is the dependent library for new features.

Change the index.js file to NPM run build for packaging.

const a = () = > Promise.resolve(1)
const getData = async() = > {const res = await a()
  console.log(res)
}
getData()
Copy the code

Once the packaging is complete, access index.html to verify that it is correct.


With the extension, Try it out converts the index.js code to the above configuration.

"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); }}function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

var a = function a() {
  return Promise.resolve(1);
};

var getData = /*#__PURE__*/function () {
  var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    var res;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return a();

          case 2:
            res = _context.sent;
            console.log(res);

          case 4:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));

  return function getData() {
    return _ref.apply(this.arguments); }; } (); getData();Copy the code

As you can see, Babel has a custom asyncToGenerator function to assist in converting async function. Therefore, if there are too many project files, as long as each file has async function, the project will repeatedly define the current function in each final package file.

Optimize ~

Install @babel/plugin-transform-runtime and @babel/runtime-corejs3.

Change Babel configuration to add index.html test.

{
  "presets": [
    ["@babel/preset-env"]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime", { "corejs": 3 }
    ]
  ]
}
Copy the code

Try it out adds the relevant plug-in conversion code as follows.

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

require("core-js/modules/es.object.to-string.js");

require("core-js/modules/es.promise.js");

var a = function a() {
  return Promise.resolve(1);
};

var getData = /*#__PURE__*/function () {
  var _ref = (0, _asyncToGenerator2["default"]) (/*#__PURE__*/_regenerator["default"].mark(function _callee() {
    var res;
    return _regenerator["default"].wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return a();

          case 2:
            res = _context.sent;
            console.log(res);

          case 4:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));

  return function getData() {
    return _ref.apply(this.arguments); }; } ();Copy the code

As can be seen from the conversion code, after optimization, the original defined functions are introduced from a unified module.

Support the Vue

Install Vue, NPM I VUE-s.

Install NPM I vue-loader vue-template-compiler-d.

Note that vue-template-Compiler should be consistent with VUE version. My experimental version is V2.6.14. The plug-in is primarily used to compile Vue templates into rendering functions, avoiding runtime compilation overhead and CSP constraints.

Content Security Policy (CSP) helps detect and mitigate certain types of attacks, including cross-site scripting (XSS) and data injection attacks.

Create new main.js and app. vue files.

// main.js
import Vue from 'vue'
import App from './App.vue'

export default new Vue({
  render: h= > h(App)
}).$mount('#app')
Copy the code
// App.vue
<template>
  <div id="app">{{ message }}</div>
</template>
<script>
export default {
  name: 'App'.data() {
    return {
      message: 'Hello Vue'}}}</script>
Copy the code

Change the webpack.config.js file.

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
+ const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
- entry: './src/index.js',
+ entry: './src/main.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader'
      }
    },
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader'
+}
   ]
 },
 mode: 'production',
+ plugins: [
+ new VueLoaderPlugin(),
   new HtmlWebpackPlugin({
     template: './src/index.html',
     filename: 'index.html',
     inject: true
   })
 ]
}
Copy the code

Delete the old package, run NPM run build to repackage, open the index.html folder in dist, and verify that Hello Vue is displayed properly.

Supports CSS and SCSS

To parse the CSS file, use CSS-loader to load it, convert it to commonJS, and then use style-loader to insert the style into the head tag using the style tag.

Install the related plug-in, NPM I CSS-loader style-loader -d.

Add CSS file parsing rules to webpack.config.js.

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

Here’s a quick tidbit: Loaders are called in chain order and executed from right to left. Therefore, you need to write style-loader first and then CSS-loader.

Create a main. CSS file and reference it in the app. vue file

// main.css
.message {
  color: red;
}
Copy the code
// App.vue
<template>
  <div id="app">
    <div class="message">{{ message }}</div>
  </div>
</template>
<script>
import './main.css'
export default {
  name: 'App'.data() {
    return {
      message: 'Hello Vue'}}}</script>
Copy the code

Most of the existing projects also use CSS preprocessors. Here, SCSS is taken as an example.

Install the related plug-in NPM I sass sass-loader -d.

Once installed, change main.css to main.scss and change the style introduction in app.vue to import ‘./main.scss’.

Add load parsing to SCSS file in webpack.config.js.

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

When you’re done, delete the old package and repackage it to test it

Supports images and fonts

You can use file-loader to parse images and fonts.

Install NPM I file-loader -d.

In the SRC folder, create a new images and fonts folder. In the images folder, add an image. In the Fonts folder, add a font file.

Introduced in app.vue.

<template>
  <div id="app">
    <div class="message">{{ message }}</div>
    <div class="image">
      <img :src="Image" alt="Image" />
    </div>
  </div>
</template>
<script>
import './main.scss'
import Image from './images/image.png'
export default {
  name: 'App'.data() {
    return {
      message: 'Hello Vue'.Image: Image
    }
  }
}
</script>
Copy the code

Add the font definition in the main. SCSS folder.

@font-face {
  font-family: 'Manrope-SemiBold';
  src: url('./fonts/Manrope-SemiBold.ttf');
}
.message {
  color: red;
  font-family: 'Manrope-SemiBold';
}
Copy the code

The webpack.config.js file adds load parsing of image and font files.

 {
   test: /.(png|jpg|gif|jpeg)$/,
   use: ['file-loader']
 }, 
 {
   test: /\.(woff|woff2|eot|ttf|otf)$/,
   use: ['file-loader']
 }
Copy the code

When finished, remove the old package and package the accessed HTML file to verify that the image and font files are loading properly.

Url-loader is mentioned in the loader section. Based on file-loader, it provides the function of converting small resources to Base64 format on the basis of loading and parsing pictures and files.

Switching to Base64 reduces HTTP requests. So large files are not suitable for base64, easy to lead to the first screen blank phenomenon.

Use the base64 format that is less than 10KB, as shown below.

{
  test: /.(png|jpg|gif|jpeg)$/,
  use: [{
    loader: 'url-loader',
    options: {
      limit: 10240
    }
  }]
}
Copy the code

If you are familiar with V5 and the resource module of Webpack5, you do not need to configure additional loaders, such as file-loader, URl-loader, and raw-loader.

type describe
asset/resource Send a separate file and export the URL. Previously by usingfile-loaderThe implementation.
asset/inline Export the data URI of a resource. Previously by usingurl-loaderThe implementation.
asset/source Export the source code of the resource. Previously by usingraw-loaderThe implementation.
asset Automatically choose between exporting a data URI and sending a separate file. Previously by usingurl-loaderAnd configure the resource volume limitation implementation.

Change the following code.

{
  test: /.(png|jpg|gif|jpeg)$/,
- use: ['file-loader'],
+ type: 'asset/resource',
+ generator: {
+ filename: '[name][hash:8].[ext]'
+}
}, 
{
  test: /\.(woff|woff2|eot|ttf|otf)$/,
- use: ['file-loader'],
+ type: 'asset/resource',
+ generator: {
+ filename: '[name][hash:8].[ext]'
+}
}
Copy the code

Development server

Set up a development server to avoid the fool-proof process of packaging and verifying. At the same time, configure hot updates, render pages in real time, correct low-level errors during development.

Install NPM I webpack-dev-server -d.

To distinguish between production and development modes, create new webpack.dev.js and webpack.prod.js files.

Make a copy of webpack.config.js and paste it into webpack.dev.js.

Change some content:

  1. The mode configuration item is changed to Development.
  2. Add devServer configuration to configure hot update, which is automatically introduced if hot is trueHotModuleReplaceMentPluginThe plug-in.
devServer: {
  port: 3000./ / port
  hot: true.// Enable hot update
  open: true // Start the browser
}
Copy the code

Do the same thing and paste it into webpack.prod.js without making any changes. When finished, delete the original webpack.config.js.

Finally, change the scripts field in the package.json file.

scripts: {
  "dev": "webpack-dev-server --config webpack.dev.js"."build": "webpack --config webpack.prod.js"."test": "echo \"Error: no test specified\" && exit 1",}Copy the code

Then, run the NPM run dev command to check whether the page is displayed properly and the hot update takes effect.

Adding a File fingerprint

File fingerprint refers to the suffix of the output file name after packaging. It is used for version management and cache clearing.

There are three types of file fingerprints.

  1. Hash: Relates to the entire project build. If the project file changes, the hash value for the entire project build changes.
  2. Chunkhash: Related to chunks packed by Webpack, different entries will generate different chunkhash values.
  3. Contenthash: Defines hash based on the content of the file. Contenthash does not change if the content of the file remains unchanged.

The role of document fingerprint is mainly used in testing, production environment. So, we just need to change the webpack.prod.js file.

For the js file type, set filename for output using [chunkhash:8].

output: {
  path: path.join(__dirname, 'dist'),
+ filename: '[name][chunkhash:8].js'
- filename: 'bundle.js'
}
Copy the code

For CSS file types, style-loader is currently used, which builds CSS into JS files and inserts style tags into HTML when js files are loaded. So how do you extract individual CSS files? That brings us to the MiniCSSExtractPlugin.

Install NPM I mini-css-extract-plugin -d, webpack.prod.js file to introduce const MiniCssExtractPlugin = require(‘ mini-CSs-extract-plugin ‘).

Plugins add the MiniCssExtractPlugin and use the Contenthash variable.

plugins: [
  new VueLoaderPlugin(),
+ new MiniCssExtractPlugin({
+ filename: '[name][contenthash:8].css'
+}).
  new HtmlWebpackPlugin({
    template: './src/index.html',
    inject: true,
    filename: 'index.html'
  })
]
Copy the code

Style – loader with MiniCSSExtractPlugin conflict, replace style – loader for MiniCSSExtractPlugin. Loader

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

[Hash :8] is used to set the configuration parameters of loader for image and font.

{
  test: /.(png|jpg|gif|jpeg)$/,
  use: [{
    loader: 'file-loader',
    options: {
      name: '[name][hash:8].[ext]'
    }
  }]
}, 
{
  test: /\.(woff|woff2|eot|ttf|otf)$/,
  use: [{
    loader: 'file-loader',
    options: {
      name: '[name][hash:8].[ext]'
    }
  }]
}    
Copy the code

Code compression

From v5, the latest version of terser-webpack-plugin comes out of the box. If you want to customize the configuration, you still need to install it.

To customize, install NPM I terser-webpack-plugin -d.

The configuration is as follows. You need to use the Optimization field, which starts from V4 and performs different optimizations based on different modes.

optimization: {
  minimize: true.// Tell WebPack to use TerserPlugin or another compression plugin defined using Optimization.minimizer
  minimizer: [
    new TerserPlugin()
  ]
}
Copy the code

In terms of CSS code compression, V5 recommends using CssMinimizer, the same as OptimizeCssAssetsPlugin, but using query strings in Sourcemaps and Assets is more accurate. It supports caching and parallelism in concurrent mode. And built-in CSSNano, no additional installation.

Install NPM I CSS-minimizer-webpack-plugin-d.

The configuration is as follows.

optimization: {
  minimizer: [
    new CssMinimizerPlugin()
  ]
}
Copy the code

An error occurs when NPM run build is executed.

Install PostCSS (v8.3.11) as prompted to resolve the problem.

Clear the build directory

If you do not clean up old packages during each build, more and more files in the output directory of the build will be generated. Therefore, clearing is necessary. To avoid manually deleting the dist directory before the build, you also need the CleanWebpackPlugin to automatically remove the old packages.

Install NPM I clean-webpack-plugin -d.

Add the CleanWebpackPlugin plugin to the plugins field of webpack.prod.js.

The header introduces const {CleanWebpackPlugin} = require(‘clean-webpack-plugin’).

plugins: [
  new VueLoaderPlugin(),
  new MiniCssExtractPlugin({
    filename: '[name][contenthash:8].css'
  }),
  new HtmlWebpackPlugin({
    template: './src/index.html',
    inject: true,
    filename: 'index.html'
  }),
+ new CleanWebpackPlugin()
]
Copy the code

According to webPack official website V5.2 +, the clean field of output is added, which has the same function without additional installation of CleanWebpackPlugin.

The configuration is as follows.

output {
  path: path.join(__dirname, 'dist'),
  filename: '[name][chunkhash:8].js',
+ clean: true
}
Copy the code

conclusion

This is the basic content of Webpack, hope to read friends can have a basic understanding of Webpack, but also have ideas to deal with the daily scaffolding configuration.

Of course, there are advanced, like friends can like + collection + attention ~

reference

How to deal with babel-env and babel-transform-Runtime in Babel 7.@babel /plugin-transform-runtime What is it? Vue-loaders & Vue-template-compiler – HTML -webpack-plugin – Time – playing with webpack