手写webpack
我们来手动实现一个简易的webpack, zf-pack
开发方式
- 新建一个仓库,然后执行npm link即可
- 在需要构建的项目下面,执行npm link zf-pack
package.json配置
{
"name": "zf-pack",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"bin": {
"zf-pack": "./bin/zf-pack.js"
},
"dependencies": {
"@babel/generator": "^7.8.4",
"@babel/traverse": "^7.8.4",
"@babel/types": "^7.8.3",
"babylon": "^6.18.0"
}
}
zf-pack.js
#! /usr/bin/env node
// console.log('start1');
// 第一步,需要找到当前执行名的路径 拿到webpack.config.js
let path = require('path');
// config配置文件
let config = require(path.resolve('webpack.config.js'));
let Compiler = require('../lib/compiler.js');
let compiler = new Compiler(config);
// 标识运行编译
compiler.run();
compiler.js
let fs = require('fs');
let path = require('path');
let babylon = require('babylon');
let traverse = require('@babel/traverse').default;
let t = require('@babel/types');
let generator = require('@babel/generator').default;
// babylon 主要就是把源码转换成ast
// @babel/traverse
// @babel/types
// @babel/generator
class Compiler {
constructor(config) {
// entry output
this.config = config;
// 需要保存入口文件的路径
this.entryId;
// 需要保存所有得到依赖模块
this.modules = {};
this.entry = config.entry;
// 工作路径
this.root = process.cwd();
}
// 解析源码
parse(source, parentPath) { // AST解析语法树
let ast = babylon.parse(source);
// console.log(ast);
let dependencies = []; // 依赖的数组
traverse(ast, {
CallExpression(p) {
let node = p.node; // 对应的节点
console.log('.....................');
console.log(p);
if (node.callee.name === 'require') {
node.callee.name = '__webpack_require__';
let moduleName = node.arguments[0].value;
moduleName = moduleName + (path.extname(moduleName) ? '' : '.js');
moduleName = './' + path.join(parentPath, moduleName);
dependencies.push(moduleName);
node.arguments = [t.stringLiteral(moduleName)];
}
}
});
let sourceCode = generator(ast).code;
console.log(sourceCode);
return {
sourceCode, dependencies
}
}
getSource(modulePath) {
let content = fs.readFileSync(modulePath, 'utf-8');
return content;
}
// 构建模块
buildModule(modulePath, isEntry) {
// 拿到模块的内容
let source = this.getSource(modulePath);
// 模块id modulePath modulePath-this.root
let moduleName = './' + path.relative(this.root, modulePath);
if (isEntry) {
this.entryId = moduleName; // 保存入口的名字
}
// 解析需要把source源码进行改造,返回一个依赖列表
console.log(modulePath);
console.log(path.dirname(moduleName));
let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName));
this.modules[moduleName] = sourceCode;
dependencies.forEach(dep => { // 副模块
this.buildModule(path.join(this.root, dep), false);
})
}
emitFile() {}
run() {
// 执行 并创建模块的依赖关系 解析当前依赖
this.buildModule(path.resolve(this.root, this.entry), true);
// 发射一个文件(打包后的文件)
this.emitFile();
}
}
module.exports = Compiler;
渲染 ,发射文件
zf-pack.js
emitFile() {
// 用数据,渲染我们的
// 拿到输出到哪个目录下
let main = path.join(this.config.output.path, this.config.output.filename);
let templateStr = this.getSource(path.join(__dirname, 'main.ejs'));
let code = ejs.render(templateStr, {entryId: this.entryId, modules: this.modules});
this.assests = {};
// 资源中 路径对应的代码
this.assests[main] = code;
fs.writeFileSync(main, this.assests[main]);
}
模板文件
main.ejs
(function (modules) { // webpackBootstrap
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "<%-entryId%>");
})
({
<%for(let key in modules){%>
"<%-key%>":
(function (module, exports, __webpack_require__) {
eval(`<%-modules[key]%>`);
}),
<%}%>
});