手写webpack——实现loader的功能
程序员文章站
2024-02-11 11:19:04
...
实现loader
zf-pack.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;
let ejs = require('ejs');
// 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; // 对应的节点
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);
// moduleName.replace(/\\/, '\\');
dependencies.push(moduleName);
node.arguments = [t.stringLiteral(moduleName)];
}
}
});
let sourceCode = generator(ast).code;
console.log(sourceCode);
return {
sourceCode, dependencies
}
}
getSource(modulePath) {
let rules = this.config.module.rules;
let content = fs.readFileSync(modulePath, 'utf-8');
for (let i = 0; i <rules.length; i++) {
let rule = rules[i];
let {test, use} = rule;
let len = use.length - 1;
if (test.test(modulePath)) {
// 获取对应的loader函数
function normalLoader() {
if (len >= 0) {
let loader = require(use[len--]);
content = loader(content);
normalLoader();
}
}
normalLoader();
}
}
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源码进行改造,返回一个依赖列表
let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName));
this.modules[moduleName] = sourceCode;
dependencies.forEach(dep => { // 副模块
console.log(dep);
this.buildModule(path.join(this.root, dep), false);
})
}
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]);
}
run() {
// 执行 并创建模块的依赖关系 解析当前依赖
this.buildModule(path.resolve(this.root, this.entry), true);
// 发射一个文件(打包后的文件)
this.emitFile();
}
}
module.exports = Compiler;
使用目录
webpack.config.js
let webpack = require('webpack');
let path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: "index.js",
path: path.resolve(__dirname, 'dist'),
},
mode: 'development',
devServer: {
port: 3000
},
plugins: [],
module: {
rules: [
{
test: /\.less/,
use: [
path.resolve(__dirname, 'loader', 'style-loader'),
path.resolve(__dirname, 'loader', 'less-loader'),
]
}
]
}
};
less-loader:
let less = require('less');
function loader(source) {
let css = '';
less.render(source, function (err, c) {
css = c.css;
});
css = css.replace(/\n/g, '\\n');
return css;
}
module.exports = loader;
style-loader:
function loader(source) {
let style = `
let style = document.createElement('style');
style.innerHTML = ${JSON.stringify((source))};
document.head.appendChild(style);
`;
return style;
}
module.exports = loader;
上一篇: 获取IMEI号耗时10秒导致黑屏的解决