Skip to content

webpack 面试了解 #22

@Lindysen

Description

@Lindysen

1.webpack 构建离线应用

==优化==
2. 减少文件搜索范围

resolve.modules 的默认值是 ['node_modules'],含义是先去当前目录下的 ./node_modules 目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules 中找,再没有就去 ../../node_modules 中找,以此类推,这和 Node.js 的模块寻找机制很相似。


resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径。


resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    // 其中 __dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname, 'node_modules')]
    // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
    mainFields: ['main'],
    // 使用 alias 把导入 react 的语句换成直接使用单独完整的 react.min.js 文件,
    // 减少耗时的递归解析操作
    alias: {
      'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'), // react15
      // 'react': path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'), // react16
    }
 
    // 尽可能的减少后缀尝试的可能性
    //在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在
    extensions: ['js'],
    // 独完整的 `react.min.js` 文件就没有采用模块化,忽略对 `react.min.js` 文件的递归解析处理
    //让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。
    noParse: [/react\.min\.js$/],
  },

3 动态链接库

在一个动态链接库中包含给其他模块调用的函数和数据

1.把网页依赖的基础模块抽离出来,打包进一个个动态链接库,一个动态链接库中包含多个模块

2.当需要导入的模块在动态链接中,这个模块不能在被重新打包 而是直接去动态链接库中

3.页面依赖的所有动态链接库需要被加载

 原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。  由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。

4 cdn 加速 内容分发网络

压缩代码减少网络传输大小

Webpack 接入cdn

1. 静态资源的导入 URL 需要变成指向 CDN 服务的绝对路径的 URL 而不是相对于 HTML 文件的 URL。
2.静态资源的文件名称需要带上有文件内容算出来的 Hash 值,以防止被缓存。
3.不同类型的资源放到不同域名的 CDN(同一时刻针对同一个域名的资源并行请求是有限制的话(具体数字大概4个左右,不同浏览器可能不同)) 服务上去,以防止资源的并行加载被阻塞。这样会增加域名解析的时间 可以通过 head 标签<link rel="dns-perfetch" href="//sxx.com"/>预解析域名 减少域名解析的时间

  1. 压缩代码
1. 混淆源码
2. 减少网络传输流量
3. 提升网页加速

5 背后的运行机制

1. plugin: 扩展插件,在webpack构建过程中的特定时机会广播事件,插件监听特定事件,在特定时机作一些操作

2. loader: 模块转换器,用于把模块原内容转换成新的内容

webpack 的构建过程是串行化的从启动到结束 经过这些过程
1. 确定配置参数: 配置文件中的参数和shell命令行中的参数合并成最终的参数

2. 初始化compiler对象: 使用得出最终的参数初始化compiler对象,加载所有插件配置,执行compiler对象的run方法开始构建

3.确定入口:根据entry参数确定入口文件

4. 编译模块:从入口文件开始调用配置的loader对文件进行转换,在找出入口文件依赖的模块,逐个对他们进行转换,入口文件依赖的模块都被转换过了

5. 完成编译: 在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;

6. 输出资源:根据入口与模块之间的依赖关系,产出一个个包含多个模块的chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这里是改变输出内容的最后机会

7. 输出完成:在确定好输出资源后,根据配置的路径和文件名,把文件内容写进到文件系统

Compiler 全局只有一个,包含完整的webpack的配置, 负责文件监听和启动编译

当 Webpack 以开发模式运行时,每当监测到文件变化,就会生成一个新的Compilation对象,Compilation对象包含了当前模块的资源,编译生成资源,变化的文件等,Compilation对象也提供了很多事件回调供插件使用


。。。。。。。。。。。
初始化阶段:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
1.初始化参数
2.实例化compiler
3.加载插件,依次调用插件的 apply 方法,让插件可以监听后续的所有事件节点。
4. environment: 应用node.js的文件系统到compiler对象
5. entry-option 根据配置的entry信息,给每个entry生成一个对应的entryPlugin,为入口的解析,递归解析依赖作准备

6. after-plugins:当所有插件apply方法调用完成
7 .after-resolvers

编译阶段:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
1. run 开始一次新的编译
2. complier: 通知插件将开始一次新的编译
3. complilation: 生成新的complilation对象
4. make 一个新的 Compilation 创建完毕,即将从 Entry 开始读取文件,根据文件类型和配置的 Loader 对文件进行编译,编译完后再找出该文件依赖的文件,递归的编译和解析。
5.after-compile 一次 Compilation 执行完成。
6.invalid 当遇到文件不存在、文件编译错误等异常时会触发该事件,该事件不会导致 Webpack 退出。


输出阶段:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。

1.emit: 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。
2.after-emit文件输出完毕
3.done:成功完成一次完成的编译和输出流程。
4.failed 如果在编译和输出流程中遇到异常导致 Webpack 退出时,就会直接跳转到本步骤,插件可以在本事件中获取到具体的错误原因。
在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系,并且把相关模块组合在一起形成一个个 Chunk。

6.输出文件分析

原来一个个单独的模块文件被合并到一个文件中
bundle.js

为什么bunlde.js能直接在浏览器中运行呢

bundle.js中能直接运行在浏览器中原因是在输出文件中通过_webpack_require_函数定义了一个在浏览器中执行的加载函数来模拟node.js中的require语句

做不到和node一样在本地加载文件,当发现需要加载新的文件时需要通过网络,但是模块多了的化,时间就很长,所有就放在数组里,一次加载完

_webpack_require_函数还有缓存处理,已经加载过的模块不会进行二次加载,会去缓存中获取

7 抽取页面公共代码

改善什么现象
1. 相同的资源被重复加载,浪费用户的流量 服务器的成本
2. 包体也大

8 按需加载

最关键的一行是 chunkFilename: '[name].js',,它专门指定动态生成的 Chunk 在输出时的文件名称。 如果没有这行,分割出的代码的文件名称将会是 [id].js。
import('/*webpackChunkName:"show"*/''./show.js').then((show)=> {
    show('Webpack');
})

9 plugin

webpack 就像一个生产线,通过一系列流程才能将源文件转换成输出结果,生产线上每个处理流程的职责是单一的,多个流程存在依赖关系
只有完成当前处理后才能交给下一个流程去处理
插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。

10 loader 链式调用 翻译员

Loader 运行在 Node.js 中,你可以调用任何 Node.js 自带的 API

loader 将源代码经过转换后输出新的结果支持链式调用

在你开发一个 Loader 时,请保持其职责的单一性,你只需关心输入和输出

module.exports = function(source){
    return source;
}
loader 有同步和异步之分
Webpack 会默认缓存所有 Loader 的处理结果, 

加载本地loader
1. npm-link

11 Prepack

Prepack 在编译阶段就预先执行源码得到结果,再把结果输出来以提高性能
而不是在运行时再去求结果
还不够成熟

不能够识别Dom APi 部分Node.js API
存在优化后代码性能更低的情况
存在优化后代码体积更大的情况

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions