webpack打包
webpack打包主要进行两个操作通过通过获取入口文件的内容,获取依赖关系
入口文件
- 获取入口文件的内容
- 借助fs文件模块获取某个文件的内容
- 获取依赖关系
-
借助@babel/parser 生成ast树
-
@babel/traverse
-
//index.js
import fs from 'fs', // 通过配置package.json type:'module' 支持esm模块
import parser from '@babel/parser'
import traverse from '@babel/traverse'
import { transformFromAst } from 'babel-core'
function createAsset(filePath){
// 1.获取文件内容
// const source = fs.readFileSync("./example/main.js",{
const source = fs.readFileSync(filePath,{
encoding:'utf-8'
})
console.log(source)
//2.获取依赖关系
cosnt ast = parser.parse(source,{
sourceType = "module"
}) // 将文件内容转换成 生成ast树
cosnt deps = [] // 存储依赖关系数组
traverse.default(ast,{
// 访问到'import'相关内容 机会调用这个函数
ImpostDeclaration({node}){
console.log(node.source.value) // foo.js 获取到依赖关系
deps.push(node.source.value)
}
})
// esm --> cjs
const { code } = transformFromAst(ast,null,{
presets:["env"], //需要安装babel-preset-env
})
return {
filePath,
//source, //当前文件的代码 字符串
code // 当前文件代码 cjs引入
deps // 当前文件所依赖的文件名
}
}
const asset = createAsset9()
console.log(asset)
获取到文件内容和依赖关系之后,合成一个图对象
合成一个图对象
// index.js
import path from 'path'
function createGraph(){
const mainAsset = createAsset("./example/main,.js")
//基于依赖关系找到下一个文件的内容和依赖关系
const queue = [mainAsset]
for( const asset of queue){
asset.deps.forEach((relativePath)=>{
console.log(relativePath) //./foo.js
const filePath = path.resolve("./example",relativePath)
const childAsset = createAsset(filePath)
console.log(children) // 文件内容,deps
queue.push(child)
})
}
return queue
}
打包后的bundle.js
function mainjs(){
//main.js
//import foo from './foo.js' //esm模块规范,import只能用于顶层作用域
// esm => cjs
const foo = require("./foo.js")
foo()
console.log('main.js')
}
function foojs(){
//foo.js
//export function foo(){
function foo(){
console.log('foo')
}
module.exports = {
foo
}
}
//bundle.js
function require(filePath){
const map = {
"./foo.js":foojs,
"./main.js":mainjs
}
const fn = map[filePath]
const module = {
exports :{}
}
fn(require, module,module.exports)
return module.exports;
}
requier("./main.js")
function mainjs(require,module,exports){
const { foo } = require("./foo.js")
foo()
console.log('main.js')
}
function foojs(require,module,exports){
//foo.js
//export function foo(){
function foo(){
console.log('foo')
}
module.exports = {
foo
}
}
//重构
(function(modules){
function require(filePath){
const fn = map[filePath]
const module = {
exports :{}
}
fn(require, module,module.exports)
return module.exports;
}
requier("./main.js")
}
})({
"./foo.js": function (require,module,exports){
const { foo } = require("./foo.js")
foo()
console.log('main.js')
}
"./main.js": function (require,module,exports){
//foo.js
//export function foo(){
function foo(){
console.log('foo')
}
module.exports = {
foo
}
})
需要改动的就是这个参数了
如何生成bundle.js
利用bundle.ejs模板
//index.js
/* 之前代码*/
funcuon build(graph){
const template = fs.readFileSync("bundle.ejs",{
encode:"utf-8"
})
const data = graph.map((asset)=>{
return {
filePath:asset.filePath,
code: asset.code
})
const code = ejs.render(template,{data})
console.log(code)
//创建文件
fs.writeFileSync("./dist/bundle.js",code)
}
build(graph)
引入的命名重复的问题
index.js中
main.js中
都是引用的相对路径./foo.js
,此时就会出现问题
解决方法:使用唯一模块id
//bundle.js
(function(modules){
function require(id){
const [fn,mapping] = map[filePath]
const module = {
exports :{}
}
function localRequire(filePath){
const id = mapping[filePath]
return require(id)
}
fn(localRequire,module,module.exports)
return module.exports;
}
requier(1)
}
})({
2: [function (require,module,exports){
const { foo } = require("./foo.js")
foo()
console.log('main.js')
},{} ]
1:[ function (require,module,exports){
//foo.js
//export function foo(){
function foo(){
console.log('foo')
}
module.exports = {
foo
} ,
{
"./foo.js":2
}
]
})
//index.js
/*其他代码*/
import fs from "fs";
import path from "path";
import parser from "@babel/parser"; //生成ast
import traverse from "@babel/traverse";
import ejs from "ejs"; //ejs 模板生成器
import { transformFromAst } from "babel-core";
// console.log(traverse);
import { jsonLoader } from "./jsonLoader.js";
let id = 0;
//相当于webpack.config.js
const webpackConfig = {
module: {
rules: [{
test: /\.json$/,
use: jsonLoader,
}, ],
},
};
function createAsset(filePath) {
//1、获取文件的内容
// ast --> 抽象语法树
let source = fs.readFileSync(filePath, {
//"./example/main.js",
encoding: "utf-8", //未加第二个参数之前打印buffer 加厚发音的是文件内容
});
// console.log(source); //buffer
//2.loader转换 init loader
const loaders = webpackConfig.module.rules;
const loaderContext = {
addDeps(dep) {
console.log("addDeps", dep);
},
};
loaders.forEach(({ test, use }) => {
if (test.test(filePath)) {
if (Array.isArray(use)) {
use.forEach((fn) => {
source = fn.call(loaderContext, source);
});
} else {
source = use.call(loaderContext, source);
}
}
console.log(test, use);
});
//3、获取依赖关系
const ast = parser.parse(source, {
sourceType: "module",
}); //生产ast 抽象语法树
// console.log("ast--------------------");
// console.log(ast);
//2、获取依赖关系 ---> import
const deps = [];
traverse.default(ast, {
//获取import 转化ast
ImportDeclaration({ node }) {
// console.log("DATA-------------");
// console.log(data); //输出数据
// console.log("NODE-------------");
// console.log(data.node); //输出数据
// console.log(node.source);
deps.push(node.source.value);
},
});
//esm转换成cjs
const { code } = transformFromAst(ast, null, {
presets: ["env"],
});
// console.log(code);
return {
filePath,
// source, //文件内容
code, // esm转为cjs后的文件内容
deps, //依赖关系
mapping: {},
id: id++,
};
}
// const asset = createAsset();
// console.log(asset);
//用得到的内容和依赖关系 绘制成图
function createGraph() {
const mainAsset = createAsset("./example/main.js");
//遍历图的方式
const queue = [mainAsset];
for (const asset of queue) {
asset.deps.forEach((relativePath) => {
// console.log(relativePath);
// console.log(path.resolve("./example", relativePath));
const childAsset = createAsset(path.resolve("./example", relativePath)); //递归查询其他
// console.log(childAsset);
asset.mapping[relativePath] = childAsset.id;
queue.push(childAsset);
});
}
return queue;
}
const graph = createGraph();
function build(graph) {
const template = fs.readFileSync("./bundle.ejs", {
encoding: "utf-8",
});
const data = graph.map((asset) => {
return {
id: asset.id,
code: asset.code,
mapping: asset.mapping,
};
});
const code = ejs.render(template, { data }); //根据模板生成代码
fs.writeFileSync("./dist/bundle.js", code); // 生成文件
// console.log(code);
}
console.log(graph);
build(graph);