Webpack 5 新特性尝鲜

安装与启动

Webpack 5 发布已经有一段时间了,很多小伙伴都在考虑要不要升级,有没有升级的必要,不知道升级后有哪些改变;
今天我们就来做个对比看看,webpack5 带来了那些全新的改变;
没有对比就没有伤害,为了更好地伤害 webpack 4 , 我们使用 webpack4 和 webpack 5 分别构建一个 React 项目来做对比:

mkdir webpack4 
mkdir webpack5 
# 分别执行 初始化命令 
npm init -y 

创建文件 /src/index.js, /src/App.js, /src/index.html

React 代码示例

index.js

import React from "react"
import ReactDom from "react-dom"
import App from "./App"
ReactDom.render(<App/>,document.getElementById('root'))

App.js

import React from "react"
const App = ()=>{
  return (
      <div>
        <h1> Webpack4 or Webpack5 </h1>
      </div>
  )
}
export default App;

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <!-- 加一行注释 -->
  <div id="root"></div>
</body>
</html>
安装与启动
webpack4
// webpack4 
npm install webpack@4 webpack-cli@3  html-webpack-plugin css-loader style-loader babel-loader @babel/core  @babel/preset-env  @babel/preset-react  -D 
npm install react react-dom 

因为仓库中目前默认就已经是 webpack5 了,所以,想要安装 webpack4, 我们需要加上 @4 的版本号;

webpack5
// webpack5 
npm install webpack webpack-cli html-webpack-plugin css-loader style-loader babel-loader @babel/core  @babel/preset-env  @babel/preset-react  -D 
npm install react react-dom 
基础配置 webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  // entry 入口,output出口,module模块,plugins 插件  mode工作模式,devServer开发服务器
  //  mode 工作模式
  mode: 'development', // production  、 development、none
  // 入口 
  entry:'./src/index.js',
  //  出口 
  output:{
    filename:'./bundle.js',
    path:path.resolve(__dirname,'dist')
  },
  // 模块 
  module:{
    rules:[
      {
        test:/\.js$/,
        exclude:/node_modules/,
        use:[
          {
            loader:'babel-loader',
            options:{
              presets:[
                '@babel/preset-env',
                '@babel/preset-react'
              ]
            }
          }
        ]
      },
    ]
  },
  //  插件 
  plugins:[
    new HtmlWebpackPlugin({
      template:'./src/index.html'
    })
  ]
}

启动命令的区别

先安装 npm install webpack-dev-server -D
配置服务器:

//  服务器
  devServer:{
    port:3004,
    open:true
  },

webpack 4 : webpack4/package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack-dev-server"
  },

webpack 5 : webpack5/package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build":"webpack",
    "start":"webpack serve"
  },

资源模块处理

https://webpack.docschina.org/guides/asset-modules/#source-assets
资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
在 webpack 5 之前,通常使用:

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

  • asset/resource 发送一个单独的文件并导出 URL(之前通过使用 file-loader 实现)
  • asset/inline 导出一个资源的 data UR(之前通过使用 url-loader 实现)
  • asset/source 导出资源的源代码(之前通过使用 raw-loader 实现)
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择(之前通过使用 url-loader,并且配置资源体积限制实现)

webpack4 :

// 模块 
  module:{
    rules:[
      {
        test:/\.js$/,
        exclude:/node_modules/,
        use:[
            …………
        ]
      },
      {
        test:/\.(png|jpg|gif)$/,
        // 安装 url-loader  file-loader 
        loader:'url-loader',
        options:{
          // 小于 8KB 转 base64 
          limit:8*1024
        }
      }
    ]
  },

webpack5 :

// 模块 
  module:{
    rules:[
      {
        test:/\.js$/,
        exclude:/node_modules/,
        ……………………
      },
      {
        test:/\.(png|jpg|gif)$/,
        // 通用资源类型
        type:'asset',
        // 现在,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:
        // 小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
        // 自定义设置
        parser:{
          dataUrlCondition:{
            maxSize:8*1024
          }
        }
      }
    ]
  },

文件缓存

https://webpack.docschina.org/configuration/other-options/#cache
缓存生成的 webpack 模块和 chunk,能够改善构建速度。
cache 会在 开发模式 下被设置成 type: 'memory' 而且在 生产模式 中被禁用。
cache: true 与 cache: { type: 'memory' } 配置作用一致。

cache.type

cache.type 将 cache 类型设置成内存或者文件系统。 'memory' | 'filesystem'
memory 选项很简单,它会告诉 webpack 将内容存放在内存中并且不允许额外的配置;
filesystem 选项,使用文件缓存系统;

cacheDirectory

cacheDirectory 定义缓存目录, 默认为 node_modules/.cache/webpack。

cache.cacheDirectory 选项仅当 cache.type 被设置成 filesystem 才可用。

webpack.config.js

  //  mode 工作模式
  mode:'development',
  cache:{
    type:'filesystem',
    // 默认缓存到 node_modules/.cache/webpack 中 
    // 也可以自定义缓存目录
    // cacheDirectory:path.resolve(__dirname,'node_modules/.cac/webpack')
  }
image-20210121211424446.png

即使内容修改,增量编译的缓存效果也很明显

更好的 Tree Shaking

https://webpack.docschina.org/guides/tree-shaking/
Tree Shaking 技术,也被称为 “树摇” ,没错,翻译的就是这么直接,意思也很简单,未使用的导出内容不会被打包生成;它依赖于 ES2015 模块语法的 静态结构 特性,例如 importexport。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。

为了更好说明这个原理,我做了一个动画,全网首发的动画效果,简单解释一下,有两个模块四个方法,在模块 x 中,使用了 B 方法和从模块Y中导入的 C 方法,而 X 模块中自己的 A 和模块 Y 中的 D 方法,并没有使用,虽然定义了,因为没有在任何地方使用过,因此,在 “摇树” 过程中,就会被 “摇掉”;

Snipaste_2021-01-20_14-00-99.gif

在 webpack 中如何使用呢?其实很简单,只要将 mode 工作模式改为 production 就会自动开启;
而如果想要感受这个树摇带来的震动酥麻酸爽的过程,我们也可以使用手动配置的方式来自行选择,首先需要将 mode 工作模式改为 none,意思就是不做任何优化,全部使用配置的方式,如何配置呢?添加 optimization.usedExports 和 optimization.minimize 选项,意思就是开启树摇及压缩

//  mode 工作模式
  mode: 'none', // production、development、none
  // production 生产环境,默认优化打包
  // none 不做任何操作

  // usedExports:true 开启优化(树摇但保留代码)
  // minimize:true 开启压缩 (删除未使用代码)
  optimization:{
    usedExports:true,
    minimize:true
    // innerGraph: true,
  }

接下来,我们再使用简单代码做对比:
index.js

import * as m1 from "./m1";
console.log(m1.m2.nu1)

m1.js

import * as m2 from './m2'
export function fun1(){
  console.log('1--11',m2.c);
}
export function fun2(){
  console.log('1--22')
}
export {m2}

m2.js

export function fun3(){
  console.log('2--33');
}
export function fun4(){
  console.log('2--44')
}
export const nu1 = 456
export const nu2 = 789
image-20210121213316812.png

相同的代码,在webpack 4 的打包结果中,我们能看到不仅代码量大,而且还有 i=789 这个多余的代码,反观 webpack 5 的打包结果,简洁到难以置信;

模块联邦

多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。
这通常被称作微前端
为了更好地说明这个原理,我做了一个动画,全球首发的动画效果

Snipaste_2021-01-20_14-0099.gif

// ======insex.js========
import React from "react"
import ReactDom from "react-dom"
import App from "./App"
ReactDom.render(<App/>,document.getElementById('root'))
// ======App.js===========
import React from "react"
import User from "./User"
let App = () => {
    return (
        <div>
            <h3>webpack55</h3>
            <User/>
        </div>
    )
}
export default App;
// ===== User.js==========
import React from "react"

const User = ()=>{
  return (
    <div>
      UserList
    </div>
  )
}
export default User
导出模块
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin")
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;
…………
  //  插件 
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new ModuleFederationPlugin({
      // 模块名字
      name: 'remote', //导入时使用名称标注
      // 编译后的模块文件名,导入时使用
      filename: 'remoteEntry.js',
      // 导出模块 关键字与模块名
      exposes: {
        // "key导入时使用的关键字" : "对应模块文件"
        "./Us": './src/User.js'
      }
    }),
  ],
导入模块
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;

…………

  //  插件 
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new ModuleFederationPlugin({
      name:'user:55',
      // 导入外部模块
      remotes:{
        // 导入别名:关键字@地址/导出文件名
        remoteHost:"remote@http://127.0.0.1:3055/remoteEntry.js"
      }
    })
  ],
import React from "react"
// React.lazy(() => import("对应导入别名/对应导出关键字"))
const Us = React.lazy(() => import("remoteHost/Us"))

let App = () => {
    return (
        <div>
            <h3>hello web webpack 5</h3>
            <p>联邦模块 webpack5 </p>
            {/* 展示导入模块内容 异步加载 */}
            <React.Suspense fallback="Loading app">
                <Us />
            </React.Suspense>
        </div>
    )
}
export default App;

在 ModuleFederationPlugin 实例化的时候传入参数 options 的字段说明:

// 模块名字
name: 'remote', //导入时使用名称标注
// 编译后的模块文件名,导入时使用
filename: 'remoteEntry.js',
// 导出模块 关键字与模块名
exposes: {
    // "key导入时使用的关键字" : "对应模块文件"
    "./Us": './src/User.js'
}
 // 导入外部模块
remotes:{
   // 导入别名:关键字@地址/导出文件名
   remoteHost:"remote@http://127.0.0.1:3055/remoteEntry.js"
}

还有就是 exposes 和 remotes 的字段小伙伴们也要注意,
exposes 的暴露字段要写上 ./name
remotes 的字段跟暴露模块的 name 保持一致,里面别名的定义也要一致
最后,两个应用同时启动,就会发现最终你要的应用就把其他应用的模块也引入进来了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容