Jason Lee
首页
  • 学习笔记

    • 常用资源
    • html&css
    • webpack
    • vue2.x
    • vue3
    • typescript入门
    • 工程化实践
  • css
  • javascript
  • es6常问
  • 手写代码
  • 错误监控
  • webpack
  • vue框架
  • 浏览器相关
  • 计算机网络
  • 数据结构和算法
  • 学习笔记
工具
首页
  • 学习笔记

    • 常用资源
    • html&css
    • webpack
    • vue2.x
    • vue3
    • typescript入门
    • 工程化实践
  • css
  • javascript
  • es6常问
  • 手写代码
  • 错误监控
  • webpack
  • vue框架
  • 浏览器相关
  • 计算机网络
  • 数据结构和算法
  • 学习笔记
工具
  • css
  • javascript
  • 错误监控
  • ES6常问
  • 手写代码
  • webpack
    • webpack 的构建流程是什么
    • webpack 的热更新原理 HMR 实现原理
    • webpack treeShaking 机制的原理
    • webpack 和 gulp 区别(模块化与流的区别)
    • webpack Plugin 和 Loader 的区别
    • webpack 插件如何实现
    • 自定义 webpack 插件
    • 自定义 webpack loader
    • Webpack 有哪些优化手段
    • 路由懒加载的原理
    • 参考资料
  • vue框架
  • 浏览器相关
  • 计算机网络
  • 数据结构和算法
  • 设计模式
  • interview
jason lee
2021-09-06
目录

webpack

# webpack 的构建流程是什么

  • 初始化参数: 解析 webpack 配置参数,合并 shell 传入和 webpack.config.js 文件配置的参数,形成最后的配置结果;

  • 开始编译: 上一步得到的参数初始化 compiler 对象,注册所有配置的插件,插件 监听 webpack 构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译;

  • 确定入口: 从配置的 entry 入口,开始解析文件构建 AST 语法树,找出依赖,递归下去;

  • 编译模块: 递归中根据文件类型和 loader 配置,调用所有配置的 loader 对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;

  • 完成模块编译并输出: 递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据 entry 或分包配置生成代码块 chunk;

  • 输出完成: 输出所有的 chunk 到文件系统;

# webpack 的热更新原理 HMR 实现原理

其实是自己开启了 express 应用,添加了对 webpack 编译的监听,添加了和浏览器的 websocket 长连接,当文件变化触发 webpack 进行编译并完成后,会通过 sokcet 消息告诉浏览器准备刷新。而为了减少刷新的代价,就是不用刷新网页,而是刷新某个模块,webpack-dev-server 可以支持热更新,通过生成 文件的 hash 值来比对需要更新的模块,浏览器再进行热替换

服务端

  • 启动 webpack-dev-server 服务器
  • 创建 webpack 实例
  • 创建 server 服务器
  • 添加 webpack 的 done 事件回调
  • 编译完成向客户端发送消息
  • 创建 express 应用 app
  • 设置文件系统为内存文件系统
  • 添加 webpack-dev-middleware 中间件
  • 中间件负责返回生成的文件
  • 启动 webpack 编译
  • 创建 http 服务器并启动服务
  • 使用 sockjs 在浏览器端和服务端之间建立一个 websocket 长连接
  • 创建 socket 服务器

客户端

  • webpack-dev-server/client 端会监听到此 hash 消息
  • 客户端收到 ok 消息后会执行 reloadApp 方法进行更新
  • 在 reloadApp 中会进行判断,是否支持热更新,如果支持的话发生 webpackHotUpdate 事件,如果不支持就直接刷新浏览器
  • 在 webpack/hot/dev-server.js 会监听 webpackHotUpdate 事件
  • 在 check 方法里会调用 module.hot.check 方法
  • HotModuleReplacement.runtime 请求 Manifest
  • 通过调用 JsonpMainTemplate.runtime 的 hotDownloadManifest 方法
  • 调用 JsonpMainTemplate.runtime 的 hotDownloadUpdateChunk 方法通过 JSONP 请求获取最新的模块代码
  • 补丁 js 取回来或会调用 JsonpMainTemplate.runtime.js 的 webpackHotUpdate 方法
  • 然后会调用 HotModuleReplacement.runtime.js 的 hotAddUpdateChunk 方法动态更新 模块代码
  • 然后调用 hotApply 方法进行热更

# webpack treeShaking 机制的原理

treeShaking 也叫摇树优化,是一种通过移除多于代码,来优化打包体积的,生产环境默认开启。

原理:

  • ES6 Module 引入进行静态分析,故而编译的时候正确判断到底加载了那些模块
  • 静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码

# webpack 和 gulp 区别(模块化与流的区别)

  • webpack 是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js 文件、css 文件等)都看成模块,通过 loader(加载器)和 plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。
  • gulp 强调的是前端开发的工作流程,我们可以通过配置一系列的 task,定义 task 处理的事务(例如文件压缩合并、雪碧图、启动 server、版本控制等),然后定义执行顺序,来让 gulp 执行这些 task,从而构建项目的整个前端开发流程。

# webpack Plugin 和 Loader 的区别

  • Loader

用于对模块源码的转换,loader 描述了 webpack 如何处理非 javascript 模块,并且在 buld 中引入这些依赖。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或者将内联图像转换为 data URL。比如说:CSS-Loader,Style-Loader 等。

  • Plugin

目的在于解决 loader 无法实现的其他事,它直接作用于 webpack,扩展了它的功能。在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

# webpack 插件如何实现

  • webpack 本质是一个事件流机制,核心模块:tabable(Sync + Async)Hooks 构造出 === Compiler(编译) + Compiletion(创建 bundles)
  • compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options、loader 和 plugin。当在 webpack 环境中应用一插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境
  • compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一个新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态的信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用
  • 创建一个插件函数,在其 prototype 上定义 apply 方法,指定一个 webpack 自身的事件钩子
  • 函数内部处理 webpack 内部实例的特定数据
  • 处理完成后,调用 webpack 提供的回调函数

# 自定义 webpack 插件

编写一个 Bundle 大小的插件:创建一个文件夹 bundlesize-webpack-plugin,在文件夹里创建 index.js,复制下边的代码

const { resolve } = require("path");
const fs = require("fs");

module.exports = class BundlesizeWebpackPlugin {
  constructor(options) {
    this.options = options || {
      sizeLimit: 3,
    };
  }
  formatBytes(bytes, decimals = 2) {
    console.log(bytes, "bytesbytesbytes");
    if (bytes === 0) return "0 Bytes";
    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
  }
  apply(compiler) {
    compiler.hooks.done.tap("BundleSizePlugin", (stats) => {
      const { path, filename } = stats.compilation.options.output;
      const bundlePath = resolve(path, filename);
      const { size } = fs.statSync(bundlePath);
      const bundleSize = this.formatBytes(size);
      const { sizeLimit } = this.options;
      console.log(bundleSize, "bundleSize"); // size in bytes
      if (bundleSize < sizeLimit) {
        console.log("Safe:Bundle-Size", "\n SIZE LIMIT:", sizeLimit);
      } else {
        if (bundleSize === sizeLimit) {
          console.warn("Warn:Bundle-Size", "\n SIZE LIMIT:", sizeLimit);
        } else {
          console.error("Unsafe:Bundle-Size", "\n SIZE LIMIT:", sizeLimit);
        }
      }
    });
  }
};

在 webpack.config.js 引入写好的自定义插件





 
 
 
 



const bundlesizeplugin = require("./bundlesize-webpack-plugin");
module.exports = {
  // ...
  plugins: [
    // 引入写好的自定义插件
    new bundlesizeplugin({
      sizeLimit: 4, // 传参
    }),
  ],
};

# 自定义 webpack loader

编写一个自定义 loader:创建一个文件夹 loader,在文件夹里创建 myLoader.js,复制下边的代码

const loaderUtils = require("loader-utils");
module.exports = function(source) {
  // 获取到用户给当前 Loader 传入的 options
  const options = loaderUtils.getOptions(this);
  console.log(options, "ppp");
  var str = `
  body {
    background: ${options.color};
    height: 500px;
  }
  `;
  return str;
};

在 webpack.config.js 引入写好的自定义 loader






















 
 








module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ["babel-loader"],
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader",
          },
          {
            loader: "css-loader",
          },
          {
            // 引入写好的自定义loader
            loader: resolve(__dirname, "./loader/myLoader.js"),
            options: {
              // loader的传参
              color: "red",
            },
          },
        ],
      },
    ],
  },
};

# Webpack 有哪些优化手段

  1. exclude/include

    通过 exclude、include 配置来确保转译尽可能少的文件。

  2. cache-loader

    在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。

npm install cache-loader -D
module.exports = {
  //...
  module: {
    //我的项目中,babel-loader耗时比较长,所以我给它配置了`cache-loader`
    rules: [
      {
        test: /\.jsx?$/,
        use: ["cache-loader", "babel-loader"],
      },
    ],
  },
};

3.happypack

让 Webpack 同一时刻处理多个任务,发挥多核 CPU 电脑的威力 它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。

const Happypack = require('happypack');
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.js[x]?$/,
                use: 'Happypack/loader?id=js',
                include: [path.resolve(__dirname, 'src')]
            },
            {
                test: /\.css$/,
                use: 'Happypack/loader?id=css',
                include: [
                    path.resolve(__dirname, 'src'),
                    path.resolve(__dirname, 'node_modules', 'bootstrap', 'dist')
                ]
            }
        ]
    },
    plugins: [
        new Happypack({
            id: 'js', //和rule中的id=js对应
            //将之前 rule 中的 loader 在此配置
            use: ['babel-loader'] //必须是数组
        }),
        new Happypack({
            id: 'css',//和rule中的id=css对应
            use: ['style-loader', 'css-loader','postcss-loader'],
        })
    ]
}

4.externals:

我们可以将一些JS文件存储在 CDN 上(减少 Webpack打包出来的 js 体积),在 index.html 中通过 <script> 标签引入,如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="root">root</div>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</body>
</html>

复制代码我们希望在使用时,仍然可以通过 import 的方式去引用(如 import $ from 'jquery'),并且希望 webpack 不会对其进行打包,此时就可以配置 externals。

//webpack.config.js
module.exports = {
    //...
    externals: {
        //jquery通过script引入之后,全局中即有了 jQuery 变量
        'jquery': 'jQuery'
    }
}

使用高版本的webpack (使用webpack4)

多线程/多实例构建:HappyPack(不维护了) thread-loader

缩小打包作用域

充分利用缓存提升二次构建速度

DLLPlugin 提前打包、分包,避免反复编译浪费时间

# 路由懒加载的原理

解决的问题:避免进入首页就加载全部的前端资源造成用户等待时间过长的问题。

# 参考资料

https://www.w3xue.com/exp/article/201810/3317.html (opens new window)

手写代码
vue框架

← 手写代码 vue框架→

Theme by Vdoing | Copyright © 2019-2022 jason lee | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式