场景引入:
存在以下三个js文件,index.js与其他两个文件存在依赖关系
//minus.js
export const minus = (a,b)=>{
return a-b
}
//add.js
export default (a,b)=>{
return a+b;
}
//index.js
import add from "./add.js"
import {minus} from "./minus.js";
const sum = add(1,2);
const division = minus(2,1);
console.log(sum);
console.log(division);
如果我们要执行index.js获取打印值,需要怎么做呢?
(1)想必你会说在node环境运行index.js,但是问题是node环境是不支持import语法的
(2)有或者有的同学会说,将js文件引入html,并启动Live server执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="./src/index.js" type="module"></script>
</body>
</html>
确实非常完美
但是同时也很遗憾,在Chrome 61之前的版本浏览器是不支持ES module的
那么在浏览器不支持ES6的时期,我们是怎么解决的呢?
那么就要回归我们文章的主角身上Webpack!
wepack mini demo的原理可总结为以下几步:
- 第1步:获取主入口文件
- 第2步:@babel/parser 解析AST语法树
- 第3步:@babel/parser收集依赖(遍历AST,寻找import语句)
- 第4步:@babel/core @babel/preset-env定义ES6的AST语法树转化成ES5方法
- 第5步:利用定义的转化方法,递归解析所有依赖
- 第6步:定义require与exports,通过eval执行代码,并且导出bundle文件
第1步:获取主入口文件
const fs = require('fs')
const getModuleInfo = (file)=>{
const body = fs.readFileSync(file,'utf-8')
}
第2步:npm install @babel/parser 解析AST语法树
const fs = require('fs')
const parser = require('@babel/parser')
const getModuleInfo = (file)=>{
const body = fs.readFileSync(file,'utf-8')
const ast = parser.parse(body,{
sourceType:'module' //表示我们要解析的是ES模块
});
第3步:npm install @babel/parser收集依赖(遍历AST,寻找import语句)
const path = require('path')
const traverse = require('@babel/traverse').default
const deps = {} //存储依赖信息
traverse(ast, {
ImportDeclaration({ node }) {
//ImportDeclaration方法代表的是
对type类型为ImportDeclaration的节点的处理
const dirname = path.dirname(file)
const abspath = './' + path.join(dirname, node.source.value)
//value指的是什么意思呢?其实就是import的值
deps[node.source.value] = abspath
},
})
第4步:npm install @babel/core @babel/preset-env将ES6的AST语法树转化成ES5
const babel = require('@babel/core')
const {code} = babel.transformFromAst(ast,null,{
presets:["@babel/preset-env"]
})
官方提供的预设:
@babel/preset-env for compiling ES2015+ syntax
@babel/preset-typescript for TypeScript
@babel/preset-react for React
@babel/preset-flow for Flow
第5步:递归解析所有依赖
const parseModules = (file) =>{
const entry = getModuleInfo(file)
const temp = [entry]
for (let i=0; i<temp.length; i++){
const deps = temp[i].deps
if(deps){
for(const key in deps){
if(deps.hasOwnProperty(key)){
temp.push(getModuleInfo(deps[key]))
}
}
}
}
const depsGraph = {}
temp.forEach(moduleInfo=>{
depsGraph[moduleInfo.file] = {
deps:moduleInfo.deps,
code:moduleInfo.code
}
})
return depsGraph
}
第6步:定义require与exports,导出bundle.js
const bundle = (file) =>{
const depsGraph = JSON.stringify(parseModules(file))
return `(function (graph) {
function require(file) {
function absRequire(relPath) {
return require(graph[file].deps[relPath])
}
var exports = {};
(function (require,exports,code) {
eval(code)
})(absRequire,exports,graph[file].code)
return exports
}
require('${file}')
})(${depsGraph})`
}
const content = bundle('./src/index.js')
fs.mkdirSync('./dist');//创建导出文件夹
fs.writeFileSync('./dist/bundle.js',content)//导出代码
执行结果
总结
Webpack通过分析模块之间的依赖,最终将所有模块打包成一份或者多份代码包 (bundle),供 HTML 直接引用。浏览器不支持ES 模块时期,解决ES6语法兼容问题的优秀方案