根据webpack设计原理手写一个简版webpack
文章目录
- 根据webpack设计原理手写一个简版webpack
- 模块解析:
- 依赖图构建:
- 代码打包:
- 上代码
根据webpack设计原理手写一个简版webpack
模块解析:
读取文件内容
使用 Babel 将 ES6 + 代码转换为 ES5
提取模块中的依赖关系(通过正则匹配 require 语句)
依赖图构建:
从入口文件开始递归解析所有依赖
为每个模块创建唯一标识(使用文件绝对路径)
记录模块间的依赖关系映射
代码打包:
将所有模块打包到一个文件中
模拟 CommonJS 模块系统(实现 require、module、exports)
处理相对路径到模块 ID 的映射
生成可在浏览器环境运行的代码
上代码
const fs = require('fs');
const path = require('path');
const { transform } = require('@babel/core');
const babelPresetEnv = require('@babel/preset-env');/*** 解析模块内容,提取依赖关系* @param {string} filePath - 模块文件路径* @returns {object} 包含模块ID、依赖和转换后代码的对象*/
function parseModule(filePath) {// 1. 读取文件内容const sourceCode = fs.readFileSync(filePath, 'utf-8');// 2. 使用Babel将ES6+代码转换为ES5const { code } = transform(sourceCode, {presets: [babelPresetEnv]});// 3. 正则匹配require语句,提取依赖模块路径const dependencies = [];const requireRegex = /require\(['"](.*?)['"]\)/g;const transformedCode = code.replace(requireRegex, (match, dependencyPath) => {// 记录依赖dependencies.push(dependencyPath);// 替换为相对路径标识,后续会处理为绝对路径IDreturn `require('${dependencyPath}')`;});// 4. 返回模块信息return {id: filePath, // 用文件绝对路径作为模块IDdependencies, // 依赖的模块路径列表code: transformedCode // 转换后的代码};
}/*** 构建模块依赖图* @param {string} entry - 入口文件路径* @returns {array} 包含所有模块信息的依赖图*/
function buildDependencyGraph(entry) {// 解析入口模块const entryModule = parseModule(entry);// 用队列来处理所有模块(广度优先遍历)const queue = [entryModule];// 遍历队列,处理每个模块的依赖for (const module of queue) {// 模块所在目录const moduleDir = path.dirname(module.id);// 为每个依赖创建映射关系,并解析依赖模块module.dependencies.forEach(dependencyPath => {// 计算依赖模块的绝对路径const absolutePath = path.join(moduleDir, dependencyPath);// 解析依赖模块const dependencyModule = parseModule(absolutePath);// 将相对路径映射到绝对路径ID,方便后续查找module.mapping = module.mapping || {};module.mapping[dependencyPath] = dependencyModule.id;// 将依赖模块加入队列,继续处理其依赖queue.push(dependencyModule);});}// 返回完整的依赖图return queue;
}/*** 将依赖图打包成单个可执行文件* @param {array} graph - 模块依赖图* @returns {string} 打包后的代码*/
function bundle(graph) {// 1. 构建模块ID到模块代码的映射const modules = {};graph.forEach(module => {modules[module.id] = {code: module.code,mapping: module.mapping};});// 2. 生成可在浏览器运行的代码// 使用自执行函数包裹,模拟模块系统return `(function(modules) {// 模拟require函数function require(moduleId) {// 模拟module和exports对象const module = { exports: {} };// 执行模块代码modules[moduleId].code.call(module.exports, require, module, module.exports,function(path) {// 处理相对路径到模块ID的映射return require(modules[moduleId].mapping[path]);});// 返回模块导出return module.exports;}// 从入口模块开始执行require('${graph[0].id}');})({// 将模块信息注入到自执行函数中${graph.map(module => {return `'${module.id}': {code: function(require, module, exports, requireRelative) {// 替换require为我们的相对路径处理版本${module.code.replace(/require\(['"](.*?)['"]\)/g, (match, path) => {return `requireRelative('${path}')`;})}},mapping: ${JSON.stringify(module.mapping || {})}}`;}).join(',\n ')}})`;
}/*** 简版Webpack的主函数* @param {object} options - 配置选项,包含entry和output*/
function webpack(options) {// 1. 解析入口文件路径const entryPath = path.resolve(options.entry);// 2. 构建依赖图const dependencyGraph = buildDependencyGraph(entryPath);// 3. 打包代码const outputCode = bundle(dependencyGraph);// 4. 确保输出目录存在const outputDir = path.dirname(options.output.path);if (!fs.existsSync(outputDir)) {fs.mkdirSync(outputDir, { recursive: true });}// 5. 写入输出文件fs.writeFileSync(options.output.path, outputCode, 'utf-8');console.log(`打包完成!输出至: ${options.output.path}`);
}// 使用示例
webpack({entry: './src/index.js',output: {path: './dist/bundle.js'}
});