三十二、1.webpack手写
- npx webpack原理
- 新建文件夹kft-pack,初始化
npm init -y,package.json添加"bin": {"kft-pack": "./bin/kft-pack.js"},,kft-pack下新建bin文件夹,bin下新建kft-pack.js文件,添加#! /usr/bin/env node,另起一行console.log('hello,world!');,命令行执行npm link,连接到全局
*. 新建webpack-dev,文件下执行npm link kft-pack,命令连到本文件下,执行npx kft-pack
- webpack-dev
- src/index.js
let str = require('./a');console.log(str);
- src/a.js
let b = require('./base/b');module.exports = 'a' + b;
- src/base/b.js
module.exports = 'b';
- node执行index.js,打印ab
- npx webpack 打包
三十三、2.webpack分析及处理
- kft-pack文件夹
// 1)需要找到当前执行名的路径,拿到webpack.config.js
let path = require('path');
// configp配置文件
let config = require(path.resolve('webpack.config.js'));
// 编译
let Compiler = require('../lib/Compiler.js');
let compiler = new Compiler(config);
compiler.run();
class Compiler {
constructor(config) {
// 输入输出配置
this.config = config;
// 需要保存入口文件路径
this.entryId; // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {};
this.entry = config.entry; // 入口路径
// 工作路径
this.root = process.cwd();
}
buildModule(modulePath,isEntry) {
}
emitFile() { // 发射文件
}
run() {
// 执行并且创建模块的依赖关系
this.buildModule(path.resolve(this.root, this.entry), true);
// 发射一个文件,打包后的文件
this.emitFile();
}
}
三十四、3.创建依赖关系
let path = require('path');
let fs = require('fs');
class Compiler {
constructor(config) {
// 输入输出配置
this.config = config;
// 需要保存入口文件路径
this.entryId; // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {};
this.entry = config.entry; // 入口路径
// 工作路径
this.root = process.cwd();
}
getSource(modulePath) { //
let content = fs.readFileSync(modulePath, 'utf8');
return content;
}
parse(source, parentPath) { //
//
// console.log(source, parentPath)
}
buildModule(modulePath, isEntry) { //
//
let source = this.getSource(modulePath);
//
let moduleName = './' + path.relative(this.root, modulePath);
//
if (isEntry) {
this.entryId = moduleName;
}
//
// console.log(source, moduleName);
//
let { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName));
//
this.modules[moduleName] = sourceCode;
}
emitFile() { // 发射文件
}
run() {
// 执行并且创建模块的依赖关系
this.buildModule(path.resolve(this.root, this.entry), true);
// 发射一个文件,打包后的文件
this.emitFile();
}
}
module.exports = Compiler;
三十五、4.AST递归解析:给名字,加源码
- babylon 源码转换成AST ast官网
- 安装包:babylon @babel/traverse @babel/types @babel/generator
- Compiler.js
let path = require('path');
let fs = require('fs');
//
let babylon = require('babylon');
let traverse = require('@babel/traverse').default;
let t = require('@babel/types');
let generator = require('@babel/generator').default;
class Compiler {
constructor(config) {
// 输入输出配置
this.config = config;
// 需要保存入口文件路径
this.entryId; // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {};
this.entry = config.entry; // 入口路径
// 工作路径
this.root = process.cwd();
}
getSource(modulePath) { // 公共方法:获取读取的文件内容
let content = fs.readFileSync(modulePath, 'utf8');
return content;
}
parse(source, parentPath) { // 解析源码 AST解析语法树
// 打印查看
// console.log(source, parentPath)
//
let ast = babylon.parse(source);
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); //
dependencies.push(moduleName); //
node.arguments = [t.stringLiteral(moduleName)];
}
}
})
let sourceCode = generator(ast).code; //
return { sourceCode, dependencies }
}
buildModule(modulePath, isEntry) { // 构建模块
// 拿到模块内容
let source = this.getSource(modulePath);
// 模块id modulePath = modulePath-this.root src/index.js
let moduleName = './' + path.relative(this.root, modulePath);
// 保存入口的名字
if (isEntry) {
this.entryId = moduleName;
}
// 打印查看对错
// console.log(source, moduleName);
// 解析,需要把source源码进行改造,返回一个依赖列表
let { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName));
//
// console.log(sourceCode, dependencies)
// 把相对路径和模块中的内容一一对应起来
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);
//
console.log(this.modules, this.entryId);
// 发射一个文件,打包后的文件
this.emitFile();
}
}
module.exports = Compiler;
三十六、5.生成打包结果
- bin/lib/Compiler.js
let path = require('path');
let fs = require('fs');
// babylon @babel/traverse @babel/types @babel/generator
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');
class Compiler {
constructor(config) {
// 输入输出配置
this.config = config;
// 需要保存入口文件路径
this.entryId; // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {};
this.entry = config.entry; // 入口路径
// 工作路径
this.root = process.cwd();
}
getSource(modulePath) { // 公共方法:获取读取的文件内容
let content = fs.readFileSync(modulePath, 'utf8');
return content;
}
parse(source, parentPath) { // 解析源码 AST解析语法树
// 打印查看
// console.log(source, parentPath)
// babylon解析
let ast = babylon.parse(source);
let dependencies = []; // 依赖数组
traverse(ast, { // 重构ast
CallExpression(p) { //
let node = p.node; //
if (node.callee.name === 'require') {
node.callee.name = '__webpack_require__'; // 修改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; // 重新生成一下
return { sourceCode, dependencies }
}
buildModule(modulePath, isEntry) { // 构建模块
// 拿到模块内容
let source = this.getSource(modulePath);
// 模块id modulePath = modulePath-this.root src/index.js
let moduleName = './' + path.relative(this.root, modulePath);
// 保存入口的名字
if (isEntry) {
this.entryId = moduleName;
}
// 打印查看对错
// console.log(source, moduleName);
// 解析,需要把source源码进行改造,返回一个依赖列表
let { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName));
// 打印
// console.log(sourceCode, dependencies)
// 把相对路径和模块中的内容一一对应起来
this.modules[moduleName] = sourceCode;
// 所有依赖项进行递归
dependencies.forEach(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.assets = {};
//
this.assets[main] = code;
//
fs.writeFileSync(main, this.assets[main]);
}
run() {
// 执行并且创建模块的依赖关系
this.buildModule(path.resolve(this.root, this.entry), true);
// 打印
// console.log(this.modules, this.entryId);
// 发射一个文件,打包后的文件
this.emitFile();
}
}
module.exports = Compiler;
(function (modules) { // webpackBootstrap
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
return __webpack_require__(__webpack_require__.s = "<%-entryId%>");
})
({
<%for(let key in modules){%>
"<%-key%>":
(function (module, exports, __webpack_require__) {
eval(`<%-modules[key]%>`);
}),
<%}%>
});
三十七、6.增加loader
- webpack-dev
let path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
module: {
rules: [
{
test: /\.less/,
use: [
path.resolve(__dirname, 'loader', 'style-loader'),
path.resolve(__dirname, 'loader', 'less-loader')
]
}
]
}
}
- 新建loader文件夹,loader/less-loader.js loader/style-loader.js
- 安装包 less
- loader/less-loader.js
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;
function loader(source) {
let style = `
let style = document.createElement('style');
style.innerHTML = ${JSON.stringify(source)}
document.head.appendChild(style);
`;
return style;
}
module.exports = loader;
- kft-pack文件夹
let path = require('path');
let fs = require('fs');
// babylon @babel/traverse @babel/types @babel/generator
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');
class Compiler {
constructor(config) {
// 输入输出配置
this.config = config;
// 需要保存入口文件路径
this.entryId; // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {};
this.entry = config.entry; // 入口路径
// 工作路径
this.root = process.cwd();
}
getSource(modulePath) { // 公共方法:获取读取的文件内容
//
let rules = this.config.module.rules;
let content = fs.readFileSync(modulePath, 'utf8');
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)) { //
//
function normalLoader() {
let loader = require(use[len--]);
content = loader(content);
if (len >= 0) {
normalLoader();
}
}
normalLoader();
}
}
return content;
}
parse(source, parentPath) { // 解析源码 AST解析语法树
// 打印查看
// console.log(source, parentPath)
// babylon解析
let ast = babylon.parse(source);
let dependencies = []; // 依赖数组
traverse(ast, { // 重构ast
CallExpression(p) { // a() require()
let node = p.node; // 对应的节点
if (node.callee.name === 'require') {
node.callee.name = '__webpack_require__'; // 修改require名
let moduleName = node.arguments[0].value; // 取到是模块的引用名字
moduleName = moduleName + (path.extname(moduleName) ? '' : '.js'); // 添加扩展名
moduleName = './' + path.join(parentPath, moduleName); // 拼接附路径 './src/a.js'
dependencies.push(moduleName); // 添加到依赖数组
node.arguments = [t.stringLiteral(moduleName)];
}
}
})
let sourceCode = generator(ast).code; // 重新生成一下
return { sourceCode, dependencies }
}
buildModule(modulePath, isEntry) { // 构建模块
// 拿到模块内容
let source = this.getSource(modulePath);
// 模块id modulePath = modulePath-this.root src/index.js
let moduleName = './' + path.relative(this.root, modulePath);
// 保存入口的名字
if (isEntry) {
this.entryId = moduleName;
}
// 打印查看对错
// console.log(source, moduleName);
// 解析,需要把source源码进行改造,返回一个依赖列表
let { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName));
// 打印
// console.log(sourceCode, dependencies)
// 把相对路径和模块中的内容一一对应起来
this.modules[moduleName] = sourceCode;
// 所有依赖项进行递归
dependencies.forEach(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'));
// ejs渲染
let code = ejs.render(templateStr, { entryId: this.entryId, modules: this.modules })
// 存放的资源
this.assets = {};
// 资源中路径对应的代码
this.assets[main] = code;
// 本来需要遍历插入,为了方便,直接用writeFileSync
fs.writeFileSync(main, this.assets[main]);
}
run() {
// 执行并且创建模块的依赖关系
this.buildModule(path.resolve(this.root, this.entry), true);
// 打印
// console.log(this.modules, this.entryId);
// 发射一个文件,打包后的文件
this.emitFile();
}
}
module.exports = Compiler;
三十八、7.增加plugins
- kft-pack
- 安装包 tapable 使用钩子
- bin/lib/Compiler.js
let path = require('path');
let fs = require('fs');
// babylon @babel/traverse @babel/types @babel/generator
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');
//
let { SyncHook } = require('tapable');
class Compiler {
constructor(config) {
// 输入输出配置
this.config = config;
// 需要保存入口文件路径
this.entryId; // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {};
this.entry = config.entry; // 入口路径
// 工作路径
this.root = process.cwd();
//
this.hooks = {
// 入口
entryOptions: new SyncHook(),
// 编译
compile: new SyncHook(),
// 编译后
afterCompile: new SyncHook(),
// 引完插件后
afterPlugins: new SyncHook(),
// 运行
run: new SyncHook(),
// 发送
emit: new SyncHook(),
// 完成
done: new SyncHook(),
}
//
let plugins = this.config.plugins;
if (Array.isArray(plugins)) {
plugins.forEach(plugin => {
plugin.apply(this);
})
this.hooks.afterPlugins.call(); //
}
}
getSource(modulePath) { // 公共方法:获取读取的文件内容
// 拿到所有规则
let rules = this.config.module.rules;
let content = fs.readFileSync(modulePath, 'utf8');
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来转化
// loader 获取对应的loader函数
function normalLoader() {
let loader = require(use[len--]);
content = loader(content);
if (len >= 0) {
normalLoader();
}
}
normalLoader();
}
}
return content;
}
parse(source, parentPath) { // 解析源码 AST解析语法树
// 打印查看
// console.log(source, parentPath)
// babylon解析
let ast = babylon.parse(source);
let dependencies = []; // 依赖数组
traverse(ast, { // 重构ast
CallExpression(p) { // a() require()
let node = p.node; // 对应的节点
if (node.callee.name === 'require') {
node.callee.name = '__webpack_require__'; // 修改require名
let moduleName = node.arguments[0].value; // 取到是模块的引用名字
moduleName = moduleName + (path.extname(moduleName) ? '' : '.js'); // 添加扩展名
moduleName = './' + path.join(parentPath, moduleName); // 拼接附路径 './src/a.js'
dependencies.push(moduleName); // 添加到依赖数组
node.arguments = [t.stringLiteral(moduleName)];
}
}
})
let sourceCode = generator(ast).code; // 重新生成一下
return { sourceCode, dependencies }
}
buildModule(modulePath, isEntry) { // 构建模块
// 拿到模块内容
let source = this.getSource(modulePath);
// 模块id modulePath = modulePath-this.root src/index.js
let moduleName = './' + path.relative(this.root, modulePath);
// 保存入口的名字
if (isEntry) {
this.entryId = moduleName;
}
// 打印查看对错
// console.log(source, moduleName);
// 解析,需要把source源码进行改造,返回一个依赖列表
let { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName));
// 打印
// console.log(sourceCode, dependencies)
// 把相对路径和模块中的内容一一对应起来
this.modules[moduleName] = sourceCode;
// 所有依赖项进行递归
dependencies.forEach(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'));
// ejs渲染
let code = ejs.render(templateStr, { entryId: this.entryId, modules: this.modules })
// 存放的资源
this.assets = {};
// 资源中路径对应的代码
this.assets[main] = code;
// 本来需要遍历插入,为了方便,直接用writeFileSync
fs.writeFileSync(main, this.assets[main]);
}
run() {
this.hooks.run.call(); //
this.hooks.compile.call(); //
// 执行并且创建模块的依赖关系
this.buildModule(path.resolve(this.root, this.entry), true);
// 打印
// console.log(this.modules, this.entryId);
this.hooks.afterCompile.call(); //
// 发射一个文件,打包后的文件
this.emitFile();
this.hooks.emit.call(); //
this.hooks.done.call(); //
}
}
module.exports = Compiler;
let fs = require('fs');
// 1)需要找到当前执行名的路径,拿到webpack.config.js
let path = require('path');
// configp配置文件
let config = require(path.resolve('webpack.config.js'));
// 编译
let Compiler = require('./lib/Compiler.js');
let compiler = new Compiler(config);
compiler.hooks.entryOptions.call(); //
compiler.run();
- webpack-dev
let path = require('path');
class P{
apply(compiler){
compiler.hooks.emit.tap('emit',function(){
console.log('emit-------------');
})
}
}
class P1{
apply(compiler){
compiler.hooks.afterCompile.tap('emit',function(){
console.log('afterCompile-------------');
})
}
}
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
module: {
rules: [
{
test: /\.less/,
use: [
path.resolve(__dirname, 'loader', 'style-loader'),
path.resolve(__dirname, 'loader', 'less-loader')
]
}
]
},
plugins: [
new P(),
new P1(),
]
}