上一篇,獲得了modules的對象,打印:
{ './src/index.js':
{ dependencies: { './expo.js': './src\\expo.js' },
code:
'"use strict";\n\nvar _expo = require("./expo.js");\n\n(0, _expo.add)(1, 2);\nconsole.log("hello webpack"); //表達式' },
'./src\\expo.js':
{ dependencies: {},
code:
'"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.minus = exports.add = void 0;\n\nvar add = function add(a, b) {\n return a + b;\n};\n\nexports.add = add;\n\nvar minus = function minus(a, b) {
\n return a - b;\n};\n\nexports.minus = minus;' } }
代碼生成了,但是裏面有require函數,exports瀏覽器是不認識的,因此接下來需要實現require和exports
具體步驟:
- 生成bundle文件main.js的路徑filePath
- bundle文件的內容, 注意第一段生成的代碼var _expo = require("./expo.js")。參數裏其實是相對路徑,我們需要把它處理成項目路徑,才能正常運行,localRequire函數的作用在於此,當做參數傳入自執行函數
(function(require,exports,code){ eval(code) })(localRequire,exports,graph[module].code)
- 第二個參數exports是一個空對象傳入,裝載導入的方法/對象,第三個參數是es6轉換後的代碼,通過eval去執行,遇到require和exports,會在參數裏找
輸出文件的代碼
//接收參數對象,生成自執行函數
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 = []
}
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')
}
}
花了幾個小時,把流程梳理了一遍,給個贊吧,完整代碼