preface

Packaging is essential to build modern front-end projects. Through packaging tools, we can not only modularize development but also integrate a series of development tools to improve development efficiency and quality. This paper takes Webpack@5 as an example to introduce the packaging knowledge necessary to build modern front-end projects. How to build a complete packaging environment from scratch to equip the reader to build modern front-end project packaging technology

Compatibility Requirements

  • Webpack: Use the latest[email protected]
  • Node.js:Webpack 5rightNode.jsThe version required is at least10.13.0 (LTS)
  • Webpack@plugin: Upgrade to the latest version
  • Webpack@loader: Upgrade to the latest version

Note: Some loaders/plugins may have a beta version and must be used to be compatible with WebPack 5. Be sure to read the release notes for each plug-in loader/ Plugin when upgrading. Also need to pay attention to possible deprecation warnings, compile phase can be achieved by the node – trace – deprecation node_modules/webpack/bin/webpack js command to check the details of which the result of loader/plugin

practice

The installation

npm install webpack webpack-cli --save-dev
Copy the code

run

There are two ways to run Webpack

  • Using configuration files

    npx webpack [--config webpack.config.js]
    Copy the code
  • No configuration file is used

    npx webpack --entry <entry> --output-path <output-path>
    Copy the code

    Such as:

    npx webpack --entry ./first.js --entry ./second.js --output-path /build
    Copy the code

When it comes to large projects, it’s mostly in the form of configuration files

The configuration file

File entity

Default configuration file

CLI will seek the default configuration files in the project path, according to the priority is the default. Webpack/webpackfile >. The webpack/webpack config. Js > webpack. Config. Js

Note: The Command Line Interface parameter takes precedence over the configuration file parameter. For example, if you pass –mode=”production” into the Webpack CLI and the configuration file uses development, production will eventually be used

Custom configuration files

In addition to the default configuration file, we can specify the configuration file via –config

npx webpack --config example.config.js
Copy the code

Basic configuration

Entry

The entry point indicates which module Webpack should use to start building its internal dependency Graph

The default entry value is./ SRC /index.js, but you can specify one or more different entry points by configuring the Entry property in the Webpack Configuration

Single file entry notation:

module.exports = {
  entry: './path/to/my/entry/file.js'};Copy the code

Multi-file entry notation:

module.exports = {
  entry: ['./src/file_1.js'.'./src/file_2.js']};Copy the code

You can also use object syntax, which is more configurable:

module.exports = {
  entry: {
    a2: 'dependingfile.js'.b2: {
      dependOn: 'a2'.import: './src/app.js',}}};Copy the code

Specific configurable items on object syntax are documented

Before WebPack 4 we used to split the library code and the application code into two different portals, but with WebPack 4 we recommend using Optimization.splitchunks instead of creating portals for code that is not an execution node, Generally, only one entry starting point is used per HTML file (see the issue for specific reasons)

Output

Tell WebPack how to write compiled files to disk by configuring the Output option

Note: Only one output configuration can be specified even if there are multiple entry starting points

There are several common configurations:

  • filename: specifyJavaScriptFile Name of the output file
  • path: A directory corresponds to oneAn absolute path
  • publicPath: sets the common path prefix for referencing resources,As shown in the
  • clean: Clear the file before generating itoutputDirectory,[email protected]This parameter is new and required when the configuration is not configuredclean-webpack-pluginImplement similar functions

devtool

This option controls whether and how the source map is generated. It is recommended to configure inline-source-map in the development environment, but not in the development environment (the default is not generated). Nosource-source-map can be used if you want to trace error information

Note: The devtool configuration affects the SOURCE map mechanism of CSS and JavaScript

module

JavaScript

JavaScript in modern front-end projects usually needs to do syndication and polyfills to use advanced syntax without worrying about browser compatibility. To do this we need to use Babel, Babel also needs to install an additional babel-loader in Webpack to integrate with Webpack

Installation:

$ npm i @babel/core @babel/preset-env babel-loader @babel/plugin-transform-runtime core-js --save-dev -d
$ npm i @babel/runtime --dev
Copy the code

In Babel a preset is a set of plugins.

Of the NPM installed above, two are important:

  • @babel/preset-env: The main function is to automatically translate the syntax according to the user configuration (automatic importplugin) andpolyfillsIs a required core package.@babel/preset-envThere are two important configuration items in:
    • useBuiltIns, this configuration controls the importpolyfillsScript the way we normally configure ituseage, indicates whether to import based on actual conditionspolyfills
    • corejs: specifycore-jsVersion (core-jsprovidespolyfillsThe configuration is only available inuseBuiltInsConfigured touseageorentryOnly when. By default, only stableECMAScriptFeature providespolyfills, if desiredproposalsFeature. You are advised to set it to{ version: "x", proposals: true }. Here,xsaidcore-jsVersion number if you have installed[email protected],xCan be written as3.20
  • @babel/plugin-transform-runtime(@babel/runtime) : During compilationBabelI’m gonna use somehelperFunctions, by defaulthelperFunctions are packaged into each file, resulting in code redundancy.@babel/plugin-transform-runtimeIt automatically recognizes its presencehelperFunction, if it exists, change it to pair@babel/runtimeIn thehelperFunction to reduce code redundancy and reduce packaging volumehelperYou can see this function right hereThe sample )

Note: Corejs is sometimes written as 3, which means 3.0, which means the latest polyfills feature of Core-JS is not available, so it is recommended to use a smaller version number such as 3.20 for the installed core-JS version. In addition, the plugin included in @babel/preset-env is only up to Stage 2, which means TC39 is not included in TC39 and may not be implemented by all browsers. If you want to use TC39 syntax, you need to manually add the plugin yourself

style

For style files, we usually need postCSs-loader, CSS-loader, and style-loader in sequence. If sASS syntax is used, we also need to install sass-loader and sass

Note: node-sass is not recommended because it is written in C++, requires node.js versions and is no longer maintained for new features. Sass is officially recommended (node-sass supports both sass and Node-sass internally, depending on which package the user installs and the specific implementation code).

Installation:

$ npm i postcss-loader css-loader style-loader sass-loader sass -d --save-dev
Copy the code

[style-loader, css-loader, postcss-loader, ass-loader] [style-loader, css-loader, postcss-loader, ass-loader]

Note: according to the official documents indicate, postcss – loader needs in CSS – loader and style – loader before use, but in other preprocessor (for example: sass | less | stylus – loader) after use

The various loader uses involved in styles are:

  • sass-loaderLoading:Sass/SCSSFiles and compile them asCSS
  • postcss-loaderUse:PostCSSTo deal withCSS
  • css-loader:@import 和 url()Processing, just like JS parsingimport/require()Same, specific usage can be seenThe official sample
  • style-loader:CSSInserted into theDOM

The simplest configuration for handling style files is as follows:

module.exports = {
    ...
    module: {
        rules: [{...test: /\.(css|scss)$/,
                use: [
                    'style-loader'.'css-loader'.'postcss-loader'.'sass-loader'}]}};Copy the code

The configuration above makes the SCSS file compile smoothly, but there are some optimizations that can be made. First, we used the PostCSS-Loader, but we didn’t configure it, so some of the power of PostCSS is not used. In addition, SCSS has a problem in handling relative paths (see details). To solve this problem, we introduce resolve-url-loader, so we do iteration:

Install a new package:

$ npm i postcss-preset-env resolve-url-loader -d --save-dev
Copy the code

Now our configuration file looks like this:

const path = require('path');

module.exports = {
    ...
    module: {
        rules: [
            ...
            {
                test: /\.(css|scss)$/,
+ include: /src/,
                use: [
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'postcss-loader',
+ options: {
+ postcssOptions: {
+ plugins: ['postcss-preset-env']
+}
+}
                    },
+ 'resolve-url-loader',
                    'sass-loader'
                ]
            }
        ]
    }
};
Copy the code

The advantage of adding postCSs-preset -env is to make advanced CSS syntax as compatible as possible, such as color:#12345678; Color :rgba(18,52,86,0.47059) for better browser compatibility. Postcss-preset -env also comes with autoprefixer, which can automatically add prefixes

Note: Postcss-preset – Env autoprefixer needs to be used in conjunction with Browserslist, which only prefixes compatible browsers, if we don’t add Firefox configuration to.browserslistrc, Then you don’t add the -moz- prefix, so keep that in mind. Autoprefixer usually does not work. Browserslistrc configuration problems

Now run our compile package command, we can see that the style content will be saved in the JS script, then style-loader creates the style tag to insert into the HTML file, packaging the style content into the HTML file will cause the JS file size and style rendering delay, we still want the style file to be extracted separately. The MiniCssExtractPlugin is used here

Note: For performance and user experience, CSS files are introduced at the top and JS files are introduced at the bottom

The MiniCssExtractPlugin, which extracts CSS into a separate file, requires Webpack 5 and has four advantages over the extract-text-webpack-plugin:

  • Asynchronous loading
  • No duplicate compilation (performance)
  • Easier to use
  • Specifically for CSS development

Install first before use:

npm i --save-dev mini-css-extract-plugin -d
Copy the code

Usage:

const path = require('path');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = { mode: 'development', devtool: 'inline-source-map', entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, '.. /dist') }, module: { rules: [ ... { test: /\.(css|scss)$/, include: /src/, use: [- 'style-loader',
+ MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: ['postcss-preset-env']
                            }
                        }
                    },
                    'resolve-url-loader',
                    'sass-loader'
                ]
            }
        ]
    },
+ plugins: [new MiniCssExtractPlugin()]
};
Copy the code

Here are two caveats:

  • MiniCssExtractPluginWill be generatedcssDocuments, but if you wishhtmlFile generated by automatic referencecssFiles are also requiredhtml-webpack-pluginWith the
  • mini-css-extract-pluginwithstyle-loaderCannot be used at the same time, but can be used in online modemini-css-extract-pluginDevelopment mode can be usedstyle-loader

Now that we have extracted the CSS file, we will compress the CSS file content

Install the corresponding package:

npm install css-minimizer-webpack-plugin --save-dev
Copy the code

Modifying a configuration file:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
    mode: 'production',
    ...
+ optimization: {
+ minimizer: [
+ '... ',
+ new CssMinimizerPlugin()
+]
+}
};
Copy the code

In webpack@5, you can use ‘… ‘syntax to extend the existing minimizer (terser-webpack-plugin). If it doesn’t work, the immediate effect is that JavaScript compression obfuscations are lost

Note: CssMinimizerPlugin must be used with mode set to Production by default for CSS compression to work, otherwise optimization. Minimize must be set to true

The picture

We previously used raw-loader, url-loader, or file-loader for image resources, but this functionality is already embedded in the module at webpack@5 and there is no need to install additional NPM packages

Before webpack@5, it is common to use:

  • Raw-loader: imports a file as a string
  • url-loader: sets the file asdata URIInline tobundle
  • File-loader: sends files to the output directory

webpack@5 Replace the three Loaders by adding four module types:

  • asset/resource: Send a separate file and export itURL, previously by usingfile-loaderimplementation
  • asset/inline: Exports a resourcedata URI, previously by usingurl-loaderimplementation
  • asset/source: Exports the source code of the resource. Previously by usingraw-loaderimplementation
  • asset: Exporting adata URIAnd automatically selects between sending a separate file, before using iturl-loaderAnd configure the resource volume limitation implementation

For images, we use the asset type in conjunction with parser.dataurlCondition to conditionally URI data. For SVG images we set them to asset/inline and added mini-SVG-data-URI to customize the dataUrl, which has the benefit of reducing the size

Installation:

$ npm i mini-svg-data-uri --save-dev -d
Copy the code

Configuration:

// Process image resources
{
    test: /\.(png|jpg|jpeg|gif)$/i,
    type: 'asset'.parser: {
        dataUrlCondition: {
            maxSize: 4 * 1024 // 4kb}},generator: {
        filename: '[path]/[name]-[contenthash:8][ext]'}},// SVG special processing, get smaller volume
{
    test: /\.svg$/,
    type: 'asset/inline'.generator: {
        dataUrl: content= > svgToMiniDataURI(content.toString())
    }
}
Copy the code

The font

Font files are relatively simple and can be processed directly by URL

Configuration:

{
    test: /\.(woff|woff2|eot|ttf|otf)$/i,
    type: 'asset/resource',}Copy the code

HTML

Usually in a project, we need to use HTML files, and we want to get a package file that automatically references these resources. For this, we need the HtmlWebpackPlugin

Installation:

$ npm i --save-dev html-webpack-plugin -d
Copy the code

First assume our file directory is as follows:

📦 SRC ┣ 📂 static ┃ ┣ 📂 img ┃ ┃ ┗ 📂 brand ┃ ┃ ┃ ┗ 📂 home ┃ ┃ ┃ ┃ ┗ 📜 bg. PNG ┃ ┣ 📂 js ┃ ┃ ┗ 📂 brand ┃ ┃ ┃ ┗ 📂 home ┃ ┃ ┃ ┃ ┗ 📜 index. Js ┃ ┗ 📂 style ┃ ┃ ┗ 📂 brand ┃ ┃ ┃ ┗ 📂 home ┃ ┃ ┃ ┃ ┗ 📜 index. The SCSS ┗ 📂 views ┃ ┗ 📜 template. The HTMLCopy the code

This is a multi-page program, HTML template only a copy, in the views directory; Image, SCSS and JS are placed under img, style and JS folders respectively, and divided by page. We want the generated code to follow the same structure

HtmlWebpackPlugin has the following configuration is the most critical:

  • filenameGenerated:htmlPath and name, here we configure to'view/brand/home.html'(Because we currently only have one page,In fact, we’re going to programmatically traverse itIt is carried out dynamicallynew HtmlWebpackPlugin())
  • template: template file, herefixedispath.resolve(__dirname, '.. /src/views/template.html')
  • chunks: Name of the entry file (entryChunkName), in the multi-page case,entryIt must beObject syntax
  • inject: to determine thejsInsert position of the script, usually set tobody(cssThe position of the unconfigurable, fixed inheadTag)
  • minify:htmlCompression configuration, which is usedhtml-minifier-terser ,Specific configuration

The complete configuration of HtmlWebpackPlugin is as follows:

new HtmlWebpackPlugin({
    filename: 'view/brand/home.html'.template: path.resolve(__dirname, '.. /src/views/template.html'),
    chunks: ['src/static/js/brand/home/index'].inject: 'body'.cache: true.minify: {
        collapseWhitespace: true.keepClosingSlash: true.removeComments: true.removeRedundantAttributes: true.removeScriptTypeAttributes: true.removeStyleLinkTypeAttributes: true.useShortDoctype: true}})Copy the code

If we want the output directory structure to be the same as the original directory structure, we need to do the following:

  • JavaScript:entryUsing object syntax andentryChunkNameContains paths, such as:
    entry: {
        'src/static/js/brand/home/index': './src/static/js/brand/home/index.js'
    }
    Copy the code
  • css: For style files, if you adoptMiniCssExtractPluginPlug-ins need to be configuredfilename, for example in our example:
    new MiniCssExtractPlugin({
        filename: arg= > `${arg.chunk.name.replace('js'.'style')}.css`
    })
    Copy the code
  • image: For image files, we are inmoduleIn the configurationgenerator.filename, as follows:
    {
        test: /\.(png|jpg|jpeg|gif)$/i,
        type: 'asset'.parser: {
            dataUrlCondition: {
                maxSize: 4 * 1024 // 4kb}},generator: {
            filename: '[path]/[name]-[contenthash:8][ext]'}}Copy the code

More pages

Let’s optimize and extend this by assuming that our SRC directory structure is as follows

We design the directory structure into js/ module name/page name /index.js, and then use glob for file query

First install:

npm i glob -d --save-dev
Copy the code

Because there are multiple pages, you need to dynamically generate entry and HtmlWebpackPlugin objects:

// getEntry.js

const path = require('path');
const glob = require('glob');

module.exports = () = > {
    const entrySrcList = glob.sync(`${path.resolve(__dirname, '.. /.. /src/static/js/**/index.js')}`);
    const entry = {};
    if (entrySrcList && entrySrcList.length) {
        entrySrcList.forEach(src= > {
            entry[src.slice(src.indexOf('src'), src.length)] = src;
        });
    }
    return entry;
};
Copy the code
// getHtmlWebpackPlugins.js

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

module.exports = entry= > Object.keys(entry).map(chunk= > new HtmlWebpackPlugin({
    filename: `view${chunk.match(/src\/static\/js(.*)\/index.js/) [1]}.html`.template: path.resolve(__dirname, '.. /.. /src/views/template.html'),
    chunks: [chunk],
    inject: 'body'.cache: true.minify: {
        collapseWhitespace: true.keepClosingSlash: true.removeComments: true.removeRedundantAttributes: true.removeScriptTypeAttributes: true.removeStyleLinkTypeAttributes: true.useShortDoctype: true}}));Copy the code

The development of the configuration

For local development, you need to start a server to provide page access capability, first install the corresponding package:

$ npm i webpack-dev-server --save-dev -d
Copy the code

In the simplest case, we only need to add HotModuleReplacementPlugin and configuration devServer, we create a separate file webpack. Dev. Js, its content is as follows:

const webpack = require('webpack');
const { merge } = require('webpack-merge');
const config = require('./webpack.config');

const port = 8000 + Math.floor(Math.random() * 100);
const host = 'local.test.com';

module.exports = merge(config, {
    plugins: [new webpack.HotModuleReplacementPlugin()],
    devServer: {
        compress: true.historyApiFallback: false,
        port,
        host,
        open: {
            target: 'view/brand/home.html'.app: {
                name: 'chrome'}}}});Copy the code

Here we set the domain name to local.test.com. Note that the host file should be configured to point to 127.0.0.1

Webpack-dev-server is available in versions 3 and 4 with different parameters. Dev Server has been initialized using an options object that does not match the API schema. The webpack-dev-server version is the same as the webpack-dev-server version

You also need the Proxy configuration item if you are involved in issues such as Ajax request mapping during development

Packaging performance optimization

DLL

configuration

In our example (multi-page application) DLL implementation needs to use 3 plug-ins, respectively:

  • webpack.DllPlugin: a separate configuration file, specifically for generationdllThe packaged file
  • webpack.DllReferencePlugin: source code to package intodllA reference to a module is changed to a referencedllModules in packages
  • add-asset-html-webpack-plugin: If usedhtml-webpack-pluginThe plug-in willdllPackage inserted into the generatedhtmlFile. ifwebpack.DllPluginThe generated target directory is not the directory at deployment time (this is very common because the directory is deployed every timebuildWill becleanAnd thedllYou don’t usually need to build it again), then you can use the plug-inoutputPathConfigure resources (in this case, that isdllPackage files) to the specified directory

Complete configuration of DLL generation:

const path = require('path');
const webpack = require('webpack');
const clearDir = require('./utils/clearDir');

// Setting output.clean at [email protected] will cause manifest.json to be lost, so manually clear the DLL folder
clearDir(path.resolve(__dirname, './dll'));

module.exports = {
    mode: 'production'.resolve: {
        extensions: ['.js'.'.jsx']},entry: {
        lib: [
            'react'.'react-dom'.'prop-types'.'axios'.'highcharts'.'redux'.'redux-thunk']},output: {
        path: path.join(__dirname, 'dll'),
        filename: '[name].[contenthash].dll.js'.library: '[name]_[fullhash]'
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.join(__dirname, 'dll'.'[name]-manifest.json'),
            name: '[name]_[fullhash]'}})];Copy the code

Note: at [email protected] once we set output.clean to true, manifest.json is lost, so we manually empty the DLL folder

Complete configuration using DLLS

/* * Created by king at 2022-1-20 21:13:49 * Copyright (c) 2022 */

const path = require('path');
const webpack = require('webpack');
const svgToMiniDataURI = require('mini-svg-data-uri');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const getEntry = require('./utils/getEntry');
const getHtmlWebpackPlugins = require('./utils/getHtmlWebpackPlugins');
const dllMainFest = require('./dll/lib-manifest.json');

// Record the last progress information
let preProgressMessage = ' ';

/ / for entry
const entry = getEntry();

// Get HtmlWebpackPlugins
const HtmlWebpackPlugins = getHtmlWebpackPlugins(entry);

module.exports = {
    mode: 'development'.devtool: 'inline-source-map',
    entry,
    output: {
        filename: '[name]-[contenthash:8]].js'.path: path.resolve(__dirname, '.. /dist'),
        clean: true
    },
    resolve: {
        extensions: ['.js'.'.jsx'.'.json'.'.scss'.'.css']},module: {
        rules: [{test: /\.(js|jsx)$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader'}}, {test: /\.(css|scss)$/,
                include: /src/,
                use: [
                    // 'style-loader',
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader'.options: {
                            postcssOptions: {
                                plugins: ['postcss-preset-env']}}},'resolve-url-loader'.'sass-loader']},// Process image resources
            {
                test: /\.(png|jpg|jpeg|gif)$/i,
                type: 'asset'.parser: {
                    dataUrlCondition: {
                        maxSize: 4 * 1024 // 4kb}},generator: {
                    filename: '[path]/[name]-[contenthash:8][ext]'}},// SVG special processing, get smaller volume
            {
                test: /\.svg$/,
                type: 'asset/inline'.generator: {
                    dataUrl: content= > svgToMiniDataURI(content.toString())
                }
            },
            / / font
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: 'asset/resource'}},plugins: [
        // Customize the printing progress
        new webpack.ProgressPlugin({
            handler(percentage, message, ... args) {
                const curProgressMessage = `The ${Math.floor(percentage * 100)}% ${message} ${args && args.join ? args.join(' ') : ' '}`;
                if(curProgressMessage ! == preProgressMessage) {// eslint-disable-next-line
                    console.log(curProgressMessage); preProgressMessage = curProgressMessage; }}}),// Extract the CSS into a separate file
        new MiniCssExtractPlugin({
            filename: arg= > `${arg.chunk.name.replace('js'.'style')}.css`
        }),
        ...HtmlWebpackPlugins,
        new webpack.DllReferencePlugin({
            context: path.resolve(__dirname, '.. / '),
            manifest: dllMainFest
        }),
        new AddAssetHtmlPlugin({
            filepath: path.resolve(__dirname, './dll/*.dll.js'),
            outputPath: '.. /dist/src/static/js'.publicPath: '.. /.. /src/static/js'})].optimization: {
        moduleIds: 'deterministic'.runtimeChunk: {
            name: 'src/static/js/runtime-bunld'
        },
        minimizer: [
            '... '.new CssMinimizerPlugin()
        ]
    }
};
Copy the code

Filepath of AddAssetHtmlPlugin supports the Glob syntax and allows contenthash to be used as the filename of DLL files to ensure that the files are unique

Note: when using the DLL, you need to use it again. Also note how the publicPath parameter is written when creating the AddAssetHtmlPlugin instance, which affects the path of the generated HTML file that references the DLL packaging results

The performance comparison

According to the official website of Webpack, DllPlugin and DllReferencePlugin work together to extract common code into DLLS, which greatly improves the speed of construction while achieving the separation of bundles

However, there are some concerns that some projects such as Vue and React have abandoned DLL technology, given the advances in caching that advanced Webpack versions have made (see article). To see if DLL technology is obsolete, we did some tests with multi-page programs, and concluded that when the number of pages and the CONTENT of the DLL is large, the build speed is still advantageous. In addition, DLL is still used as the recommended technology in the official documentation of Webpack (see details).

We conducted a set of control tests on whether to use DLL or not, and the test conditions were as follows:

  • The machine USES theMacBook Pro
  • WebpackVersion is5.66.0
  • usingdevelopmentmodel
  • A project is a multi-page program with a number of pages162a
  • The two groups were tested separately10Times (build faster and faster thanks to caching)
  • DLLcontainsreact,react-dom,prop-types,axios,highcharts,redux,redux-thunk

Time comparison between using DLL and not using DLL:

The serial number useDLLTime consuming (ms) Do not useDLLTime consuming (ms)
1 11837 25473
2 10537 18068
3 12779 19004
4 13026 20380
5 11033 18337
6 17265 20309
7 11140 19726
8 13198 17315
9 11715 18129
10 13553 17805

SplitChunksPlugin

The CommonsChunkPlugin was used to avoid duplicate dependencies between modules, but further optimization was not possible. CommonsChunkPlugin has been removed from webpack@4 and replaced with Optimization.splitchunks

Note: A core point of implementing modularity in code is that a module can only be initialized once, otherwise the module’s global sharing, on which many modules depend, will fail. If there is only one entrance, so this is not a problem, but if there are multiple entry, the code must be the optimization. The runtimeChunk set to single (often a runtime. Bundle. Js files), the detailed introduction As shown in the

The complete code

The source code

.browserslistrc

ie > 9
chrome > 38
firefox > 20
Copy the code

.babelrc

{
    "presets": [["@babel/preset-env", {
            "useBuiltIns": "usage"."corejs": {
                // Specify the latest version of core-js
                "version": "3.20".// The proposals are supported
                "proposals": true}}]."@babel/preset-react"]."plugins": [
        // Extract the help function from each file
        "@babel/plugin-transform-runtime"]}Copy the code

webpack.dll.js

const path = require('path');
const webpack = require('webpack');
const clearDir = require('./utils/clearDir');

// Setting output.clean at [email protected] will cause manifest.json to be lost, so manually clear the DLL folder
clearDir(path.resolve(__dirname, './dll'));

module.exports = {
    // mode: "development || "production",
    mode: 'production'.resolve: {
        extensions: ['.js'.'.jsx']},entry: {
        lib: [
            'react'.'react-dom'.'prop-types'.'axios'.'highcharts'.'redux'.'redux-thunk']},output: {
        path: path.join(__dirname, 'dll'),
        filename: '[name].[contenthash].dll.js'.library: '[name]_[fullhash]'
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.join(__dirname, 'dll'.'[name]-manifest.json'),
            name: '[name]_[fullhash]'}})];Copy the code

webpack.config.js

const path = require('path');
const webpack = require('webpack');
const svgToMiniDataURI = require('mini-svg-data-uri');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const getEntry = require('./utils/getEntry');
const getHtmlWebpackPlugins = require('./utils/getHtmlWebpackPlugins');
const dllMainFest = require('./dll/lib-manifest.json');

// Record the last progress information
let preProgressMessage = ' ';

/ / for entry
const entry = getEntry();

// Get HtmlWebpackPlugins
const HtmlWebpackPlugins = getHtmlWebpackPlugins(entry);

module.exports = {
    mode: 'development'.devtool: 'inline-source-map',
    entry,
    output: {
        filename: '[name]-[contenthash:8].js'.path: path.resolve(__dirname, '.. /dist'),
        clean: true
    },
    resolve: {
        extensions: ['.js'.'.jsx'.'.json'.'.scss'.'.css']},module: {
        rules: [{test: /\.(js|jsx)$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader'}}, {test: /\.(css|scss)$/,
                include: /src/,
                use: [
                    // 'style-loader',
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader'.options: {
                            postcssOptions: {
                                plugins: ['postcss-preset-env']}}},'resolve-url-loader'.'sass-loader']},// Process image resources
            {
                test: /\.(png|jpg|jpeg|gif)$/i,
                type: 'asset'.parser: {
                    dataUrlCondition: {
                        maxSize: 4 * 1024 // 4kb}},generator: {
                    filename: '[path]/[name]-[contenthash:8][ext]'}},// SVG special processing, get smaller volume
            {
                test: /\.svg$/,
                type: 'asset/inline'.generator: {
                    dataUrl: content= > svgToMiniDataURI(content.toString())
                }
            },
            / / font
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: 'asset/resource'}},plugins: [
        // Customize the printing progress
        new webpack.ProgressPlugin({
            handler(percentage, message, ... args) {
                const curProgressMessage = `The ${Math.floor(percentage * 100)}% ${message} ${args && args.join ? args.join(' ') : ' '}`;
                if(curProgressMessage ! == preProgressMessage) {// eslint-disable-next-line
                    console.log(curProgressMessage); preProgressMessage = curProgressMessage; }}}),// Extract the CSS into a separate file
        new MiniCssExtractPlugin({
            filename: arg= > `${arg.chunk.name.replace('js'.'style')}.css`
        }),
        ...HtmlWebpackPlugins,
        new webpack.DllReferencePlugin({
            context: path.resolve(__dirname, '.. / '),
            manifest: dllMainFest
        }),
        new AddAssetHtmlPlugin({
            filepath: path.resolve(__dirname, './dll/*.dll.js'),
            outputPath: '.. /dist/src/static/js'.publicPath: '.. /.. /src/static/js'})].optimization: {
        moduleIds: 'deterministic'.runtimeChunk: {
            // Extract runtime content
            name: 'src/static/js/runtime-bunld'
        },
        minimizer: [
            '... '.new CssMinimizerPlugin()
        ]
    }
};
Copy the code

webpack.dev.js

/* * Created by king at 2022-1-20 22:16:12 * Copyright (c) 2022 */

const webpack = require('webpack');
const { merge } = require('webpack-merge');
const { app } = require('./config/app');
const config = require('./webpack.config');

const port = 8000 + Math.floor(Math.random() * 100);
const host = 'local.test.com';

module.exports = merge(config, {
    plugins: [new webpack.HotModuleReplacementPlugin()],
    devServer: {
        compress: true.historyApiFallback: false,
        port,
        host,
        open: {
            target: 'view/brand/home.html'.app: {
                name: app
            }
        }
    }
});
Copy the code

The command

generateDLL

$ webpack --config ./webpack/webpack.dll.js
Copy the code

Generate packaging results

$ webpack --config ./webpack/webpack.config.js
Copy the code

Local development

$ webpack serve --config ./webpack/webpack.dev.js
Copy the code

conclusion

This article focuses on building a common packaging architecture, including some details, and does not cover new features such as module federation that are not common enough in usage scenarios. In addition, emerging technologies such as Esbuild were not adopted, but rather a more mature and reliable Webpack, which readers can consider for themselves. In the next article, we’ll cover another important aspect of modern front-end projects: code quality assurance, including unit testing and code specifications

reading

  • The cache
  • Sass 3 generation compiler: Ruby Sass, Node-sass, Dart-Sass
  • Dig deeper into Webpack5 and other build tools series two (10) – postcss-preset-env and configuration extraction
  • Intensive reading Webpack5 new features – module federation
  • Multiple Entry Points Per Page