我正在参加「掘金·启航计划」
前言:需要学习的重点 每个阶段的流程 以及原理 是干什么的 怎么干的 解析规则 缓存
1 官网学习笔记
从webpack4.0开始 可以不用引入配置文件 现在应该是5.0版本了 核心概念为: 入口 entry 输出 output loader 插件 plugins
entry默认入口为./src output默认出口为./dist
loader原理? 在 webpack 的配置中 loader 有两个目标:必要属性
entry
可以写单个:
const config = {
entry: './path/to/my/entry/file.js'
};
module.exports = config;
也可以写多个:
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};
当你向entry传入一个数组的时候,enrty属性将创建多个主入口文件,想要多个依赖文件一起注入,并且将他们的依赖导向(graph)到一个chunk中,传入数组的方式是比较有效的。
loader
loader特点: 1 loader支持链式传递,能够对资源使用流水线 (pipeline)。一组链式的loader会按照相反的顺序去执行。loader链中的第一个loader返回值给下一个loader,在最后loader,返回webpack所预期的javascript 2 loader是可以同步的,也是可以异步的 3 loader运行在node.js中 并且能够执行任何可能的操作。 4 loader能接受查询参数,用于对loader传递配置 5 loader能够使用options对象进行配置 6 除了使用package.json常见的main属性进行配置 还可以将普通的npm模块导出为loader 做法是在package.json里定义一个loader字段。 7 loader能产生额外的任意文件
解析loader: 大部分情况下 loader将从模块路径,通常默认的模块路径为(npm install ,nod-moduler)里面解析 loader会导出为一个函数 并且使用node.js兼容的javascript编写 使用npm 进行管理 后缀一般为loader
插件(plugins)
webpack插件 本质上是一个具有apply属性的javascript对象,apply属性会被webpack compiler调用,并且compiler对象可在整个编译生命周期进行访问 ConsoleLogOnBuildWebpackPlugin.js
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, compilation => {
console.log("webpack 构建过程开始!");
});
}
}
compiler hook的tap方法的第一个参数 应该是驼峰式命名法 建议使用为一个常量 一遍后续在所有的hook中复用。
用法: 插件可以携带参数 必须在webpack中的配置中 传入new实例
配置 (configuration)
很少有webpack配置看起来完全相同,这是因为webpack的配置文件 是一个导出对象的javascript文件 由webpack根据对象定义的属性进行解析
因为webpack配置是标准的nodejs CommonJs模块 可以做到: 1 通过require(...)导入其他文件 2 通过 require(...)使用npm的工具函数 3 使用javaScript控制流表达式。例如 ?: 操作符 4 对常用值使用常量或者变量 5 编写并执行函数来生成部分配置
模块 modules
对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:
- ES2015import 语句
- CommonJS require() 语句
- AMD define 和 require 语句
- css/sass/less 文件中的 @import语句。
- 样式(url(...))或 HTML 文件()中的图片链接(image url)
模块解析
resolver是一个库,用于帮助找到模块的绝对路径 一个模块可以作为另一个模块的依赖模块,然后被后者引用 如下:
import foo from 'path/to/module'
// 或者
require('path/to/module')
所依赖的模块可以是来自应用程序代码或者第三方库 resolver帮助webpack找到bundle中需要引入的模块代码,这些代码在包含在每个require/import语句汇总 当打包模块时 webpack使用enhanced-resolve来解析文件路径
解析规则
绝对路径: 由于已经取的了绝对路径 因此不需要在做解析
import "/home/me/file";
import "C:\Users\me\file";
相对路径: 在这种情况下 使用 import或者require的资源文件所在的目录被认为是上下文目录:在import/require中给定的相对路径。会添加此上下文路径(contextpath) 以产生模块的绝对路径
import "../src/file1";
import "./file2";
模块路径:
import "module";
import "module/lib/file";
模块将在resolve.modules中指定所有目录内搜索。可以替换初始模块路径,此替换路径通过使用resolve.alias 配置选项来创建一个别名
一旦根据上述规则解析路径之后,解析器将检查路径是否指向文件或者目录: 如果指向一个文件
1如果路径具有文件扩展名,则将直接被打包 2 否则: 将使用【resolve.extensions]选项作为文件扩展名来解析,此选项告诉解析器在解析中能够接受哪些扩展名 (列如:.js; .jsx)
如果路径指向一个文件夹 : 则采取以下步骤找到具有正确扩展名的正确文件
1 如果文件夹中包含package.json文件;则按照顺序查找resolve.mainFields配置选项中的指定字段。并且在package.json中的第一个这样的字段确定文件路径。 2 如果package.json文件夹不存在后者package.json文件中的main字段没有返回一个有效的路径,则按照顺序查找 resolve.maxinFiles 配置选项中指定的文件名 看是否能在imort/require目录下找到一个匹配文件夹名 3 文件扩展名通过resolve.extensions选项采用类似的方式进行解析
mainfest
mainfest数据,主要指的是:在浏览器运行时,webpack用来链接模块化的应用程序的所有代码, runtime包含:在模块交互时,连接模块所需要的加载和解析逻辑,包括浏览器中的已加载模块的链接。以及懒加载模块的执行逻辑。 一旦在应用程序中。像index.html文件,一些bundle和各种资源架子啊到浏览器中,会发现精细安排的/src目录文件已经不存在了,mainfest就是用来管理模块之间的交互的。 当编译器(compiler)开始执行的时候,解析和映射应用程序时 会保留所有模块的详细要点,这个数据集合称为 mainifest 当打包并发送到浏览器时,会在运行时通过mainfest来解析和加载模块,无论选择那种模块,那些import或者requrie语句已经转化为webpack_require方法 此方法指向的模块标识符(module-indentifier)通过使用mainfest中的数据 runtime将能够查询模块标识符,检索出对应的模块。
构建目标(target)
用法: 在webpack中配置:
module.exports = {
target: 'node'
};
在这个例子中 使用node webpack会编译为用于【类node.js】环境(使用node,js的require,而不是任意的内置模块,如fs path来加载chunk 可以支持多个target
模块热替换(HMR)
PS:最近的北京市真的热呀 热死我了 概述: 模块热替换(HMR hot -module-replacement)功能会在应用程序运行的过程中替换 添加或者删除模块,而无需加载整个页面,以达到加快开发速度:
1 保留在完全重新加载页面时丢失的应用程序状态 2 只更新变更内容 以节省开发时间 3 调整样式更加快速 几乎相当于在浏览器中调改样式
原理
1 在应用程序中: 通过一下步骤 可以做到在应用程序中置换(swap in and out)模块:
1 应用程序代码要求HMR runtime 检查更新 2 HMR runtime(异步)下载更新然后通过应用程序代码 3 应用程序代码要求HMR runtime应用更新 4 HMR runtime(同步)应用更新
2 在编译器中 除了普通资源 编译器(compiler)需要发出 updata 以允许更新之前的版本到新版本 updata由两部分组成
1 更新后的 mainfest(JSON) 2 一个或者多个更新的chunk
mainfest包括新的编译 hash 和所有的待更新chunk目录,每个更新的chunk都含有对应于此的chunk的全部更新模块,(或一个flag 用于表明此模块需要被移除)的代码
3 在模块中: HMR 是可选功能 只会影响包含HMR代码的模块 举个例子 通过style-loader为style样式追加补丁,为了运行追加补丁,style-loader实现HMR接口 当他通过HMR接收到更新之后 会使用新的接口去替换旧的接口 类似的 当在一个模块中实现了HMR接口,可以描述出当模块被更新之后发生了什么,在大部分情况下 不需要强制在每个模块中写入HMR代码 如果一个模块没有HMR处理函数 更新就会冒泡,这意味着一个简单额处理函数可以对整个模块进行更新,如果这个模块树中一共担单独的模块被更新,意味着整个依赖模块都会被更新。 4 在HMR Runtime中 对于模块系统runtime附加的代码被发送到parents和children跟踪模块中 在管理方面 runtime支持两个方法 check和apply
check发送HTTP请求来更新mainFest 如果请求失败 说明没有可用更新 如果请求成功,待更新的chunk会和当前加载过的chunk进行比较,对每个加载过的chunk 会下载相对应的待更新chunk 当所有待更新chunk完成下载之后 就会准备切换到ready状态
apply方法将所有被更新的模块标记为无效 对于每个无效模块,都需要在模块中有一个更新处理函数 (updata handler)或者在他的父级模块中有更新处理函数 否则无效标记冒泡 并也使用父级无效 每个冒泡继续 知道到达应用程序入口起点,或者到达带有更新处理函数的模块(以最先到达的为准 冒泡停止)如果他从入口起点开始冒泡。则此过程失败。
之后 所有的无效模块都被(通过dispose处理函数) 处理和解除加载。然后更新当前hash 并且调用所有的accept处理函数 ,runtime 切换会闲置状态 (idle state)一切照常继续
\