詳細實現收錄在https://github.com/jdkwky/webstudydeep/tree/webstudydeep/webpackstudy 中,主要以webpack03、webpack04、pwebpack文件為主,如果覺得對您有幫助歡迎給個star。
npm link
使用 npm link 創建自己本地開發包然后通過npm link modelName 引入到需要引用該模塊的項目中
舉例:
pwebpack 是我們的本地打包js文件的項目,當前pwebpack是空項目;
- cd pwebpack & npm init;
- package.json 中的name字段為"pwebpack"(name字段可以更改但是后面引入的包名也會跟著這個名字的改變而改變)
- package.json 中 加入 "bin":"./bin/pwebpack.js";
- 在"./bin/pwebpack.js"文件中寫如下代碼
#! /usr/bin/env node
// 作用 是告訴系統此文件用node執行 并且引用那個環境下的node模塊
console.log('start');
- 在需要引入pwebpack包中的文件中執行 npm link pwebpack;
- 測試 npx pwebpack; 輸出 "start" 即成功,修改一下pwebpack.js文件中的輸出字段,重新npx pwebpack一下更新新的數據。
手寫簡易版 webpack
- 入口文件
const path = require('path');
// 獲取配置文件內容
const config = require(path.resolve('webpack.config.js'));
// 引用編譯類
const Compiler = require('../lib/Compiler.js');
// 創建對象
const compiler = new Compiler(config);
// 調用run 方法
compiler.run();
- Compiler 類
const path = require('path');
const fs = require('fs');
const babylon = require('babylon');
const traverse = require('@babel/traverse').default;
const types = require('@babel/types');
const generator = require('@babel/generator').default;
const ejs = require('ejs');
const { SyncHook } = require('tapable');
class Compiler {
constructor(config) {
this.config = config || {};
// 保存所有模塊依賴
this.modules = {};
// 入口文件
this.entry = config.entry;
this.entryId = '';
// 工作目錄
this.root = process.cwd();
}
// 獲取資源
getSource(modulePath) {
let content = fs.readFileSync(modulePath, 'utf8');
return content;
}
// 解析語法
parse(source, parentPath) {
// AST 語法樹解析
const ast = babylon.parse(source);
// 依賴的數組
const dependencies = [];
traverse(ast, {
CallExpression(p) {
const 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'); // ./a.js
moduleName = './' + path.join(parentPath, moduleName); // ./src/a/js
dependencies.push(moduleName);
node.arguments = [types.stringLiteral(moduleName)]; // 改掉源碼
}
}
});
const sourceCode = generator(ast).code;
return { sourceCode, dependencies };
}
// 構建模塊
buildModule(modulePath, isEntry) {
// 模塊路徑 是否是入口文件
// 拿到模塊內容
const source = this.getSource(modulePath);
// 獲取模塊id 需要相對路徑
const moduleName = './' + path.relative(this.root, modulePath);
if (isEntry) {
this.entryId = moduleName;
}
// 解析代碼塊
const { sourceCode, dependencies } = this.parse(
source,
path.dirname(moduleName)
);
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);
this.emitFile();
}
}
- 添加簡易版 loader plugin(簡易版都是同步鉤子)解析
構造函數constructor函數中
// 插件
this.hooks = {
entryOption: new SyncHook(),
compile: new SyncHook(),
afterCompile: new SyncHook(),
afterPlugins: new SyncHook(),
run: new SyncHook(),
emit: new SyncHook(),
done: new SyncHook()
};
// 解析plugins 通過tapable
const plugins = this.config.plugins;
if (Array.isArray(plugins)) {
plugins.forEach(plugin => {
plugin.apply(this);
});
}
this.hooks.afterPlugins.call();
getSource函數中
// 解析loader
const rules = this.config.module.rules || [];
for (let i = 0; i < rules.length; i++) {
let rule = rules[i];
const { test, use } = rule || {};
let len = use.length;
if (test.test(modulePath)) {
// 需要通過 loader 進行轉化
while (len > 0) {
let loader = require(use[--len]);
content = loader(content);
}
}
}
plugin 中 tapable鉤子
同步鉤子
const { SyncHook } = require('tapable');
const hook = new SyncHook(['name']);
hook.tap('hello', name => {
console.log(`hello ${name}`);
});
hook.tap('Hello again', name => {
console.log(`Hello ${name},again`);
});
hook.call('wky');
// 輸出 hello wky , Hello wky , again
簡易版實現
class SyncHook {
constructor() {
this.tasks = [];
}
tap(name, fn) {
this.tasks.push(fn);
}
call(...args) {
this.tasks.forEach(task => {
task(...args);
});
}
}
const hook = new SyncHook(['name']);
hook.tap('hello', name => {
console.log(`hello ${name}`);
});
hook.tap('Hello again', name => {
console.log(`Hello ${name},again`);
});
hook.call('wky');
SyncBailHook 熔斷性執行
const { SyncBailHook } = require('tapable');
const hook = new SyncBailHook(['name']);
hook.tap('node', function(name) {
console.log('node', name);
return '停止學習';
});
hook.tap('react', function(name) {
console.log('react', name);
});
hook.call('wky');
// node wky 停止學習, 就不會返回執行下面的代碼
實現原理
class SyncBailHook {
constructor() {
this.tasks = [];
}
tap(name, fn) {
this.tasks.push(fn);
}
call(...args) {
let index = 0,
length = this.tasks.length,
tasks = this.tasks;
let result;
do {
result = tasks[index](...args);
index++;
} while (result == null && index < length);
}
}
const hook = new SyncBailHook(['name']);
hook.tap('node', function(name) {
console.log('node', name);
return '停止學習';
});
hook.tap('react', function(name) {
console.log('react', name);
});
hook.call('wkyyc');
同步瀑布鉤子(上一個監聽函數的值會傳遞給下一個監聽函數)
const { SyncWaterfallHook } = require('tapable');
const hook = new SyncWaterfallHook(['name']);
hook.tap('node', function(name) {
console.log('node', name);
return 'node 學的還不錯';
});
hook.tap('react', function(data) {
console.log('react', data);
});
hook.call('wky');
實現原理
class SyncWaterfallHook {
constructor() {
this.tasks = [];
}
tap(name, fn) {
this.tasks.push(fn);
}
call(...args) {
const [firstFn, ...others] = this.tasks;
others.reduce((sum, task) => task(sum), firstFn(...args));
}
}
const hook = new SyncWaterfallHook(['name']);
hook.tap('node', function(name) {
console.log('node', name);
return 'node 學的還不錯';
});
hook.tap('react', function(data) {
console.log('react', data);
});
hook.call('wkyyc');
異步鉤子, 并行執行的異步鉤子,當注冊的所有異步回調都并行執行完畢之后再執行callAsync或者promise中的函數
const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name']);
hook.tapAsync('hello', (name, cb) => {
setTimeout(() => {
console.log(`hello ${name}`);
cb();
}, 1000);
});
hook.tapAsync('hello again', (name, cb) => {
setTimeout(() => {
console.log(`Hello ${name} again`);
cb();
}, 2000);
});
hook.callAsync('wkyyc', () => {
console.log('end');
});
實現原理
class AsyncParallelHook {
constructor() {
this.tasks = [];
}
tapAsync(name, fn) {
this.tasks.push(fn);
}
callAsync(...args) {
// 要最后執行的函數
const callbackFn = args.pop();
let index = 0;
const next = () => {
index++;
if (index == this.tasks.length) {
callbackFn();
}
};
this.tasks.forEach(task => {
task(...args, next);
});
}
}
const hook = new AsyncParallelHook(['name']);
hook.tapAsync('hello', (name, cb) => {
setTimeout(() => {
console.log(`hello ${name}`);
cb();
}, 1000);
});
hook.tapAsync('hello again', (name, cb) => {
setTimeout(() => {
console.log(`Hello ${name} again`);
cb();
}, 3000);
});
hook.callAsync('wkyyc', () => {
console.log('end');
});
異步串行執行
const { AsyncSeriesHook } = require('tapable');
const hook = new AsyncSeriesHook(['name']);
hook.tapPromise('hello', name => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`hello ${name}`);
resolve();
}, 1000);
});
});
hook.tapPromise('hello again', data => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Hello ${data} again`);
resolve();
}, 1000);
});
});
hook.promise('wkyyc').then(() => {
console.log('end');
});
實現原理
class AsyncSeriesHook {
constructor() {
this.tasks = [];
}
tapPromise(name, fn) {
this.tasks.push(fn);
}
promise(...args) {
const [firstFn, ...others] = this.tasks;
return others.reduce(
(sum, task) =>
sum.then(() => {
return task(...args);
}),
firstFn(...args)
);
}
}
const hook = new AsyncSeriesHook(['name']);
hook.tapPromise('hello', name => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`hello ${name}`);
resolve();
}, 1000);
});
});
hook.tapPromise('hello again', data => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Hello ${data} again`);
resolve();
}, 1000);
});
});
hook.promise('wkyyc').then(() => {
console.log('end');
});