- webpack打包原理分析和实现(一)
- webpack打包原理分析和实现(二)
- webpack打包原理分析和实现(三)
上一篇,获得了modules的对象,打印:
代码语言:javascript复制{ './src/index.js':
{ dependencies: { './expo.js': './src\expo.js' },
code:
'"use strict";nnvar _expo = require("./expo.js");nn(0, _expo.add)(1, 2);nconsole.log("hello webpack"); //表达式' },
'./src\expo.js':
{ dependencies: {},
code:
'"use strict";nnObject.defineProperty(exports, "__esModule", {n value: truen});nexports.minus = exports.add = void 0;nnvar add = function add(a, b) {n return a b;n};nnexports.add = add;nnvar minus = function minus(a, b) {
n return a - b;n};nnexports.minus = minus;' } }
代码生成了,但是里面有require函数,exports浏览器是不认识的,因此接下来需要实现require和exports
具体步骤:
生成bundle文件main.js的路径filePath
bundle文件的内容, 注意第一段生成的代码var _expo = require("./expo.js")。参数里其实是相对路径,我们需要把它处理成项目路径,才能正常运行,localRequire函数的作用在于此,当做参数传入自执行函数
代码语言:javascript复制 (function(require,exports,code){
eval(code)
})(localRequire,exports,graph[module].code)
第二个参数exports是一个空对象传入,装载导入的方法/对象,第三个参数是es6转换后的代码,通过eval去执行,遇到require和exports,会在参数里找
输出文件的代码
代码语言:javascript复制//接收参数对象,生成自执行函数
savefile(code){
//! 生成bundle.js => ./dist/main.js
const filePath=path.join(this.output.path,this.output.filename)
console.log(filePath)
//对象序列化, //如果不序列化,参数是对象=>
// (function(){
// })([object Object])
//处理参数对象
const newModules=JSON.stringify(code)
//创建bundle.js 自执行函数
const bundle=`(function(graph){
//执行参数中代码,需要实现require函数,实现exports
function require(module){
function localRequire(relativePath){
return require(graph[module].dependencies[relativePath])//递归解析
}
var exports={};//要加分号,不然会连接面的(),把内容加到exports
//require做进一步路径解析
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[module].code)
return exports
}
require('${this.entry}')
})(${newModules})`
fs.writeFileSync(filePath,bundle,'utf-8')
}
webpack.js完整代码
const fs = require(‘fs’)//node的核心模块fs
const parser = require(’@babel/parser’)//@babel/parser//分
const traverse = require(’@babel/traverse’).default// 处理得到的信息
const path = require(‘path’)
const babel = require(’@babel/core’)
// 析依赖内容
module.exports = class webpack {
constructor(options) {
console.log(options)
const {entry, output} = options
this.entry = entry
this.output = output
//存所有模块信息
this.modules = []
}
代码语言:javascript复制run() {//入口函数
const entryModule = this.moduleAnalyser(this.entry)
console.log(entryModule)
//!处理其他模块,做一个信息汇总
this.modules.push(entryModule);
for (let i = 0; i < this.modules.length; i ) {
const item = this.modules[i]
const {dependencies} = item
if (dependencies) {
for (let j in dependencies) {
this.modules.push(this.moduleAnalyser(dependencies[j]))//数组递归
}
}
}
console.log(this.modules)
//! 数组处理成对象
const obj = {}
this.modules.forEach((item) => {
obj[item.entryFile] = {
dependencies: item.dependencies,
code: item.code
}
})
console.log('haha',obj)//已完成分析入口依赖
this.savefile(obj)
}
moduleAnalyser(entryFile) {
//! 分析入口模块的内容
const content = fs.readFileSync(entryFile, 'utf-8')
console.log(content)
//!分析出哪些是依赖?以及依赖的路径
const ast = parser.parse(content, {
sourceType: 'module'
})
const dependencies = {}
traverse(ast, {
//提取哪个字段就用哪个函数
ImportDeclaration({node}) {
console.log(node.source.value)
// path.dirname(entryFile)
// console.log(path.dirname(entryFile))
//路径拼接
const newPathName = "./" path.join(path.dirname(entryFile), node.source.value)
console.log(newPathName)
dependencies[node.source.value] = newPathName
console.log(dependencies)
}
})
console.log(ast.program.body)
//! 处理内容,转换ast
const {code} = babel.transformFromAst(ast, null, {
presets: ['@babel/preset-env']
})
console.log(code)
return {
entryFile,
dependencies,//如果没有值,说明没有依赖
code
}
}
//接收参数对象,生成自执行函数
savefile(code){
//! 生成bundle.js => ./dist/main.js
const filePath=path.join(this.output.path,this.output.filename)
console.log(filePath)
//对象序列化, //如果不序列化,参数是对象=>
// (function(){
// })([object Object])
//处理参数对象
const newModules=JSON.stringify(code)
//创建bundle.js 自执行函数
const bundle=`(function(graph){
//执行参数中代码,需要实现require函数,实现exports
function require(module){
function localRequire(relativePath){
return require(graph[module].dependencies[relativePath])//递归解析
}
var exports={};//要加分号,不然会连接面的(),把内容加到exports
//require做进一步路径解析
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[module].code)
return exports
}
require('${this.entry}')
})(${newModules})`
fs.writeFileSync(filePath,bundle,'utf-8')
}
}
花了几个小时,把流程梳理了一遍,给个赞吧,完整代码