webpack原理 (2)

会首先判断当前moduleId是否已经存在缓存installedModules中,若是存在则直接返回,不需要再继续解析其依赖。若是不存在,则会构造一个对象并将其同时存到installedModules中和module中。第一次执行时installedModules为空对象,moduleId为./src/index.js。

执行modules[moduleId]函数,即执行modules['./src/index.js'],会通过call改变其作用域并传递module, module.exports, __webpack_require__三个形参,执行的内容就是入口文件模块./src/index.js中的js代码。

call传递的作用域置为module.exports,由于module.exports此时为空对象,则index.js中的作用域就是指向它,这也是典型的使用闭包来解决作用域的问题。

module, module.exports的作用就是用于模块内抛出对象使用的,作用是一个的,可以参考require.js进行这块的理解

__webpack_require__的作用就很巧妙了,此时入口index.js中使用的require('./parent.js')已经被替换成__webpack_require__("./src/parent.js\"),执行modules[moduleId]函数时便会在此调用__webpack_require__函数进行递归调用,会再次回到第二步,直到child.js执行完毕,整个bundle才算执行结束。

分析完bundle之后,会发现对于webpack的打包结果,除了形参modules会跟着代码的业务逻辑修改而变化之外,自执行函数中的代码始终是固定不变的,因此想要编写一个属于自己的webpack时,重点关注和需要解决的就是modules这个对象是如何生成的。

创建bundle

分析完webpack打包完成之后的bundle文件,以结果为导向反推实现过程便会简单许多,若是让我们自己动手实现一个简单版的webpack,便会有了些思路。

首先需要一个简单的wbepack配置

const path = require('path') module.exports = { entry: './src/index.js', output:{ path: path.join(__dirname, 'dist'), filename: 'bundle.js' } }

简单版本的webpack实现思路

获取webpack配置文件

封装一个用于解析配置并将其简单打包的方法

利用抽象语法书解析模块内容

递归解析模块依赖

使用模版引擎输出结果

有了思路,接下来就是按部就班的实现

获取webpack配置文件,而需要做的事情就是将这个配置文件进行解析,根据配置文件进行打包生成bundle。首先就是读取需要打包项目的配置文件

const config = require(path.resolve('webpack.config.js'))

获取配置文件之后,便是如何解析并实现webpack的功能,这些功能全部封装在Compiler类中,用于解析配置文件的配置,并通过start进行启动解析

const Compiler = require('../lib/Compiler') new Compiler(config).start()

重点就是如何实现这个方法,定义一个Compiler类,提供一个start方法开始webpack打包,通过depAnalyse便可以获取到入口文件index的内容

const path = require('path') const fs = require('fs') class Compiler { constructor(config){ this.config = config const { entry } = config // 配置文件 this.entry = entry // 入口文件 this.root = process.cwd() // 输入 webpack-theory 的路径 this.modules = {} // 初始化一个控对象,存放所有的模块 } /** * 开始打包 * 打包最主要的就是依赖的分析 */ start(){ this.depAnalyse(path.resolve(this.root, this.entry)) } /** * 依赖分析 * 需要根据入口entry进行开始分析 */ depAnalyse(modulePath){ // 获取 index.js 的内容 let source = this.getSource(modulePath) } // 读取文件 getSource(path){ return fs.readFileSync(path, 'utf-8') } } module.exports = Compiler

获取到index.js的文件内容之后,并不能直接使用,需要通过将其解析成抽象语法树进行处理,需要使用一个插件@babel/parser将模块代码解析成AST,然后插件@babel/traverse配合着使用,将AST的节点进行替换,替换完成之后,使用插件@babel/generator将AST转换成模块的原有代码,改变的只是将require变成__webpack_require__,需要注意的是需要将路径处理一下,因为此时的路径是相对于src下面的。处理完index之后需要递归调用处理全部的模块,并声称bundle中自执行函数的参数modules

此时index的模块代码经过处理之后,变成了需要的代码

const parent = __webpack_require__("./src/parent.js"); console.log(parent);

在函数depAnalyse中添加如下处理

// 获取 index.js 的内容 let source = this.getSource(modulePath) // ------- // 准备一个依赖数组,用于存储当前模块 let dependenceArr = [] // 将js代码 解析成AST let ast = parser.parse(source) // 将AST中的 require 替换为 __webpack_require__ traverse(ast, { // p 是抽象语法树的节点 CallExpression(p) { if (p.node.callee.name === 'require') { // 将代码中的 require 替换为 __webpack_require__ p.node.callee.name = '__webpack_require__' const oldValue = p.node.arguments[0].value // 修改路径,避免windows出现反斜杠 \ p.node.arguments[0].value = ('./' + path.join('src', oldValue)).replace(/\\+/g, 'http://www.likecs.com/') // 每找到一个require调用,就将其中的路径修改完毕后加入到依赖数组中 dependenceArr.push(p.node.arguments[0].value) } } }) // 构建modules对象 const sourceCode = generator(ast).code const modulePathRelative = './' + (path.relative(this.root, modulePath)).replace(/\\+/g, 'http://www.likecs.com/') this.modules[modulePathRelative] = sourceCode // 递归调用加载所有依赖 dependenceArr.forEach(dep => this.depAnalyse(path.resolve(this.root, dep)))

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdwzs.html