webpack简单实现
1. 注册命令行指令(可以直接通过mypack
指令打包)
-
项目根目录创建mypack文件夹
-
npm init
npm初始化,并修改bin为主文件路径
{
"name": "mypack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": {
"mypack": "bin/mypack.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
- 第一行标识运行环境为node,然后使用
npm link
创建软链接,实现命令行直接调用运行
#! /usr/bin/env node
console.log('mypack start');
2. webpack打包ejs模板
传入参数是所有引用的路径和对应执行函数,函数里面存了代码,通过eval执行
// 自执行函数,modules就是传入的模块及对应代码
(function (modules) {
function myRequire(moduleName) {
var module = {
exports: {}
};
// 执行该文件对应的内容代码,里面若含有调用会继续使用myRequire函数
modules[moduleName].call(module.exports,module, module.exports, myRequire);
// 代码执行过则会将结果返回
return module.exports;
}
// 从入口开始
return myRequire("<%-entry%>");
})({
// 入口模块
"<%-entry%>": function (module, exports, myRequire) {
// 将代码放到字符串中,eval执行
eval(\`<%-script%>\`);
}
// ...其他模块
<%for (let i = 0; i < modules.length; i++) {%> ,
"<%-modules[i].name%>": function (module, exports, myRequire) {
eval(\`<%-modules[i].content%>\`);
}
<%}%>
})
3. 读取文件转化为字符串
从入口文件开始读取文件,对文件中的字符串匹配require,将require涉及的文件进行读取,对每一个content进行replace校验,转化为调用自定义函数加载代码
// 设置模板所需数据
let entry = "./src/index.js";
let output = "./dist/main.js";
let script = fs.readFileSync(entry, 'utf8');// 读取到入口文件代码内容
let modules = [];// 存模块路径和对应函数
// 递归,将所有require替换,并且将所有依赖代码转化为字符串
function setContent(content) {
return content.replace(/require\(['"](.+?)['"]\)/g, function () {
// 拼接文件路径
let filePath = arguments[1];
let name = path.join('./src', filePath).replace(/\\/, '/');// src/1.js
// 递归处理content
content = setContent(fs.readFileSync(name, 'utf8'));
modules.push({ name, content });
// 调用函数
return `myRequire('${name}')`;
})
}
script = setContent(script);
4. loader使用
function styleLoader(cssText) {
return `
var style = document.createElement('style');
style.innerText = ${JSON.stringify(cssText).replace(/\\r\\n/g, '')};
document.head.appendChild(style);
`
}
function setContent(content) {
return content.replace(/require\(['"](.+?)['"]\)/g, function () {
// 省略...
// loader加载css文件
if (/\.css$/.test(name)) {
content = styleLoader(content);
}
modules.push({ name, content });
// 调用函数
return `myRequire('${name}')`;
})
}
5. 文件导出
let template = `
// 自执行函数,modules就是传入的模块及对应代码
(function (modules) {
function myRequire(moduleName) {
var module = {
exports: {}
};
// 执行该文件对应的内容代码,里面若含有调用会继续使用myRequire函数
modules[moduleName].call(module.exports,module, module.exports, myRequire);
// 代码执行过则会将结果返回
return module.exports;
}
// 从入口开始
return myRequire("<%-entry%>");
})({
// 入口模块
"<%-entry%>": function (module, exports, myRequire) {
// 将代码放到字符串中,eval执行
eval(\`<%-script%>\`);
}
// ...其他模块
<%for (let i = 0; i < modules.length; i++) {%> ,
"<%-modules[i].name%>": function (module, exports, myRequire) {
eval(\`<%-modules[i].content%>\`);
}
<%}%>
})
`
let result = ejs.render(template, { entry, script, modules });
// 将结果代码写入
fs.writeFileSync(output, result);
console.log('success');
6. 总结
果然,学习一样技能最好的方式就是实现,原本高深莫策的webpack其实就是将代码转化为了路径和代码块调用函数的映射,在调用时使用eval来执行代码块,同事对传入的引用参数进行更改和使用,而loader就是一个处理函数,对文本进行处理,通过js插入到文档中,以上为我的一些初体会,如有错误欢迎指正,代码已上传至仓库,仓库:mypack。