Webpack5 has been released with a summary of the major new features involved and how they can be used.

Document address: webpack.js.org/

英 文 名 称 : webpack.docschina.org/

Github address: github.com/webpack/web…

Built-in static resource building capability — Asset Modules

Before WebPack 5, it was common to use:

  • Raw-loader imports the file as a string
  • Url-loader inlines the file into the bundle as a data URI
  • File-loader sends files to the output directory

Asset Module Type replaces all of these loaders by adding 4 new module types:

  • Asset/Resource sends a separate file and exports the URL. This was previously implemented using file-loader.
  • Asset /inline exports the data URI of a resource. This was previously implemented using urL-loader.
  • Asset /source Exports the source code for the resource. This was previously implemented using raw-loader.
  • Asset automatically selects between exporting a data URI and sending a separate file. Previously, the urL-loader was used and the resource volume limit was configured.

1.1 Type are respectivelyasset/resource,asset/inline,asset/source

webpack.config.js

module: { rules: [ { test: /\.(png|jpg|jpeg|gif)$/, type: 'asset/resource' }, { test: /\.svg/, type: 'asset/inline'}, {test: /\.txt/, type: 'asset/source'Copy the code

src/index.js

import imgUrl from './assets/img/pic.jpeg'; import svgUrl from './assets/img/delete.svg'; import txt from './assets/example.txt'; Let img = document.createElement('img'); img.src = imgUrl; // imgUrl: 'file:///Users/yujian2018/work/learning/project/webpack5/dist/assets/img/f972bcf4.pic.jpeg' img.style.width = '150px'; img.style.height = '150px'; document.body.appendChild(img); let svg = document.createElement('img'); svg.src = svgUrl; // svgUrl: data:image/svg+xml; base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgM TYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5O S94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjYgKDY3NDkxKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtL T4KICAgIDx0aXRsZT7liKDpmaQ8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz4KICAgICAgICA8cGF0a CBkPSJNNy4zMzMzMzMzMywxLjMzMzMzMzMzIEwxMC41MzMzMzMzLDEuMzMzMzMzMzMgQzEwLjYwNjk3MTMsMS4zMzMzMzMzMyAxMC42NjY2NjY3LDEuMzkzM DI4NyAxMC42NjY2NjY3LDEuNDY2NjY2NjcgTDEwLjY2NjY2NjcsMi41MzMzMzMzMyBDMTAuNjY2NjY2NywyLjYwNjk3MTMgMTAuNjA2OTcxMywyLjY2NjY2N jY3IDEwLjUzMzMzMzMsMi42NjY2NjY2NyBMMTAsMi42NjY2NjY2NyBMMTAsMTIuOCBDMTAsMTMuMDk0NTUxOSA5Ljc2MTIxODUzLDEzLjMzMzMzMzMgOS40N jY2NjY2NywxMy4zMzMzMzMzIEwxLjIsMTMuMzMzMzMzMyBDMC45MDU0NDgxMzMsMTMuMzMzMzMzMyAwLjY2NjY2NjY2NywxMy4wOTQ1NTE5IDAuNjY2NjY2N jY3LDEyLjggTDAuNjY2NjY2NjY3LDIuNjY2NjY2NjcgTDAuMTMzMzMzMzM1LDIuNjY2NjY2NjcgQzAuMDU5Njk1MzY3NiwyLjY2NjY2NjY3IDQuNzI4OTIyN jVlLTE2LDIuNjA2OTcxMyAzLjMzMDY2OTA3ZS0xNiwyLjUzMzMzMzMzIEwwLDEuNDY2NjY2NjcgQy05LjAxODA1MDAxZS0xOCwxLjM5MzAyODcgMC4wNTk2O TUzNjY3LDEuMzMzMzMzMzMgMC4xMzMzMzMzMzMsMS4zMzMzMzMzMyBMMy4zMzMzMzMzMywxLjMzMzMzMzMzIEwzLjMzMzMzMzMzLDAuMTMzMzMzMzM1IEMzL jMzMzMzMzMzLDAuMDU5Njk1MzY3NiAzLjM5MzAyODcsMS4yNDU0OTM3OGUtMTYgMy40NjY2NjY2NywxLjExMDIyMzAyZS0xNiBMNy4yLDAgQzcuMjczNjM3O TcsMi40ODA4NzU0ZS0xNiA3LjMzMzMzMzMzLDAuMDU5Njk1MzY3NiA3LjMzMzMzMzMzLDAuMTMzMzMzMzM1IEw3LjMzMzMzMzMzLDEuMzMzMzMzMzMgWiBNM y42NjY2NjY2Nyw0LjY2NjY2NjY3IEMzLjU5MzAyODcsNC42NjY2NjY2NyAzLjUzMzMzMzMzLDQuNzI2MzYyMDMgMy41MzMzMzMzMyw0LjggTDMuNTMzMzMzM zMsOS44NjY2NjY2NyBDMy41MzMzMzMzMyw5Ljk0MDMwNDYzIDMuNTkzMDI4NywxMCAzLjY2NjY2NjY3LDEwIEw0LjMzMzMzMzMzLDEwIEM0LjQwNjk3MTMsM TAgNC40NjY2NjY2Nyw5Ljk0MDMwNDYzIDQuNDY2NjY2NjcsOS44NjY2NjY2NyBMNC40NjY2NjY2Nyw0LjggQzQuNDY2NjY2NjcsNC43MjYzNjIwMyA0LjQwN jk3MTMsNC42NjY2NjY2NyA0LjMzMzMzMzMzLDQuNjY2NjY2NjcgTDMuNjY2NjY2NjcsNC42NjY2NjY2NyBaIE02LjMzMzMzMzMzLDQuNjY2NjY2NjcgQzYuM jU5Njk1MzcsNC42NjY2NjY2NyA2LjIsNC43MjYzNjIwMyA2LjIsNC44IEw2LjIsOS44NjY2NjY2NyBDNi4yLDkuOTQwMzA0NjMgNi4yNTk2OTUzNywxMCA2L jMzMzMzMzMzLDEwIEw3LDEwIEM3LjA3MzYzNzk3LDEwIDcuMTMzMzMzMzMsOS45NDAzMDQ2MyA3LjEzMzMzMzMzLDkuODY2NjY2NjcgTDcuMTMzMzMzMzMsN C44IEM3LjEzMzMzMzMzLDQuNzI2MzYyMDMgNy4wNzM2Mzc5Nyw0LjY2NjY2NjY3IDcsNC42NjY2NjY2NyBMNi4zMzMzMzMzMyw0LjY2NjY2NjY3IFoiIGlkP SJwYXRoLTEiPjwvcGF0aD4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSLliKDpmaQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lI iBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxyZWN0IGZpbGw9IiNGRkZGRkYiIG9wYWNpdHk9IjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxNiIgaGVpZ 2h0PSIxNiI+PC9yZWN0PgogICAgICAgIDxnPgogICAgICAgICAgICA8ZyBpZD0iNF9JY29uLzBfYmFzZS9iZy3mm7/mjaIiPjwvZz4KICAgICAgICAgICAgP GcgaWQ9Imljb24vZ3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIuNjY2NjY3LCAxLjMzMzMzMykiPgogICAgICAgICAgICAgICAgPG1hc2sgaWQ9Im1hc 2stMiIgZmlsbD0id2hpdGUiPgogICAgICAgICAgICAgICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+CiAgICAgICAgICAgICAgICA8L 21hc2s+CiAgICAgICAgICAgICAgICA8dXNlIGlkPSJNYXNrIiBmaWxsPSIjODM4NjhGIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgI CAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+ document.body.appendChild(svg); let txtEl = document.createElement('div'); txtEl.innerHTML = txt; / / TXT: here is pure text document. The body. The appendChild (txtEl);Copy the code

1.2 the type isasset

For Type: asset, Webpack will automatically choose between resource and inline by default: files less than 8KB will be treated as inline module types, otherwise they will be treated as Resource module types.

Can also through the module in webpack configuration rule hierarchy and set rule. The parser. DataUrlCondition. MaxSize options to modify the conditions:

Module: {rules: [{test: / \. (PNG | JPG | jpeg | GIF) $/, type: 'asset', / / set a custom parser: {dataUrlCondition: {maxSize: 8 * 1024}}}]}Copy the code

1.3 Customize the output file name

By default, the Asset/Resource module is sent to the output directory with the filename [hash][ext][query].

Can pass in webpack. Config. Js will output. AssetModuleFilename and Rule. The generator. The filename used in combination to the output of the customization file directory:

output: { filename: 'main.js', path: path.resolve(__dirname, 'dist'), assetModuleFilename: 'images/[hash][ext][query]' }, module: { rules: [ { test: /\.(png|jpg|jpeg|gif)$/, type: 'asset/resource', parser: {dataUrlCondition: {maxSize: 8 * 1024}}, Generator: {// [ext] 'assets/img / [8] hash: [name] [ext]', / / custom output directory]}}}Copy the code

Note: Rule. The generator. With the output filename. The same assetModuleFilename, and applies only to the asset and the asset/resource module type.

2. File caching

In Webpack 4, we use cache-loader to cache some loaders with high performance overhead, or use 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.

cache: { type: 'filesystem', // default cache to node_modules/. Cache /webpack // can also be customized cache directory, Cache. cacheDirectory is available only when cache.type is set to filesystem. // cacheDirectory:path.resolve(__dirname,'node_modules/.cac/webpack'), buildDependencies : { // 2. Add your configuration to buildDependency to invalidate the cache when configuration changes config: [__filename] // 3. If you have other build dependencies you can add them here // Note that webpack, loaders, and all modules referenced from your configuration are automatically added}}Copy the code

3. Better Treeshaking

Unused export content is not packaged for generation. Setting mode to Production automatically enables this.

3.1, Nested Treeshaking

module1.js

import * as module2 from './module2'
export function fun1() {
  console.log('fun1');
}

export function fun2() {
  console.log('fun2')
}
export { module2 }
Copy the code

module2.js

export function fun3() {
  console.log('fun3');
}

export function fun4() {
  console.log('fun4')
}

export const num1 = 111
export const num2 = 222
Copy the code

index.js

import * as module1 from "./module1";
console.log(module1.module2.num1)
Copy the code

Comparison of webPack4 and WebPack5 packaging results:

Internal module treeshaking(inner-module tree-shaking)

Webpack 4 does not analyze dependencies between module exports and imports. Webpack 5 has a new option, optimization.innergraph, enabled by default in production mode, which runs analysis on symbols in a module to find dependencies from export to import.

import { something } from "./something";

function usingSomething() {
  return something;
}

export function test() {
  return usingSomething();
}
Copy the code

The innerGraph calculates that something is only used when using the Test export. This allows more exports to be marked unused and more code to be omitted from the package.

When “sideEffects”: false is set, this allows even more modules to be omitted. In this example,./something is omitted when the test export is not in use.

3.3 commonjs treeshaking

Webpack 5 adds support for certain CommonJs constructs, allowing you to eliminate unused CommonJs exports and track export names referenced in require() calls.

4. Module federation

The module federation itself is a common Webpack plugin ModuleFederationPlugin that has several important parameters:

  • Name Indicates the name of the current application. The value must be globally unique.
  • Remotes maps the names of other projects to the current project.
  • An exposes represents an exported module, and only the module declared here can be used as a remote dependency.
  • Shared is an important parameter that allows remotely loaded modules to use React or ReactDOM instead.

With Module Federation, each application block is a separate build that compiles into a container.

Containers can be applied by other applications or by other containers.

A referenced container is called remote, the referrer is called host, remote exposes modules to host, and hosts can use those exposed modules, which are called remote modules.

Main code:

Weback.config.js in app_remote project

New ModuleFederationPlugin({name: 'app_remote', filename: "remoteentry. js", exposes: {// The module name './Button': './src/components/Button.vue', }, shared: ["vue", "element-ui"] })Copy the code

Weback.config.js in the host project

New ModuleFederationPlugin({name: "app_remote", filename: 'remoteentry. js', remotes: {// Declare the remote application to be referenced remote: 'app_remote@http://localhost:3000/remoteEntry.js' }, shared: ["vue", "element-ui"] })Copy the code

SRC /app.vue when the remote project component is used in the host project

Button: () => import("remote/Button"),
Copy the code

Problems encountered:

Uncaught Error: Shared module is not available for eager consumption the solution is as follows:

Create bootstrap.js and paste the contents of index.js into this file. As follows:

import Vue from 'vue';
import App from './app.vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

new Vue({
  render: h => h(App)
}).$mount('#app');
Copy the code

Change the contents of index.js to:

import('./bootstrap');
Copy the code

The final effect is as follows:

There are the child app and the main app respectively, where the normal button comes from the child app, with a Ele style button for self-application.

The complete project code is as follows:

App_remote projects:

webpack.config.js

const path = require('path'); const webpack = require("webpack"); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const VueLoaderPlugin = require('vue-loader/lib/plugin'); module.exports = { mode: 'development', // production none entry: './src/index.js', output: { filename: '[name].js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(woff|ttf)$/, loader: 'file-loader' }, ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'app_remote', template: path.resolve(__dirname, './public/index.html'), filename: 'index.html' }), new ModuleFederationPlugin({ name: 'app_remote', filename: "remoteentry. js", exposes: {// The module name exposed by the remote application './Button': './src/components/Button.vue', }, remotes: { host: "app_host@http://localhost:9000/remoteEntry.js" }, shared: ['vue', 'elemental-ui ']}), new VueLoaderPlugin()], devServer: {hot: true, host: '0.0.0.0', port: 3000},};Copy the code

src/app.vue

<template> <div> Hello,{{ name }} <Button /> <List /> </div> </template> <script> export default { components: { Button: () => import("./components/Button.vue"), List: () => import("host/list"), }, data() { return { name: "Child application ",}; }}; </script>Copy the code

src/components/Button.vue

<template>
  <div>
    <button>hahaha</button>
  </div>
</template>
Copy the code

In the app_host project:

webpack.config.js

const path = require('path'); const webpack = require("webpack"); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const VueLoaderPlugin = require('vue-loader/lib/plugin'); module.exports = { mode: 'development', // production entry: './src/index.js', output: { filename: '[name].js', path: Path. resolve(__dirname, 'dist'), // publicPath: "http://localhost:9000/", // Deployed resource address}, module: {rules: [{test: /\.vue$/, loader: 'vue-loader', include: [ path.resolve(process.cwd(), 'src'), ] }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(woff|ttf)$/, loader: 'file-loader' }, ] }, plugins: [ new VueLoaderPlugin(), new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'app_host', template: path.resolve(__dirname, './public/index.html'), filename: 'index.html' }), new ModuleFederationPlugin({ name: "app_host", filename: 'remoteEntry.js', exposes: { "./list": "./src/components/list.vue", }, remotes: {/ / statement need to refer to the remote application remote: 'app_remote @ http://localhost:3000/remoteEntry.js}, Shared: [' vue 'and' element - the UI]})], devServer: {hot: true, host: '0.0.0.0, port: 9000}};Copy the code

app.vue

<template> <div> Hello,{{ name }} <Button /> <el-button type="primary"></el-button> </div> </template> <script> export default { components: { // Button: (resolve) => require(["remote/Button"], resolve), Button: () = > import (" remote/Button ")}, the data () {return {name: "main application",}; }}; </script>Copy the code

src/components/list.vue

<template> <div> <el-button type="primary"> </el-button> </div> </template> <script>Copy the code

src/bootstrap.js

import Vue from 'vue';
import App from './app.vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

new Vue({
  render: h => h(App)
}).$mount('#app');
Copy the code

src/index.js

import('./bootstrap');
Copy the code