【面试题】Webpack

sqs2023/5/19面试题webpack 性能优化

1. webpack的五大核心

  • entry(入口):指示 Webpack 从哪个文件开始打包
  • output(输出):指示 Webpack 打包完的文件输出到哪里去,如何命名等
  • loader(加载器):webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader
  • plugins(插件):扩展Webpack功能
  • mode(模式):主要由两种模式:developmentproduction

2. webpack的构建流程

  1. 读取参数:从配置文件和shell中读取合并参数
  2. 开始编译:用参数初始化Compiler对象,加载所有的配置插件,执行Compiler对象的run方法开始编译
  3. 确认入口:从entry中找到所有的入口文件
  4. 编译模块:从入口出发,调用配置的loader对模块进行编译,找到该模块依赖的模块,再递归进行本步骤
  5. 完成编译:得到编译后内容以及依赖关系
  6. 输出资源:根据入口与模块关系,组装成一个个包含多模块的chunk,再把每个chunk转为一个单独文件输出到列表
  7. 输出完成:在确定输出内容后,根据配置确定输出的路径和文件名,写入到文件系统

3. 什么是loader?原理是什么?手写一个loader

loader作用:

帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块

loader原理:

loader本质是一个函数,用来处理源代码

loader参数:

  • content 源文件的内容
  • map SourceMap 数据
  • meta 数据,可以是任何内容
module.exports = function (content, map, meta) {
  // 传递map,让source-map不中断
  // 传递meta,让下一个loader接收到其他参数
  this.callback(null, content, map, meta);
};

loader分类:

  1. 同步loader
  2. 异步loader(如果有异步代码需要使用异步loader)
  3. Raw loader
  4. Pitching loader

手写loader例子:

// 用来清理 js 代码中的console.log
module.exports = function cleanLogLoader(content) {
  // 将console.log替换为空
  return content.replace(/console\.log\(.*\);?/g, "");
};
# 先需要安装依赖
npm i @babel/core @babel/preset-env -D
// loaders/banner-loader/schema.json
{
  "type": "object",
  "properties": {
    "presets": {
      "type": "array"
    }
  },
  "additionalProperties": true
}
// 编译 js 代码,将 ES6+语法编译成 ES5-语法。
const schema = require("./schema.json");
const babel = require("@babel/core");

module.exports = function (content) {
  const options = this.getOptions(schema);
  // 使用异步loader
  const callback = this.async();
  // 使用babel对js代码进行编译
  babel.transform(content, options, function (err, result) {
    callback(err, result.code);
  });
};
# 先需要安装依赖
npm i loader-utils -D
// 编译 js 代码,将 ES6+语法编译成 ES5-语法。
const schema = require("./schema.json");
const babel = require("@babel/core");

module.exports = function (content) {
  const options = this.getOptions(schema);
  // 使用异步loader
  const callback = this.async();
  // 使用babel对js代码进行编译
  babel.transform(content, options, function (err, result) {
    callback(err, result.code);
  });
};
// webpack使用这个loader的配置
{
  test: /\.(png|jpe?g|gif)$/,
  loader: "./loaders/file-loader.js",
  type: "javascript/auto", // 解决图片重复打包问题
},
// 动态创建 style 标签,插入 js 中的样式代码,使样式生效。
const styleLoader = () => {};

styleLoader.pitch = function (remainingRequest) {
  /*
    remainingRequest: C:\Users\86176\Desktop\source\node_modules\css-loader\dist\cjs.js!C:\Users\86176\Desktop\source\src\css\index.css
      这里是inline loader用法,代表后面还有一个css-loader等待处理

    最终我们需要将remainingRequest中的路径转化成相对路径,webpack才能处理
      希望得到:../../node_modules/css-loader/dist/cjs.js!./index.css

    所以:需要将绝对路径转化成相对路径
    要求:
      1. 必须是相对路径
      2. 相对路径必须以 ./ 或 ../ 开头
      3. 相对路径的路径分隔符必须是 / ,不能是 \
  */
  const relativeRequest = remainingRequest
    .split("!")
    .map((part) => {
      // 将路径转化为相对路径
      const relativePath = this.utils.contextify(this.context, part);
      return relativePath;
    })
    .join("!");

  /*
    !!${relativeRequest} 
      relativeRequest:../../node_modules/css-loader/dist/cjs.js!./index.css
      relativeRequest是inline loader用法,代表要处理的index.css资源, 使用css-loader处理
      !!代表禁用所有配置的loader,只使用inline loader。(也就是外面我们style-loader和css-loader),它们被禁用了,只是用我们指定的inline loader,也就是css-loader

    import style from "!!${relativeRequest}"
      引入css-loader处理后的css文件
      为什么需要css-loader处理css文件,不是我们直接读取css文件使用呢?
      因为可能存在@import导入css语法,这些语法就要通过css-loader解析才能变成一个css文件,否则我们引入的css资源会缺少
    const styleEl = document.createElement('style')
      动态创建style标签
    styleEl.innerHTML = style
      将style标签内容设置为处理后的css代码
    document.head.appendChild(styleEl)
      添加到head中生效
  */
  const script = `
    import style from "!!${relativeRequest}"
    const styleEl = document.createElement('style')
    styleEl.innerHTML = style
    document.head.appendChild(styleEl)
  `;

  // style-loader是第一个loader, 由于return导致熔断,所以其他loader不执行了(不管是normal还是pitch)
  return script;
};

module.exports = styleLoader;

4. 常见的loader有哪些

  • css-loader, scss-loader, less-loader:将 Css, less, scss 文件编译成 Webpack 能识别的模块
  • style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容
  • babel-loader:解决ES6语法兼容性问题
  • postcss-loader:解决CSS兼容性问题
  • vue-loader :加载并编译 Vue 组件
  • react-hot-loader:实时调整 react 组件

5. 什么是plugin?原理是什么?

plugin作用:

通过插件我们可以扩展 webpack,加入自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力。

plugin原理:

webpack 在编译代码过程中,会触发一系列 Tapable 钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当 webpack 构建的时候,插件注册的事件就会随着钩子的触发而执行了。

Webpack内部的钩子:

Webpack 内部的钩子open in new window

手写plugin例子:

  1. 作用:分析 webpack 打包资源大小,并输出分析文件。
  2. 开发思路:
  • 在哪做? compiler.hooks.emit, 它是在打包输出前触发,我们需要分析资源大小同时添加上分析后的 md 文件。
// 一个最简单的插件
class TestPlugin {
  constructor() {
    console.log("TestPlugin constructor()");
  }
  // 1. webpack读取配置时,new TestPlugin() ,会执行插件 constructor 方法
  // 2. webpack创建 compiler 对象
  // 3. 遍历所有插件,调用插件的 apply 方法
  apply(compiler) {
    console.log("TestPlugin apply()");
  }
}

module.exports = TestPlugin;
// plugins/analyze-webpack-plugin.js
class AnalyzeWebpackPlugin {
  apply(compiler) {
    // emit是异步串行钩子
    compiler.hooks.emit.tap("AnalyzeWebpackPlugin", (compilation) => {
      // Object.entries将对象变成二维数组。二维数组中第一项值是key,第二项值是value
      const assets = Object.entries(compilation.assets);

      let source = "# 分析打包资源大小 \n| 名称 | 大小 |\n| --- | --- |";

      assets.forEach(([filename, file]) => {
        source += `\n| ${filename} | ${file.size()} |`;
      });

      // 添加资源
      compilation.assets["analyze.md"] = {
        source() {
          return source;
        },
        size() {
          return source.length;
        },
      };
    });
  }
}

module.exports = AnalyzeWebpackPlugin;

6. 常见的plugin有哪些?

  • html-webpack-plugin:简化 html 文件创建
  • mini-css-extract-plugin:将css提取成单独文件
  • css-minimizer-webpack-plugin:css压缩
  • eslint-webpack-plugin:使用 eslintopen in new window来检查js代码
  • terser-webpack-plugin: 使用 terseropen in new window来压缩 JavaScript

7. webpack有哪些可以提高效率的插件

  • HotModuleReplacementPlugin:热模块更新替换,提高重新打包速度。
  • webpack-merge:提取公共配置,减少重复配置代码。
  • webpack-dashboard:可以更有好的展示相关打包信息。
  • size-plugin:监控资源体积变化,尽早发现问题。
  • speed-measure-webpack-plugin:简称SMP,分析出 webpack 打包过程中 loader 和 plugin的耗时,有助于找到构建过程中的性能瓶颈。

8. 如何提高webpack的构建速度

webpack配置优化open in new window

  1. HMR:在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。
  2. OneOf:顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了。
  3. Include/Exclude:包含,只处理 xxx 文件,排除某些文件(eslint检查去掉node_modules)
  4. cache:对 Eslint 检查 和 Babel 编译结果进行缓存。
  5. thead:多进程打包:开启电脑的多个进程同时干一件事,速度更快。

9. 如何减少webpack的打包体积

  1. Tree shaking:用于移除 JavaScript 中的没有使用上的代码。
  2. Image Minimizer:image-minimizer-webpack-plugin用来压缩图片的插件
  3. Code Split:代码分割,需要哪个加载哪个

10. 如何利用webpack优化性能

  1. OneOf
  2. Include/Exclude
  3. Image Minimizer
  4. Network Cache:使用contenthash来命令文件,当文件内容改了hash才会变
Last Updated 2023/5/20 22:17:30