webpack面试题

本文详细解答了webpack面试中常见的问题,包括webpack的作用、Loader和Plugin的使用及其区别、打包流程、SourceMap、编写Loader和Plugin的思路,以及热更新原理、文件指纹、优化策略等,全面覆盖了webpack的核心知识点。
摘要由CSDN通过智能技术生成

1.你知道webpack的作用是什么吗?

1.模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。保证项目结构的清晰和可读性。

2.编译兼容。通过webpackLoader机制,兼容ES6,以编译转换诸如.less, .vue, .jsx这类在浏览器无法识别的格式文件。

3.能力扩展。通过webpackPlugin机制,可以实现诸如按需加载代码压缩等一系列功能,提高自动化程度,工程效率以及打包输出的质量。

2. 用过哪些Loader?都有什么作用?

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
  • url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试
  • image-loader:加载并且压缩图片文件
  • json-loader 加载 JSON 文件(默认包含)
  • babel-loader:把 ES6 转换成 ES5
  • ts-loader: 将 TypeScript 转换成 JavaScript
  • sass-loader:将SCSS/SASS代码转换成CSS
  • less-loader :将LESS代码转换成CSS
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
  • postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
  • eslint-loader:通过 ESLint 检查 JavaScript 代码
  • tslint-loader:通过 TSLint检查 TypeScript 代码
  • vue-loader:加载 Vue.js 单文件组件
  • jsx-loader:加载 jsx 单文件组件

3. 用过哪些Plugin?都有什么作用?

  • html-webpack-plugin: 复制HTML 模板,压缩HTML代码
  • clean-webpack-plugin: 目录清理
  • mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载
  • css-minimizer-webpack-plugin : 压缩CSS ,减少CSS 文件大小
  • speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时
  • webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积
  • webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
  • serviceworker-webpack-plugin:为网页应用增加离线缓存功能

4. 说一说Loader和Plugin的区别?

  • Loader 主要工作时编译兼容。本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。
  • Plugin 主要是能力扩展,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
  • Loadermodule.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
  • Pluginplugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
  • Loader运行在打包文件之前(loader为在模块加载时的预处理文件) Plugin在整个编译周期都起作用。

5. 说一下webpack的整个打包流程?

首先我们应该简单了解一下webpack的整个打包流程:

  1. 初始化参数:合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果;
  2. 开始编译:创建Compiler对象并开始解析项目;监听webpack构建生命周期的事件节点,执行对象的run方法开始执行编译
  3. 确定入口:从入口文件(entry)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树;
  4. 编译模块:对不同文件类型的依赖模块文件使用对应的Loader进行编译,最终转为Javascript文件;
  5. 整个过程中webpack会通过发布订阅模式,向外抛出一些hooks,而webpackPlugin即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。
  6. 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry或分包配置生成代码块chunk;
  7. 输出完成:输出所有的chunk到文件系统;

6.说一下模块打包运行原理?

文件的解析与构建是一个比较复杂的过程,在webpack源码中主要依赖于compilercompilation两个核心对象实现。

compiler对象是一个全局单例,他负责把控整个webpack打包的构建流程。

compilation对象是每一次构建的上下文对象,它包含了当次构建所需要的所有信息,每次热更新和重新构建,compiler都会重新生成一个新的compilation对象,负责此次更新的构建过程。

而每个模块间的依赖关系,则依赖于AST语法树。每个模块文件在通过Loader解析完成之后,会通过acorn库生成模块代码的AST语法树,通过语法树就可以分析这个模块是否还有依赖的模块,进而继续循环执行下一个模块的编译解析。

最终Webpack打包出来的bundle文件是一个IIFE的执行函数。

// webpack 5 打包的bundle文件内容

(() => {
    // webpackBootstrap
    var __webpack_modules__ = ({
   
        'file-A-path': ((modules) => {
    // ... })
        'index-file-path': ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
    // ... })
    })
    
    // The module cache
    var __webpack_module_cache__ = {
   };
    
    // The require function
    function __webpack_require__(moduleId) {
   
        // Check if module is in cache
        var cachedModule = __webpack_module_cache__[moduleId];
        if (cachedModule !== undefined) {
   
                return cachedModule.exports;
        }
        // Create a new module (and put it into the cache)
        var module = __webpack_module_cache__[moduleId] = {
   
                // no module.id needed
                // no module.loaded needed
                exports: {
   }
        };

        // Execute the module function
        __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

        // Return the exports of the module
        return module.exports;
    }
    
    // startup
    // Load entry module and return exports
    // This entry module can't be inlined because the eval devtool is used.
    var __webpack_exports__ = __webpack_require__("./src/index.js");
})

webpack4相比,webpack5打包出来的bundle做了相当的精简。在上面的打包demo中,整个立即执行函数里边只有三个变量和一个函数方法,__webpack_modules__存放了编译后的各个文件模块的JS内容,__webpack_module_cache__ 用来做模块缓存,__webpack_require__是Webpack内部实现的一套依赖引入函数。最后一句则是代码运行的起点,从入口文件开始,启动整个项目。

其中值得一提的是__webpack_require__模块引入函数,我们在模块化开发的时候,通常会使用ES Module或者CommonJS规范导出/引入依赖模块,webpack打包编译的时候,会统一替换成自己的__webpack_require__来实现模块的引入和导出,从而实现模块缓存机制,以及抹平不同模块规范之间的一些差异性。

7. 你知道sourceMap是什么吗?

sourceMap是一项将编译、打包、压缩后的代码映射回源代码的技术。sourceMap可以帮助我们快速定位到源代码的位置,提高我们的开发效率。

使用方法是可以通过浏览器的开发者工具中的Sources追根溯源

8. 是否写过Loader?简单描述一下编写loader的思路?

  • loader的开发需要遵循一些规范,比如返回值必须是标准的JS代码字符串,以保证下一个loader能够正常工作,同时在开发上需要严格遵循“单一职责”,只关心loader的输出以及对应的输出。
  • loader函数中的this上下文由webpack提供,可以通过this对象提供的相关属性,获取当前loader需要的各种信息数据。
  • 推荐通过loader-utils读取options配置

根目录创建loaders文件夹,里面创建replaceLoader.js:

const loaderUtils = require('loader-utils');

module.exports = function (source) {
   
  // 推荐通过`loader-utils`读取`options`配置
  const options = loaderUtils.getOptions(this);
  // 如果 loader 配置了 options 对象,那么this.query将指向 options
  // const options = this.query;
  const result = source.replace('world', options.name);
  // return result;
    /*
     * this.callback 参数:
     * error:Error | null,当 loader 出错时向外抛出一个 error
     * content:String | Buffer,经过 loader 编译后需要导出的内容
     * sourceMap:为方便调试生成的编译后内容的 source map
     * ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
     */
    this.callback(null, result);
}

在webpack中配置

module: {
   
    rules: [{
   
      test: /\.js$/,
      use: {
   
        loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
        options: {
   
          name: 'echo'
        }
      }
    }]
  }

这个loader的作用是将js文件中的world转换成echo

9. 是否写过Plugin?简单描述一下编写plugin的思路?

compilercompilationWebpack两个非常核心的对象,其中compiler暴露了和 Webpack整个生命周期相关的钩子(compiler-hooks),而compilation则暴露了与模块和依赖有关的粒度更小的事件钩子(Compilation Hooks)。

Plugin的开发和开发Loader一样,需要遵循一些开发上的规范和原则:

  • 插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问compiler实例;
  • 传给每个插件的 compilercompilation 对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件;
  • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住;

根目录创建loaders文件夹,里面创建MyPlugin.js:

class MyPlugin {
   
	// constructor会接受plugin的配置参数
    constructor(options) {
   
        console.log('通过options接受参数',options)
    }
    apply (compiler) {
   
        // 找到合适的事件钩子,实现自己的插件功能
        // emit钩子,它执行的时刻是生成资源到 output 目录之前
        compiler.hooks.emit.tap('MyPlugin', (compilation, cb) => {
   
            compilation.assets['MyPlugin.js'] = {
   
                source: function () {
   
                    return 'console.log(\'12345\');'
                },
                size: function () {
   
                    return 21
                }
            }
            cb&&cb();
        })
    }
}

module.exports = MyPlugin;

在webpack中配置:

  plugins:[ // 配置插件
    new MyPlugin({
   
      name: 'MyPlugins十分牛逼'
    }), // 自定义插件
  ],

此plugin会生成一个MyPlugin.js文件

更详细的开发文档可以直接查看官网的 Plugin API

10. 说一下Webpack 文件监听

Webpack 开启监听模式,有两种方式:

  1. 启动命令:package.json中添加watch:'webpack --watch'
  2. webpack.config.js中设置
module.exports = {
   
  entry:{
   ...},
  output:{
   path:...,filename:...}
  watch:true,//默认false,不开启
  watchOptions:{
   
      ignored:/node-modules/,//忽略,默认为空
      aggregateTimeout:300,//默认300ms,监听文件发生变化等待300ms,再去执行
      poll:1000//轮询1s默认1000次
  }
}

原理:轮询监听文件的编辑时间,如果发生变化,将变化了的文件记录缓存起来,等待aggregateTimeout时间之后,将记录下来的文件一起进行构建,构建到filename指定的文件中

缺点:需要手动刷新浏览器

11. 文件指纹是什么?怎么用?

文件指纹是打包后输出的文件名的后缀。

  • Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改
  • Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash
  • Contenthash:根据文件内容
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值