前端工程化之 webpack <二>

八、webpack开发服务器配置

8.1 本地服务器 webpack-dev-server

  • 目前开发的代码,为了运行需要有两个操作:
    • 操作一:npm run build,编译相关的代码
    • 操作二:通过 live server或者直接通过浏览器,打开 index.html 代码,查看效果
  • 这个过程经常操作会影响开发效率,希望可以做到,当文件发生变化时,可以自动的完成 编译 展示
  • 为了完成自动编译,webpack 提供了几种可选的方式:
    • webpack watch mode
    • webpack-dev-server(常用)
    • webpack-dev-middleware
  • 安装:npm i webpack-dev-server -D
  • 修改配置文件,启动时加上serve参数:"serve": "webpack serve"
  • webpack-dev-server 在编译之后不会写入到任何输出文件,而是将 bundle 文件保留在内存中
    • 事实上webpack-dev-server使用了一个库叫 memfs(memory-fs webpack自己写的)
    • 直接操作内存

8.2 devServer的static

  • 引入了静态文件
  • 如果打包后的资源,又依赖于其他的一些资源,就需要指定从哪里来查找这个内容
  • 设置 static 去查找到这个文件的存在:
  devServer: {
    static:['public','content']//默认配置就是 public
  },

8.3 hotOnly、host 配置

  • liveReload 是当代码编译失败时,是否刷新整个页面:
    • 默认情况下当代码编译失败修复后,会重新刷新整个页面
    • 不希望重新刷新整个页面,可以设置 liveReload 为 false
  • host设置主机地址:
    • 默认值是 localhost
    • 希望其他地方也可以访问,可以设置为 0.0.0.0
  • localhost 和 0.0.0.0 的区别:
    • localhost:本质上是一个域名,通常情况下会被解析成 127.0.0.1
    • 127.0.0.1:回环地址(Loop Back Address),0
      • 正常的数据库包经常 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层
      • 而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;
      • 监听 127.0.0.1 时,在同一个网段下的主机中,通过 ip 地址是不能访问的
    • 0.0.0.0:监听IPV4上所有的地址,再根据端口找到不同的应用程序
      • 监听 0.0.0.0 时,在同一个网段下的主机中,通过ip地址是可以访问的

8.4 port、open、compress

  • port 设置监听的端口,默认情况下是 8080

  • open 是否打开浏览器:

    • 默认值是 false,设置为 true 会打开浏览器
    • 也可以设置为类似于 Google Chrome 等值
  • compress 是否为静态文件开启gzip compression:默认值是 false,可以设置为 true

  • 一般使用默认值

8.5 Proxy代理

  • 设置代理来解决跨域访问的问题

    • 一个api请求是 http://localhost:8888,但是本地启动服务器的域名是 http://localhost:8080,这个时候发送网络请求就会出现跨域的问题
    • 那么可以将请求先发送到一个代理服务器,代理服务器和 API 服务器没有跨域的问题,就可以解决跨域问题
  • 配置

    • target:表示的是代理到的目标地址,比如 /api-lili/moment会被代理到 http://localhost:8888/api-lili/moment
    • pathRewrite:默认情况下, /api-lili 也会被写入到 URL中,如果希望删除,可以使用 pathRewrite
    • changeOrigin:是否更新代理后请求的 headers 中 host 地址
  • 配置 proxy(node 的代理)

    • 网络请求:axios.get('/api/users/list')
    • webpack.config.js
      devServer: {
        static: ["public", "content"],
        liveReload: false,
        proxy: {
          "/api": {
            target: "http://localhost:9000",
            pathRewrite: {
              "^/api": "",
            },
            changeOrigin:true
          },
        },
      },
    

8.6 changeOrigin的解析

  • 服务器设置了校验,就不行了,就需要 changeOrigin
  • 将 host 改成服务器的端口地址
  • 要修改代理请求中的 headers 中的 host 属性:
    • 因为真实的请求,其实是需要通过 http://localhost:8888来请求的
    • 但是因为使用了代码,默认情况下它的值时 http://localhost:8000
    • 如果需要修改,那么可以将 changeOrigin 设置为 true 即可

8.7 historyApiFallback

  • 解决 SPA 页面在路由跳转之后,进行页面刷新时,返回 404 的错误

    • 手动刷新会报错:cannot get /about
  • boolean 值:默认是 false

    • 如果设置为 true ,那么在刷新时,返回404错误时,会自动返回 index.html 的内容
  • object 类型的值,可以配置 rewrites 属性

    • 可以配置 from 来匹配路径,决定要跳转到哪一个页面
  • 事实上 devServer 中实现historyApiFallback功能是通过connect-history-api-fallback库的:可以查看connect-history-api-fallback 文档

  • webpack 和 react 都对应进行了设置

九、webpack 性能优化方案

9.1 分类

  • webpack的性能优化较多,对其进行分类:
    • 优化一:打包后的结果,上线时的性能优化(分包处理[路由懒加载]、减小包体积、CDN 服务器、代码进行压缩、删除无用的代码等)
    • 优化二:优化打包速度,开发或者构建时优化打包速度(exclude、cache-loader 等)
  • 大多数情况下,会更加侧重于优化一,这对于线上的产品影响更大
  • 在大多数情况下 webpack 都做好了该有的性能优化
    • 配置 mode 为 production 或者 development 时,默认 webpack 的配置信息
    • 但是也可以针对性的进行项目优化

9.2 代码分离

  • 所有的东西放在一个包中不方便管理
  • bundle.js 包非常大(路由懒加载)
    • 首屏渲染速度大大降低
    • 长时间用户看到的都是一个空白页面
    • 分包处理(prefetch)
    • SSR
      • 加快首屏渲染速度
      • 增加 SEO 优化
  • 代码分离(Code Splitting)是 webpack 一个非常重要的特性
    • 主要的目的是将代码分离到不同的 bundle 中,之后可以按需加载,或者并行加载这些文件
    • 默认情况下,所有的 JavaScript 代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度
    • 代码分离可以分出更小的 bundle ,以及控制资源加载优先级,提供代码的加载性能
  • Webpack 中常用的代码分离有三种
    • 入口起点:使用 entry 配置手动分离代码
      • 设置多入口
    • 防止重复:使用 Entry Dependencies 或者 SplitChunksPlugin 去重和分离代码
    • 动态导入:通过模块的内联函数调用来分离代码

9.3 多入口起点

  • 配置一个 index.js 和 main.js 的入口,分别有自己的代码逻辑
  entry: {
    main: "./src/main.js",
    index:'./src/index.js'
  },
  • 打包出来的东西放在不同的文件里面

    • 使用占位符
    • 手动进行分包
      // placeholder占位
        filename: "[name]-bundle.js",
    

9.4 Entry Dependencies(入口依赖)

  • 假如 index.js 和 main.js 都依赖两个库:lodash、dayjs
    • 如果单纯的进行入口分离,那么打包后的两个 bunlde 都有会有一份 lodash 和 dayjs
    • 打包了两份
    • 可以对他们进行共享
  entry: {
    main: {
      import: "./src/main.js",
      dependOn: shared,
    },
    index: { import: "./src/index.js", dependOn: shared },
    shared: ["axios", "dayjs"],
  },

9.5 动态导入

  • 两种实现动态导入的方式
    • 第一种,使用 ECMAScript 中的 import() 语法来完成,也是目前推荐的方式
    • 第二种,使用 webpack 遗留的 require.ensure ,目前已经不推荐使用
  • 模块 bar.js
    • 该模块希望在代码运行过程中来加载它(比如判断一个条件成立时加载)
    • 因为并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的 js 文件
    • 这样可以保证不用到该内容时,浏览器不需要加载和处理该文件的 js 代码
    • 这个时候就可以使用动态导入
  • 使用动态导入 bar.js
    • 在 webpack 中,通过动态导入获取到一个对象
    • 真正导出的内容,在该对象的default属性中,所以需要做一个简单的解构
    • 使用:btn1.onclick = function(){import(/*webpackChunName:"about"*/'./router/about').then(res=>{log(res.about() res.default())})}
      • 魔法注释指定 name
    • 模块导出:export {about} export default about
  • 配置:动态导入的文件命名
 output: {
    path: path.resolve(__dirname, "./build"),
    // placeholder占位
    filename: "[name]-bundle.js",
    //  单独针对分包的文件进行命名
    // id:有不同的设置方法
    // name:可以进行魔法注释
    // chunkFilename: "[id]_[name]_chunk.js",
    chunkFilename: "[id]_[name]_chunk.js",
  },

## 9.6 SplitChunks

  • 另外一种分包的模式是 splitChunk,它底层是使用 SplitChunksPlugin 插件来实现的

    • 该插件 webpack 已经默认安装和集成,并不需要单独安装和直接使用该插件
    • 只需要提供 SplitChunksPlugin 相关的配置信息
  • Webpack 提供了 SplitChunksPlugin 默认的配置,也可以手动来修改它的配置

    • 默认配置中,chunks 仅仅针对于异步(async)请求,可以设置为 initial 或者 all
      // 优化配置
      optimization: {
        splitChunks: {
          // chuncks:'async'默认为async
          chuncks:'all' //不管是动态还是第三方都会单独分包
        }
      }
    
  • vue /react 都是:主包、懒加载、第三方库

9.7 SplitChunks 自定义配置解析

  • Chunks:
    • 默认值是 async
    • 另一个值是 initial,表示对通过的代码进行处理
    • all 表示对同步和异步代码都进行处理
  • minSize:
    • 拆分包的大小, 至少为 minSize
    • 如果一个包拆分出来达不到 minSize,那么这个包就不会拆分
  • **maxSize:**将大于 maxSize 的包,拆分为不小于 minSize 的包
  • cacheGroups:
    • 用于对拆分的包就行分组,比如一个 lodash 在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打包
    • name 属性:拆分包的 name 属性
    • test 属性:匹配符合规则的包
    • filename 属性:拆分包的名称,可以自己使用 placeholder 属性
  // 优化配置
  optimization: {
    splitChunks: {
      // chuncks:'async'默认为async
      chunks: 'all', //不管是动态还是第三方都会单独分包,
      // 当一个包大于指定的大小时,继续进行拆包
      maxSize: 20000,
      // 将包拆分成不能小于这个大小
      minSize: 10000,//默认值是20kb
      cacheGroups:{
        vendors: {
          // window 上 / 和 mac 上面/ 是不一样的
          // [] 中列出所有的可能性
          // test:/[\\/]node_modules[\\/]/,完整写法
          test:/node_modules/,//只要路径有node_modules
          filename:'[name]_vendors.js'
        }
      }
    }
  }

9.8 optimization.chunkIds 配置

  • 作用:optimization.chunkIds 配置用于告知 webpack 模块的 id 采用什么算法生成
  • 有三个比较常见的值
    • natural:按照数字的顺序使用 id
    • named:development 下的默认值,一个可读的名称的 id
    • deterministic:确定性的,在不同的编译中不变的短数字 id
      • 在 webpack4 中是没有这个值的
      • 那个时候如果使用 natural ,那么在一些编译发生变化时,就会有问题
  • 最佳实践
    • 开发过程中,推荐使用 named
    • 打包过程中,推荐使用 deterministic
  optimization: {
    // 设置生成的chunkId的算法
    // development:named
    // production:deterministic(确定性)
    // webpack4使用的是:natural
    chunkIds:'named',
  }

9.9 Prefetch 和 Preload

  • webpack v4.6.0+ 增加了对预获取和预加载的支持
  • 在声明 import 时,使用内置指令,来告知浏览器
    • prefetch( 预获取 ):将来某些导航下可能需要的资源
    • preload( 预加载 ):当前导航下可能需要资源
  • prefetch VS preload 指令有许多不同之处
    • preload chunk 会在父 chunk 加载时,以并行方式开始加载;prefetch chunk 会在父 chunk 加载结束后开始加载
    • preload chunk 具有中等优先级,并立即下载;prefetch chunk 在浏览器闲置时下载
    • preload chunk 会在父 chunk 中立即请求,用于当下时刻;prefetch chunk 会用于未来的某个时刻
  • 是在代码中进行配置的
btn1.onclick = function () {
  import(
    /* webpackChunkName:"about" */
    /* webpackPrefetch:true*/
    "./router/about"
  ).then((res) => {
    res.about();
    res.default();
  });
};

9.10 CDN

  • 内容分发网络(Content Delivery Network 或 Content Distribution Network,缩写:CDN)

    • 通过相互连接的网络系统,利用最靠近每个用户的服务器
    • 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户
    • 来提供高性能、可扩展性及低成本的网络内容传递给用户
  • 在开发中,使用 CDN 主要是两种方式

    • 方式一:打包的所有静态资源,放到CDN服务器,用户所有资源都是通过 CDN 服务器加载的

      • 购买
        • 目前阿里、腾讯、亚马逊、Google 等都可以购买 CDN 服务器
        • 可以直接修改 publicPath,在打包时添加上自己的 CDN 地址
        • 要求很高的用户体验
        • 主要看公司的需求
      • 脚本地址都改成 CDN 服务器
        output: {
          path: path.resolve(__dirname, "./build"),
          // placeholder占位
          filename: "[name]-bundle.js",
          clean: true,
          publicPath: "http://lili.com/",
        },
      
    • 方式二:一些第三方资源放到 CDN 服务器上

9.11 第三方库的 CDN 服务器

  • 通常一些比较出名的开源框架都会将打包后的源码放到一些比较出名的、免费的 CDN 服务器上
    • 国际上使用比较多的: unpkg、JSDelivr、cdnjs
    • 国内比较好用的 CDN :bootcdn
  • 配置不需要进行打包,然后使用 cdn 引入
  // 排除某些包不需要进行打包
  externsal: {
    // key:排除的框架的名称
    // value:从 cdn地址 中拿到的名字 (可能与框架不同 )
    react: "React",
    axios: "axios",
  },

9.12 shimming

  • 某一类功能的统称

  • shimming 翻译为垫片,相当于给代码填充一些垫片来处理一些问题

    • eg:依赖一个第三方的库,这个第三方的库本身依赖 lodash,但是默认没有对 lodash 进行导入(认为全局存在 lodash ),那么就可以通过 ProvidePlugin 来实现 shimming 的效果
  • webpack 并不推荐随意的使用 shimming

    • Webpack 背后的整个理念是使前端开发更加模块化
    • 需要编写具有封闭性的、不存在隐含依赖(比如全局变量)的彼此隔离的模块
  • ProvidePlugin 能够帮助在每个模块中,通过一个变量来获取一个 package

  • 如果 webpack 看到这个模块,将在最终的 bundle 中引入这个模块

  • ProvidePlugin 是 webpack 默认的一个插件,所以不需要专门导入

  • 注意 axios 使用时候是一个 module!需要使用 default ,才是默认导出的对象

  plugins: [
    new ProvidePlugin({
      dayjs: "dayjs",
      axios: ["axios", "default"],
    }),
  ],
  • 开发时候并不推荐这样使用

9.13 MiniCssExtractPlugin

  • 作用:将 css 提取到一个独立的 css 文件中,该插件需要在 webpack4+ 才可以使用

  • 使用

    • 安装:npm i style-loader css-loader -D
         {
            test: /\.css$/,
            use: [
              "style-loader",
              "css-loader"
            ]
          }
    
    • 安装 mini-css-extract-pluginnpm install mini-css-extract-plugin -D
    • 配置插件
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
         new MiniCssExtractPlugin({
          // css/   放在单独的文件夹里面
          filename: "css/[name].css",
          chunkFilename: "css/[name]_chunk.css",
        }),
    
    • 配置规则
      {
            test: /\.css$/,
            use: [
              //'style-loader',内联的方式,开发阶段
              MiniCssExtractPlugin.loader, //提取到 css 文件当中,通过 link 进行引入,生产阶段
            ],
          },
    

9.14 Hash、ContentHash、ChunkHash

  • 新建一个项目

    • 使用 pnpm:npm install pnpm -g
    • 初始化:pnpm init
    • 安装:pnpm add webpack webpack-cli -D
    • 建立 webpack.config.js
    • 配置脚本:"build": "webpack"
  • 在给打包的文件进行命名的时候,会使用 placeholder 进行占位

    • hash、chunkhash、contenthash
    • hash 本身是通过 MD4 的散列函数处理后,生成一个 128 位的 hash 值( 32 个十六进制)
  • hash 值的生成和整个项目有关系

    • 项目中文件发生变化,hash 值都会发生变化
  • chunkhash 可以有效的解决上面的问题,它会根据不同的入口来解析来生成 hash 值

    • 和他有关的整个包有关系
    • 这个包中的任何一个元素变化,文件名就会发生变化
  • contenthash 表示生成的文件 hash 名称,只和内容有关系

    • 只有自己的内容发生变化的时候,自己对应分出来的那个包才会有变化
  • 只要用到 hash 尽量使用 contenthash

9.15 DLL 库

  • 动态链接库(Dynamic Link Library),是为软件在 Windows 中实现共享函数库的一种实现方式
  • webpack 中也有内置 DLL 的功能,可以将能够共享,并且不经常改变的代码,抽取成一个共享的库
  • 这个库在之后编译的过程中,会被引入到其他项目的代码中
  • 现在已经不再使用
  • 使用
    • 第一步:打包一个 DLL 库
    • 第二步:项目中引入 DLL 库
  • 升级到 webpack4 之后,React 和 Vue 脚手架都移除了 DLL 库

9.16 Terser

  • Terser
    • 一个J avaScript 的**解释(Parser)、Mangler(绞肉机)/Compressor(压缩机)**的工具集
    • 早期会使用 uglify-js 来压缩、丑化 JavaScript 代码,但是目前已经不再维护,并且不支持 ES6+ 的语法
    • Terser 是从 uglify-es fork 过来的,并且保留它原来的大部分 API 以及适配 uglify-es和 uglify-js@3 等
  • 帮助压缩、丑化代码,让bundle变得更小
  • Terser是一个独立的工具,可以单独安装
# 全局安装
npm install terser -g
# 局部安装
npm install terser -D
  • 使用:若需要进一步的优化,可以使用 -c xxxxx -m yyyyy

    • Compress option 和 Mangle(乱砍) option
    terser [input files] [options]
    # 举例说明
    npx terser js/file1.js -o foo.min.js -c -m
    
  • Compress 和 Mangle 的 options

    • Compress option
      • arrows:class 或者 object 中的函数,转换成箭头函数
      • arguments:将函数中使用 arguments[index] 转成对应的形参名称
      • dead_code移除不可达的代码(tree shaking)死代码
    • Mangle option
      • toplevel:默认值是 false,顶层作用域中的变量名称,进行丑化(转换)
      • keep_classnames:默认值是 false,是否保持依赖的类名称
      • keep_fnames:默认值是 false,是否保持原来的函数名称
    • 命令行使用
  npx terser ./src/abc.js -o abc.min.js -c 
  arrows,arguments=true,dead_code -m 
  // 顶层作用域中的变量名称,进行丑化 保持 类的名字 函数的名字
  toplevel=true,keep_classnames=true,keep_fnames=true
  • Terser 在 webpack 中配置

    • 真实开发中,不需要手动的通过 terser 来处理代码,可以直接通过 webpack 来处理

      • 在 webpack 中有一个 minimizer 属性,在 production 模式下,默认就是使用 TerserPlugin 来处理我们的代码的
        • 如果对默认的配置不满意,也可以自己来创建 TerserPlugin 的实例,并且覆盖相关的配置
    • 首先,需要打开 minimize,让其对代码进行压缩(默认production模式下已经打开了)

    • 其次,可以在minimizer创建一个 TerserPlugin

      • extractComments:默认值为 true,表示会将注释抽取到一个单独的文件
        • 在开发中,不希望保留这个注释时,可以设置为 false
        • parallel:使用多进程并发运行提高构建的速度,默认值是 true
          • 并发运行的默认数量: os.cpus().length - 1
          • 也可以设置自己的个数,但是使用默认值即可
        • terserOptions:设置我们的terser相关的配置
          • compress:设置压缩相关的选项
          • mangle:设置丑化相关的选项,可以直接设置为 true
          • toplevel:顶层变量是否进行转换
          • keep_classnames:保留类的名称
          • keep_fnames:保留函数的名称

9.17 CSS 的压缩

  • CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等
  • CSS的压缩可以使用另外一个插件:css-minimizer-webpack-plugin
  • css-minimizer-webpack-plugin是使用 cssnano 工具来优化、压缩 CSS(也可以单独使用)
  • 安装:npm install css-minimizer-webpack-plugin -D
  • 配置
minimizer:[
	new TerserPlugin({})
]

9.18 抽取

  • 创建 config 文件夹

    • comm.config.js: entry、output、resolve、module(loader)、plugins(垫片、html)

      • 导出了一个对象,webpack 导入对象
      • 也可以导出一个函数
      const path = require("path");
      const { merge } = require("webpack-merge");
      
      const devConfig = require("./dev.config");
      const prodConfig = require("./prod.config");
      const commonConfig =  {
        entry: "./src/main.js",
        output: {
          clean: true,
          path: path.resolve(__dirname, "../build"),
          filename: "[name]_[hash]_bundle.js",
          chunkFilename: "[chunkhash]_chunk.js",
        },
        // 解决路径后缀名
        resolve: {
          extensions: [".js", ".json", ".wasm", ".jsx", ".ts"],
        },
        // module
        // plugins
      };
      
      // webpack 允许导出一个函数
      module.exports = function (env) {
        const isProduction = env.production;
        let mergeConfig = isProduction ? prodConfig : devConfig;
        // if (isProduction) {
        //   mergeConfig = prodConfig;
        // } else {
        //   mergeConfig = devConfig;
        // }
        console.log(env);
        return merge(mergeConfig,commonConfig);
      };
      
      
      
  • package.json 脚本中配置参数

 "scripts": {
    "build": "webpack --config ./config/comm.config.js --env production",
    "serve": "webpack serve --config ./config/comm.config.js --env development"
  },
  • dev.config.js:mode、devServer、‘css-loader’
const path = require("path");

module.exports = {
  mode: "development",
  // 本地 服务器
  devServer: {
    static: ['public', 'content'],
    port: 3000,
    compress: true,
    proxy: {
      '/api': {
        target: "http://localhost:9000",
        pathRewrite: {
          '^/api':''
        },
        changeOrigin:true
      }
    },
    historyApiFallback:true
  },
}


  • prod.config.js:optimization、plugins(css提取)、MiniCssExtractPlugin.loader
const path = require("path");

module.exports = {
  mode: "production",
// 分包优化的配置放在生产环境
  // optimization
}

  • 把多个 webpack 配置进行合并:npm i webpack-merge -D

  • MiniCssExtractPlugin.loader、css-loader

    • 通过在 comm.config.js 的一个变量解决
    const path = require("path");
    const { merge } = require("webpack-merge");
    
    const devConfig = require("./dev.config");
    const prodConfig = require("./prod.config");
    const getCommonConfig = function (isProduction) {
      return {
        entry: "./src/main.js",
        output: {
          clean: true,
          path: path.resolve(__dirname, "../build"),
          filename: "[name]_[hash]_bundle.js",
          chunkFilename: "[chunkhash]_chunk.js",
        },
        // 解决路径后缀名
        resolve: {
          extensions: [".js", ".json", ".wasm", ".jsx", ".ts"],
        },
        // module
        module: {
          rules: [
            {
              test: /\.css$/,
              use: [isProduction ? MiniCssExtractPlugin.loader : "css-loader", "css-loader"],
            },
          ],
        },
        // plugins
      };
    };
    
    // webpack 允许导出一个函数
    module.exports = function (env) {
      const isProduction = env.production;
      let mergeConfig = isProduction ? prodConfig : devConfig;
      // if (isProduction) {
      //   mergeConfig = prodConfig;
      // } else {
      //   mergeConfig = devConfig;
      // }
      console.log(env);
      return merge(mergeConfig, getCommonConfig(isProduction));
    };
    
    
    • 抽取顺序
      • 将配置文件导出的是一个函数,而不是一个对象
      • 从上到下查看所在的配置属性应该属于哪一个文件:comm、dev、prod
      • 针对单独的配置文件进行定制化
        • css 加载使用不同的 loader 可以根据 isProduction 进行动态获取

9.19 Tree Shaking

  • 概念
    • Tree Shaking 是一个术语,在计算机中表示消除死代码(dead_code)
    • 最早的想法起源于 LISP,用于消除未调用的代码(纯函数无副作用,可以放心的消除,这也是为什么要求在进行函数式编程时,尽量使用纯函数的原因之一)
    • 后来 Tree Shaking 也被应用于其他的语言: JavaScript、Dart
  • JavaScript 的 Tree Shaking
    • 对 JavaScript 进行 Tree Shaking 是源自打包工具 rollup
    • Tree Shaking 依赖于 ES Module 的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)
    • webpack2 正式内置支持了 ES2015 模块和检测未使用模块的能力
    • 在webpack4 正式扩展了这个能力,并且通过 package.json 的 sideEffects 属性作为标记,告知 webpack 在编译时,哪些文件可以安全的删除掉
    • webpack5 中,也提供了对部分 CommonJS 的 tree shaking 的支持

9.20 webpack 实现 Tree Shaking

  • usedExports:Tree Shaking 的核心,通过标记某些函数是否被使用,之后通过 Terser来进行优化

    • 将 mode 设置为 development 模式,会自动开启 useExportsL 为 true
    • **usedExports 设置为 true **时,会有一段注释:unused harmony export mul
      • 告知 Terser 在优化时,可以删除掉这段代码
    • 将 minimize 设置 true,必须结合 terser 进行使用
      • usedExports 设置为 false 时,mul 函数没有被移除掉
      • usedExports 设置为 true 时,mul 函数有被移除掉
  • sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用

    • sideEffects 用于告知 webpack compiler 哪些模块是有副作用的

      • 副作用:里面的代码有执行一些特殊的任务,不能仅仅通过export来判断这段代码的意义
        • 正常使用
        • 只导入模块,但是没有引入任何的内容:import './demo'
        • import css 文件
    • 在 package.json 中设置 sideEffects 的值

      • 将 sideEffects 设置为 false:告知 webpack 可以安全的删除未用到的 exports
      • 如果有一些希望保留,存在副作用,可以设置为数组
      • 在 package.json 中:
      "sideEffects":[
          "./src/util/format.js",
          "*.css"//这个东西不能 treeShaking
      ]
      
    • 比如有一个 format.js、style.css 文件

      • 该文件在导入时没有使用任何的变量来接受
      • 那么打包后的文件,不会保留 format.js、style.css 相关的任何代码
    • 在编写模块的时候,尽量编写纯模块

  • 最佳方案

    • 在 optimization 中配置 usedExports为true ,来帮助 Terser 进行优化
    • 在 package.json 中配置 sideEffects ,直接对模块进行优化

9.21 CSS 实现 Tree Shaking

  • 使用一个库来完成 CSS 的 Tree Shaking :PurgeCSS,也是一个帮助删除未使用的 CSS 的工具
  • 安装 PurgeCss的webpack 插件:npm install purgecss-webpack-plugin -D
    • 和 MiniCssExtractPlugin 有冲突,因为是两方开发的
    • 降版本安装 mini-css-extract-pluginnpm install mini-css-extract-plugin@1.3.6 -D
  • 一般情况下,标签选择器不做 tree shaking
  • 需要和 mini-css-extract-plugin 一起使用
  • 配置 PurgeCss
const path = require("path");
const glob = require("glob"); //node 提供的模块:但是没有内置,需要自己安装:`npm i glob -D`
const { PurgeCSSPlugin } = require("purgecss-webpack-plugin");
module.exports = {
  mode: "production",
  // 分包优化的配置放在生产环境
  // optimization
  plugins: [
    // 对 css 进行 tree shaking
    new PurgeCSSPlugin({
      // 立即去找所有文件夹下面的所有文件
      // nodir:不是文件夹
      paths: glob.sync(`${path.resolve(__dirname, "./src")}/**/*`, { nodir: true }),
      // 白名单
      safelist: function () {
        return {
          standard: ["body"],
        };
      },
    }),
  ],
};

9.22 Scope Hoisting

  • 概念

    • Scope Hoisting 从 webpack3 开始增加的一个新功能
    • 功能是对作用域进行提升,并且让 webpack 打包后的代码更小、运行更快
  • 默认情况下 webpack 打包会有很多的函数作用域,包括一些(比如最外层的)IIFE

    • 无论是从最开始的代码运行,还是加载一个模块,都需要执行一系列的函数
    • Scope Hoisting 可以将函数合并到一个模块中来运行
  • 配置该插件,会查看哪些代码可以进行作用域的提升

const path = require("path");
const webpack = require('webpack')
module.exports = {
  plugins: [
    // 作用域提升
    new webpack.optimize.ModuleConcatenationPlugin()
  ],
  mode: "development",
}
  • webpack 已经内置了对应的模块
    • 在 production 模式下,默认这个模块就会启用
    • 在 development 模式下,需要自己来打开该模块

9.23 HTTP 压缩

  • HTTP 压缩是一种内置在 服务器 和 客户端 之间的,以改进传输速度和带宽利用率的方式
  • 已经删得不能再删了,但是还是想要压缩
  • 只是压缩单个文件,可以采用压缩算法:gzip => main-bundle.js.gz
    • 变成压缩文件后,浏览器可以运行吗?
    • 可以!
      • 浏览器对 gz 文件进行解压
      • 执行 js 代码
  • 压缩的流程
    • HTTP 数据在服务器发送前就已经被压缩了(可以在 webpack 中完成)
    • 兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式
    • 服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器:Content-Encoding

9.24 目前的压缩格式

  • compress – UNIX 的 “compress” 程序的方法(历史性原因,不推荐大多数应用使用,应该使用 gzip 或 deflate)
  • deflate:基于deflate算法(定义于RFC 1951)的压缩,使用 zlib 数据格式封装
  • gzip :GNU zip格式(定义于RFC 1952),是目前使用比较广泛的压缩算法
  • br :一种新的开源压缩算法,专为 HTTP 内容的编码而设计

9.25 Webpack对文件压缩

  • 使用 CompressionPluginnpm install compression-webpack-plugin -D

  • 配置

new CompressionPlugin({
	test:/\.(css|js)$/,// 匹配哪些文件需要压缩
	minRatio:0.7,// 至少的压缩比例
	algorithm:"gzip",// 压缩算法
})

9.26 HTML文件中代码的压缩

  • 压缩得很彻底了,剩下 html 文件

  • 使用了 HtmlWebpackPlugin 插件来生成 HTML 的模板,这个插件还有一些其他的配置

    • inject:设置打包的资源插入的位置

      • true

      • false

      • body

      • head

    • cache:设置为 true ,只有当文件改变时,才会生成新的文件(默认值也是true)

    • minify:默认会使用一个插件 html-minifier-terser

      • 生产环境进行压缩

      • 开发环境不压缩

      • 设置变量:isProduction

          new HtmlWebpackPlugin({
            // 配置模板
            template: "./index.html",
            minify: isProduction
              ? {
                  // 压缩的时候移除注释
                  removeComments: true,
                  // 移除属性:空属性么有意思
                  removeEmptyAttributes: true,
                  // 移除默认属性:可要可不要
                  removeRedundantAttributes: true,
                  // 折叠空行
                  collapseWhitespace: true,
                  // 压缩内联的 css
                  minifyCSS: true,
                  // 压缩 js
                  minifyJS: {
                    mangle: {
                      toplevel: true,
                    },
                  },
                }
              : false,
          }),
      

9.27 打包的时间分析

  • 借助于一个插件:speed-measure-webpack-plugin

    • 能够看到每一个 loader、每一个 Plugin 消耗的打包时间,
  • 安装:npm install speed-measure-webpack-plugin -D

const SpeedMeasurePlugin = require("speed-measure-plugin");
const smp = new SpeedMeasurePlugin();
smp.wrap(finalConfig);
  • 可以排除掉 node_modules:exclude:/node_modules/

9.28 打包后文件分析

  • 生成一个 stats.json 的文件

    • "buiebpack ld:stats": "w--config ./config/webpack.common.js --env production --profile --json=stats.json",
    • 通过执行 npm run build:status 可以获取到一个 stats.json 的文件
      • 利用 analyse-master
      • 安装:
        • yarn --version
        • npm i tarn -g
        • yarn
        • grant dev/npm run dev
        • npm i grunt -g
    • 选择 stats.json文件进行上传
    • 进行分析
  • 使用 webpack-bundle-analyzer 工具

    • 安装:npm install webpack-bundle-analyzer -D
    • 在webpack配置中使用该插件
    const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
     // 对打包后的结果进行分析
        new BundleAnalyzerPlugin(),
    
    • 在打包 webpack 的时候,这个工具帮助打开一个 8888 端口上的服务,可以图形化直接的看到每个包的大小
      • 比如有一个包时通过一个 Vue 组件打包的,但是非常的大,那么可以考虑是否可以拆分出多个组件,并且对其进行懒加载
      • 比如一个图片或者字体文件特别大,是否可以对其进行压缩或者其他的优化处理
  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值