preface

I have been using scaffolding for Vue project development. I know little about Webpack, the black box, and always have no idea when there is a problem. Therefore, I learned it completely while Webpack5.0 was released soon. This article summarizes the learning results. The overall outline is shown below. This article is the basic part. For the advanced part, please click portal Webpack5.0 to learn summary – advanced part.

Know Webpack

What is Webpack?

Webpack is a front-end resource builder, a static module packer.

  • Front-end resource Builder: Basically understand what this front-end resource is. These front-end resources are web resources that the browser does not know, such as Sass, less, TS, including the advanced syntax in JS. For these resources to work in the browser, they must be compiled. Webpack is a total build tool that integrates these build tools.
  • Static module packer: A static module is a variety of resource files in the process of Web development. Webpack builds a dependency diagram based on the reference relationship, and then uses this diagram to package all the static modules into one or more bundles.

Why do we need Webpack

The answer to this question is obvious when you compare it to before Webpack and without build tools. Here are some of the pain points of not using build tools.

  • In Web development, cross-domain invocation of back-end interfaces requires other tool proxies or other methods to avoid.
  • After making code changes, you need to refresh the browser manually, or clean the cache if you have done so.
  • Because of the compatibility of JS and CSS, many new grammars can not be used after learning, which affects both development efficiency and personal growth.
  • Packing problems. We need to use additional platforms such as Jekins packaging, write our own packaging scripts, and deal with all aspects such as compressed images, js packaging, and CSS packaging. .

Webpack provides a solution to these problems, and you only need to do some simple configuration to get started. Of course, Webpack does more than that, so here’s how.

Using Webpack

Webpack core configuration

This section introduces common Webpack configurations, mainly in the form of code and comments. As a reminder, there are few configurations covered in this article, but you can see the official Webpack documentation for detailed configurations. Especially for Loaders and plugins, they are mostly third-party integrations and their content is constantly updated, so if you need to use them, go directly to the corresponding website to find their integration and usage methods.

entry

Entry: Indicates which file Webpack starts packing as the entry point, analyzing and building internal dependency diagrams.

// String mode: single entry, packaged into a trunk, output a buldle file. The default trunk name is main.js entry: "./ SRC /index.js"./ / array: multiple entries. ["./ SRC /index.js", "./ SRC /test.js"], // object: multiple entry files form several trunk files, output several bundle files. {index:"./ SRC /index.js", test:"./ SRC /test.js",}Copy the code

output

Output: Indicates where and how to name the output of resource bundles packaged by Webpack.

    output: {
        // Output file directory (public directory for future output of all resources, CSS, static files, etc.)
        path: path.resolve(__dirname, "dist"), / / the default
        // File name (specify name + directory)
        filename: "[name].js"./ / the default
        // All resources are prefixed with a common path
        publicPath: ""./* The name of the chunk of the non-entry file. A non-import trunk or a common trunk extracted by splitChunks in Optimization that supports the built-in variable */ as filename does
        chunkFilename: "[contenthash:10].chunk.js"./* Library */ is needed when using Webpack to build a library that can be imported and used by other modules
        library: {
            name: "[name]".// The name of the variable exposed throughout the library
            type: "window"// How the library is exposed}},Copy the code

loader

Loader: Webpack itself can only understand JavaScript and JSON files. Loader enables Webpack to process other files. Loader configurations for several common types of files are listed here.

rules: [
    {
        // Which files to match
        test: /\.css$/.// Which loaders are used for processing. Execution order, right to left, bottom to top
        use: [
            // Create a style tag, take the js style resource (the string converted by CSS-loader) and add it to the page head tag to take effect
            "style-loader".// Change the CSS file to commonJS
            "css-loader",        
             {

                 // CSS is compatible with postCSS. Note that browserslist needs to be configured in package.json
                 loader: "postcss-loader".options: {
                     postcssOptions: {
                         ident: "postcss".// PostCSs-preset -env plugin: Help PostCSS find browserslist configuration in package.json and load specified compatibility styles according to the configuration
                         plugins: [require("postcss-preset-env")() [,},},},],}, {test: /\.js$/.// Note that browserslist needs to be configured in package.json, otherwise babel-loader will not take effect
        // js compatible with Babel
        loader: "babel-loader".// This is recommended when only one loader is used
        options: {
            presets: [["@babel/preset-env".// Preset: tell Babel what compatibility processing to do
                    {
                        useBuiltIns: "usage".// Load as needed
                        corejs: {
                            version: "3",},targets: "defaults",}]]}},/* Webpackage 5.0 added asset Module, which is a module type that allows you to use resource files (fonts, ICONS, etc.) without having to configure additional loaders. The following four configurations are supported: Asset/Resource sends a single file and exports the URL. This was previously implemented using file-loader. Asset /inline exports the data URI of a resource. This was previously implemented using urL-loader. Asset /source Exports the source code for the resource. This was previously implemented using raw-loader. Asset automatically selects between exporting a data URI and sending a separate file. This is done by using urL-loader and configuring resource volume limits. * /
    // Webpack4 is implemented using file-loader
    {
        test: /\.(eot|svg|ttf|woff|)$/,
        type: "asset/resource".generator: {
            // Outputs the file location and file name
            filename: "fonts/[name][ext]"}},// Webpack4 uses url-loader
    {
        // Process image resources
        test: /\.(jpg|png|gif|)$/,
        type: "asset".generator: {
            // Outputs the file location and file name
            filename: "images/[name][ext]"
        },
        parser: {
            dataUrlCondition: {
                maxSize: 10 * 1024 If the value exceeds 10kb, base64 will not be transferred}}},].Copy the code

plugin

Plugins: Can be used to perform a wider range of tasks. From packaging optimization and compression, all the way to redefining variables in the environment.

// CleanWebpackPlugin helps you to automatically clean up dist files while you are packing
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

// HtmlWebpackPlugin helps you create HTML files and automatically introduces bundles for the output. Support HTML compression.
const HtmlWebpackPlugin = require("html-webpack-plugin");

// This plug-in extracts CSS into a separate file. It creates a CSS file for each trunk. It must be used with loader
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// This plugin will search for CSS resources during Webpack build and optimize \ minimize CSS
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

// VueLoaderPlugin needs to be introduced into vue files after V15. It is used to apply js, CSS and other rules to vue files.
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
    module: {
        rules: [{test: /\.vue$/,
                loader: "vue-loader"
            },
            {
                test: /\.css$/,
                use: [
                    / / MiniCssExtractPlugin. The role of the loader is the CSS - loader processing good style resources in a file (js), extracted separately Become a CSS file
                    MiniCssExtractPlugin.loader,// Style-loader is recommended for development environments
                    "css-loader",],},],},plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template:"index.html"
        }),
        new MiniCssExtractPlugin({
            filename: "css/built.css",}).new OptimizeCssAssetsWebpackPlugin(),
        new VueLoaderPlugin(),
    ]
}
Copy the code

mode

Mode: Instructs Webpack to use the configuration of the corresponding mode. The default is production. Taking a look at the chart on the official website, it’s worth looking at what Webpack does for the two modes we use most often.

options describe
development The value of process.env.node_env in DefinePlugin is set to development. Enable valid names for modules and chunks.
production The value of process.env.node_env in DefinePlugin is set to production. For the module and the chunk enable deterministic confusing name, FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin and TerserPlugin.
none Do not use any default optimization options
  • DefinePlugin: Define the global variable process.env.node_env to distinguish program running state.
  • Dependence on FlagDependencyUsagePlugin: tag not used.
  • FlagIncludedChunksPlugin: Flags chunks to prevent them from being loaded multiple times.
  • ModuleConcatenationPlugin: enhancing the scope (scope hosting), precompiled function, ascending or precompile all modules into a closure, improve the code in the browser execution speed.
  • NoEmitOnErrorsPlugin: Prevents the program from reporting errors and continues compiling even if there are errors.
  • TerserPlugin: Compress JS code.

Other Common Configurations

module.exports = {
    // Parse module rules:
    resolve: {
        // Configure the parsing module path alias: short path.
        alias: {
            "@": path.resolve(__dirname, "src")},// Omit the file path suffix. Js and JSON are omitted by default. These are the two file types that WebPack recognizes by default
        extensions: [".js".".json".".css"].// Add new CSS file
        // Tells Webpack which directory to look for
        // This configuration explicitly tells WebPack to go straight up to node_modules.
        modules: [path.resolve(__dirname, ".. /node_modules")]},// devServer (development environment configuration) :
    devServer: {
        // Directory to run the code
        contentBase: path.resolve(__dirname, "build"),
        // Enable gzip compression for each static file
        compress: true.host: "localhost".port: 5000.open: true.// Automatically open the browser
        hot: true.// Enable HMR
        // Set the proxy
        proxy: {
            // Once devServer(port 5000) receives a request for/API/XXX, it forwards the request to another server using devServer's service (port 3000).
            // To solve cross-domain problems in development
            api: {
                target: "htttp://localhost:3000".// the request path is rewritten: / API/XXX --> / XXX (/ API)
                pathRewrite: {
                    "^api": "",},},},},// Optimization (production environment configuration)
    optimization: {
        // Extract the common code
        splitChunks: {
            chunks: "all",},minimizer: [
            // Configure the compression scheme for the production environment: JS and CSS
            new TerserWebpackPlugin({
                // Multi-process packaging
                parallel: true.terserOptions: {
                    / / start the source - the map
                    sourceMap: true,},}),],},};Copy the code

Webpack package optimization

Development environment optimization

Use source-map

Source-map: A technique that provides a mapping of source code to post-build code so that if something goes wrong post-build code can be traced through the mapping. Optimize code debugging. Enabling source-map configuration is simple: devtool:”source-map”. There are several types of source-map values, just to explain. The source – the map of each option are commonly used: [the inline – | eval -] [being – [module -]] source – the map

  • Inline: Inline, a trunk generates a total source-map
  • Eval: inline, generating a source-map for each file
  • The location of the error should only be accurate to the line.
  • Cheap-module: Displays source-map of third-party libraries

The difference between inline and external: inline does not generate map.js files, but is directly injected into chunk in the form of data-URL. Inline builds are faster.

Ii. HMR (Module hot Replacement)

DevServer starts a proxy server. Modifying the code after startup will refresh the browser automatically, but this is not HMR. HMR: Module hot replacement, also known as local replacement. Replace, add, or remove modules without reloading the entire page. Enable HMR as follows

    devServer: {
        contentBase: path.resolve(__dirname, "dist"),
        hot: true.// Enable HMR
    },
    // Note: After Webpack is upgraded to 5.0, the target default value will change according to browserslist in package.json, invalidating the automatic update of devServer. So the Development environment is directly configured to the Web.
    target: "web".Copy the code

After HMR is enabled, you need to perform some configuration to make it take effect.

  • Style file: style-loader internal implementation, so as long as the loader is configured with style-loade can directly use the HMR function
  • Vue file: internal implementation of VUe-loader. In the same way, vUE -loader is directly configured using HMR.
  • Js file: need to modify the source code, receive update notification, the code is as follows
import test from "./test.js"
if(module.hot){
    module.hot.accept("./test.js".() = >{
        console.log('Accepting the updated test module! '); })}Copy the code

When the test file is changed, the update event is passed layer by layer until it is passed to the entry file. During delivery, any place that receives the update event, the module.hot.accept method above, stops delivery and executes the callback. If it never receives, Webpack is finally told to refresh the entire page.

Production environment optimization

A, oneOf

By default, the file matches every rule under rules and continues to match even if a rule has already been matched. If you place the rule in the oneOf attribute, the match stops once a rule is matched.

rules:[
    {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "eslint-loader"}, {// Only one of the following loader files will match
        oneOf: [
            // There cannot be two configurations that handle the same type of file. If there are, another rule should be placed outside.
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: "babel-loader",},],}, {test: /\.css$/,
                use: [
                    "style-loader"."css-loader",],},],},]Copy the code

Rules placed in the oneOf attribute will only match successfully once, so if you have a file type that requires multiple Loaders, either use the Use array or place it outside of oneOf.

Second, the cache

During compilation and packaging, files can be cached in two ways. One is that the loader that parses files has the caching function (such as babel-loader and vue-loader), and the other is to use a special loader (cache-loader). When caching is enabled, WebPack reads unchanged files directly from the cache without recompiling, greatly speeding up builds.

{
    test: /\.js$/,
    use: [
        // Use cache-loader to cache js files compiled by Babel.
        "cache-loader",
        {
            loader: "babel-loader".options: {
                presets: [["@babel/preset-env".// Preset: tell Babel what compatibility processing to do]],// Enable the Babel cache. On the second build, the previous cache will be read.
                cacheDirectory: true,}}}Copy the code
3. Multi-process Packaging (Thread-loader)

Thread-loader is usually used only when compilation takes a long time, because it is expensive to start and communicate with the loader. If compilation takes a short time, using the Loader is not worth the cost.

// If the "thread-loader" is placed before the babel-loader, the babel-loader will work on multiple processes.
{
    loader: "thread-loader".options: {
        workers: 2.// The number of started processes. The default value is -1}, {},loader: "babel-loader".options: {
        presets: [["@babel/preset-env",],],},},Copy the code
Iv. Externals

Externals is used to tell Webpack what modules are used in the code to be built that do not need to be packaged, possibly introduced through an external environment such as a CDN.

module.export = {
  externals: {
    // Replace jquery in the import statement with the global jquery variable in the runtime
    jquery: 'jQuery'}}/ / the source code
 import $ from "jquery"
Copy the code

After externals is configured, Webpack does not bundle the library even if you include it in your code, but instead uses global variables directly.

Five, the DLL

DLL (dynamic link library) : Use DLL technology to pack public libraries in advance, which can greatly improve the construction speed. Common libraries are generally unchangeable, so modules need only be compiled once and can be packed in advance. If it is detected that the common library is already packaged through a DLL during a subsequent build of the main program, it is not compiled but fetched directly from the dynamically linked library. The following three steps are required to implement DLL packaging:

  1. Extract common libraries and package them into one or more dynamically linked libraries.
  2. Introduce the packaged dynamic link library into the page.
  3. A main program that uses a public library in a dynamically linked library cannot be bundled and should be directly fetched from the dynamically linked library.

The code for this step

1 Create a webpack.dll. Js file to pack the dynamic link library in advance

// webpack.dll.js
module.exports = {
    // JS executes entry files
    entry: {
        // Put vUE related modules into a separate dynamic link library
        vendor: ['vue'.'axios'].// Other modules are placed in another dynamically linked library
        other: ['jquery'.'lodash'],},output: {
        // The file name of the output dynamic link library, [name] stands for the name of the current dynamic link library ("vendor" and "other")
        filename: '[name].dll.js'.// The output files are placed in the DLL folder in the dist directory
        path: path.resolve(__dirname, 'dist'."dll"),
        // The exposed variable name of the dynamically linked library, for example, _dll_vendor
        library: '_dll_[name]',},plugins: [
        // Package to generate a mainfest.json file. Tell WebPack which libraries do not participate in the subsequent packaging, already pre-packaged via DLLS.
        new webpack.DllPlugin({
            // The name of the dynamically linked library must be the same as that in output.library
            // The value of this field is the value of the name field in the output manifest.json file
            // For example, vendor.manifest.json has "name": "_dll_vendor"
            name: '_dll_[name]'.// The name of the manifest.json file that describes the output of the dynamic link library
            path: path.join(__dirname, 'dist'."dll".'[name].manifest.json'),})]};Copy the code
  1. Introduce a packaged dynamic link library in the template page index.html
<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Webpack</title>
    <script src="./dll/vendor.dll.js"></script>
    <script src="./dll/other.dll.js"></script>
</head>
<body>
    <div id="app"></div>
</body>
</html>

Copy the code
  1. In the main program Webpack configuration used in Webpack. DllReferencePlugin plug-in, read Webpack. DllPlugin generated manifest. Json file, obtain dependencies.
// webpack.config.js
module.exports = {
    mode: "production".plugins: [
        new HtmlWebpackPlugin({
            template: "./index.html"
        }),
        // Tell Webpack which dynamic link libraries are used
        new webpack.DllReferencePlugin({
            // The manifest file tells Webpack which libraries have been pre-packaged via DLLS, which can be retrieved directly from the dynamic link library for subsequent builds.
            manifest: path.resolve(__dirname, "dist"."./dll/vendor.manifest.json"),}),new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, "dist"."./dll/other.manifest.json"),})],}Copy the code
It is Tree Shaking.

Tree Shaking: Remove dead-code from JavaScript context. Think of the entire application as a tree, with the green leaves representing the source code and Library actually used, and the gray leaves representing the unused code, the withered leaves. To get rid of these dead, useless leaves, you need to shake the tree to make it fall. That’s where Tree Shaking comes from.

// import file index.js
import test from "./test.js"
console.log(test.add(2.3));

// Test the file test.js
const add = (x, y) = > x + y
const print = (msg) = > {
    console.log(msg);
}
export default { add, print }

// Finally package the output bundle: main.js file
!function(){"use strict";console.log(2+3)} ();Copy the code

As can be seen from the above example, although the test file was introduced in index.js, the print method exposed by the test file was not used, so it was removed from the final package. This is not possible in Webpack4, which only removes modules that are never used. Taking the example above, if test is not used in the index.js file, it is Tree Shaking. This is because Webpack4 by default assumes that all file code has side effects. How to tell Webpack if your code has sideEffects is via the sideEffects field in package.json.

// All files have side effects
{
 "sideEffects": true
}
// All files have no side effects,
{
 "sideEffects": false
}
// Only these files have side effects, all other files can be Tree Shaking, but these files are kept
{
 "sideEffects": [
  "./src/file1.js"."./src/file2.js"]}Copy the code

For example, style files are considered sideEffects by default in webpackage 5.0, so imported style files are not used and will not be removed, but if sideEffects: false is set, Tree Shaking will remove the code. How do YOU set Tree Shaking? You don’t need to configure it. If you set mode to “production”, Webpack will automatically enable Tree Shaking. Two caveats:

  • Source code must use static ES6 modularity syntax. The reason is that Webpack uses static analysis at build time to figure out the dependencies between code. Dynamic import, such as require, does not know which module is being imported until it is executed, so Tree Shaking is not possible.
  • Tripartite libraries cannot do Tree Shaking. The guess is that Webpack can’t guarantee that third-party library imports will directly affect the program.
7, Code Split

By default, Webpack packs all dependent files into a bundle.js file (single entry). As your application gets more complex, the bundle.js file gets bigger and the browser loads slower, so you need to use code splitting to pack different code into separate trunk outputs. There are two main ways

First, use Optimization to package common code into a separate trunk

optimization: {
    splitChunks: {
        // Select which chunks to optimize. By default async will optimize only the trunk formed by dynamic import.
        chunks: 'all'.// Extract the minimum chunk size
        minSize: 20000.// The minimum number of references of the chunk to be extracted
        minChunks: 1.// Group the trunk to be extracted
        cacheGroups: {
            // Match the tripartite library in node_modules and pack it into a trunk
            defaultVendors: {
                test: /[\\/]node_modules[\\/]/./ / the name of the trunk
                name: 'vendors'.priority: -10,},default: {
                // Extract modules imported by at least two trunks and pack them into a single trunk
                minChunks: 2.name: 'default'.priority: -20,},},},},Copy the code

Dynamic import This is recommended when you want to split bundles based on business. Import Dynamically imported modules Webpack are packaged as separate trunks.

import( /* webpackChunkName: 'test' */ './test.js').then((result) = > {
    console.log(result);
}).catch(() = > {
    console.log('Load failed! ');
});
Copy the code

conclusion

Because the length is longer, the content of the principle part is divided and written in the advanced part. If there are any mistakes or inaccuracies in the article, please point them out and discuss them.