【前端面经】Webpack篇

185 阅读7分钟

webpack是目前用的最多的一款模块加载器兼打包工具。

Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。

Loader可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果。module.loaders 告知 webpack 每一种文件都需要使用什么加载器来处理。

webpack常用配置

  • entry:打包入口文件,可以是单个或者多个JS文件,决定了webpack从哪个模块开始生成依赖关系图;
  • output:输出的目录和文件名,包括path、filename、publicPath等;
  • resolve:设置webpack如何解析模块依赖;
  • plugins:插件使用
  • devServer:提供了一个简单的web服务器和实时重载功能,包含了contentBase、port、proxy等;
  • optimization:使用splitChunks和runtimeChunk配置代码拆分和运行时代码提取等优化策略
  • externals:用于配置排除打包的模块;
  • devtool:配置source-map类型;
  • context:webpack使用的根目录,string类型必须是绝对路径; -target:指定Webpack编译的目标环境;
  • performance:输出文件的性能检查配置;
  • noParse:不用解析和处理的模块;
  • stats:控制台输出日志控制。

Loader和Plugins

Loader和Plugin的区别

  • 功能不同
    • Loader本质是一个js函数,是一个“翻译官”。由于webpack只能解析原生js文件,因此,loader用于将其它类型的文件进行转换;
    • Plugin本质就是一个具有apply方法的JS对象,用于扩展、增强webpack的功能。webpack在运行周期中会广播出很多事件,plugin可监听这些事件,在合适时机通webpack提供的API改变输出的结果;
  • 用法不同
    • loader的配置在module.rules下进行,类型为数组,每一项都是一个Object,包含了对于什么类型的文件(test)、使用什么加载(loader)、和使用的参数(options);
    • Plugin的配置在plugins下进行,类型为数组,每一项都是一个Plugin的实例,参数都通过构造函数传入。

常用的Loader和Plugin

  1. 常用Loader

    • babel-loader:将ES6+转换为ES5;
    • css-loader:解析css文件,并处理css依赖关系;
    • style-loader:将css代码注入到HTML中;
    • file-loader:解析文件路径,将文件复制到输出目录,并返回文件路径;
    • url-loader:类似于file-loader,但可以将小于指定大小的文件转换为base64编码的Data URL格式;
    • sass-loader/less-loader:将Sass/less文件编译为css文件;
    • postcss-loader:自动添加css前缀,优化css代码;
    • vue-loader:将Vue单文件组件编译为js代码;
  2. 常用Plugins

    • HtmlWebpackPlugin:生成HTML文件,并自动将打包后的JS和CSS文件引入到HTML文件中;
    • CleanWebpackPlugin:清除输出目录;
    • ExtraTextWebpackPlugin/MiniCssExtractPlugin:将css代码提取到单独的CSS文件中;
    • DefinePlugin:定义全局变量;
    • UglifyJsWebpackPlugin:压缩JS代码;
    • HotModuleReplacemenetPlugin:热模块替换,用于在开发环境下实现热更新;
    • BundleAnalyzePlugin:打包分析大小和依赖;

webpack的构建流程

webpack构建流程,是一个串行的过程,即将各个插件串联起来,在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条webpack机制中运作,使得整个系统扩展性更好。

需要了解的可以看下神三元大神的实现一个简单的Webpack文章

  1. 初始化:读取配置参数,启动webpack,创建Compiler对象并开始解析; 2.初始化Compiler(一个全局单例,负责整个webpack打包的构建流程),定义了很多钩子函数;
  2. 确定入口:entry参数;
  3. 编译模块:根据每个Module和loader配置,递归的进行编译处理;
    • Compiler开始编译,执行run方法,构建Compilation对象
    • Compilation对象是每一次构建的上下文对象,包含了当次构建所需要的所有信息,每次热更新和重新构建,都会重新生成;
    • build module 完成模块编译,此时loader开始发挥作用,因为要分清文件的依赖关系,需要通过遍历AST 抽象语法树分析依赖的模块,进而继续循环执行下一个模块的编译解析。
  4. 完成模块编译,得到模块编译后之间的依赖关系;
  5. 输出资源,即多个模块的Chunk转换成的单独的文件输出;
  6. 输出完成,根据配置,把文件内容写入到文件系统;

webpack的热更新

热更新:即在不刷新页面的前提下,将新代码替换掉旧代码。

热更新原理

  1. 通过webpack-dev-serve创建两个服务器,提供静态资源的服务(node.js)和socket服务;
  2. socket服务是一个websocket的长链接,双方可以通信
  3. 当某一个文件或者模块发生变化时,webpack监听到变化则对文件重新编译打包,编译生成唯一的hash值,作为下一次热更新的标识;
  4. 当文件发生改动,服务端会向浏览器推送一条消息,包含改动的hash
  5. 创建一个ajax去服务端请求获取到变化内容的manifest文件
  6. 浏览器根据manifest文件获取模块变化的内容,触发render流程,实现局部模块更新

实现原理 webpack的热更新,实际上是webpack-dev-server和浏览器之间维护的一个websocket服务。当本地资源发生改变后,webpack会将先打包生成新的模块代码放入内存中,然后webpack-dev-server向浏览器推送更新,并带上构建时的hash,让客户端和上一次资源进行对比、更新。

webpack和vite的实现原理有何区别

webpack优化怎么做的,怎么做到优化9倍的,怎么定位耗时长的地方和原因的

webpack升级踩坑

vite和webpack的区别

Vite

  • 冷启动
  • 即时的模块热更新:利用现代浏览器支持ES Module的特性,不需要分析依赖,在热模块方面,当修改一个模块的时候,仅需让浏览器重新请求该模块即可,无需重新全部编译;
  • 按需编译

Webpack

  • 对CommonJS、AMD、ES6的语法做了兼容
  • 具有强大的Plugin接口,具有很好的灵活性和扩展性
  • 社区更丰富

编写webpack的扩展

Webpack是一个强大的模块打包器,允许通过编写插件来扩展功能。Webpack插件是一个具有apply方法的JS对象,该方法会被webpack compiler调用,并且可以在整个编译生命周期中访问compiler对象。

编写webpack插件步骤示例:

  1. 创建一个新的JS文件,用于存放插件代码,例:ExPlugin.js;
  2. 定义插件类,易于组织代码,例:
class ExPlugin {
    constructor(options) { 
        // 初始化插件,接受一些选项 
        this.options = options; 
    } 
    apply(compiler) { 
        // 在这里编写你的插件逻辑
        // compiler 是 webpack 编译器的实例
    } 
}
  1. apply方法中编写插件逻辑;

    apply方法中,可以使用compiler对象来访问webpack的编译生命周期钩子,可以在这些钩子中添加时间监听器来执行相关的插件逻辑;

class ExPlugin {
    constructor(options) { 
        // 初始化插件,接受一些选项 
        this.options = options; 
    } 
    apply(compiler) { 
        // 使用tapAsync方法来添加一个异步钩子监听器
        compiler.hooks.run.tapAsync('ExPlugin', (compilation, callback) ={
            console.log('webpack编译开始了');
            callback();
        })
    } 
}
  1. 使用插件;

    在webpack配置文件中,导入插件使用。

const ExPlugin = require('./ExPlugin');

module.exports = {
    //...其它配置
    plugins: [
        new ExPlugin({ /* 相关传参 */ })
    ]
}
  1. 测试插件;
  2. 发布插件(看需要):npm + 文档;

babel转换的过程

代码分割(Code Splitting)是什么

代码分割,是一种优化技术。允许将一个大的chunk拆分成多个小chunk,从而实现按需加载,减少初始加载时间,提高应用程序的性能。

使用:optimization.splitChunks配置项来开启代码分割。