webpack 进行打包的基本原理
如何自己实现一个 loader 和 plugin
注: 本文使用的 webpack 版本是 v4.43.0 , webpack-cli 版本是 v3.3.11 , node 版本是 v12.14.1 , npm 版本 v6.13.4 (如果你喜欢 yarn 也是可以的),演示用的 chrome 浏览器版本 81.0.4044.129(正式版本) (64 位)
1. webpack打包基本原理
webpack的一个核心功能就是把我们写的模块化的代码,打包之后,生成可以在浏览器中运行的代码,我们这里也是从简单开始,一步步探索webpack的打包原理
1.1 一个简单的需求
我们首先建立一个空的项目,使用 npm init -y 快速初始化一个 package.json ,然后安装 webpack webpack-cli
接下来,在根目录下创建 src 目录, src 目录下创建 index.js , add.js , minus.js ,根目录下创建 index.html ,其中 index.html 引入 index.js ,在 index.js 引入 add.js , minus.js ,
目录结构如下:
文件内容如下:
// add.js export default (a, b) => { return a + b } // minus.js export const minus = (a, b) => { return a - b } // index.js import add from './add.js' import { minus } from './minus.js' const sum = add(1, 2) const division = minus(2, 1) console.log('sum>>>>>', sum) console.log('division>>>>>', division)
<!--index.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta content="width=device-width, initial-scale=1.0"> <title>demo</title> </head> <body> <script src="https://www.jb51.net/article/src/index.js"></script> </body> </html>
这样直接在 index.html 引入 index.js 的代码,在浏览器中显然是不能运行的,你会看到这样的错误
Uncaught SyntaxError: Cannot use import statement outside a module
是的,我们不能在 script 引入的 js 文件里,使用 es6 模块化语法
1.2 实现webpack打包核心功能
我们首先在项目根目录下再建立一个bundle.js,这个文件用来对我们刚刚写的模块化 js 代码文件进行打包
我们首先来看webpack官网对于其打包流程的描述:
it internally builds a dependency graph which maps every module your project needs and generates one or more bundles(webpack会在内部构建一个 依赖图(dependency graph),此依赖图会映射项目所需的每个模块,并生成一个或多个 bundle)
在正式开始之前,结合上面 webpack 官网说明进行分析,明确我们进行打包工作的基本流程如下:
首先,我们需要读到入口文件里的内容(也就是index.js的内容) 其次,分析入口文件,递归的去读取模块所依赖的文件内容,生成依赖图 最后,根据依赖图,生成浏览器能够运行的最终代码 1. 处理单个模块(以入口为例) 1.1 获取模块内容
既然要读取文件内容,我们需要用到 node.js 的核心模块 fs ,我们首先来看读到的内容是什么:
// bundle.js const fs = require('fs') const getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') console.log(body) } getModuleInfo('https://www.jb51.net/article/src/index.js')
我们定义了一个方法 getModuleInfo ,这个方法里我们读出文件内容,打印出来,输出的结果如下图:
我们可以看到,入口文件 index.js 的所有内容都以字符串形式输出了,我们接下来可以用正则表达式或者其它一些方法,从中提取到 import 以及 export 的内容以及相应的路径文件名,来对入口文件内容进行分析,获取有用的信息。但是如果 import 和 export 的内容非常多,这会是一个很麻烦的过程,这里我们借助 babel
提供的功能,来完成入口文件的分析
1.2 分析模块内容
我们安装 @babel/parser ,演示时安装的版本号为 ^7.9.6
这个babel模块的作用,就是把我们js文件的代码内容,转换成js对象的形式,这种形式的js对象,称做 抽象语法树(Abstract Syntax Tree, 以下简称AST)
// bundle.js const fs = require('fs') const parser = require('@babel/parser') const getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') const ast = parser.parse(body, { // 表示我们要解析的是es6模块 sourceType: 'module' }) console.log(ast) console.log(ast.program.body) } getModuleInfo('https://www.jb51.net/article/src/index.js')
使用 @babel/parser 的 parse 方法把入口文件转化称为了 AST ,我们打印出了 ast ,注意文件内容是在 ast.program.body 中,如下图所示: