深刻理解 webpack 打包机制。node
依赖环境 node.jswebpack
// index.js
import { message } from './message.js'
console.log(message)
// message.js
import { word } from './word.js'
export const message = `say ${ word }`
// word.js
export const word = 'hello world !'
复制代码
须要安装以下 babelweb
const fs = require('fs');
const path = require('path')
const parser = require('@babel/parser'); // 帮助咱们分析字符串块,即咱们经过 fs 来读取文件时遇到的 import、export 语法
const traverse = require('@babel/traverse').default // traverse 采用的 ES Module 导出,咱们经过 requier 引入的话就加个 .default
const babel = require('@babel/core');
const moduleAnalyser = (filename) => {
const content = fs.readFileSync(filename, 'utf-8');
// parser 解析咱们的 content 以后会返回一个 AST (抽象语法树)
const AST = parser.parse(content, {
sourceType: "module"
})
// 依赖收集
const dependencies = {};
// 使用 traverse 来遍历 AST
traverse(AST, {
ImportDeclaration({ node }) { // 函数名是 AST 中包含的内容,参数是一些节点,node 表示这些节点下的子内容
const dirname = path.dirname(filename); // 咱们从抽象语法树里面拿到的路径是相对路径,而后咱们要处理它,在 bundler.js 中才能正确使用
const newDirname = './' + path.join(dirname, node.source.value).replace('\\', '/'); // 将dirname 和 获取到的依赖联合生成绝对路径
dependencies[node.source.value] = newDirname; // 将源路径和新路径以 key-value 的形式存储起来
}
})
// 将抽象语法树转换成浏览器能够运行的代码
const { code } = babel.transformFromAst(AST, null, {
presets: ['@babel/preset-env']
})
return {
filename,
dependencies,
code
}
}
// 建立依赖图谱函数, 递归遍历全部依赖模块
const makeDependenciesGraph = (entry) => {
const entryModule = moduleAnalyser(entry)
const graghArray = [ entryModule ]; // 首先将咱们分析的入口文件结果放入图谱数组中
for (let i = 0; i < graghArray.length; i ++) {
const item = graghArray[i];
const { dependencies } = item; // 拿到当前模块所依赖的模块
if (dependencies) {
for ( let j in dependencies ) { // 经过 for-in 遍历对象
graghArray.push(moduleAnalyser(dependencies[j])); // 若是子模块又依赖其它模块,就分析子模块的内容
}
}
}
const gragh = {}; // 将图谱的数组形式转换成对象形式
graghArray.forEach( item => {
gragh[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
})
return gragh;
}
const graghInfo = makeDependenciesGraph('./src/index.js')
console.log(graghInfo)
复制代码
下图graghArray的数据结构数组
建立生成代码的函数,传入咱们的路口文件的路径,整合以前的代码 。浏览器
const generateCode = (entry) => {
// 注意:咱们的 gragh 是一个对象,key是咱们全部模块的绝对路径,须要经过 JSON.stringify 来转换
const gragh = JSON.stringify(makeDependenciesGraph(entry));
// 咱们知道,webpack 是将咱们的全部模块放在闭包里面执行的,因此咱们写一个自执行的函数
// 注意: 咱们生成的代码里面,都是使用的 require 和 exports 来引入导出模块的,而咱们的浏览器是不认识的,因此须要构建这样的函数
return `
(function( gragh ) {
function require( module ) {
// 相对路径转换成绝对路径的方法
function localRequire(relativePath) {
return require(gragh[module].dependencies[relativePath])
}
const exports = {};
(function( require, exports, code ) {
eval(code)
})( localRequire, exports, gragh[module].code )
return exports;
}
require('${ entry }')
})(${ gragh })
`;
}
const code = generateCode('./src/index.js');
console.log(code)
复制代码
将代码整合以后,在命令行运行,会打印出一段代码,将其复制到浏览器中运行。bash
大功告成 !babel