webpackClass
bundle 就是一个自执行的函数
参数为 一个对象, key : 模块的路径, value : function 函数包裹, 内部使用eval 包含模块的主要内容
webpack(options)
/*
(1)
读取配置的入口
1 可以获取入口模块的路径
2 入口模块的依赖
模块的依赖 (依赖的路径)
可以用递归的方式处理依赖模块
获取依赖模块其对应的依赖
依赖模块的内容进行处理
内容的处理 (处理后的代码)
从而可以得到依赖图谱对象
webpackbootstrap 函数
生成文件
文件存放的位置
文件
读取配置的出口
生成文件
文件存放的位置
文件的名称
*/
执行函数, 模拟打包操作
// 读取配置
const options = require("./webpack.config.js");
// 引入webpack
// 自己定义的webpack
const webpack = require("./lib/webpack1.js");
// webpack接收配置 启动入口函数,执行打包
// 自己定义的webpack
new webpack(options).run();
// new webpack(options);
const fs = require('fs');
const path = require('path');
// 读取文件的信息
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");
module.exports = class webpack {
constructor(options) {
const { entry, output} = options;
this.entry= entry; // 保存入口配置
this.output = output // 保存出口配置
this.modules = [];
}
run () {
// 从入口开始
const info = this.parse(this.entry);
this.modules.push(info);
// 获取所有的module 信息, 存放在 this.modules 为一个数组
for (let i = 0; i < this.modules.length; i += 1) {
const item = this.modules[i];
const { yilai} = item;
if (yilai) {
for (let i in yilai ) {
// 发现有有依赖, 继续添加
this.modules.push( this.parse(yilai[i]));
}
}
}
// console.log(this.modules);
const obj = {};
// 将modules , 数组信息转化为 webpackbootSrap 参数
this.modules.forEach((item) => {
obj[item.entryFile] = {
yilai: item.yilai,
code: item.code,
}
});
// console.log(obj);
// bundle 的生成
this.file(obj);
}
parse(entryFile) {
// console.log(entryFile);
const content = fs.readFileSync(entryFile, 'utf-8');
// ast 树的形成
const dirname = path.dirname(entryFile);
const ast = parser.parse(content, {
sourceType: "module",
});
// console.log(ast);
// console.log(ast.program.body)
const yilai = {};
traverse(ast, {
// 过滤符合条件的 import 语句, 将import
ImportDeclaration({node}) {
// 获取当前import 导入文件的路径信息
const pathValue = node.source.value
const newPathName= './' + path.join(dirname , pathValue);
// 依赖相对与入口的路径: 依赖相对于 src 的 路径
yilai[pathValue] = newPathName;
// console.log(yilai);
// 获取依赖的位置
// 初始ast 树
}
});
// 简化了webpack 的打包流程
const { code} = transformFromAst(ast, null, { // null 是否有错误
presets: ["@babel/preset-env"],
});
// css, tsx 交给loader 来进行处理
// console.log(code);
return {
code,
yilai,
entryFile
}
}
file(code) {
// console.log(code);
// 生成bundle 文件的内容
// webpackBootstroap
// 生成文件,放入目录
const filePath = path.join(this.output.path, this.output.filename);
// console.log(filePath);
const newCode = JSON.stringify(code); // 对象转化成json
console.log(newCode);
// 包裹一层, 自执行函数
const bundle = `(function( modules){ // webpackbootSrap 参数
function require(module) { // 文件入口
// 相对路径
// relativePath : ./a.js -> ./src/a.js
// 覆盖里面的require 函数的真实调用, 嵌套, 使用内部的require 函数
function pathRequire(relativePath) {
// 获取真实的path, 从modules 中可以获取对应的module 信息
return require(modules[module].yilai[relativePath])
// 数据返回
}
const exports = {};
//
(function(require,exports , code) {
eval(code) // 执行里面的代码, 遇到require ,会使用
// 从外传入的require: 为 pathRequire
})(pathRequire,exports, modules[module].code);
return exports;
}
// 入口文件的调用
require('${this.entry}'); // 文件的入口
})(${newCode})`;
// 里面就是一个自执行函数
fs.writeFileSync(filePath, bundle, 'utf-8');
}
// Ast 抽象语法树
// tree 结构, 一个节点, 就是一个对象信息
// @babel/parser 接受内容, 形成一个ast 树
// @babel/traverse 对节点进行遍历
// @babel/core 处理js
// @babel/preset-env 处理js
// 如果模块之间相互依赖,怎么办?
}
/*
打包的整体流程
1 获取webpack.config.js 中的配置项信息
2 获取入口配置和出口配置信息
3 获取入口文件的依赖信息, 和执行内容,
4 对入口文件的执行内容进行处理,
5 遍历入口文件的依赖内容, 获取其依赖, 和对其内容进行处理
6 依次遍历, 直到整棵树都遍历完毕,内容处理完毕
7 构建整棵树的依赖图谱,
8 从入口文件开始, 执行webpack_require 函数, 从入口地址开始, 生成最终的文件
9 根据output 将代码输出到 对应文件
*/
webpack 打包原理分析
1 接收webpack 配置, 进行读取
2 入口: 从哪一个文件开始分析
3 哪些模块是依赖模块
4 这些依赖模块的信息, 也就是路径信息
5 内容, 对内容该处理, 处理成能被游览器正确解析的内容
6 递归处理其他依赖模块
7 生成chunk 代码片段
8 补齐函数, 生成bundle 文件的内容, 启动器函数函数
9 出口, 生成资源文件的名称和位置