webpack相关

229 阅读7分钟

webpack构建流程

  • 初始化参数: 从配置文件和shell语句中读取合并参数,得到最终的配置

  • 开始编译: 利用上一步得到的配置参数初始化compiler对象,它扩展自Tapable类,注册配置的所有插件,并执行compilerrun方法开始执行编译

  • 确定入口: 根据配置文件中的entry字段查找所有的入口文件

  • 编译模块: 从入口文件出发递归查找所有的模块,并利用配置的loader规则对相应的模块进行解析,直到所有的模块都被解析完成。得到每个模块被翻译后的内容和他们之间的依赖关系,这个过程是利用@babel/parser解析代码为ast,并用@babel/traverse识别模块依赖,构建模块依赖图

  • 输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转化为一个单独的文件加入到输出列表中,此时是插件可修改输出内容的最后时机

  • 输出完成: 确定好输出内容后,根据配置的输出路径和文件名,把文件内容输出到文件系统。

如何提高webpack的构建速度

  • 多进程构建: thread-loader对耗时的loader使用多进程转化

  • 压缩代码 uglifyjs-webpack-pluginterser-webpack-plugin开启parallel参数,开启多进程压缩

  • 缓存

    • 使用cache-loaderhard-source-webpack-plugin开启构建缓存
    • babel-loader开启缓存
  • 提取页面公共资源 将页面中的基础包通过CDN的方式引入,避免其打包进bundle中

  • 缩小打包范围

    • exclude/include ,确定 loader 规则范围
    • 合理使用alias,减少不必要的查找
  • Tree shaking 利用tree-shaking减小打包范围

  • 动态polyfill

如何利用webpack优化前端性能

  • 路由懒加载
  • 压缩图片
  • 拆分大文件,合并相同模块代码
  • tree shaking
  • cdn引入基础库
  • gzip压缩文件compression-webpack-plugin

loader

loader用于将各种各样的模块转化为标准的js模块输出,供webpack处理,例如:

  • vue-loader:将vue文件转化为js和css
  • url-loader:将小于limit大小的文件转化为内敛Data Url的形式
  • raw-loader:将文件以字符串的形式输出

loader按照配置的顺序从前往后pitch,再从后往前执行

loader的编写思路

loader导出一个可执行的函数,函数上还挂载又pitch函数,例如

const loader = function (source){
    console.log('后执行')
    return source;
}

loader.pitch = function(requestString) {
    console.log('先执行')
}

module.exports = loader

plugin

通过插件可以扩展webpack的能力,插件可以参与到webpack构建的各个时机,在webpack向外暴露的构建进程的事件上执行所需操作,从而改变构建的输出。

Webpack 会在启动后按照注册的顺序逐次调用插件对象的 apply 函数,同时传入编译器对象 compiler ,插件开发者可以以此为起点触达到 webpack 内部定义的任意钩子。

plugin的编写思路

插件是一个构造函数或是类,拥有apply方法,例如

class SomePlugin {
    apply(compiler) {
        compiler.hooks.thisCompilation.tap('SomePlugin', (compilation) => { })
    }
}
function SomePlugin () {}

SomePlugin.prototype.apply = function (compiler) {
    compiler.hooks.thisCompilation.tap('SomePlugin', (compilation) => { })
}

webpack热更新原理

webpack-dev-server启动一个web服务器,同时通过websocket与客户端建立一个长连接,当webpack监听到文件变化时,会重新对文件进行编译打包,保存到内存中,webpack-dev-server监听到webpack的done事件,会通过socket将打包后的新模块的hash值发送到客户端,客户端代码通过对比前后hash值,确定是否存在修改的代码,如果存在,则去请求最新模块代码,通过jsonp的方式插入页面,实现热更新。

  • webpak-dev-server:创建了web服务器,实现了websocket长连接
  • webpack-dev-middleware:本地文件的监听、编译、输出
  • ### HotModuleReplacementPlugin:部分代码插入到客户端代码中,实现客户端的websocket处理,主要对比前后hash值,从而去请求hot-updata.jsonhot-update.js文件,实现热更新

如何配置多入口应用

  • 在entry中配置多入口文件路径
  • 配置CommonsChunkPlugin实现各自引入所需的chunk

tree-shaking原理

  • ESM 输出的是值得引用,CommonJs输出的是值得拷贝
  • ESM 是编译时执行,CommonJs是运行时加载

ESM 最大的特点就是静态化,在编译时就可以确定模块之间的依赖关系,和运行时的状态无关,可以进行可靠的静态分析,从而确定哪些代码是不可到达的,从而有实现Tree-shaking的可能性。

Tree-shaking是依赖ESM的。

在webpack中开启tree-shaking后,webpack会为那些没有用到的代码打上unused的标志,在代码压缩工作压缩时,会删除带有该标志的代码,从而实现Tree-shaking

source map 原理

source map是将编译、打包、压缩后的代码映射回源代码的文件

source map只有在打开开发者工具时才会加载

开发环境配置如下:

  • eval-source-map - 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。

  • eval-cheap-module-source-map - 类似 eval-cheap-source-map,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。

生产环境配置如下:

  • (none)(省略 devtool 选项) - 不生成 source map。这是一个不错的选择。

  • source-map - 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它

webpack如何实现懒加载

本质其实就是利用jsonp去加载对应的异步模块,然后执行该模块,从而实现按需加载

webpack本地开发如何解决跨域问题

使用webpack-dev-server提供的proxy配置项,原理是在后端实现一个代理服务器转发跨域请求,因为跨域是浏览器的限制,服务直接请求服务不受跨域限制。

webpack和rollup的区别

webpack支持度很广,适合打包项目中包含众多静态资源、commonjs规范

rollup适合打包纯js的项目,打包类库的体积更小,不需要各种webpack的辅助函数

模块化种类和区别

ESM:es6的模块规范

CommonJS

ESM与CommonJS区别

  • ESM模块输出的是值的引用,CommomJS模块输出的是值的拷贝
  • ESM模块是编译时输出接口,CommomJS模块是运行时加载
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

得益于ESM的静态属性,才使得构建工具可以在编译阶段实现tree-shaking优化。 得益于ESM的异步加载性,vite才能利用原生ESM模块实现拦截浏览器的异步加载模块请求

新一代构建工具--vite

vite:

  • 基于esbuild实现的极速开发体验
  • 多框架支持,vue,react,angular
  • 兼容Rollup的插件机制
  • SSR
  • HMR

vite启动时只做了两件事:

  1. 启动一个可以承载资源的server
  2. 使用esbuild预构建npm依赖包

之后vite就一直等着,等待浏览器已http方式发来ESM规范的模块请求,vite便开始按需编译被请求的模块。