Module packaging tool

Problems that need to be solved

1. New special new code compilation 2. Modular JS packaging 3

Module Bundler compilers and transforms the application through the Module Loader during the packaging process. Webpack also has the ability of Code Splitting, which means that the Code in the application is packaged according to the needs of users. We don’t have to worry about all the code packed together, creating a file that’s too big

We can pack together the files necessary for the initial page load and store the files of other modules separately. When we need them, we can load them asynchronously to achieve incremental or progressive loading. This way, we don’t have to worry about the extreme problem of the file being too broken or too big

Front-end module type problem, support in JS in a modular way, load arbitrary type of file

All of the packaging tools are aimed at modularity, and the modularity here is for the whole front-end project modularity, more macro than what we call JS modularity, so that we can enjoy the advantages of modules in the development stage, and do not have to worry about the impact of modularity on production

webpack

Quick start of Webpack

  1. npm init -yInitialize onepackage.json
  2. npm i webpack webpack-cli --devAfter the installation is complete, thepackage.jsonthescriptsadd"build":"webpack"So that we can passnpm run build --versionLooking at the webPack version we downloaded, it is important to note that webpack4 is quite different from WebPack 5. The main focus here is the 4.x version
  3. I can use it hereserveTools to start a front-end static service, view the packaged project, and then wenpm run buildWebpack4 and later support zero configuration mode, directly start packaging, the default entry issrc/index.jsThe exit is in the root directorydist/main.js

Most of the time, we need to customize the entry configuration. In this case, we need to add a special configuration file for Webpack. This method is to add a webpack.config.js file in the root directory of the project. So we need to write the code the commonJS way

const path = require('path')
/ * * *@type {import('webpack').Configuration}* /
module.exports = {   // This file exports an object with properties to configure the Webpack option
    entry: './src/main.js'.// Since it is a relative path, the preceding './ 'cannot be omitted
    output: {   // Sets the location of the output file. The value is an object
        filename: 'bundle.js'.//filename can specify the output filename,
        path: path.join(__dirname,'output')  The path property must be an absolute path to the output file. You can obtain the complete absolute path of the output file from node's path module}}Copy the code

Webpack4 adds a new usage of working mode, which greatly simplifies the complexity of webpack configuration. During the previous packaging process, a configuration warning was printed in the command line terminal, as shown in the following figure

No mode property is configured. Mode has three configurable properties.

  1. When no one is configuredmodeProperty, at this timewebpackWill default toproductionTo work in this modewebpackSome optimization plug-ins, such as code compression, are enabled by default
  2. developmentDevelopment mode, will automatically optimize the packaging speed, will add some debugging success of some assistance to the code, command line start method isnpm run build --mode develpoment, you can also directly configure topackage.jsonthescriptIn the
  3. noneMode, which runs raw packaging without doing any extra processing

The specific differences between the three modes can be found in the official documentation

In addition to specifying our working mode through cli parameters, we can also configure our working mode in the WebPack configuration file, so that WebPack will perform the tasks according to our configured working mode

Module.exports = {// This file exports an object that can be configured with the webpack option mode:'development', // working mode, There are three types of entries: 'development'/' none '/' production ': './ SRC /main.js' // Filename: 'bundle.js', //filename can specify the name of the output file, path: Path. join(__dirname,'output') // Specify the directory where the output file resides. The value of the path attribute must be an absolute path.Copy the code

Webpack results run principle

The whole generated code is an immediate function, which is the working entry to WebPack. It takes an argument to Modules, and when it is called, it passes in an array. Each element in the array is a parameter list of the same function, and each function corresponds to a module in the source code. Each module is wrapped in such a function to achieve the module’s private scope

Load the resource module

Webpack only processes JS files by default. When other types of files need to be processed, different loaders are required. Loaders are the core of webpack resource loading, and different types of loaders can load any type of resources

The entry of webpack can be various types of files, but generally we still use JS files as the entry of packaging, because the entry of Webpack can be regarded as the entry of running our application to some extent. At present, front-end business is driven by JS.

In traditional code we need to separate CSS and JS, maintain them separately, and import them separately. Webpack suggests that we load CSS in JS. Why is that

Webpack not only advises us to introduce CSS in JS, but also advises us to introduce any resource files required by the current code in the process of writing code. What really needs resources is not the application, but the code. The code must load corresponding resources to work properly, which is the philosophy of WebPack

Through JS code to introduce resource files or establish resource files and JS dependency, there is a very obvious advantage, on-demand loading, all resource files will not be missing is necessary, JS drives the whole front-end application, the benefits of establishing this dependency is

Js does need these resource files to make sure they are not missing, both are necessary

Loader

File resource loader file-loader

Webpack met in packaging, image files, etc., according to our configuration in the configuration file is matched to the corresponding file loader, loader to meet such a file, first we import file, copied to the output directory, and then copy the output of the directory path as the return value of the return current module, so for our application, The required resource is published, and the access path of the resource can be obtained through the module’s export member.

Url-loaders represent files in the form of data urls, a special Url protocol that can be used to represent files directly. The traditional URL usually requires a corresponding file on the server, and we get this file by requesting this address. DataUrl is the way that the current URL can directly represent the content of the file. The text in this URL already contains the content of the file, and this URL will not send any type of HTTP request

Best practices

For small files in a project, urL-loader is used to convert small files into data urls to reduce the number of requests. Large files are stored in traditional file-loader format as a single file to improve the loading speed

The configuration is as follows

Common Loader classification

Compile and convert csS-loader file operation file-loader code check Class code check

Webpack handles es2015 WebPack because module packaging is required, so it handles import and export and some corresponding transformations, but it cannot handle other ES6 features. If it needs to handle these new features, We need to configure an additional compiled loader, the most common of which is babel-loader

The usage method is as follows:

npm i babel-loader @babel/core @babel/preset-envDue to thebabel-loaderNeed to rely onbabelCore module of@babel/coreAnd a collection of plug-ins to perform specific feature transformations@babel/preset-env

Webpack is a packaging tool by default and does not handle new features in ES6 or later versions of the code. If we need to handle these new features, we need to configure a separate loader for the JS code to implement them

How webpack loads resources

How resources are loaded in js code

  1. followESMThe standardimportThe statement
  2. followCommonJSThe standardrequirefunction

If you need to load an ESM using the require function, note that the default export of the ESM needs to be obtained by require(‘ XXXX ‘).default

  1. Search for AMD standarddefineFunctions andrequirefunction

Webpack is compatible with a wide range of modular standards, so don’t mix them in your project unless necessary, as this can significantly reduce the maintainability of your project

Standalone loaders load resources

It also handles some imported modules in the load resource

When the CSS-loader loads the CSS file, the @import directive and url functions in some attributes also trigger the loading of the corresponding resource module. The SRC attribute of the image label in the HTML file loaded by the HTml-loader also triggers the loading of the corresponding resource module

abouthtml-loaderConfiguration problems of

The core working principle of Webpack is to find the entry of packaging according to the configuration, usually a JS file, and then according to the import and require statements of the entry file, parse the resource module that the file depends on, parse the corresponding dependency of the resource module, and finally parse the dependency tree of the dependency relationship between all files in the project. Webpack can recurse the dependency tree, find the corresponding resource file of each node, and then find the corresponding loader required by the corresponding resource file according to Modules/Rules, and finally put the loading result into bundle.js, so as to realize the packaging of the whole project.

The Loader mechanism is at the heart of WebPack

Write a loader by hand

The process of loading resources by Webapck is similar to the pipeline at work. The final result must be a JS code loader responsible for converting resource files from input to output. Multiple Loaders can be used for the same resource in turn

Each Loader needs to export a function. The whole function is the processing process of Loader for resources. The input is the content of the loaded resource file, and the final output (return) is a js code string

// Export by hand
const  marked =  require('marked')
module.exports = source= > {
  console.log(source)
  const  html = marked(source)
  // return `module.exports = ${JSON.stringify(html)}`
  return `export default The ${JSON.stringify(html)}`	// Both export methods are acceptable

}
Copy the code

Webpack plugin

Webpack’s plug-in mechanism enhances Webpack’s ability in project automation to solve the automation work in addition to resource loading in a project

Copy the zip code to clean the dist directory clean-webpack-plugin

Automatic generation of HTML html-webpack-plugin using packaged results

When going online, we need to publish the HTML file in the root directory and all the packing results in the dist directory at the same time, and confirm the reference path of JS files in the HTML file. Based on these two problems, htML-webpack-plugin is used to realize the automatic injection of packed JS files in the HTML file. There is no need to hardcode usage

  1. npm i html-webpack-plugin --dev
New HtmlWebpackPlugin({template:'index.html', // specify template title: "webpack plugin sample", meta: { viewport:'width=device-width' } })Copy the code

The title here set the title of a need in the template HTML file template tags to add syntax < % = htmlWebpackPlugin. Options. The title % >, and the title attribute set when using HTML – loader, can’t take effect, There’s a little bit of caution here. HtmlWebpackPlugin. The options this property can be accessed to the plug-in configuration data, is a plug-in provided by internal variables.

If you need to output multiple page files at the same time, you can add multiple HtmlWebpackPlugin instance objects to your Plugins. Each object is responsible for generating one page file

plugins:[
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
        template:'index.html',
        title: "webpack plugin sample",
        meta: {
            viewport:'width=device-width'
        }
    }),
    new HtmlWebpackPlugin({
        template:'index.html',
        title: "webpack plugin",
        filename:'about.html'
    })
]
Copy the code

In the project, we also have some files that we don’t need to deal with. When packaging, we just need to copy directly to the target directory, copy-webpack-plugin. This function requires us to pass in an array to specify the path of the file that we want to copy

New CopyWebpackPlugin([// 'public/**', 'public' // this means that the contents of the public directory are copied to the output directory])Copy the code

Write a WebPack plug-in by hand

The plug-in mechanism of Webpack is a common hook mechanism in the process of software development. There are many links in the working process of Webpack, and Webpack has hooks buried in each link. When developing plug-ins, different tasks are mounted on different nodes

Wepack requires that the plug-in must be a function or an object that contains the Apply method. Generally, we define a plug-in as a class, define the Apply method in the class, and build an instance from the class to use it

Plug-in is to declare the cycle to webpack hook function to mount the task function to achieve, if you need to in-depth understanding of the plug-in mechanism, we need to in-depth understanding of the underlying implementation principle of Webpack, need to read the source code to understand

The source code

// Implement a plugin class myPluguin {apply(Compiler) {// This method is automatically called when Webpack starts // Compiler parameter // console.log('myPlugin starts ',compiler) // Continue with compiler.hooks. Tap ('myPluguin',compilation => {// Register hook functions through the TAP method, which takes two arguments, one is the name of the plug-in, The second is the function // compilation that needs to be mounted on this hook. For (const name in compiler.assets) {// Compiler.assets gets the information about the resource file to be written to // console.log('name',name); // console.log('name1',compilation. Assets [name].source()); If (name.endswith ('.js')){const contents = compilation.assets[name].source() const WithoutComments = contents. Replace (/\/\*\*+\*\/ g, ") // () => withoutComments, size: () => withoutComments.length } } } }) } }Copy the code

Enhance the WebPack development experience

Automatically compile watch working mode

When you start the webpack command, add a “watch” parameter to the command, so that the webpack will run in watch mode. After the package is finished, the webpack will not exit immediately, wait for the file to change, and automatically compile a new result “watch”: “Webpack –watch” we need to add this directive to the package.json script to use the file listening function

BrowserSync principle: WebPack automatically packages code into dist. Changes in Dist files are monitored by Browser-sync, which automatically refresh the dist.

webpack-dev-server

An HTTP Server for development is provided, and a number of development-friendly features such as auto-compile and auto-refresh browsers are integrated. 1. 'NPM I webpack-dev-server --dev', can be defined in 'NPM script' after installation, ==> '"start": "Webpack-dev-serve", run 'NPM run start' to start the service, we can also add '--open' parameter, 2. 'webpack-dev-server' will automatically use Webpack to package our application, and the packaging results are temporarily stored in memory **, which will greatly reduce unnecessary IO operations and greatly improve our build efficiency. 3. By default, 'webpack-dev-server' will use the build output file as the resource file for the build service. As long as the normal output file is packaged by Webpack, it can be accessed normally, but some static resources need to be accessed as resources of the development server. DevServer :{contentBase:['./public'] // specifies an additional way to specify an additional resource path}, ' 'Copy the code

In daily development, due to the same origin policy, we often need local cross-domain access interface address. Here we introduce the configuration of proxy service, as shown in the following figure

If you are interested in the concept of a host name as part of the HTTP protocol, learn about it yourself

Source Map

Build and compile to convert the source code to code that can be run in the production environment. There is a big difference between this code and the source code. In this case, we need to debug our application or generate error messages that cannot be located. The Source Map was created to address these issues, an alias Source Map that maps the relationship between the transformed code and the Source code

At present, many third-party libraries have a. Map file, which is actually a JSON file that records the mapping relationship before and after code conversion. As it is meaningless to production, the production environment does not need such a file

Version Version of the source map standard used by the current file Source name of the source file before conversion. Because multiple files may be merged into one file, So this is an array. The member names used in the names source code are base64-VLQ encoded strings that record the mappings before and after the transformation

We usually import such a file by adding a line of comment to the converted code, using the method //# sourceMappingURL=xxx.map

Role: to solve the front-end reference to build after compilation, resulting in the code and run the code is not the same, generated debugging problems

Source Mapconfiguration

When we configure source-map in webpack using the devtool property, the generated bundle.js file automatically introduces the annotations we mentioned earlier and the.map generated file


So far, WebPack supports 12 different ways to configure source-Map. Each way generates different source-Map effects and speeds. Good source-map effects are slow, and fast source-Map effects are poor

The different configuration modes are shown in the following figure

From the speed of initial construction, monitoring mode rebuild speed, whether suitable for production use, the quality after construction four dimensions of comparison

Eval mode in this mode, the code converted by each module is executed in the eval function. At the end of the string, the url path corresponding to the file is explained by sourceURL. Only the file can be located, and source-map file is not generated in this mode

With such a Webapck configuration, we can compare the differences between different configurations ourselves

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

const allModes = [
    'eval',
    'cheap-eval-source-map',
    'cheap-module-eval-source-map',
    'eval-source-map',
    'cheap-source-map',
    'cheap-module-source-map',
    'inline-cheap-source-map',
    'inline-cheap-module-source-map',
    'source-map',
    'inline-source-map',
    'hidden-source-map',
    'nosource-source-map',
]

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

module.exports = allModes.map(item => {
    return {
        devtool: item,
        mode: 'none',
        entry: './src/main.js',
        output: {
            filename: `js/${item}.js`
        },
        module: {
            rules: [
                {
                    test:/\.js$/,
                    use: {
                        loader:'babel-loader',
                        options:{
                            presets: ['@babel/preset-env']
                        }
                    }
                }
            ]
        },
        plugins:[
            new HtmlWebpackPlugin({
                filename:`${item}.html`
            })
        ]
    }
})
Copy the code

Conclusion:

  1. Eval – Whether to useevalExecute module code
  2. cheap – Source MapDoes it contain line information — yescheapContains no row information
  3. Module – Whether it can be obtainedLoaderDeal with the previous source code — addmoduleYou can get the source code before processing
  4. Nosource – You can see where the error occurred during a throw, but not the source code, which is protected for production mode
  5. Hidden – Produced at build timemapFile, but the code is not introduced through comments, we can not see the effect in the development tool, once there is a problem, and then putmapFile import back
  6. inline – source-mapMode,mapThe file exists as a physical file, whileinline-source-mapIn order tobase64thedataUrlIs embedded in the code, which makes the code very large,eval-source-mapIt’s also in linesource-mapEmbedded ones are generally the least usable because they make the code very large.
Select the appropriate Source map

The development environment individual chooses cheap-module-eval-source-map

  1. It is enough for the code to locate to the line
  2. Generally, the framework is used for development, and the code varies greatly before and after loader conversion. Debugging is generally to debug the source code
  3. While first packaging is slow, repackaging is fast, most of the time in monitoring mode

The producer chooses None or nosource-source-map

  1. Debugging is a development phase thing
  2. Preventing source code exposure

Choose no jedi, what suits you is the best

The HMR module is hot updated

When an application is running, a module in an application is replaced in real time. The running status of the application is not affected. Hot update Only the modified module is replaced to the application in real time, without completely refreshing the application

HMR is currently integrated with webpack-dev-server, we just need to run the webpack-dev-server command to add the parameter –hot, or add the configuration in the configuration file

The configuration needs to be divided into two steps

  1. We sometimes refresh the page when we modify js files because HMR is not as out-of-the-box as other modules
  2. Our style files can be hot updated in real time, because style-loader automatically handles hot updates of styles.
  3. When we use framework to modify JS, we do not do any processing, modify JS files also support hot update, because under framework development, every file is regular, framework is integrated within the scaffolding creation project HMR solution

Conclusion: We still need to manually deal with the hot replacement of JS module after hot update, different modules have different logic, different logic, processing process is also different

Matters needing attention:

  1. Error handling of HMR code will cause automatic refresh to find no error when we can in devServer: { hot: true }todevServer: { hotOnly: true }HotOnly will only use HMR and will not fallback to live reloading.
  2. If HMR is not enabled, the HMR API will report an error because it is used during the js hot update processmodule.hotObject is provided by HMR, not when HMR is not enabledmodule.hotThe solution to this object is to add a judgment to the outer layer
  3. Much of the code that does not handle hot updates is removed during packaging

Optimization of production environment Production environment focuses on operating efficiency, development environment focuses on development efficiency, creating different configurations for different working environments

  1. Configuration files export different configurations based on different environments
  2. Each environment corresponds to a configuration file. Different environments have different configuration files

Large projects have different configuration files for different environments, while small projects can be determined in a single configuration file. For general configuration and production configuration, there is a webpack-Merge module that provides this function

Tree Shaking

It is automatically enabled in production mode, detects unreferenced code, and removes it. This is not a configuration option for WebPack, but a set of optimized features that are used together

The development mode configuration is as follows

Need to be inoptimizationPropertiesusedExportsandminimizeProperty is true

We can optimize it even further

The normal result of packaging is to end up with each of our modules in a separate function. If we have many modules, then we will have many concatenateModules of module functions in the output: The true function is to combine all modules into one function as much as possible, which not only improves the operation efficiency, but also reduces the code volume. This feature is also called Scope reactorization

The premise for implementing Tree Shaking and Babel Tree Shaking is to use EMS to organize our code, and because webPack processes code that must be modular in the ESM way, when we deal with new JS features, Need to use Babel-loader’s preset-env, this plugin converts ESM code to CommonJS during conversion

Latest versionbabel-loaderIt does not cause tree-shaking to fail because the ESM transformation plugin is automatically turned off. When the current environment supports EMS,babel-loaderGenerates an identity,preset-envThe ESM conversion code is automatically disabled based on this identity

SideEffects side effects

A new feature in WebPack4 that allows you to configure ways to identify if your code has side effects provides Tree Shaking with larger compression controls

Side effects: The module does something other than export members when it executes

SideEffects is generally used to flag NPM module packages for sideEffects

SideEffects is the same as Tree Shaking. It is not the same as Tree Shaking

} // Package. Json "sideEffects": False // Indicates that none of the code in the current project has side effectsCopy the code

SideEffects needs to be set up in two places in the development environment, one to enable functionality and one to indicate that the current project code has no sideEffects

optimization: { sideEffects: When enabled, webpack checks for sideEffects in the package.json file of the project to which the current code belongs. If this module has no sideEffects, unused modules will not be packaged

Note: the only way to use this property is to make sure that your code really has no sideEffects, otherwise the code with sideEffects will be deleted by mistake when webpack is packaged. When the code with sideEffects needs to be saved, we can configure “sideEffects”: true, or

Optimization: {sideEffects: ['xx/xx/xx.js', // sideEffects: ['xx/xx/xx.js', // sideEffects: ['xx/xx/xx.js', // sideEffects: ['xx/xx/xx.js']}Copy the code

Code Splitting/Code Splitting

All of the code will eventually be packaged together, and the bundle size is too large. Most of the time, when the application starts working, not all modules need to be loaded into the bundle, which makes no sense for page loading, etc. The reasonable solution is to divide the package results into multiple bundles, subcontract them, and load them on demand according to the running needs

Http1.1 shortcomings

  1. You cannot make multiple requests to the same domain name at the same time
  2. Each request has a certain amount of delay
  3. Request headers waste loan traffic, so module packaging is necessary

Way of subcontracting

  1. Multiple entry packing
  2. ESM dynamic import function, realize module loading on demand
Multiple entry packing

Generally applicable to traditional multi-page applications, the common partition rule is that a page corresponds to a package entry, the common parts of different pages, extracted into the common results

The Settings of multi-entry packaging are as follows:

By default, the html-webpack-plugin outputs an HTML file that automatically injects all the packed results, so to inject the corresponding HTML into the js file of the packed result, we need to specify the bundle that the output HTML needs to inject

Extract the common module Split Chunks

There will certainly be common modules in different entrances. When large three-party JS libraries are used together, the influence will be greater. At this time, public module extraction is needed

Enabling the extract configuration for the public module is simple

This packaging generates bundle.js for the common module part

Dynamic import of Webpack

On-demand loaded to our browser application is very common requirement, need a module, loading a module, greatly save bandwidth and traffic webpack support dynamic on-demand loaded into the way of module, and all the dynamic import module will be automatically extracted to a separate bundle. Js, so as to realize the subcontract

Compared with multi-entry mode, dynamic import is more flexible, and the loading and loading timing of modules are controlled by the logic of the code. The ultimate goal of subcontracting is to make modules load on demand, so as to improve the response speed of applicationsThe figure above shows the dynamic import of the route mapping component to load on demand when routing is configured in the VUE project.

/* webpackChunkName /* webpackChunkName /* webpackChunkName /* webpackChunkName: “BindBankCard” */ will help you, so that the generated bundle.js will be named bindBankcard.bundle.js.

The same webpackChunkName will eventually be packaged together for the business module packaging we need

MiniCSSExtractPlugin extracts CSS into a single file

Is a plug-in that extracts CSS code from the packaging result, enabling on-demand loading of CSS

Using MiniCSSExtractPlugin our style will be kept to a file, also no longer need style – loader, instead of using MiniCSSExtractPlugin. Loader provided in the loader, To achieve the style file through the link tag injection

Use as follows

Personal experience is that CSS code over 150KB needs to be pulled out and introduced separately

When we separate CSS from webpack, we find that the compression plug-in built in Webpack does not compress files other than JS, and the compression of other resource files requires additional plug-in support

Optimize – CSS -assets-webpack-plugin for CSS compression terser-webpack-pluginWebpack built-in JS compression plugin

Output file name hash

Hash File name in production mode Hash is classified into three types, each of which has different effects

  1. filename: '[name]-[hash].bundle.jsAt the project level, any place in the project has changed, all the fileshashAre going to change
  2. filename: '[name]-[chunkhash].bundle.js.chunkIn the packaging process, as long as the same chunk is packaged,chunkhashThey’re all the same. Change somethingchunkOnly the corresponding contentchunkthehashI’m going to change. I’m going to introduce thischunkThe filehashThere will be changes
  3. filename: '[name]-[contenthash].bundle.jsThis is file levelhashBased on the contents of the output filehash, different fileshashDifferent values, modify a file only corresponding to the filechunkAnd introduce thischunkOf the filehashThat will change

Contenthash is probably the best way to solve the cache problem, because it precisely targets the file level hash and only updates the file name when the corresponding file changes

Webpack allows you to specify the length of the hash by specifying the length of the hash as :8 colons, for example filename: ‘[name]-[contenthash:8].bundle.js

For cache control, the 8-bit Contenthash should be the most versatile choice