建几个文件add.js,index.js,index.html
// src/add.js
exports.default = function(a, b) {return a + b;}
// src/index.js
var add = require('./add.js')
console.log(add(1,3))
<!-- src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试模块化开发的代码</title>
<script src="./index.js"></script>
</head>
<body>
</body>
</html>
运行html
Es5不使用webpack出现的问题
往往模块是别的库(比如nodejs),用的commonjs来写的,要处理加载模块的问题:
1.强大的eval函数
读取子模块add.js文件后获取到的是字符串
`exports.default = function(a, b) {return a + b;}`
//可以将此语句放入html文件中测试
eval(`exports.default = function(a, b) {return a + b;}`)
此时浏览器会报错
export is not defined
2.手动创建一个exports对象
var exports = {}
eval(`export.default = function(a,b) {return a + b}`)
console.log(exports.default(1,1))
此时出现了正确结果
3.其他情况,变量全局污染
如果在导出的文件中,还要一些其它的变量,比如var a = 1;之类的,就会造成全局污染
解决:为了避免全局污染,我们使用自执行函数包裹起来,它会为其创建一个独立的作用域
var exports = {};
(function(exports, code) {
eval(code)
})(exports, `exports.default = function(a, b) {return a + b;}`)
console.log(exports.default(1,1))
第二步,实现加载模块
//我们将index.js中调用子模块的方法实现
<script>
var exports = {}
(function(exports, code) {
eval(code)
})(exports, `exports.default = function(a,b) { return a + b ;}`)
//index.js的内容
var add = require('./add.js')
console.log(add(1,3))
</script>
直接引入提示require未定义
自己模拟实现一个require方法
function require(file) {
(function(exports, code) {
eval(code)
})(exports, `exports.default = function(a, b){ return a + b; }`)
return exports.deault;
}
var add = require('./add.js');
console.log(add(1,3))
第三步,文件获取
require(’./add.js’)这时的文件是写死的,不能按照参数形式处理
var add = require('./add.js')
console.log(add(1,3))
// 文件列表对象大概长这样
{
"index.js": `
var add = require('./add.js')
console.log(add(1,3))
`,
"add.js": `
exports.default = function(a, b) {return a + b;}
`
}
再套一个立即执行函数,将文件列表对象以参数的形式传入
//src/index.html
<script>
var exports = {};
(function(list) {
function require(file) {
(function(exports, code) {
eval(code)
})(exports, list[file])
return exports.default;
}
require('./index.js')
})({
"./index.js": `
var add = require('./add.js')
console.log(add(1,3))
`,
"./add.js":`
exports.default = function(a,b) { return a + b;}
`
})
</script>
浏览器查看结果
简单的webpack雏形已经完成
二. 分析模块间的依赖,获取依赖图
主要解决依赖嵌套问题以及一个模块依赖多个模块问题
步骤分为
1.收集依赖
2.ES6转ES5
3.实现import 和 export
// ./webpack.js
// 引入fs模块,用来读写文件
const fs = require('fs')
// 引入path模块,处理路径问题
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')
/**
* 模块分析
* @param {*} file
*/
function getModuleInfo(file) {
// 1. 读取文件
const body = fs.readFileSync(file, 'utf-8')
// 2. 转换AST语法树
const ast = parser.parse(body, {
sourceType: 'module' // ES模块
})
// console.log("ast:", ast)
// 3. 收集依赖
const deps = {}
traverse(ast, {
ImportDeclaration({node}) {
// 获取当前目录名
const dirname = path.dirname(file)
// 设置绝对路径
const abspath = './' + path.join(dirname, node.source.value)
deps[node.source.value] = abspath
}
})
console.log("deps:", deps) // deps: { './add.js': './src\\add.js' }
// 4. ES6转换ES5
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
// console.log("code:", code)
// 5. 输出模块信息
const moduleInfo = {
file,
deps,
code
}
return moduleInfo
}
// const info = getModuleInfo('./src/index.js')
// console.log({info})
// 6. 解析模块间的关系
function parseModules(file) {
// 从入口开始
const entry = getModuleInfo(file)
const temp = [entry]
// 依赖关系图
const depsGraph = {}
// 7. 递归获取依赖关系
getDeps(temp, entry)
// 8. 组装依赖
temp.forEach((moduleInfo) => {
depsGraph[moduleInfo.file] = {
deps: moduleInfo.deps,
code: moduleInfo.code,
};
});
return depsGraph
}
// 递归获取依赖关系
function getDeps(temp, {deps}) {
Object.keys(deps).forEach(key => {
const child = getModuleInfo(deps[key])
temp.push(child)
getDeps(temp, child)
})
}
// console.log('graph:', graph)
// 9. 打包
function bundle(file) {
// 获取依赖图
const depsGraph = JSON.stringify(parseModules(file))
// 跟第一个demo中的打包文件,拼接起来
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')
console.log(content)
// 判断有没dist目录,没有就创建
!fs.existsSync("./dist") && fs.mkdirSync("./dist");
// 将打包后的文件写入./dist/bundle.js中
fs.writeFileSync("./dist/bundle.js", content);