Authors: Wind, Skyler, ZRJ, ZJ

preface

Webpack5 was officially released on October 10, 2020, and has been evolving and iterating rapidly over the past few months. As of January 28, Webpack5 has been updated in 18 minor versions, bringing a number of very attractive new features. According to the official website, the overall direction of Webpack5 changes are as follows:

  • Improve build performance by persisting hard disk caching capabilities
  • Improved long-term caching with better algorithms (reduced cache failure rate for production resources)
  • Optimize the size of the artifacts with better Tree Shaking capability and code generation logic
  • Improve web platform compatibility
  • Cleaned up some weird states in the internal architecture that were left over when Webpack4 introduced some new features without a major update
  • Long term stability in Webpack5 releases by introducing some significant changes to prepare for future features

The last two points are abstract, but as a whole, upgrading Webpack5 has significantly improved the efficiency of build time and the performance of runtime for developers, including some new features that may bring us more comfortable “coding posture”.

The team practice

At present, the team uses Webpack4 to undertake the entire construction system. When migrating, we refer to the upgrade manual officially provided by Webpack. Here, we integrate some points of practical value to our business at the current stage (new features are basically of actual benefits) into the team’s construction process. Through practice verification and record some problems and solutions we encountered in the process of infrastructure landing.

The preparatory work

The early stage of the research

First of all, through the guidance given by the official documents, we can identify some areas that clearly need to be changed, including the following aspects:

  • Webpack5 requires Node version ^10.13.0 support. For local use, upgrade Node to 10.13.0 or later. When some CI tools are used for production builds, the Node version configured on the corresponding image or CI needs to be updated synchronously.
  • The htML-webpack-plugin in the project needs to be upgraded to support webpack 5
  • Workbox-webpack-plugin used in the project needs to be upgraded to support webPack 5 version
  • DevServer needs to upgrade webpack-dev-server to ^4(next), otherwise HMR will fail.
  • Webpack5 removes node.js Polyfill, which will cause some packages to become unavailable (output to the console)'XXX' is not defined), if you want to be compatible with Nodejs Polyfill such as Process, install the relevant Polyfill: process and explicitly declare injection in the Plugin
// webpack.config.js{... .plugins: [...new webpack.ProvidePlugin({
          process: 'process/browser',]}})Copy the code
  • If you encounter deprecated API warnings while using Webpack5, you can do a Google search, usually asking you to upgrade some plugins to solve the problem, and these warnings will not have a disruptive effect on your build at this stage. Below we provide some references to precipitation in our practice:

    • Warning: Terser-webpack-plugin

      • Compilation.cache was removed

      • optimizeChunkAssets

    • Warning warning:

      • MainTemplate.hooks.renderManifest

      • ChunkTemplate.hooks.renderManifest

      • MainTemplate.hooks.hashForChunk

      • Module.id: Use new ChunkGraph API

      • Module.updateHash

      • Chunk.modulesIterable

      • MainTemplate.outputOptions

      • MainTemplate.renderCurrentHashCode

      • MainTemplate.getAssetPath

      • MainTemplate.requireFn

      • ChunkGroup.getModuleIndex2 was renamed to getModulePostOrderIndex

With all this in mind, we can upgrade a shuttle:

npm install webpack@latest --dev
Copy the code

Next, you can try the exciting Webpack5 ~

On the pit diary

Eco-align Webpack5

If you upgrade your project to Webpackage 5 and start build or devServer directly in your project, you will receive an error message like the following:

TypeError: Cannot add property htmlWebpackPluginAlterChunks, object is not extensible
    at /path/to/node_modules/html-webpack-plugin/index.js:59:56
Copy the code

If you are a Webpack driver, you will immediately find the key to the problem — html-webpack-plugin. Open the Github homepage of the plugin and you will find the solution in front of you:

Similarly, if an error or warning is thrown on the console, most of the problems can be solved by upgrading the corresponding dependent version. The upgrade of Webpack5 is still much smoother than that of Webpack4, and the ecology of the community is gradually following up and improving.

For example, the workbox-webpack-plugin that supports serviceworkers relies on webPack core events and some apis, so it needs to be upgraded. Here are some examples: Github.com/GoogleChrom… CSS optimize – CSS – assets – webpack – the plugin, In Plugin Github home page also clearly indicates that after Webpack5, the use of official Webpack csS-Minimizer-webpack-plugin is preferred.

Of course, there are some other ecological alignment points that won’t be covered in this document, most of which you can find in the official website introduction and upgrade manual, and if there are general problems that need to be solved case by case, Most of the community already has issues or other documents for clues, but if you find a problem “waiting to be solved”, this is your chance to contribute to the open source community

With all of the above dependencies upgraded, your project should be running with Webpack5.

PS: Optimize – CSS-assets-webpack-plugin is a plugin with no cache and no parallel. For me, I would rather use CSsnano in loader to optimize CSS logic. I didn’t want to waste my life on this plugin 🤫, but the CSS-Minimizer-webpack-plugin saved me from obsessive-compulsive behavior, and it was the perfect choice.

Touch and die HMR

Since webpack-dev-server relies on a lot of webPack build configurations, it is inevitable that some places are not as smooth as the ground after the upgrade. HMR is the most obvious example, it becomes very fragile, and you suddenly realize that it is not working. !

My first instinct was to think that I was overlooking some devServer configuration, but didn’t I follow the official CV documentation? ! When I calmed down, I clicked Google, and sure enough, there were people in the community who had experienced the same problem.

This is a BUG left over from webpack-dev-server, only if you explicitly set target in the Webpack configuration: ‘web’, HMR only works, even if you set target in an array, such as target: [‘web’, ‘es5’], which blocks HMR capability.

However, with the official release of v4 for Webpack5, you just need to upgrade webpack-dev-server to the next version (if you use it in your project via the Node API provided by Webpack5) to resolve the above issues:

However, since webpack-dev-server V4 only released a next version of Beta0 as of January 28, 2020, there are still some unstable capabilities, such as the inability to launch your project via localhost, but these are just details. Does not affect the overall function of the complete running.

New build time features

Built-in static resource building capability — Asset Modules

Webpack only packs JS files. If you want to pack other files, you need to add the corresponding loader.

Before Webpack5, we generally used the following loaders to process some common static resources, such as PNG images, SVG ICONS, etc. Their final effect is roughly as follows:

  • Raw-loader: Allows a file to be processed and imported as a string
  • File-loader: packages files to the output directory and returns a file URI when it is imported
  • Url-loader: When a file reaches a certain size, it can be processed as a Base64 URIS with built-in file-loader

Webpack5 provides built-in static resource building capabilities. We do not need to install additional loaders, and only need simple configuration to realize the packaging and directory storage of static resources. As follows: Resources that match the rule can be stored in the assets folder.

// webpack.config.js
module.exports = { ... .module: {
      rules: [{test: /\.(png|jpg|svg|gif)$/,
            type: 'asset/resource'.generator: {
                // [ext] comes with a "."
                filename: 'assets/[hash:8].[name][ext]',},},],},}Copy the code

Type can be:

  • Asset/Source – functions as raw-loader.
  • Asset /inline – Functions as urL-loader. If you want to set the encoding rules, you can set the dataUrl in the generator. For details, see the official documentation.
  • Asset/Resource – Functions equivalent to file-loader. Resources in the project are packaged in this way. Thanks to the fact that the team project has fully rolled out the features of HTTP2 multiplexing, we can unify the resources into files and transfer them in parallel when acquiring them, instead of building them into JS files in encoded form. The increase of resource volume will affect the loading of resources.
  • Asset — The default type is selected based on the file size, asset/inline is used when the file is smaller than 8 KB, otherwise asset/ Resource is used. You can also manually set thresholds. For details, see the official documents.

Built-in FileSystem Cache capability to speed up secondary builds

Before Webpack5, we used cache-loader to cache some loaders with high performance overhead, or used hard-source-webpack-plugin to provide some intermediate cache for modules. After Webpack5, a built-in caching capability (caching modules and chunks) was integrated for us by default. You can speed up the secondary build by doing the following.

// webpack.config.js
module.exports = { ... .cache: {
        type: 'filesystem'.// Optional
        buildDependencies: {
            config: [__filename],  // The cache is invalidated when the contents of the config file that builds the dependency (via the require dependency) change
        },
        name: ' '.// Create different cache files with name as isolation. For example, generate different configuration cache for PC or mobile. ,}}Copy the code

Production environment deposit the default cache directory in node_modules/cache/webpack/default – production, if want to change, can be configured name, to realize classified depositing. If name: ‘production-cache’ is set to the following cache location:

PS: If you call the Run method of the Webpack Compiler instance to execute the custom build operation, you may encounter the situation that the build cache does not generate the cache file. After searching the related Issues of Webpack, we found that: You will also need to manually call Compiler.close () to print the cache file.

Built-in WebAssembly compilation and asynchronous loading capabilities (sync/async)

WebAssembly is designed as a web-oriented binary file format that is closer to machine code with a smaller file size and faster execution efficiency. C/C ++ and other high-level languages can be directly compiled into.wasm files and called by JS. Webpackage 4 already integrates WebAssembly loading capabilities, but the extension of WebAssembly asynchronous loading capabilities in WebPackage 5 gives us more flexibility to do more interesting things with WebAssembly.

Here’s a quick overview of how WebAssembly can get started in a real project:

Taking a simple summation function as an example, in addition to generating WebAssembly files from official documentation, WasmFiddle is an online site that lets you write C/C ++ programs, convert them directly to WebAssembly files, and provides instant debugging capabilities.

By doing this, we can quickly get a program. Wasm file, download it to your local project, and you can start using it.

Before Webpack5, we used wASM-loader to process WebAssembly files, and used the following code to call functions that we wrapped in WASM:

import wasm from '/program.wasm';

wasm().then(instance= > {
  const sum = instance.exports.sum;
  console.log(sum(1.2));
}
Copy the code

This process is bound to be repetitive and tedious, but with Webpack5’s built-in WebAssembly build capabilities, we only need to configure the following:

// webpack.config.js
module.exports = { ... .experiments: {
        asyncWebAssembly: true,},module: {
        rules: [..., {test: /\.wasm$/,
                type: 'webassembly/async',},],},}Copy the code

When it comes to using WASM, it’s that simple:

import { sum } from './program.wasm'
console.log(sum(1.2))
Copy the code

Built-in Web Worker build capability

Web workers provide an easy way for Web content to run scripts in background threads. Threads can perform tasks without interfering with the user interface. Usually, we can put some encryption and decryption or image processing and other more complex algorithms in the child thread, when the child thread completed the execution, and then communicate to the main thread.

If we have a time-consuming computation in a calc.js file, when we are done, we inform the main thread of the file information via postMessage.

// calc.worker.js
let num;
for (let i = 0; i <= 20000000; i++) {
  if (i === 20000000) {
    num = 20000000;
  }
}
postMessage({
  value: num,
});

Copy the code

The master.js main thread listens for messages from child threads and performs some operations when it receives messages from them.

// master.js
worker.onmessage = e= > {
  console.log(e.data.value);
};
Copy the code

For previous Web Worker processing, we need to use worker-loader to process, through the following configuration

// webpack.config.js
module.exports = { ... .module: {
        rules: [{test: /\.worker\.js$/,
                use: { loader: 'worker-loader'},},],},}Copy the code

When used, a worker object can be constructed by importing the calc.worker.js file directly, so the main thread is handled as follows:

// master.js
import Worker from './calc.worker.js';
const worker = new Worker();
worker.onmessage = e= > {
  console.log(e.data.value);
};
Copy the code

In Webpack5, we do not need to add loader processing mode, and do not need to configure specific file names such as.worker.js for worker, with the help of new URL, we can realize the creation of worker. As follows, you can also refer to the official example:

// master.js
const worker = new Worker(new URL('./calc.js', import.meta.url), {
    name: "calc"
  /* webpackEntryOptions: { filename: "workers/[name].js" } */
});
worker.onmessage = e => {
  console.log(e.data.value);
};
Copy the code

However, for the sake of developers’ programming habits and convenience, we did not remove the worker-loader completely. You can still use the Web worker directly by importing a.worker.js suffix. At the same time, you can also use the new Webpack5 features mentioned above, but please note that the.worker.js file cannot be named in new URL(), otherwise it will be preferentially parsed by worker-loader and eventually your worker will not work properly.

New runtime features

Remove Node.js Polyfill, Polyfill at developer’s discretion

With node.js Polyfills removed, manually add Polyfill support if process and PATH dependencies are used in the front-end package. Such as the process described in the previous preparation.

For example, process. CWD is not a function when you upgrade your project to Webpackage 5. For example, a familiar NodeJS API error is not defined on the console. In this case, we need to add the relevant Browser polyfills, because Webpack5 will not handle these polyfills for you. For details about the configuration of process, see Preparations.

Better resource packaging strategies, more “lightweight” builds

Prepack is Facebook’s open source JavaScript code optimization tool that runs in the “compile” phase and generates optimized code. Here’s an example from Prepack’s website. If a function gets a fixed output for any input, Prepack can calculate the result for us at compile time. For some complex and fixed computational logic, this “predictive computation” capability can both reduce the size of our packages and speed up the runtime. As the official example shows:

Webpack5 includes some of Prepack’s capabilities to optimize the volume of your project beyond the extreme:

Deep Tree Shaking capability support

Tree Shaking capability is the ability to remove variables that are not referenced in the JavaScript context during packaging, thereby reducing the size of the packaging. When we during the development phase, for example, the introduction of a file, the referenced code in the presence of no use, or in front of the development of forget to delete, or developers legacy code but too bold to make some cuts work, has the function of Tree Shaking this, after all of these will not be a problem, Webpack automatically removes unused code when packaging. In particular, Webpack5 supports Tree Shaking of deeply nested export.

For example, with Webpack5’s ability to Prepack the class mentioned above, the redundant code is automatically cleared:

So, do you need to worry about redundant code in your project that will burden your online package volume?

Friendlier Long Term Cache support, chunkid unchanged

Before Webpack5, the names of packaged files are sorted by ID. If a subsequent file is changed, the file name of the packaged files will be changed, even if the file content is not changed. Therefore, the cache of resources will be invalidated.

Webpack5 has a more friendly long-term cache capability. It calculates a short numeric ID for modules and chunks through a hash generation algorithm, so that even if a file is deleted, a large number of file cache failures are not caused. For details, see the official website.

For example, we add a new page to the project and compare the product name of Webpack4 with that of Webpack5:

Webpack4: the creation of a new chunk in the first place invalidates all js file caches, as we can see from sourcemap that two js files with completely different names always refer to the same source file.

Webpack5: applies only to modified files

As an additional extension, Webpack5 also uses the actual Contenthash to support a more friendly Long Term cache. If you delete a comment or change the name of a variable in your logic, your code logic is essentially unchanged. Therefore, changes to these contents will not cause contenthash to change in a compressed file.

Support Top Level Await, farewell async

Webpack5 also supports Top Level Await. This allows developers to use await fields outside async functions. It is like huge async functions because the module that imports them waits for them to start executing its code, so this omission of async is only available at the top level.

You can enable the following configuration:

// webpack.config.js
module.exports = { ... .experiments: {
        topLevelAwait: true,}}Copy the code

Before and after comparison, take the i18N pull dictionary logic frequently used in our internationalization project as an example:

// before opening top level await
import i18n from 'XXX/i18n-utils'

(async() = > {// Internationalize copywriting to initialize logic asynchronously
  await i18n.init({/ *... * /})
  root.render(<AppContainer />)
})()

// After top level await is enabled
import i18n from 'XXX/i18n-utils'

await i18n.init({/ *... * /})
root.render(<AppContainer />)
Copy the code

We no longer need to wrap async ife around our asynchronous logic, and at the top of the JS logic we can directly use await to control asynchronous logic.

Of course, we can also use this feature for asynchronous export or import modules:

// src/Home/index.jsx
import React from 'react';

const Test = () = > {
  return <div>123</div>;
};

let Home = null;
await new Promise(resolve= > {
  Home = Test;
  resolve();
});

export default Home;

// src/index.jsx
import Home from './Home'
Copy the code

To support esLint syntax detection, we also need to add the @babel/plugin-syntax-top-level-await plugin to make our Babel recognize the top level await syntax.

However, unlike the way we deal with other ES6 grammars, we can’t encapsulate the compilation of the top level await syntax in Babel. As shown in the plug-in’s front page screenshot below, the plug-in simply enables Babel to parse the new syntax and convert it into an AST. It is not compiled away, so the actual compilation process is handled by a build tool like Webpack or Rollup.

The final result

In order to show the benefits of Webpack5 to the project during the packaging build process, we made the following comparison:

Construction efficiency comparison

To enable or disable filesystem caching, we conducted the following comparison experiment for the departmental project templates. It can be seen that, in addition to the initial build time is relatively long, the subsequent secondary build speed has been greatly improved.

  • Primary and secondary builds without configuration:

  • First and second builds with caching configured:

configuration

The first building

Secondary building

Modify the code and build

Modify the configuration and build

no-filesystem

13.443 s

9.782 s

9.857 s

9.964 s

filesystem

18.08 s

1.181 s

2.4 s

1.77 s

Cache failure rate comparison of Chunk update products

Take the packaged 8 JS chunk resources we gave in the Long Term Cache feature above.

Webpack4: a new chunk created in the first place invalidates all JS file caches.

Webpack5: applies only to modified files

Webpack version

Cache failure rate

Webpack4

8/8 = 100%

Webpack5

3/8 = 37.5%

Of course, the final benefits of the Webpack5 upgrade go far beyond those mentioned above, and we need to collect more data to verify the deeper performance benefits of the Webpack5 upgrade at runtime, but for now, Webpack5 already has many features that make us more comfortable during development.

conclusion

Although Webpack5 provides us with a lot of excellent new features, but for developers in the actual development process of change is relatively weak, we can be comfortable to use these new features, and in the upgrade process with the gradual improvement of the Webpack system ecology, We’re all quick to find a clear solution to a problem, so try it out.

If you are also interested in Webpack5, we can also discuss it at any time

Recruitment hard wide

Our team is hiring!! Welcome to join bytedance’s commercial realization front end team. The technical construction we are doing includes: Front-end engineering system upgrade, team Node infrastructure construction, the front-end one-click CI publishing tools, support of service components, the internationalization of the front-end general micro front-end solutions, heavy reliance on business system reform, visual page structures, systems, business intelligence (BI, front end test automation and so on, has the nearly hundred people of the north grand front team, If you want to join us, please click on our inpush channel:

✨ ✨ ✨ ✨ ✨

Push portal (for prime recruiting season, click here to get Bytedance push opportunities!)

Bytedance enrollment exclusive entry: Bytedance enrollment promotion code: HTZYCHN, post link: Bytedance enrollment – Recruitment

✨ ✨ ✨ ✨ ✨

If you want to know about our department’s daily life (dou) and work environment (Li), you can also click here


Welcome to “Byte front end ByteFE”

Resume mailing address: [email protected]