本文将实现一个基础版本的webpack,既可以对依赖JS文件进行打包的基础版本。
本文需要安装的包:
文件目录:
文件内容:
-
1,根目录下index.js既入口JS文件:
// .index.js:入口JS文件 import { add, mul } from './math/index.js' console.log('1+1:', add(1, 1)); console.log('2*2:', mul(2, 2)); -
2,根目录下的math文件夹中所有文件被入口文件直接或者间接引用:
// ./math/index.js import add from './tools/add.js' import mul from './tools/mul.js' console.log('欢迎使用 math 工具方法!'); export { add, mul }// ./math/tools/add.js export default function add(...args) { return args.reduce((p, c) => p + c) }// ./math/tools/mul.js export default function add(...args) { return args.reduce((p, c) => p * c) } -
3,根目录下的dist文件夹既打包后的bundle文件输出位置
webpack实现:
-
4,根目录下的webpack.js既我们实现的webpack(代码全注释,按照注释阅读)
const fs = require('fs') const { dirname, join } = require('path') // 主要使用parser.parse 将ES6代码转成抽象语法树 const parser = require('@babel/parser') // 使用traverse遍历ES6代码的抽象语法树,找到当前JS文件所有依赖文件路径 const traverse = require('@babel/traverse').default // 使用transformFromAstSync将ES5的抽象语法树转成ES5代码 const { transformFromAstSync } = require('@babel/core') function getModule(path) { // 1,根据文件路径读取对应文件内容 const content = fs.readFileSync(path, 'utf-8') // 2,将文件内容ES6转换成抽象语法树(AST) const ast = parser.parse(content, { sourceType: 'module' }) // 3,在抽象语法树中找到当前文件所有依赖的文件路径保存 const dependencies = [] traverse(ast, { ImportDeclaration: ({ node }) => { dependencies.push(node.source.value) } }) // 4,将当前文件的语法树转成ES5代码,并添加函数体包裹,模拟CJS中的require行为 const code = `function (require,module,exports){${transformFromAstSync(ast, null, { presets: ['@babel/preset-env'] }).code}}` // 5,返回当前文件的模块信息:文件路径,所有依赖的文件路径,函数体包裹的ES6转ES5后的代码 return { path, dependencies, code } } function getGraph(module, path) { // 1, 首先创建当前模块的路径与代码映射关系: // `'./index.js' : some code ~ ,` const initialMappingCodeToPath = `'${path}':${module.code},` // 2, 递归处理当前模块的依赖,创建所有模块的路径与代码映射关系,最终合并在一起(字符串),像下面这样 // ` // './index.js' : some string_code ~ , // './math/index.js' : some string_code ~ , // './tools/add.js' : some string_code ~ , // './tools/mul.js' : some string_code ~ , // ` return module.dependencies.reduce( (mappingCodeToPath, depPath) => { // 2.1,根据当前模块的路径获取依赖模块的绝对路径 const depAbspath = join(dirname(module.path), depPath) // 2.2,获取依赖模块的路径与代码映射关系 const depMappingCodeToPath = getGraph(getModule(depAbspath), depPath) // 2.3,合并所有模块的路径与代码映射关系,生成依赖关系图 return mappingCodeToPath + depMappingCodeToPath }, // 2.4,reduce方法初始模块的路径与代码映射关系 initialMappingCodeToPath ) } function getBundle(entryPath) { // 1.1,首先创建入口文件模块信息:入口文件路径 入口文件所有依赖文件路径 入口文件ES6转ES5代码 const entryModule = getModule(entryPath) // 1.2,之后从入口文件开始递归创建所有模块的依赖关系图(字符串),每个模块依赖关系既模块路径与模块内部代码之间的映射关系: // ` // { // './index.js' : some string_code ~ , // './math/index.js' : some string_code ~ , // './tools/add.js' : some string_code ~ , // './tools/mul.js' : some string_code ~ // } // ` const graph = `{${getGraph(entryModule, entryPath)}}` // 1.3,返回一个立即执行函数字符串,既打包后的代码块: // 立即执行函数参数既模块依赖关系图,且立即执行函数内部实现了类CJS的require,即可根据模块路径找到对应模块代码执行 // 当立即执行函数运行,我们会从入口文件路径开始require,执行入口文件内部代码,遇到require依赖模块,因为注入了实现的类CJS的require // 所以会继续执行当前依赖模块代码,最终所有模块代码执行将完毕 return `(function(modules){ function require(path){ const module= {exports:{}} modules[path](require,module,module.exports) return module.exports } require('${entryPath}') })(${graph})` } // 1,开始打包 // 传入入口文件路径,开始打包 const bundle = getBundle('./index.js') // 4,按照webpack,最后会将打包后的代码注入到dist文件夹下面的bundle.js中既完成打包 !fs.existsSync("./dist") && fs.mkdirSync("./dist"); // 若没有dist文件夹创建dist文件夹 fs.writeFileSync("./dist/bundle.js", bundle); // 将打包后内容写进dist文件夹下面的bundle.js中
打包后的JS代码:
(function(modules){
function require(path){
const module= {exports:{}}
modules[path](require,module,module.exports)
return module.exports
}
require('./index.js')
})({'./index.js':function (require,module,exports){"use strict";
var _index = require("./math/index.js");
console.log('1+1:', (0, _index.add)(1, 1));
console.log('2*2:', (0, _index.mul)(2, 2));},'./math/index.js':function (require,module,exports){"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "add", {
enumerable: true,
get: function get() {
return _add["default"];
}
});
Object.defineProperty(exports, "mul", {
enumerable: true,
get: function get() {
return _mul["default"];
}
});
var _add = _interopRequireDefault(require("./tools/add.js"));
var _mul = _interopRequireDefault(require("./tools/mul.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
console.log('欢迎使用 math 工具方法!');},'./tools/add.js':function (require,module,exports){"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = add;
function add() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return args.reduce(function (p, c) {
return p + c;
});
}},'./tools/mul.js':function (require,module,exports){"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = add;
function add() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return args.reduce(function (p, c) {
return p * c;
});
}},})