Open Cosen95 opened 4 years ago
compiler.js 的 run 方法里面,迭代构建modules的时候,上面的写法仅支持一层依赖,如果 greeting.js 中也有依赖的话打包完运行会报错的吧?
弔图一堆
compiler.js 的 run 方法里面,迭代构建modules的时候,上面的写法仅支持一层依赖,如果 greeting.js 中也有依赖的话打包完运行会报错的吧?
从逻辑上看是会报错,但是这个文章的目的是让大致了解学习webpack打包的思路,所以实现起来肯定不会考虑那么全面
没有讲到插件哦
引言
前一段时间我把
webpack
源码大概读了一遍,webpack
到4.x
版本后,其源码已经比较庞大,对各种开发场景进行了高度抽象,阅读成本也愈发昂贵。过度分析源码对于大家并没有太大的帮助。本文主要是想通过分析
webpack
的构建流程以及实现一个简单的webpack
来让大家对webpack
的内部原理有一个大概的了解。(保证能看懂,不懂你打我 🙈)webpack 构建流程分析
首先,无须多言,上图~
webpack
的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:首先会从配置文件和Shell
语句中读取与合并参数,并初始化需要使用的插件和配置插件等执行环境所需要的参数;初始化完成后会调用Compiler
的run
来真正启动webpack
编译构建过程,webpack
的构建流程包括compile
、make
、build
、seal
、emit
阶段,执行完这些阶段就完成了构建过程。初始化
entry-options 启动
从配置文件和
Shell
语句中读取与合并参数,得出最终的参数。run 实例化
compiler
:用上一步得到的参数初始化Compiler
对象,加载所有配置的插件,执行对象的run
方法开始执行编译编译构建
entry 确定入口
根据配置中的
entry
找出所有的入口文件make 编译模块
从入口文件出发,调用所有配置的
Loader
对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理build module 完成模块编译
经过上面一步使用
Loader
翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系seal 输出资源
根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会emit 输出完成
在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
分析完构建流程,下面让我们自己动手实现一个简易的
webpack
吧~实现一个简易的 webpack
准备工作
目录结构
我们先来初始化一个项目,结构如下:
这里我先解释下每个文件/文件夹对应的含义:
dist
:打包目录lib
:核心文件,主要包括compiler
和parser
compiler.js
:编译相关。Compiler
为一个类, 并且有run
方法去开启编译,还有构建module
(buildModule
)和输出文件(emitFiles
)parser.js
:解析相关。包含解析AST
(getAST
)、收集依赖(getDependencies
)、转换(es6转es5
)index.js
:实例化Compiler
类,并将配置参数(对应forstpack.config.js
)传入test.js
:测试文件,用于测试方法函数打console
使用src
:源代码。也就对应我们的业务代码forstpack.config.js
: 配置文件。类似webpack.config.js
package.json
:这个就不用我多说了~~~(什么,你不知道??)先完成“造轮子”前 30%的代码
项目搞起来了,但似乎还少点东西~~
对了!基础的文件我们需要先完善下:
forstpack.config.js
和src
。首先是
forstpack.config.js
:内容很简单,定义一下入口、出口(你这也太简单了吧!!别急,慢慢来嘛)
其次是
src
,这里在src
目录下定义了两个文件:greeting.js
:index.js
:ok,到这里我们已经把需要准备的工作都完成了。(问:为什么这么基础?答:当然要基础了,我们的核心是“造轮子”!!)
梳理下逻辑
短暂的停留一下,我们梳理下逻辑:
Q
: 我们要做什么?A
: 做一个比webpack
更强的super webpack
(不好意思,失态了,一不小心说出了我的心声)。还是低调点(防止一会被疯狂打脸)Q
: 怎么去做?A
: 看下文(23333)Q
: 整个的流程是什么?A
: 哎嘿,大概流程就是:AST
语法树。AST
语法树,生成浏览器能够运行的代码正式开工
compile.js 编写
compile.js
主要做了几个事情:forestpack.config.js
配置参数,并初始化entry
、output
run
方法。处理构建模块、收集依赖、输出文件等。buildModule
方法。主要用于构建模块(被run
方法调用)emitFiles
方法。输出文件(同样被run
方法调用)到这里,
compiler.js
的大致结构已经出来了,但是得到模块的源码后, 需要去解析,替换源码和获取模块的依赖项, 也就对应我们下面需要完善的parser.js
。parser.js 编写
看完这代码是不是有点懵(说好的保证让看懂的 😤)
别着急,你听我辩解!!😷
这里要先着重说下用到的几个
babel
包:@babel/parser
:用于将源码生成AST
@babel/traverse
:对AST
节点进行递归遍历babel-core
/@babel/preset-env
:将获得的ES6
的AST
转化成ES5
parser.js
中主要就三个方法:getAST
: 将获取到的模块内容 解析成AST
语法树getDependencies
:遍历AST
,将用到的依赖收集起来transform
:把获得的ES6
的AST
转化成ES5
完善 compiler.js
在上面我们已经将
compiler.js
中会用到的函数占好位置,下面我们需要完善一下compiler.js
,当然会用到parser.js
中的一些方法(废话,不然我上面干嘛要先把parser.js
写完~~)直接上代码:
关于
compiler.js
的内部函数,上面我说过一遍,这里主要来看下emitFiles
:这里的
bundle
一大坨,什么鬼?我们先来了解下
webpack
的文件 📦 机制。下面一段代码是经过webpack
打包精简过后的代码:简单分析下:
webpack
将所有模块(可以简单理解成文件)包裹于一个函数中,并传入默认参数,将所有模块放入一个数组中,取名为modules
,并通过数组的下标来作为moduleId
。modules
传入一个自执行函数中,自执行函数中包含一个installedModules
已经加载过的模块和一个模块加载函数,最后加载入口模块并返回。__webpack_require__
模块加载,先判断installedModules
是否已加载,加载过了就直接返回exports
数据,没有加载过该模块就通过modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
执行模块并且将module.exports
给返回。(你上面说的这一坨又是什么鬼?我听不懂啊啊啊啊!!!)
那我换个说法吧:
webpack
打包出来的是一个匿名闭包函数(IIFE
)modules
是一个数组,每一项是一个模块初始化函数__webpack_require__
用来加载模块,返回module.exports
WEBPACK_REQUIRE_METHOD(0)
启动程序(小声 bb:怎么样,这样听懂了吧)
lib/index.js 入口文件编写
到这里,就剩最后一步了(似乎见到了胜利的曙光)。在
lib
目录创建index.js
:这里逻辑就比较简单了:实例化
Compiler
类,并将配置参数(对应forstpack.config.js
)传入。运行
node lib/index.js
就会在dist
目录下生成bundle.js
文件。和上面用
webpack
打包生成的js
文件作下对比,是不是很相似呢?来吧!展示
我们在
dist
目录下创建index.html
文件,引入打包生成的bundle.js
文件:此时打开浏览器:
如你所愿,得到了我们预期的结果~
总结
通过对
webpack
构建流程的分析以及实现了一个简易的forestpack
,相信你对webpack
的构建原理已经有了一个清晰的认知!(当然,这里的forestpack
和webpack
相比还很弱很弱,,,,)参考
本文是看过极客时间程柳锋老师的「玩转 webpack」课程后整理的。这里也十分推荐大家去学习这门课程~
❤️ 爱心三连击
1.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~
2.关注公众号前端森林,定期为你推送新鲜干货好文。
3.特殊阶段,带好口罩,做好个人防护。
4.添加微信fs1263215592,拉你进技术交流群一起学习 🍻