面试题 - Webpack

161 阅读9分钟

最近找工作整理了一些面试题,分享给大家一起来学习。如有问题,欢迎指正。

前端面试题系列文章:

webpack是什么

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。
在项目中我们可能会用到.less文件和es6文件,我们需要将这些文件转换成html能直接使用的文件,比如less转css , es6转es5 。 我们可以先转换文件,再引用到html中,但这样做非常的繁琐,类似的文件数量一多,将会变成非常麻烦的事情。这时,我们可以使用webpack打包工具,将这些文件一次性打包并使用,简化打包的过程。

Webpack核心概念:

  • Entry

    entry是配置模块的入口,作为构建其内部依赖图的开始。

  • Output

    output输出的配置。 告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。

    基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。

  • Module

    模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

  • Chunk

    代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

  • Loader

    模块转换器,用于把模块原内容按照需求转换成新内容。

  • Plugin

    plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务。

    在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果

Webpack打包流程

  1. 读取webpack的配置参数;
  2. 启动webpack,创建Compiler对象并开始解析项目;
  3. 从入口文件(entry)开始解析,并且找到其导入的依赖模块,对不同文件类型的依赖模块文件使用对应的Loader进行编译
  4. 根据依赖模块递归遍历分析,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  5. 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表
  6. 整个过程中webpack会通过发布订阅模式,向外抛出一些hooks,而webpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。

webpack打包后的几种hash模式

  • hash模式

    底层是创建生成一个新的hash实例,打包时,每个文件都同时写入同一个hash值。而这种形式,相当于把所有代码资源都更新了,类似于全量更新

  • chunkhash模式

    基于hash模式进行改进,当前文件改变时,当前文件所引入的依赖和当前文件的hash值都会重新生成一个新的hash值。而其他未改变的内容的文件,或者和它们无依赖关系的文件,保持原有的hash值。未更新的文件可以进行缓存的作用,它可以说是一种局部更新

  • contenthash模式

    contenthash模式: contenthash可以说是较好的一种hash模式,它是在chunkhash上再一次改良。当前文件的内容发生变化时,当前文件的hash值才会重新生成。而涉及到的依赖的文件和其他文件没有改变内容的情况下,保持原有的hash值。对long term cache长期缓存很友好。这种行为类似于最小量更新

重新打包部署后浏览器缓存问题

应用部署后,为了防止客户端反复请求资源,服务器都会设置缓存,来减少带宽流量压力。

但是设置缓存之后就会出现问题,当我们进行打包部署之后,因为缓存问题,用户访问的还是未更新的页面,

每次打包生成和上一次不同的文件名,全新的文件名就代表全新的请求,自然不会有缓存问题。

解决方案: Nginx配置+打包文件后缀修改

Ngnix配置:

location = /index.html {
    add_header Cache-Control "no-cache, no-store"; 
}

vue.config.js文件:(时间戳或者hash模式)

configureWebpack: { // webpack 配置
    output: { // 输出重构 打包编译后的js文件名称,添加时间戳.
        filename: `js/js[name].${timeStamp}.js`,
        chunkFilename: `js/chunk.[id].${timeStamp}.js`
    },
},
css: {
    extract: { // 打包后css文件名称添加时间戳
        filename: `css/[name].${timeStamp}.css`,
        chunkFilename: `css/chunk.[id].${timeStamp}.css`
    }
}

sourceMap

sourceMap是一项将编译、打包、压缩后的代码映射回源代码的技术,由于打包压缩后的代码并没有阅读性可言,一旦在开发中报错或者遇到问题,直接在混淆代码中debug问题会带来非常糟糕的体验,sourceMap可以帮助我们快速定位到源代码的位置,提高我们的开发效率。

devtool: 'eval-source-map'
  • source-map

    • 外部生成,在外部生成独立的同名.map 文件
    • 可以提示
      • 错误代码准确信息和源代码的错误位置。
      • 错误位置信息可以精确到某行某列
  • inline-source-map

    • 内联生成,既映射的内容在生成的 js 文件内部,不独立生成.map 文件
    • 只生成一段内联 source-map 内容(集中生成)。
    • 可以提示:错误代码准确信息和源代码的错误位置。
  • eval-source-map

    • 同inline-source-map属于内联生成。
    • 不同之处是在生成的 js 文件内部,每一个加载进来的 js 文件内容,都会有一段独立的 source-map 内容。
    • 可以提示:错误代码准确信息和源代码的错误位置。
  • cheap-source-map | cheap-module-source-map

    • 外部生成,在外部生成独立的同名.map 文件。
    • 可以提示:
      • 错误代码准确信息和源代码的错误位置。
      • 错误位置信息只精确到行,无法精确到列。
  • nosources-source-map

    • 外部生成,在外部生成独立的同名.map 文件。
    • 可以提示:
      • 错误代码错误信息,但是没有任何源代码信息(源代码和构建后代码都没有)。
  • hidden-source-map

    • 外部生成,在外部生成独立的同名.map 文件。
    • 可以提示:
      • 错误代码错误信息,但是没有错误位置。

      • 不能追踪到源代码的错误位置,只能提示到构建后代码的错误位置。

开发环境:

  • 速度快(eval>inline>cheap>...)

    • 例如:eval-cheap-souce-map eval-source-map
  • 调试更友好

    • 例如:souce-map cheap-module-souce-map cheap-souce-map
  • 推荐选择

    • eval-source-map 或 eval-cheap-module-souce-map
    • VUE默认使用 eval-source-map 模式

生产环境

  • 推荐选择

    • source-map 或 cheap-module-souce-map
  • 如要要考虑隐藏源代码

    • hidden-source-map 只隐藏源代码,会提示构建后代码错误信息

    • nosources-source-map 全部隐藏

常用的 Loader

  • css-loader: 加载CSS文件(@import 和 url()
  • style-loader: 使用<style>将css文件样式注入到我们的HTML页面
  • postcss-loader: 自动添加 CSS3 部分属性的浏览器前缀
  • less-loader: 编译 less 转 css
  • file-loader: 解析文件 url,并将图片 copy 到指定目录
  • url-loader: 一般与 file-loader 搭配使用,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中
  • svg-sprite-loader
  • babel-loader:将 ES6/7/8 语法转换为 ES5 语法,但是对新 api 并不会转换(promise、Generator、Set、Maps、Proxy 等),需要借助 babel-polyfill 来帮助我们转换
  • vue-loader:用于解析.vue 文件
  • vue-template-compiler:用于编译模板
  • vue-style-loader:解析 vue 文件的样式

常用的 plugins

plugin作用
html-webpack-pluginwebpack 打包出来的文件我们需要引入到 html
clean-webpack-plugin打包输出前清空文件夹
webpack-bundle-analyzer生成代码分析报告,帮助提升代码质量和网站性能。

webpack优化

  1. external:排除第三方包,使用cdn引入

    externals配置项用来告诉Webpack要构建的代码中使用了哪些不用被打包的模块,也就是说这些模版是外部环境提供的,Webpack在打包时可以忽略它们

    使用场景:用webpack构建的工程,当工程规模达到一定程度时,都有必要将体积较大的、基本无变动的第三方包处理为externals,以减小入口js文件的大小缩短首屏加载时长减小依赖包体积

    index.html

     <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    

    vue.config.js

    externals: {
          vue: 'Vue'
    }
    
  2. 分包splitchunks:文件过大,分包

  • 什么是chunks: 对于打包产物bundle, 有些情况下,我们觉得太大了。 为了优化性能,我们需要对bundle进行以下拆分,对于拆分出来的东西叫chunk。

  • 代码分割的三种方式

    • 入口起点:使用 entry 配置手动地分离代码

      // `entry` 里配置多个入口即可:
      entry: { app: "./index.js", app1: "./index1.js" }
      
    • 动态引入:通过模块的内联函数调用来分离代码。

      //`import()` 加载的模块分离成独立的包:
      import("./a");
      
    • 防止重复:使用 splitChunks 去重和分离 chunk。使用 splitChunks 配置分离规则,然后 webpack 自动将满足规则的 chunk 分离。一切都是自动完成的。

  • optimization.splitChunks.chunks: async(默认) | initial | all

    • async: 对于动态加载的模块,默认配置会将该模块单独打包。使用以下语法进行动态加载(还有其他写法):
    import('lodash')
    
    • all: 使用默认配置进行分包(optimization.splitChunks的配置),所有类型的模块进行拆分;

    • initial:使用默认配置进行分包(optimization.splitChunks的配置),将动态引入和静态引入的同一模块分开处理(多个相同的chunk包)

    只有当chunks 不为async时,webpack打包的默认配置才会是default配置

  1. 使用contentHash,对修改的内容文件部署进行更新

    利用浏览器缓存,重新部署后,不会重新加载未更新的代码文件

    configureWebpack: {
        output: {
          filename: `js/[name]_[contenthash].js`,
          chunkFilename: `js/[name]_[contenthash]js`
        },
    },
    css: {
        //重点.
        extract: {
          // 打包后css文件名称添加时间戳
          filename: `css/[name]_[contenthash].css`,
          chunkFilename: `css/[name]_[contenthash].css`
        }
    },
    

Vite 和 webpack 的区别

  • Vite构建速度快。Vite在开发模式下使用本地ES模块,不需要生成项目依赖,使用go构建,所以构建速度较快。Vite的插件生态相对较新。
  • Webpack作为构建工具,使用node构建,速度较慢