Talk about your understanding of Webpack

1.1 background

Webpack’s goal is to make front-end projects modular so that every resource in a project can be managed and maintained more efficiently. In early front-end projects, we implemented modularity in the form of file partitioning, where each function and its associated state data was placed in a separate JS file. The convention is that each file is a separate module, then these JS files are introduced into the page, one script tag for each module, and then the modular members are called. Such as:

<script src="module-a.js"></script>
<script src="module-b.js"></script>
Copy the code

However, the disadvantages of this modular development are also very obvious, modules are working in the global, a large number of module members pollute the environment, there is no dependency between modules, maintenance difficulties, no private space and other problems. Subsequently, the namespace approach emerged, specifying that each module exposes only one global object to which the contents of the module are mounted.

window.moduleA = {
  method1: function () {
    console.log('moduleA#method1')
  }
}
Copy the code

However, this approach does not address the dependency issues of the first approach. Then came the use of immediate-execution functions to provide private space for modules, in the form of dependency declarations in the form of parameters.

(function ($) {
  var name = 'module-a'

  function method1 () {
    console.log(name + '#method1')
    $('body').animate({ margin: '200px' })
  }
    
  window.moduleA = {
    method1: method1
  }
})(jQuery)
Copy the code

The above approach solved the module approach early, but there are still some unsolved problems. For example, we used the script tag to introduce these modules in the page, the loading of these modules is not controlled by the code, and it is very troublesome to maintain after a long time.

In addition to the problem of module loading, there is also a need to specify the specification of modularity, currently popular is CommonJS, ES Modules.

Especially, as front-end projects get bigger and bigger, front-end development becomes very complex. We often encounter the following problems in the development process:

  • It needs to be developed in a modular way
  • Use advanced features such as ES6+, TypeScript scripting logic, SASS, less, and CSS style code to speed up development efficiency or security
  • Monitor changes to files and reflect them to the browser, improving development efficiency
  • JavaScript code needs to be modularized, and HTML and CSS resource files also face the problem of needing to be modularized
  • After development, we need to compress, merge, and optimize the code

The emergence of Webpack is to solve the above problems. In general, Webpack is a module packaging tool that developers can use to manage module dependencies and compile the static files that output modules need.

1.2 Webpack

Webpack is a static module packaging tool for modern JavaScript applications that manages module dependencies.

1.2.1 Static Module

Static modules here refer to resources at the development stage that can be directly referenced by Webpack (resources that can be directly retrieved and packaged into bundle.js). When Webpack processes an application, it internally builds a dependency graph that maps to each module (no longer js files) required for the project and generates one or more bundles, as shown below.

1.2.2 Webpack role

  • Ability to compile code, improve efficiency and solve browser compatibility problems

  • Module integration capability, improve performance, maintainability, solve the problem of frequent browser requests for files

  • Everything module capability, enhanced project maintenance, support for different types of front-end module types, unified modular scheme, all resource file loading can be controlled by code.

Talk about the webPack build process

The running flow of WebPack is a serial process, its work flow is the various plug-ins in series. Events are broadcast during the run, and the plug-in only needs to listen for the events it cares about to join the WebPack mechanism and change the way WebPack works.

From start to finish, there are three main processes:

  • Initialization phase: reads and merges parameters from configuration files and Shell statements, initializes required plug-ins and configures required parameters for the execution environment, such as plug-ins.
  • Construction phase: from Entry, the corresponding Loader is successively called for each Module to translate the file content, and then the Module dependent on the Module is found and compiled recursively.
  • Output phase: Combine compiled modules into chunks, convert Chunk into files, and output them to the file system.

2.1 Initialization Phase

The initialization phase mainly reads and merges parameters from configuration files and Shell statements to arrive at the final parameters. The default configuration file is webpack.config.js, or you can specify the configuration file in the form of a command, which is used to activate add-ons and plug-ins for WebPack. The following is the webpack.config.js file configuration, content analysis is as follows:

var path = require('path'); var node_modules = path.resolve(__dirname, 'node_modules'); var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js'); Module.exports = {// import file, which is the starting point of module construction, and each import file corresponds to the last chunk generated. Entry: '/ path/to/my/entry/file. The js', / / file path points to (can speed up the packing process). Resolve: {alias: {'react': pathToReact}}, // generate file, is the end of the module construction, including the output file and output path. output: { path: path.resolve(__dirname, 'build'), filename: '[name].js'}, // Loader is configured to process various modules, including CSS preprocessor Loader, ES6 compiler Loader, and Image processor Loader. module: { loaders: [ { test: /\.js$/, loader: 'babel', query: { presets: ['es2015', 'react'] } } ], noParse: [pathToReact]}, // Webpack each plugin object, in the Webpack event flow to execute the corresponding method. plugins: [ new webpack.HotModuleReplacementPlugin() ] };Copy the code

Webpack copies the various configuration items in webpack.config.js into the Options object and loads user-configured plugins. After the above steps are complete, the Compiler starts to initialize the Compiler Compiler object, which is the master of the Webpack declaration cycle and does not perform specific tasks, but just some scheduling work.

class Compiler extends Tapable { constructor(context) { super(); this.hooks = { beforeCompile: new AsyncSeriesHook(["params"]), compile: new SyncHook(["params"]), afterCompile: new AsyncSeriesHook(["compilation"]), make: new AsyncParallelHook(["compilation"]), entryOption: New SyncBailHook(["context", "entry"]) // Defines many different types of hooks}; / /... } } function webpack(options) { var compiler = new Compiler(); . // Check options. If the watch field is true, enable the Watch thread return compiler. }...Copy the code

In the code above, the Compiler object inherits from Tapable and is initialized with a number of hook functions defined.

2.2 Compilation and Construction

Initialize the Compiler object with the parameters obtained in the previous step, load all configured plug-ins, and execute the object’s run method to start compiling. Then, locate all the entry files according to the entry in the configuration, as follows.

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

After initialization, Compiler’s RUN will be called to really start the Webpack compilation and construction process, which is as follows:

  • Compile: The system starts to compile
  • Make: Analyzes modules and their dependencies from entry points and creates these module objects
  • Build-module: indicates the building module
  • Seal: encapsulates the build result
  • Emit: Output each chunk to the result file

1,The compile compile

After the run method is executed, compile is first triggered, essentially to build a Compilation object. This object is the main executor of the compile phase, and will perform the following steps: module creation, dependency collection, chunking, packaging, and other major tasks of the object.

2, make compile module

After completing the programmer operation, I started reading from the Entry file, using the _addModuleChain() function.

_addModuleChain(context, dependency, onModule, callback) { ... Const Dep = /** @type {DepConstructor} */ (dependency. Constructor); const moduleFactory = this.dependencyFactories.get(Dep); // Call the factory function NormalModuleFactory to create an empty NormalModule object moduleFactory.create({dependencies: [dependency]... }, (err, module) => { ... const afterBuild = () => { this.processModuleDependencies(module, err => { if (err) return callback(err); callback(null, module); }); }; this.buildModule(module, false, null, null, err => { ... afterBuild(); })})}Copy the code

_addModuleChain receives the parameters in the dependency of the incoming entry depend on, using the corresponding NormalModuleFactory factory function. The create method to generate an empty module object. This module is stored in the compiler. modules object and dependencies. Module object in the callback, and since it is an entry file, in compiler. entries. We then perform buildModule to get into the contents of the actual building module module.

3, Build Module to complete the module compilation

The main call to this process is configured loaders to convert our module into a standard JS module. After converting a module with Loader, acorn is used to parse the converted content and output the corresponding abstract syntax tree (AST) to facilitate code analysis after Webpack.

Start from the configured entry module, analyze its AST, and add it to the list of dependent modules when importing other module statements such as require. At the same time, perform recursive analysis on the newly identified dependent modules, and finally figure out the dependencies of all modules.

2.3 Output Stage

The SEAL method basically generates chunks, performs a series of optimizations on the chunks, and generates code to output. Chunk in Webpack can be interpreted as a module configured in entry or a dynamically imported module.

According to the dependency relationship between the entry and modules, the chunks containing multiple modules are assembled one by one, and then each Chunk is converted into a separate file and added to the output list. After determining the output content, determine the output path and file name based on the configuration.

output: {
    path: path.resolve(__dirname, 'build'),
        filename: '[name].js'
}
Copy the code

The hook emit is executed before Compiler starts generating the file, which is our last chance to modify the final file. The whole process is illustrated below.

Common Loader in Webpack

3.1 What is Loader

Loader is essentially a function that converts the received content and returns the converted result. Since Webpack only knows JavaScript, the Loader acts as a translator, preprocessing the translation of other types of resources.

By default, Webpack only supports packaging JS files when it comes to import or load modules. Webpack is unable to handle CSS, SASS, PNG files, etc. In this case, you need to configure the corresponding Loader to parse the file content. When loading the module, the execution sequence is as shown in the following figure.There are three common modes for configuring Loader:

  • Configuration method (recommended) : Specify loader in webpack.config.js
  • Inline: Specify loader explicitly in each import statement
  • Cli: Specify them in shell commands

3.1 Configuration Modes

For Loader configuration, we usually write in the module.rules property, which is described as follows:

  • Rules is an array, so we can configure many Loaders.
  • Each Loader corresponds to an object. The object attribute test is a matching rule. In general, it is a regular expression.
  • The use attribute invokes the corresponding Loader to process the matching file type.

Here is a sample code for module.rules:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          },
          { loader: 'sass-loader' }
        ]
      }
    ]
  }
};
Copy the code

3.2 Loader features

As can be seen from the above code, three Loaders are configured in the use property to process CSS files respectively. Since Loaders support chain calls, each loader in the chain will process previously processed resources, which will eventually become JS code. The sequence is reversed, that is, the preceding execution modes are ass-loader, CSS-loader, and style-loader.

In addition, loader has the following features:

  • The Loader can be synchronous or asynchronous
  • Loader runs in Node.js and can perform any operation
  • In addition to the usual way of exporting an NPM module as a Loader through package.json’s main, you can also reference a module directly in module.rules using the Loader field
  • Plugins can bring more features to the Loader
  • Loader can generate additional arbitrary files

You can add more power to the JavaScript ecosystem through loader’s preprocessor functions. Users now have more flexibility to introduce fine-grained logic such as compression, packaging, language translation, and more.

3.3 commonly used Loader

In the process of page development, in addition to importing some scene JS files, you also need to configure the loader to load the response. Common WebPack loaders are as follows:

  • Style-loader: Adds CSS to the DOM inline style tag style, and then loads CSS via DOM manipulation.
  • Css-loader: allows you to import CSS files via require and return CSS code.
  • Less-loader: Processes less and converts less code to CSS.
  • Sas-loader: Processes Sass and converts SCSS/SASS code to CSS.
  • Postcss-loader: uses postCSS to process CSS.
  • Autoprefixer-loader: processes cSS3 attribute prefixes. This feature is deprecated. You are advised to use PostCSS.
  • File-loader: distributes files to the output directory and returns the relative path.
  • Url-loader: similar to file-loader, but returns a Data URL if the file is smaller than the specified limit.
  • HTML minify – loader: compressed HTML
  • Babel-loader: Use Babel to convert ES6 files to ES.
  • Awesome-typescript-loader: converts typescript to JavaScript with better performance than TS-Loader.
  • Eslint-loader: Checks JavaScript code through ESLint.
  • Tslint-loader: Checks TypeScript code using tsLint.
  • Cache-loader: can be added before some loaders with high performance overhead to cache results to disks

The following uses CSS-Loader as an example to explain how to use loader. First, we install the CSS-Loader plug-in in the project.

npm install --save-dev css-loader
Copy the code

Then configure the rules in module.rules, for example:

Rules: [..., {$/ test: / \. CSS, use: {loader: "CSS - loader", the options: {/ / enable/disable url () handles the url: True, // enable/disable @import processing import: true, // enable/disable Sourcemap Sourcemap: false}}]Copy the code

Plugin common in Webpack

4.1 basis

Plugin is a plug-in, based on the event flow framework Tapable, plug-in can extend the function of Webpack, in the life cycle of Webpack running will broadcast many events, Plugin can monitor these events, at the appropriate time through the API provided by Webpack to change the output result.

The same is true of the Plugin in Webpack, which gives it various flexible features such as package optimization, resource management, environment variable injection, etc., that run at different stages of The Webpack (hook/life cycle) throughout the Webpack compilation cycle.

To do so, pass in the new instance object via the plugins property in the configuration file export object.

const HtmlWebpackPlugin = require('html-webpack-plugin'); // install const webpack = require('webpack') via NPM; // Access the built-in module. Exports = {... plugins: [ new webpack.ProgressPlugin(), new HtmlWebpackPlugin({ template: './src/index.html' }), ], };Copy the code

4.2 features

A Plugin is essentially a Javascript object with the Apply methods. The Apply method is called by the Webpack Compiler and the Compiler object is accessible throughout the compile life cycle.

const pluginName = 'ConsoleLogOnBuildWebpackPlugin'; class ConsoleLogOnBuildWebpackPlugin { apply(compiler) { compiler.hooks.run.tap(pluginName, (Compilation) => {console.log(' Webpack build process started! '); }); } } module.exports = ConsoleLogOnBuildWebpackPlugin;Copy the code

We can use the above method to obtain Plugin lifecycle hooks. As follows:

  • Entry-option: initializes the option
  • Compile: The compilation that actually begins, before the compilation object is created
  • Compilation: Generated the compilation object
  • Make: Analyzes dependencies recursively from entry and prepares to build each module
  • After-compile: The build process ends
  • Emit: Before writing the contents of assets in memory to the disk folder
  • After-emit: After writing the contents of assets in memory to the disk folder
  • Done: Completes all compilation
  • Failed: Indicates a compilation failure

4.3 Common Plugin

Common plugins in Weebpack are as follows:

  • Define -plugin: Define environment variables (Webpack4 after specifying mode is automatically configured)
  • Ignore-plugin: ignores some files
  • Html-webpack-plugin: Simplifies HTML file creation (dependent on html-loader)
  • Web-webpack-plugin: It is easier to output HTML for a single page application than the html-webpack-plugin
  • Uglifyjs-webpack-plugin: no SUPPORT for ES6 compression (pre-Webpack 4)
  • Terser-webpack-plugin: Supports compression of ES6 (Webpack4)
  • Webpack-parallel-ugli-fi -plugin: Multiple processes perform code compression to improve build speed
  • Mini-css-extract-plugin: Separate style files, extract CSS as separate files, support on-demand loading (alternative to extract-text-webpack-plugin)
  • Serviceworker-webpack-plugin: Adds offline caching for web applications
  • Clean-webpack-plugin: directory cleaning
  • ModuleConcatenationPlugin: open the Scope Hoisting
  • Speed-measure-webpack-plugin: you can view the execution time of each Loader and plugin (total packaging time, each plugin and Loader time).
  • Webpack-bundle-analyzer: Visualize the volume of webpack output files (business components, dependent third party modules)

Let’s take a look at the use of the clean-webpack-plugin. First, you need to install the clean-Webpack-plugin.

npm install --save-dev clean-webpack-plugin
Copy the code

Then, import the plug-in and use it.

const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
 ...
  plugins: [
    ...,
    new CleanWebpackPlugin(),
    ...
  ]
}
Copy the code

5. The difference between Loader and Plugin, and how to customize Loader and Plugin

5.1 the difference between

Loader is essentially a function that converts the received content and returns the converted result. Since Webpack only knows JavaScript, the Loader acts as a translator, preprocessing the translation of other types of resources.

Plugin is a plug-in, based on the event flow framework Tapable, plug-in can extend the function of Webpack, in the life cycle of Webpack running will broadcast many events, Plugin can monitor these events, at the appropriate time through the API provided by Webpack to change the output result.

  • Loader runs before packing files. Loader is configured in module.rules as a parsing rule for modules and of type array. Each item is an Object and contains properties such as test(type file), Loader, and Options.

  • Plugins are configured separately in Plugins and are of type array. Each item is an instance of the Plugin, and the parameters are passed in through the constructor.

5.2 Custom Loader

As we know, Loader is essentially a function in which this is populated by webpack as a context, so we can’t make Loader an arrow function. This function takes an argument for the file source content that WebPack passes to loader.

In addition, this is the object provided by WebPack, which can get all the information required by the current loader. The function has asynchronous or synchronous operations, which are returned by this.callback with a string or Buffer, as shown below.

// Export a function, Module.exports = function(source) {const content = doSomeThing2JsString(source); module. Exports = function(source) {const content = doSomeThing2JsString(source); // If the loader configures the options object, this.query will point to options const options = this.query; Console. log('this.context'); // Console. log('this.context'); / * * this. The callback parameter: * error: error | null, when loader error to throw an error * content: String | Buffer, after loader compiled need to export the * sourceMap: the content of the generated for the convenience of debugging the compiled contents of the source map * ast: */ this.callback(null, content); */ this.callback(null, content) // async return content; / / synchronize}Copy the code

5.3 Custom Plugin

Webpack Plugin is based on event flow framework Tapable. Because Webpack is based on publish and subscribe mode, many events will be broadcast during the running life cycle. By listening for these events, plug-ins can perform their own plug-in tasks in a specific stage.

Also, Webpack compilation creates two core objects: Compiler and Compilation.

  • Compiler: contains all configuration information of the Webpack environment, including options, loader, plugin, and Webpack lifecycle related hooks.
  • Compilation: Parameter to the Plugin’s built-in event callback function, containing the current module resources, compile-generated resources, changed files, and state information about the dependencies being tracked. When a file change is detected, a new Compilation is created.

If you want to customize a Plugin, you also need to follow certain specifications:

  • The plug-in must either be a function or an object containing the Apply method in order to access the Compiler instance
  • The Compiler and Compilation objects passed to each plug-in are the same reference and therefore not recommended
  • Asynchronous events need to call a callback function to tell Webpack to proceed to the next flow when the plug-in has finished processing the task, otherwise it will get stuck

Here is a template for a custom Plugin:

Class MyPlugin {// Webpack will call MyPlugin instance's apply method and pass in the compiler object apply (Compiler) {// Find the appropriate event hook, Tap ('MyPlugin', compilation => {// compilation: console.log(compilation); // compilation: console.log(compilation); // do something... }}})Copy the code

After the EMIT event is fired, the conversion and assembly on behalf of the source file is complete, and you can read the final output resource, code block, module and its dependencies, and modify the contents of the output resource.

6. Webpack hot update

6.1 hot update

Hot updates to Webpack are also known as Hot Module Replacement, or HMR. This mechanism allows you to replace the old module with the new one without refreshing the browser.

The core of HMR is that the client pulls the updated file from the server, namely chunk diff (the part of chunk that needs to be updated). In fact, WDS maintains a Websocket between the browser and WDS. When the local resource changes, WDS pushes updates to the browser. With the build-time hash, let the client compare to the last resource. The client compares the difference and makes an Ajax request to WDS to get the change (file list, hash), which can then be used to make jSONP requests to WDS to get incremental updates of the chunk.

Configuring and enabling hot modules in Webpack is also very easy, just add the following code.

const webpack = require('webpack') module.exports = { // ... DevServer: {// enable HMR hot: true // hotOnly: true}}Copy the code

It should be noted that implementing hot updates also requires specifying which modules do HRM when they are updated, since BY default HRM is only valid for CSS files.

If (module. Hot) {module. Hot. Accept ('. / util. Js', () = > {the console. The log (" util. Js updated ")})}Copy the code

6.2 Implementation Principles

First, let’s look at a graph:There are many different concepts involved in the diagram above, as follows:

  • Webpack Compile: Compile the JS source code into bundle.js
  • HMR Server: outputs hot updated files to the HMR Runtime
  • Bundle Server: Static resource file Server that provides file access paths
  • HMR Runtime: The socket server will be injected into the browser to update the file changes
  • Bundle.js: Build the output file
  • Set up websocket between HMR Runtime and HMR Server, namely line 4 in the figure, for updating file changes in real time

The whole process can be divided into two phases: startup phase and update phase.

The main work of the startup phase is that Webpack Compile the source code together with the HMR Runtime into a bundle file, which is transmitted to the Bundle Server static resource Server. Next, let’s focus on the update phase:

When a file or module changes, WebPack listens for the change and recompiles the file to produce a unique hash value that identifies the next hot update. Then, two patch files are generated based on the changed content: the manifest (which contains hash and chundId to illustrate the changed content) and the chunk.js module.

Since the Socket Server establishes a Websocket link between HMR Runtime and HMR Server, when a file is changed, the Server pushes a message to the browser. The message contains the hash value generated after the file is changed, as shown in the h attribute in the following figure, which is used to indicate that the file will be hot again.

Before the browser receives this message, the browser has already remembered the hash identifier in the previous socket message. In this case, we create an Ajax request to the server to retrieve the manifest file of the changed content. The Mainfest file contains the hash values generated by the rebuild, as well as the changed modules, corresponding to the C attribute shown above. The browser retrieves the contents of module changes according to the manifest file, which triggers the Render process to implement partial module updates.

6.3 summarize

Through the previous analysis, the steps of Webpack hot module are summarized as follows:

  • Create two servers with webpack-dev-server: a static resource service (Express) and a Socket service
  • Express Server is responsible for serving static resources directly (packaged resources are directly requested and parsed by browsers)
  • A Socket Server is a websocket long connection that communicates with each other
  • Json (manifest file) and.js (Update chunk) files are generated when the Socket Server detects that the corresponding module has changed.
  • Using a long connection, the Socket Server can actively send these two files directly to the client (browser).
  • The browser takes the two new files, loads them through the HMR Runtime mechanism, and updates them for the modified modules

7. Working principle of Webpack Proxy

7.1 the agent

In project development, it is inevitable to encounter cross – domain problems. Proxy in Webpack is one of the ways to solve cross – domain front-end problems. The so-called proxy refers to the behavior of receiving the request sent by the client and forwarding it to other servers. The tool that provides the server in Webpack is Webpack-dev-server.

7.1.1 webpack – dev – server

Webpack-dev-server is an official development tool for WebPack, which integrates a series of development-friendly functions such as automatic compilation and automatic refresh of the browser. At the same time, in order to improve the daily development efficiency of developers, only apply to the development phase. The code for configuring the proxy in the WebPack configuration object property is as follows:

// ./webpack.config.js const path = require('path') module.exports = { // ... devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, port: 9000, proxy: { '/api': { target: 'https://api.github.com' } } // ... }}Copy the code

In devServetr, proxy is the configuration of proxy. This attribute is in the form of an object, and each attribute in the object is a rule match of a proxy.

The attribute name is the prefix of the request path to be proxied. Generally, the prefix is set to/API and the value is the corresponding proxy matching rule, which is as follows:

  • Target: indicates the target address of the proxy.
  • PathRewrite: By default, our /api-hy is also written to the URL. If you want to delete it, you can use pathRewrite.
  • Secure: by default, servers that do not receive forwarding to HTTPS are not supported. You can set this parameter to false if you want to support HTTPS.
  • ChangeOrigin: Indicates whether to update the host address of headers requested by the agent.

7.2 the principle

In essence, the principle of proxy is to use HTTP-proxy-middleware, the HTTP proxy middleware, to realize the request forward to other servers. Take the following example:

const express = require('express');
const proxy = require('http-proxy-middleware');

const app = express();

app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
app.listen(3000);

// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
Copy the code

In the example above, where the local address is http://localhost:3000, the browser sends a request prefixed with the/API identifier to the server to get the data, but the server responding to the request simply forwards the request to another server.

7.1 cross domain

During development, Webpack-dev-server starts a local development server, so our application runs independently on one port of localhost during development, and the back-end service runs on another address. Therefore, in the development phase, due to the same origin policy of the browser, cross-domain requests will occur when accessing the back end locally.

To solve this problem, you only need to set up the WebPack Proxy proxy. When the local sends a request, the proxy server responds to the request and forwards the request to the target server. The target server responds to the request and then returns the data to the proxy server. Finally, the proxy server responds to the local server.

When the proxy server sends data to the local browser, the two servers are of the same origin and there is no cross-domain behavior. In this case, the browser can receive data normally.

Note: There is no cross-domain behavior when requesting data from server to server. Cross-domain behavior is restricted by browser security policies

How to optimize performance with Webpack

As a package construction tool of a project, Webpack is often used to optimize the performance of the front-end project after the completion of project development. The common optimization means are as follows:

  • JS code compression
  • CSS code compression
  • Html file code compression
  • File size compression
  • Image compression
  • Tree Shaking
  • The separation
  • Inline the chunk

8.1 JS code compression

Terser is a set of JavaScript interpreters, meat mincers, and compressors that help us compress and uglify our code to make bundles smaller. In Production mode, WebPack uses the TerserPlugin to process our code by default. If you want to customize it, do so as follows.

const TerserPlugin = require('terser-webpack-plugin') module.exports = { ... Optimization: {minimize: true, minimizer: [new TerserPlugin({parallel: true})]}}Copy the code

The common attributes of the TerserPlugin are as follows:

  • ExtractComments: The default value is true, which means that comments will be extracted to a separate file. During development, we can set it to false and not keep comments
  • Parallel: increases build speed by running multiple processes concurrently. The default value is true, and the default number of concurrent runs is os.cpus().length-1
  • TerserOptions: set our terser-related configuration: compress: set compression options, mangle: set uglification options, which can be set to true mangle: set uglification options, which can be set to true toplevel: Keep_classnames: indicates the name of the reserved class. Keep_fnames: indicates the name of the reserved function

8.2 COMPRESSION of CSS code

CSS compression is usually used to remove unnecessary whitespaces, but since it is difficult to change the names of selectors, properties, values, etc., we can use another plugin: CSS-minimizer-webpack-plugin. The configuration is as follows:

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
    // ...
    optimization: {
        minimize: true,
        minimizer: [
            new CssMinimizerPlugin({
                parallel: true
            })
        ]
    }
}
Copy the code

8.3 Html file code compression

When using the HtmlWebpackPlugin to generate HTML templates, you can optimize HTML by configuring the attribute minify as follows.

module.exports = { ... Plugin: [new HtmlwebpackPlugin ({... minify: {whether minifyCSS: false, / / compress CSS collapseWhitespace: false, // whether to fold space removeComments:true // whether to removeComments}})]}Copy the code

8.4 File Size Compression

Compression of file size can effectively reduce the loss of broadband during HTTP transmission. File compression needs to use the compression-webpack-plugin, and the configuration is as follows.

New ComepressionPlugin ({test: / \ | js (CSS) $/, / / which files need to compression threshold: 500, / / Settings file how to compress minRatio: 0.7, // The ratio of compression algorithm:"gzip", // the compression algorithm used})Copy the code

8.5 Image Compression

If we analyze the bundle, we will find that the size of multimedia files such as images is much larger than THAT of JS and CSS files, so image compression is also very important in packaging. You can use the following configuration methods:

module: { rules: [ { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader', options: { name: '[name]_[hash].[ext]', outputPath: 'images/',}}, {loader: 'image-webpack-loader', options: {// Compress JPEG configuration mozJPEG: {progressive: true, quality: 65}, // Use imagemin**-optipng to compress PNG, enable: false to disable optipng: {enabled: Imagemin-pngquant: {quality: '65-90', speed: 4}, // Compress GIF configuration gifsicle: {interlaced: false,}, // Open WebP, which compresses JPG and PNG images to webP format webp: {quality: 75}}}]},]}Copy the code

8.6 the Tree Shaking

Tree Shaking is a term for static parsing in a computer that eliminates dead code and relies on ES Modules. There are two different approaches to implementing Trss Shaking in WebPack:

  • UsedExports: Indicates whether certain functions are used and then optimizes them using Terser
  • SideEffects: skip the entire module/file to see if the file has sideEffects

To configure usedExports, you only need to set usedExports to true.

module.exports = {
    ...
    optimization:{
        usedExports
    }
}
Copy the code

SideEffects is used to tell the webpack compiler which modules have sideEffects at compile time by setting the sideEffects property in package.json. If sideEffects is set to false, it tells WebPack that it is safe to remove exports that are not used, or that it can set them to an array if any files need to be retained.

"SideEffecis" : ". / SRC/util/format. Js ", "*. CSS"] / / all CSS fileCopy the code

8.7 Code Separation

By default, all JavaScript code (business code, third party dependencies, modules not currently used) is loaded on the home page, which affects the loading speed of the home page. If you can separate out smaller bundles and control resource load priorities, you can optimize load performance.

Code separation can be achieved through splitChunksPlugin, a plug-in webPack that is installed and integrated by default and only needs to be configured.

module.exports = {   
 ...    
    optimization:{    
        splitChunks:{       
             chunks:"all"     
                }  
        }}
Copy the code

SplitChunks have the following properties:

  • Chunks: Works on synchronous or asynchronous code
  • MinSize: The size of the package should be at least minSize. How can the package not be split if the package size does not exceed minSize
  • MaxSize: Split a package larger than maxSize into a package larger than minSize
  • MinChunks: number of introduced chunks. The default is 1

8.8 inline the chunk

InlineChunkHtmlPlugin can be used to inline some chunk modules to HTML, such as runtime code (for module parsing, loading, module information related code). The code quantity is not large but must be loaded, such as:

const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
     module.exports = {  
           ...    plugin:[      
              new InlineChunkHtmlPlugin(HtmlWebpackPlugin,[/runtime.+\.js/]}

Copy the code

To sum up, the front-end performance optimization of Webpack is mainly based on file size, and the main measures include subcontracting and reducing the number of Http requests.

Nine, improve the speed of Webpack construction

With more and more functions and business codes, the corresponding Webpack construction time will be longer and longer, and the construction efficiency will be lower and lower. Therefore, how to improve the speed of Webpack construction is an important part of front-end engineering. The common means are as follows:

  • Optimizing loader Configuration
  • Use resolve.extensions appropriately
  • To optimize the resolve. Modules
  • To optimize the resolve. Alias
  • Use the DLLPlugin plugin
  • Use the cache – loader
  • Terser starts multithreading
  • Use sourceMap wisely

9.1 Optimizing Loader Configuration

When using Loader, you can configure include, exclude, and test attributes to match files, and use include and exclude to specify the Loader to match applications. For example, here is an example of configuring babel-Loader in an ES6 project:

Module. exports = {module: {rules: [{// do not write /\.jsx?$/ if js files are available. /\.js$/, // babel-loader supports caching of converted results with cacheDirectory enabled: ['babel-loader? CacheDirectory '], // use babel-loader include only for files in SRC directory under project root: path.resolve(__dirname, 'src'), }, ] }, };Copy the code

9.2 reasonable resolve. Extensions

Resolve helps Webpack find the right module code to import from each require/import statement.

In particular, the extension name is automatically added when resolved to a file through the resolve.extensions. The default is as follows:

module.exports = {
    ...
    extensions:[".warm",".mjs",".js",".json"]
}
Copy the code

When we import a file, if there is no file suffix, we will look it up according to the values in the array. So, when dealing with configuration, don’t just write all suffixes in it.

9.3 optimize the resolve. Modules

Resolve. modules is used to configure the directory in which Webpack looks for third-party modules. The default is [‘node_modules’]. So, when building a project, you can reduce the search time by specifying the absolute path to store third-party modules.

Module.exports = {modules: [path.resolve(__dirname, 'node_modules')] // __dirname indicates the current working directory},};Copy the code

9.4 optimize the resolve. Alias

Alias Specifies an alias for some commonly used paths, especially if our project directory structure is deep, a file path might be./.. /.. / to reduce the search process by configuring alias.

module.exports = {
    ...
    resolve:{
        alias:{
            "@":path.resolve(__dirname,'./src')
        }
    }
}
Copy the code

9.5 Using the DLL Plugin

DLL full name is a dynamic link library, is for software in WinODW to achieve a shared function library, and Webpack also built DLL function, is to share, do not often change the code, extract into a shared library. The steps are divided into two parts:

  • Package a DLL library
  • Introduction of DLL library

Package a DLL library

Webpack has a built-in DllPlugin to help us package a DLL library file, as shown below.

module.exports = {
    ...
    plugins:[
        new webpack.DllPlugin({
            name:'dll_[name]',
            path:path.resolve(__dirname,"./dll/[name].mainfest.json")
        })
    ]
}
Copy the code

Introduction of DLL library

First, the DllReferencePlugin plugin of WebPack was used to analyze the mainfest.json mapping file and obtain the DLL library to be used. We then introduce our packaged DLL library into the Html module through the AddAssetHtmlPlugin plugin.

module.exports = {
    ...
    new webpack.DllReferencePlugin({
        context:path.resolve(__dirname,"./dll/dll_react.js"),
        mainfest:path.resolve(__dirname,"./dll/react.mainfest.json")
    }),
    new AddAssetHtmlPlugin({
        outputPath:"./auto",
        filepath:path.resolve(__dirname,"./dll/dll_react.js")
    })
}
Copy the code

9.6 Rational Use Using cache-loader

Add cache-loader before some loaders that have high performance overhead to cache the results to disks and significantly improve the secondary build speed. Such as:

module.exports = {
    module: {
        rules: [
            {
                test: /\.ext$/,
                use: ['cache-loader', ...loaders],
                include: path.resolve('src'),
            },
        ],
    },
};
Copy the code

It is important to note that saving and reading these cache files takes some time, so use this loader only for loaders with high performance overhead.

9.7 Enabling Multi-Threading

Enabling the parallel running of multiple processes can improve the construction speed. The configuration is as follows:

Module. exports = {optimization: {minimizer: [new TerserPlugin({parallel: true, // enable multithread}),],},};Copy the code

In addition to Webpack, what other module management tools do you know

Modularity is a way of dealing with the decomposition of complex systems into better manageable modules. It can be used to split, organize, and package applications. Each module performs a specific sub-function, and all the modules are somehow assembled into a whole.

On the front end, popular module packaging tools besides Webpack include Rollup, Parcel, Snowpack, and the recent Vite craze.

10.1 a Rollup

Rollup is an ES Modules wrapper that compiles small pieces of code into large, complex pieces of code, such as libraries or applications. Rollup is very similar to Webpack in action. However, Rollup is much smaller than Webpack. It is now used in many bitter packages, such as Vue, React and three.js.

Before use, you can use the NPM install –global rollup command to install. Rollup can be invoked through a command line interface with an optional configuration file, or through a JavaScript API. Run rollup –help to see the available options and parameters.

Here is a simple example of how to package using Rollup. First, create the following files:

// ./src/messages.js
export default {
  hi: 'Hello World'
}

// ./src/logger.js
export const log = msg => {
  console.log('---------- INFO ----------')
  console.log(msg)
  console.log('--------------------------')
}

export const error = msg => {
  console.error('---------- ERROR ----------')
  console.error(msg)
  console.error('---------------------------')
}

// ./src/index.js
import { log } from './logger'
import messages from './messages'
log(messages.hi)

Copy the code

Finally, use the following command to pack.

npx rollup ./src/index.js --file ./dist/bundle.js
Copy the code

Reference: Rollup. Js

10.2 Parcel

Parcel, a completely zero-configuration front-end packer, provides a “dumb-ass” experience that allows you to build front-end applications with simple commands.

The process for using a Parcel is as follows:

  1. Create a directory and use itnpm init -yInitialize thepackage.json.
  2. Install the modulenpm i parcel-bundler --save-dev
  3. Create a SRC /index.html file as the entry point. Parcel supports any file as the entry point, but it is recommended to use HTML as the entry point because HTML is the way the browser runs.

To use it, you need to configure the script in package.json as follows:

"scripts": {
    "parcel": "parcel"
},
Copy the code

Parcel, like Webpack, supports any type of file as a package entry, but it is recommended to use an HTML file as an entry, as shown below.

<! --index.html--> <! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ">< title>Document</title> </head> <body> <script SRC ="main.js"></script> </body> </ HTML >Copy the code

Then, import the other module members using the ES Moudle method in the main.js file.

// ./src/main.js
import { log } from './logger'
log('hello parcel')

// ./src/logger.js
export const log = msg => {
  console.log('---------- msg ----------')
  console.log(msg)
}
Copy the code

Then, use the following command to package:

npx parcel src/index.html
Copy the code

Upon execution, Parcel not only packages the application, but also launches a development Server, just like WebPack Dev Server.

10.3 Vite

Vite is a Web development and construction tool developed by the author of Vue, especially Yuji. It is a development server based on the browser native ES module import. Under the development environment, the browser is used to parse import, and the server is compiled and returned as required. At the same time, Vue files are not only supported, but also support hot update, and the speed of hot update does not slow down with the increase of modules.

Vite has the following features:

  • Quick cold start
  • Instant Hot Module Replacement (HMR, Hot Module Replacement)
  • Truly compile on demand

Vite consists of two parts:

  • A development server that provides rich built-in features based on native ES modules, such as the astonishingly fast module hot update HMR.
  • A set of build instructions that use Rollup to package your code and that are pre-configured to output optimized static resources for production.

Vite can start the development server directly during development, with no packaging required, which means there is no need to analyze module dependencies, no need to compile, so it is very fast to start. When a browser requests a module, the content of the module is compiled as needed, greatly reducing the compilation time. The working principle is shown in the figure below.In terms of hot module HMR, when modifying a module, the browser only needs to request the module again. There is no need to compile all the dependent modules of the module like Webpack, so the efficiency is also higher.