當G和百度的時候,看到一些不錯文章,可是老是給我一種印象,看了就忘記了,可是此次我打算看完以後,從算法的角度寫一篇本身理解的文章,來看看webpack這一類打包工具它們作了什麼?最困難的點在哪裏?node
先看看這個圖webpack
上圖就是webpack的實現流程web
問題: 如何經過遍歷AST收集依賴?其中經過了什麼算法?算法
由於當有不少複雜的依賴的時候,如何解決這些依賴的問題,應該是其中最困難的點npm
第一個文件數組
//word.js
export const word = 'hello'
複製代碼
第二個文件瀏覽器
//message.js
import { word } from './word.js';
const message = `say ${word}`
export default message;
複製代碼
第三個文件babel
//index.js
import message from './message.js'
console.log(message)
複製代碼
看到了 index --> message --> word閉包
看到這三個文件互相依賴的關係;你腦海裏有一些解題思路:函數
這就是咱們的問題,編寫一個函數,將上面的ES6 代碼轉成 ES5 並將這些文件代碼,生成一段瀏覽器能運行起來的代碼
按照上面的思路實現代碼
//先安裝好相應的包
npm install @babel/parser @babel/traverse @babel/core @babel/preset-env -D
//導入包
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')
//處理函數
function one(filename){
// 讀入文件
const ast = readFile(filename)
// 遍歷AST抽象語法🌲
const dependencies = traverseAST(ast)
//經過@babel/core和@babel/preset-env進行代碼的轉換
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
//返回文件名稱,和依賴關係
return {
filename,
dependencies,
code
}
}
// 最近多是看了代碼整潔之道以後,就會特別注意命名合理性和代碼塊不在同一層盡力不放在一塊兒,即便這只是一個demo
function readFile(filename){
const content = fs.readFileSync(filename, 'utf-8')
const ast = parser.parse(content, {
sourceType: 'module'//babel官方規定必須加這個參數,否則沒法識別ES Module
})
return ast
}
function traverseAST(ast){
const dependencies = {}
traverse(ast, {
//獲取經過import引入的模塊
ImportDeclaration({node}){
const dirname = path.dirname(filename)
const newFile = './' + path.join(dirname, node.source.value)
//保存所依賴的模塊
dependencies[node.source.value] = newFile
}
})
return dependencies
}
複製代碼
function two(entry){
// 先拿到帶有依賴的AST對象
const entryModule = one(entry)
// 下面就是深度算法運用部分
const graphArray = getGraphArray(entryModule)
// 接下來就是生成圖譜
const graph = getGraph(graphArray)
// 返回圖譜
return graph
}
function getGraphArray(entryModule) {
const graphArray = [entryModule]
for(let i = 0; i < graphArray.length; i++){
const item = graphArray[i];
const {dependencies} = item;//拿到文件所依賴的模塊集合(鍵值對存儲)
for(let j in dependencies){
graphArray.push(
one(dependencies[j])
)//敲黑板!關鍵代碼,目的是將入口模塊及其全部相關的模塊放入數組
}
}
}
function getGraph(graphArray) {
const graph = {}
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
})
return graph
}
// 能夠測試下:console.log(two('./src/index.js'))
複製代碼
看到這裏就應該大概知道代碼是如何處理這種AST樹的了,就和算法中處理二叉樹是同樣,找到規律,不斷的循環
function three(entry){
// //要先把對象轉換爲字符串,否則在下面的模板字符串中會默認調取對象的toString方法,參數變成[Object object],顯然不行
const graph = JSON.stringify(stepTwo(entry))
return ` (function(graph) { //require函數的本質是執行一個模塊的代碼,而後將相應變量掛載到exports對象上 function require(module) { //localRequire的本質是拿到依賴包的exports變量 function localRequire(relativePath) { return require(graph[module].dependencies[relativePath]); } var exports = {}; (function(require, exports, code) { eval(code); })(localRequire, exports, graph[module].code); return exports;//函數返回指向局部變量,造成閉包,exports變量在函數執行後不會被摧毀 } require('${entry}') })(${graph})`
}
// 能夠測試下:console.log(three('./src/index.js'))
複製代碼
其實整個下來你會發現,假設你找到了那個最爲複雜的問題,而且經過算法或者其餘的方式解決,那麼你就基本上能夠搞定webpack了;固然真實的webpack還作了不少其餘的事情,這不是咱們此次題目中的重點;閱讀各類經典的工具,你都會發現核心,最爲複雜的仍是算法!