首先搭建webpack环境配置:
1.在package.json文件中创建
可以直接黑窗中使用testWebpack执行bin目录下的这个脚本
注: 需要在运行的脚本中声明node环境执行 #!/usr/bin/env node
//执行的脚本文件
/**
1.获取打包的配置内容
2.使用自己写的testWebpack分析依赖来打包
*/
//src下创建js脚本 news.js index.js
//news.js 导出一个对象属性
module.exports = {
name:'张三'
}
//index.js 使用commentjs规范 导入news.js文件
let news = require('./news');
console.log(news.name);
//testWeb.pack.js 模拟webpack中的entry 入口
module.exports = {
entry:'./src/index.js',
}
package.json执行的脚本
1.首先获取打包的配置文件
2.创建lib下compiler.js来执行打包,分析打包入口entry依赖
//bin目录testWebpack.js
//bin/testWebpack.js
#!/usr/bin/env node
//导入node path模块处理文件路径
const path = require('path');
// 读取需要打包的配置文件 获取配置文件的绝对路径
let webpack = require(path.resolve("testWeb.pack.js"))
//配置文件内容 模拟webpack中的entry
console.log(webpack)
// 通过面向对象的方式来进行项目推进
let compiler = require("../lib/compiler.js")
//实例化compiler执行打包依赖分析
new compiler(webpack).start()
lib下compiler.js。
webpack打包文件是通过{"./src/news.js“:webpack_require()}来执行
1.读取entry入口的文件,获取该入口文件的内容 使用node的fs模块来获取脚本内容fs.readFileSync(path, ‘utf-8’);
2.获取的入口文件包含require 浏览器无法解析需要将require替换成__webpack_require__
(此时需要使用到3个babel全家桶替换获取到的内容,1.使用@babel/parser将es6代码转换成ast抽象语法树。 2.使用@babel/traverse替换抽象语法树符合条件的内容。3.使用@babel/generator将对象语法树转换成源码)
//lib/compiler.js
const path = require('path');
const fs = require('fs');
// 将es6代码转换成ast抽象语法树
const paser = require('@babel/parser')
// 将ast抽象语法树替换内容 es6模块导出的需要点一下default
const traverse = require('@babel/traverse').default;
// 将ast转换成源码使用generator转换
const generator = require('@babel/generator').default;
class Compiler {
constructor(config) {
this.config = config;;
this.entry = config.entry;
// 获取执行指令的目录
this.root = process.cwd();
}
getSource(path) {
//读取当前路径下的文件 获取文件的代码
return fs.readFileSync(path, 'utf-8');
}
depAnalyse(modlePath) {
let result = this.getSource(modlePath)
// 将代码转换成抽象语法树 对象
let ast = paser.parse(result)
// console.log(ast);
// 使用traverse模块将ast抽象语法树找到的内容进行替换 修改ast
traverse(ast, {
CallExpression(p) {
console.log(p.node.callee.name)
if (p.node.callee.name == 'require') {
// 将代码中requre替换成__webpack_require__
p.node.callee.name = '__webpack_require__';
// 将./news.js 替换成./src/news.js
let value = p.node.arguments[0].value
value = "./" + (path.join("src", value));
// 多做一个处理将所有\\改成/
value.replace(/\\+/g, "/")
p.node.arguments[0].value = value
console.log(p.node.arguments[0].value)
}
}
})
// 获取替换后的代码
let sourceCode = generator(ast).code;
console.log(sourceCode)
}
start() {
//开始打包了
// 依赖分析
this.depAnalyse(path.resolve(this.root, this.entry))
}
}
module.exports = Compiler
此时已经分析完单个文件的依赖了,但一个入口文件可能会有多个依赖,需要对这些依赖递归获取内容处理,创建一个内部数组存储所有的文件依赖,遍历该数组将依赖一层一层递归下去修改依赖替换相应的代码
depAnalyse(modlePath) {
let result = this.getSource(modlePath)
// 将代码转换成抽象语法树 对象
let ast = paser.parse(result)
// console.log(ast);
//存储所有的依赖
let depArr =[]
// 使用traverse模块将ast抽象语法树找到的内容进行替换 修改ast
traverse(ast, {
CallExpression(p) {
console.log(p.node.callee.name)
if (p.node.callee.name == 'require') {
// 将代码中requre替换成__webpack_require__
p.node.callee.name = '__webpack_require__';
// 将./news.js 替换成./src/news.js
let value = p.node.arguments[0].value
value = "./" + (path.join("src", value));
// 多做一个处理将所有\\改成/
value.replace(/\\+/g, "/")
p.node.arguments[0].value = value
console.log(p.node.arguments[0].value)
depArr.push(p.node.arguments[0].value)
}
}
})
// 获取替换后的代码
let sourceCode = generator(ast).code;
console.log(sourceCode)
//遍历所有的依赖文件 递归处理所有依赖
// 遍历该数组 将依赖一层一层递归获取内容替换相应代码
depArr.forEach(dep => this.depAnalyse(path.resolve(this.root, dep)))
}
所有依赖获取并修改内容后创建一个modules对象,存储所有源码 将所有依赖文件处理成{’./src/index.js’:’./src/index.js源码’}以此类推
let result = this.getSource(modlePath)
// 将代码转换成抽象语法树 对象
let ast = paser.parse(result)
// console.log(ast);
// 用来存储所有获得的文件路径
let depArr = []
// 使用traverse模块将ast抽象语法树找到的内容进行替换 修改ast CallExpression()钩子
traverse(ast, {
CallExpression(p) {
console.log(p.node.callee.name)
if (p.node.callee.name == 'require') {
// 将代码中requre替换成__webpack_require__
p.node.callee.name = '__webpack_require__';
// 将./news.js 替换成./src/news.js
let value = p.node.arguments[0].value
value = "./" + (path.join("src", value));
// 多做一个处理将所有\\改成/
value.replace(/\\+/g, "/")
p.node.arguments[0].value = value
// 每找到一个require调用,修改路径后将其存入依赖数组中
depArr.push(p.node.arguments[0].value)
}
}
})
// 获取替换后的代码 将ast语法树转换成源码
let sourceCode = generator(ast).code;
// 获取相对路径
let newModulesPath = path.relative(this.root, modlePath)
// 将获取到到路径手动拼接修改得到一个新的相对路径
newModulesPath = "./"+newModulesPath.replace(/\\+/g, "/");
//将所有模块存入modules中 {'./src/inde.js':'xxx代码'}
this.modules[newModulesPath] = sourceCode;
console.log(this.modules)
// 遍历该数组 将依赖一层一层递归获取内容替换相应代码
depArr.forEach(dep => this.depAnalyse(path.resolve(this.root, dep)))
此时打印的this.modules内容如下
使用模版拼接代码然后使用node fs模块读写到打包配置文件output出口目录下
//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 = "<%-entry%>");
})
({
<% for (let k in modules) { %>
"<%-k%>": (function (module, exports, __webpack_require__) {
eval(`<%-modules[k]%>`);
}),
<% } %>
});
// 将文件输出
outputFile() {
// 读取写好的模版字符串
let template = this.getSource(path.resolve(__dirname, "../template/output.ejs"));
//读取模版
let result = ejs.render(template, {
entry: this.entry,
modules: this.modules
})
console.log(result)
// 拼接打包配置文件的路径获取输出文件的路径
let outputFilePath = path.join(this.config.output.path, this.config.output.filename);
//使用fs模块写出内容到配置输出目录下
fs.writeFileSync(outputFilePath, result)
}
start() {
//开始打包了
// 依赖分析
this.depAnalyse(path.resolve(this.root, this.entry))
// 依赖分析完后 将文件输出
this.outputFile()
}
打印的模版内容如下
此时已经完成基本的打包步骤了,浏览器正常解析运行
总结:ps纯属练习不好莫怪
/**
* webpack打包原理 将所有依赖文件处理成{'./src/index.js':'./src/index.js源码'}
* 1.创建配置文件
* 2.创建执行脚本 package.json中配置 bin/**.js文件
* 3.运行脚本中获取配置文件配置
* 4.创建打包分析依赖脚本Compiler 1.获取配置文件的入口路径 2.使用node中fs模块读取该配置文件的内容,因webpack无法解析require 需要将内容的require修改成__webpack_require__字段,将路径修改成./src/index.js 使用babel全家桶 @babel/parser将获取到的入口文件内容转换成ast语法树 使用@babel/traverse修改ast语法树匹配的内容,使用@babel/generator将ast语法树转换成源码
* 5.一个入口文件可能会有多个依赖,需要对这些依赖递归获取内容处理。创建一个内部数组存储所有的文件依赖,遍历该数组将依赖一层一层递归下去修改依赖替换相应的代码
* 6.创建modules对象 存储所有源码 将所有依赖文件处理成{'./src/index.js':'./src/index.js源码'}以此类推
* 7.将打包js使用模版生成字符串 写出到打包配置中到出口也就是dist/bundels.js中
*/