node --inspect-brk ./node_modules/webpack-cli/bin/cli.js
launch.json
配置文件.vscode\launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "debug webpack",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/node_modules/webpack-cli/bin/cli.js"
}
]
}
const webpack = require("webpack");
const webpackOptions = require("./webpack.config");
const compiler = webpack(webpackOptions);
//4.执行对象的run方法开始执行编译
compiler.run((err, stats) => {
console.log(err);
console.log(stats.toJson(
{
assets: true,
chunks: true,
modules: true,
entries: true,
}
));
});
git clone https://github.com/webpack/webpack.git
git reset --hard vx.x.x
yarn
yarn link
git clone https://github.com/webpack/webpack-cli.git
git reset --hard webpack-cli@x.x.x
cd packages\webpack-cli
yarn link webpack
yarn link
yarn link webpack
yarn link webpack-cli
yarn unlink webpack
yarn unlink webpack-cli
class SyncHook {
constructor() {
this.taps = []
}
tap(name, fn) {
this.taps.push(fn);
}
call() {
this.taps.forEach(tap => tap());
}
}
let hook = new SyncHook();
hook.tap('some name',()=>{
console.log("some name");
});
class Plugin {
apply() {
hook.tap('Plugin', () => {
console.log('Plugin ');
});
}
}
new Plugin().apply();
hook.call();
entry
找出入口文件Loader
对模块进行编译在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果
debugger.js
const webpack = require('./webpack');
const webpackOptions = require('./webpack.config');
//compiler代表整个编译过程.
const compiler = webpack(webpackOptions);
//调用它的run方法可以启动编译
compiler.run((err,stats)=>{
console.log(err);
let result = stats.toJson({
files:true,//产出了哪些文件
assets:true,//生成了那些资源
chunk:true,//生成哪些代码块
module:true,//模块信息
entries:true //入口信息
});
console.log(JSON.stringify(result,null,2));
});
webpack.config.js
const path = require('path');
const RunPlugin = require('./plugins/run-plugin');
const DonePlugin = require('./plugins/done-plugin');
const AssetPlugin = require('./plugins/assets-plugin');
module.exports = {
mode:'development',
devtool:false,
context:process.cwd(),//上下文目录, ./src .默认代表根目录 默认值其实就是当前命令执行的时候所在的目录
entry:{
entry1:'./src/entry1.js',
entry2:'./src/entry2.js'
},
output:{
path:path.join(__dirname,'dist'),
filename:'[name].js'
},
resolve:{
extensions:['.js','.jsx','.json']
},
module:{
rules:[
{
test:/\.js$/,
use:[
path.resolve(__dirname,'loaders','logger1-loader.js'),
path.resolve(__dirname,'loaders','logger2-loader.js')
]
}
]
},
plugins:[
new RunPlugin(),
new DonePlugin(),
new AssetPlugin()
]
}
webpack.js
const Compiler = require('./Compiler');
function webpack(options){
//1. 初始化参数:从配置文件和Shell语句中读取并合并参数,得出最终的配置对象
let shellConfig= process.argv.slice(2).reduce((shellConfig,item)=>{
//item= --mode=development
let [key,value] = item.split('=');
shellConfig[key.slice(2)]=value;
return shellConfig;
},{});
let finalConfig = {...options,...shellConfig};
//2. 用上一步得到的参数初始化Compiler对象
let compiler = new Compiler(finalConfig);
//3. 加载所有配置的插件
let {plugins} = finalConfig;
for(let plugin of plugins){
plugin.apply(compiler);
}
return compiler;
}
module.exports = webpack;
Compiler.js
let {SyncHook} = require('tapable');
let Complication = require('./Complication');
let path = require('path');
let fs = require('fs');
class Compiler{
constructor(options){
this.options = options;
this.hooks = {
run:new SyncHook(),//开始启动编译 刚刚开始
emit:new SyncHook(['assets']),//会在将要写入文件的时候触发
done:new SyncHook()//将会在完成编译的时候触发 全部完成
}
}
//4. 执行Compiler对象的run方法开始执行编译
run(callback){
this.hooks.run.call();//触发run钩子
//5. 根据配置中的entry找出入口文件
this.compile((err,stats)=>{
this.hooks.emit.call(stats.assets);
//10. 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
for(let filename in stats.assets){
let filePath = path.join(this.options.output.path,filename);
fs.writeFileSync(filePath,stats.assets[filename],'utf8');
}
callback(null,{
toJson:()=>stats
});
});
//监听入口的文件变化,如果文件变化了,重新再开始编译
/* Object.values(this.options.entry).forEach(entry=>{
fs.watchFile(entry,()=>this.compile(callback));
}); */
//中间是我们编译流程
this.hooks.done.call();//编译之后触发done钩子
}
compile(callback){
let complication = new Complication(this.options);
complication.build(callback);
}
}
module.exports = Compiler;
Complication.js
const path = require('path');
const fs = require('fs');
const types = require('babel-types');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const baseDir = toUnitPath(process.cwd());//\
function toUnitPath(filePath) {
return filePath.replace(/\\/g, '/');
}
class Complication {
constructor(options) {
this.options = options;
//webpack4 数组 webpack5 set
this.entries = [];//存放所有的入口
this.modules = [];// 存放所有的模块
this.chunks = [];//存放所的代码块
this.assets = {};//所有产出的资源
this.files = [];//所有产出的文件
}
build(callback) {
//5. 根据配置中的entry找出入口文件
let entry = {};
if (typeof this.options.entry === 'string') {
entry.main = this.options.entry;
} else {
entry = this.options.entry;
}
//entry={entry1:'./src/entry1.js',entry2:'./src/entry2.js'}
for (let entryName in entry) {
//5.获取 entry1的绝对路径
let entryFilePath = toUnitPath(path.join(this.options.context, entry[entryName]));
//6.从入口文件出发,调用所有配置的Loader对模块进行编译
let entryModule = this.buildModule(entryName, entryFilePath);
//this.modules.push(entryModule);
//8. 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
let chunk = {
name: entryName, entryModule, modules: this.modules.filter(item => {
return item.name === entryName || item.extraNames.includes(entryName);
})
};
this.entries.push(chunk);
this.chunks.push(chunk);
}
//9. 再把每个Chunk转换成一个单独的文件加入到输出列表
this.chunks.forEach(chunk => {
let filename = this.options.output.filename.replace('[name]', chunk.name);
// this.assets就是输出列表 key输出的文件名 值就是输出的内容
this.assets[filename] = getSource(chunk);
});
callback(null, {
entries: this.entries,
chunks: this.chunks,
modules: this.modules,
files: this.files,
assets: this.assets
});
}
//name=名称,modulePath=模块的绝对路径
buildModule(name, modulePath) {
//6. 从入口文件出发,调用所有配置的Loader对模块进行编译
//1.读取模块文件的内容
let sourceCode = fs.readFileSync(modulePath, 'utf8');//console.log('entry1');
let rules = this.options.module.rules;
let loaders = [];///寻找匹配的loader
for (let i = 0; i < rules.length; i++) {
let { test } = rules[i];
//如果此rule的正则和模块的路径匹配的话
if (modulePath.match(test)) {
loaders = [...loaders, ...rules[i].use];
}
}
sourceCode = loaders.reduceRight((sourceCode, loader) => {
return require(loader)(sourceCode);
}, sourceCode);
/* for(let i=loaders.length-1;i>=0;i--){
let loader = loaders[i];
sourceCode = require(loader)(sourceCode);
} */
//console.log('entry1');//2//1
//console.log(sourceCode);
//7. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
//获得当前模块模块ID ./src/index.js
let moduleId = './' + path.posix.relative(baseDir, modulePath);
let module = { id: moduleId, dependencies: [], name, extraNames: [] };
let ast = parser.parse(sourceCode, { sourceType: 'module' });
traverse(ast, {
CallExpression: ({ node }) => {
if (node.callee.name === 'require') {
//依赖的模块的相对路径
let moduleName = node.arguments[0].value;//./title1
//获取当前模块的所有的目录
let dirname = path.posix.dirname(modulePath);// /
//C:/aproject/zhufengwebpack202106/4.flow/src/title1
let depModulePath = path.posix.join(dirname, moduleName);
let extensions = this.options.resolve.extensions;
depModulePath = tryExtensions(depModulePath, extensions);//已经包含了拓展名了
//得到依赖的模块ID C:/aproject/zhufengwebpack202106/4.flow/src/title1.js
//相对于项目根目录 的相对路径 ./src/title1.js
let depModuleId = './' + path.posix.relative(baseDir, depModulePath);
//require('./title1');=>require('./src/title1.js');
node.arguments = [types.stringLiteral(depModuleId)];
//依赖的模块绝对路径放到当前的模块的依赖数组里
module.dependencies.push({ depModuleId, depModulePath });
}
}
});
let { code } = generator(ast);
module._source = code;//模块源代码指向语法树转换后的新生成的源代码
//7. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
module.dependencies.forEach(({ depModuleId, depModulePath }) => {
let depModule = this.modules.find(item => item.id === depModuleId);
if (depModule) {
depModule.extraNames.push(name);
} else {
let dependencyModule = this.buildModule(name, depModulePath);
this.modules.push(dependencyModule);
}
});
return module;
}
}
function getSource(chunk) {
return `
(() => {
var modules = ({
${chunk.modules.map(module => `
"${module.id}":(module,exports,require)=>{
${module._source}
}
`).join(',')
}
});
var cache = {};
function require(moduleId) {
var cachedModule = cache[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = cache[moduleId] = {
exports: {}
};
modules[moduleId](module, module.exports, require);
return module.exports;
}
var exports = {};
(() => {
${chunk.entryModule._source}
})();
})()
;
`
}
function tryExtensions(modulePath, extensions) {
extensions.unshift('');
for (let i = 0; i < extensions.length; i++) {
let filePath = modulePath + extensions[i];// ./title.js
if (fs.existsSync(filePath)) {
return filePath;
}
}
throw new Error(`Module not found`);
}
module.exports = Complication;
plugins\run-plugin.js
class RunPlugin{
apply(compiler){
//对于代码执行来说没有用,但是对于阅读代码的人来说可以起到提示的作用
compiler.hooks.run.tap('RunPlugin',()=>{
console.log('开始编译了');
});
}
}
module.exports = RunPlugin;
plugins\assets-plugin.js
class AssetPlugin{
apply(compiler){
//对于代码执行来说没有用,但是对于阅读代码的人来说可以起到提示的作用
compiler.hooks.emit.tap('AssetPlugin',(assets)=>{
debugger
assets['assets.md']= Object.keys(assets).join('\n');
});
}
}
module.exports = AssetPlugin;
plugins\done-plugin.js
class DonePlugin{
apply(compiler){
//对于代码执行来说没有用,但是对于阅读代码的人来说可以起到提示的作用
compiler.hooks.done.tap('DonePlugin',()=>{
console.log('编译结束了');
});
}
}
module.exports = DonePlugin;
loaders\logger1-loader.js
function loader(source){
console.log('logger1-loader.js');
return source+'//1';
}
module.exports = loader;
loaders\logger2-loader.js
function loader(source){
console.log('logger2-loader.js');
return source+'//2';
}
module.exports = loader;
src\entry1.js
let title1 = require('./title1');
console.log(title1);
src\entry2.js
let title1 = require('./title1');
console.log(title1);
src\title1.js
module.exports = 'title1';
src\title2.js
module.exports = 'title2';
Compilation.getStats()
,返回的是主要含有modules
、chunks
和assets
三个属性值的对象。字段 | 含义 |
---|---|
modules | 记录了所有解析后的模块 |
chunks | 记录了所有 chunk |
assets | 记录了所有要生成的文件 |
npx webpack --profile --json > stats.json
{
"hash": "780231fa9b9ce4460c8a", //编译使用的 hash
"version": "5.8.0", // 用来编译的 webpack 的版本
"time": 83, // 编译耗时 (ms)
"builtAt": 1606538839612, //编译的时间
"publicPath": "auto", //资源访问路径
"outputPath": "C:\\webpack5\\dist", //输出目录
"assetsByChunkName": { //代码块和文件名的映射
"main": ["main.js"]
},
"assets": [ //资源数组
{
"type": "asset", //资源类型
"name": "main.js", //文件名称
"size": 2418, //文件大小
"chunkNames": [ //对应的代码块名称
"main"
],
"chunkIdHints": [],
"auxiliaryChunkNames": [],
"auxiliaryChunkIdHints": [],
"emitted": false,
"comparedForEmit": true,
"cached": false,
"info": {
"javascriptModule": false,
"size": 2418
},
"related": {},
"chunks": [
"main"
],
"auxiliaryChunks": [],
"isOverSizeLimit": false
}
],
"chunks": [ //代码块数组
{
"rendered": true,
"initial": true,
"entry": true,
"recorded": false,
"size": 80,
"sizes": {
"javascript": 80
},
"names": [
"main"
],
"idHints": [],
"runtime": [
"main"
],
"files": [
"main.js"
],
"auxiliaryFiles": [],
"hash": "d25ad7a8144077f69783",
"childrenByOrder": {},
"id": "main",
"siblings": [],
"parents": [],
"children": [],
"modules": [
{
"type": "module",
"moduleType": "javascript/auto",
"identifier": "C:\\webpack5\\src\\index.js",
"name": "./src/index.js",
"nameForCondition": "C:\\webpack5\\src\\index.js",
"index": 0,
"preOrderIndex": 0,
"index2": 1,
"postOrderIndex": 1,
"size": 55,
"sizes": {
"javascript": 55
},
"cacheable": true,
"built": true,
"codeGenerated": true,
"cached": false,
"optional": false,
"orphan": false,
"dependent": false,
"issuer": null,
"issuerName": null,
"issuerPath": null,
"failed": false,
"errors": 0,
"warnings": 0,
"profile": {
"total": 38,
"resolving": 26,
"restoring": 0,
"building": 12,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 26,
"dependencies": 0
},
"id": "./src/index.js",
"issuerId": null,
"chunks": [
"main"
],
"assets": [],
"reasons": [
{
"moduleIdentifier": null,
"module": null,
"moduleName": null,
"resolvedModuleIdentifier": null,
"resolvedModule": null,
"type": "entry",
"active": true,
"explanation": "",
"userRequest": "./src/index.js",
"loc": "main",
"moduleId": null,
"resolvedModuleId": null
}
],
"usedExports": null,
"providedExports": null,
"optimizationBailout": [],
"depth": 0
},
{
"type": "module",
"moduleType": "javascript/auto",
"identifier": "C:\\webpack5\\src\\title.js",
"name": "./src/title.js",
"nameForCondition": "C:\\webpack5\\src\\title.js",
"index": 1,
"preOrderIndex": 1,
"index2": 0,
"postOrderIndex": 0,
"size": 25,
"sizes": {
"javascript": 25
},
"cacheable": true,
"built": true,
"codeGenerated": true,
"cached": false,
"optional": false,
"orphan": false,
"dependent": true,
"issuer": "C:\\webpack5\\src\\index.js",
"issuerName": "./src/index.js",
"issuerPath": [
{
"identifier": "C:\\webpack5\\src\\index.js",
"name": "./src/index.js",
"profile": {
"total": 38,
"resolving": 26,
"restoring": 0,
"building": 12,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 26,
"dependencies": 0
},
"id": "./src/index.js"
}
],
"failed": false,
"errors": 0,
"warnings": 0,
"profile": {
"total": 0,
"resolving": 0,
"restoring": 0,
"building": 0,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 0,
"dependencies": 0
},
"id": "./src/title.js",
"issuerId": "./src/index.js",
"chunks": [
"main"
],
"assets": [],
"reasons": [
{
"moduleIdentifier": "C:\\webpack5\\src\\index.js",
"module": "./src/index.js",
"moduleName": "./src/index.js",
"resolvedModuleIdentifier": "C:\\webpack5\\src\\index.js",
"resolvedModule": "./src/index.js",
"type": "cjs require",
"active": true,
"explanation": "",
"userRequest": "./title.js",
"loc": "1:12-33",
"moduleId": "./src/index.js",
"resolvedModuleId": "./src/index.js"
},
{
"moduleIdentifier": "C:\\webpack5\\src\\title.js",
"module": "./src/title.js",
"moduleName": "./src/title.js",
"resolvedModuleIdentifier": "C:\\webpack5\\src\\title.js",
"resolvedModule": "./src/title.js",
"type": "cjs self exports reference",
"active": true,
"explanation": "",
"userRequest": null,
"loc": "1:0-14",
"moduleId": "./src/title.js",
"resolvedModuleId": "./src/title.js"
}
],
"usedExports": null,
"providedExports": null,
"optimizationBailout": [
"CommonJS bailout: module.exports is used directly at 1:0-14"
],
"depth": 1
}
],
"origins": [
{
"module": "",
"moduleIdentifier": "",
"moduleName": "",
"loc": "main",
"request": "./src/index.js"
}
]
}
],
"modules": [ //模块数组
{
"type": "module",
"moduleType": "javascript/auto",
"identifier": "C:\\webpack5\\src\\index.js",
"name": "./src/index.js",
"nameForCondition": "C:\\webpack5\\src\\index.js",
"index": 0,
"preOrderIndex": 0,
"index2": 1,
"postOrderIndex": 1,
"size": 55,
"sizes": {
"javascript": 55
},
"cacheable": true,
"built": true,
"codeGenerated": true,
"cached": false,
"optional": false,
"orphan": false,
"issuer": null,
"issuerName": null,
"issuerPath": null,
"failed": false,
"errors": 0,
"warnings": 0,
"profile": {
"total": 38,
"resolving": 26,
"restoring": 0,
"building": 12,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 26,
"dependencies": 0
},
"id": "./src/index.js",
"issuerId": null,
"chunks": [
"main"
],
"assets": [],
"reasons": [
{
"moduleIdentifier": null,
"module": null,
"moduleName": null,
"resolvedModuleIdentifier": null,
"resolvedModule": null,
"type": "entry",
"active": true,
"explanation": "",
"userRequest": "./src/index.js",
"loc": "main",
"moduleId": null,
"resolvedModuleId": null
}
],
"usedExports": null,
"providedExports": null,
"optimizationBailout": [],
"depth": 0
},
{
"type": "module",
"moduleType": "javascript/auto",
"identifier": "C:\\webpack5\\src\\title.js",
"name": "./src/title.js",
"nameForCondition": "C:\\webpack5\\src\\title.js",
"index": 1,
"preOrderIndex": 1,
"index2": 0,
"postOrderIndex": 0,
"size": 25,
"sizes": {
"javascript": 25
},
"cacheable": true,
"built": true,
"codeGenerated": true,
"cached": false,
"optional": false,
"orphan": false,
"issuer": "C:\\webpack5\\src\\index.js",
"issuerName": "./src/index.js",
"issuerPath": [
{
"identifier": "C:\\webpack5\\src\\index.js",
"name": "./src/index.js",
"profile": {
"total": 38,
"resolving": 26,
"restoring": 0,
"building": 12,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 26,
"dependencies": 0
},
"id": "./src/index.js"
}
],
"failed": false,
"errors": 0,
"warnings": 0,
"profile": {
"total": 0,
"resolving": 0,
"restoring": 0,
"building": 0,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 0,
"dependencies": 0
},
"id": "./src/title.js",
"issuerId": "./src/index.js",
"chunks": [
"main"
],
"assets": [],
"reasons": [
{
"moduleIdentifier": "C:\\webpack5\\src\\index.js",
"module": "./src/index.js",
"moduleName": "./src/index.js",
"resolvedModuleIdentifier": "C:\\webpack5\\src\\index.js",
"resolvedModule": "./src/index.js",
"type": "cjs require",
"active": true,
"explanation": "",
"userRequest": "./title.js",
"loc": "1:12-33",
"moduleId": "./src/index.js",
"resolvedModuleId": "./src/index.js"
},
{
"moduleIdentifier": "C:\\webpack5\\src\\title.js",
"module": "./src/title.js",
"moduleName": "./src/title.js",
"resolvedModuleIdentifier": "C:\\webpack5\\src\\title.js",
"resolvedModule": "./src/title.js",
"type": "cjs self exports reference",
"active": true,
"explanation": "",
"userRequest": null,
"loc": "1:0-14",
"moduleId": "./src/title.js",
"resolvedModuleId": "./src/title.js"
}
],
"usedExports": null,
"providedExports": null,
"optimizationBailout": [
"CommonJS bailout: module.exports is used directly at 1:0-14"
],
"depth": 1
}
],
"entrypoints": { //入口点
"main": {
"name": "main",
"chunks": [
"main"
],
"assets": [
{
"name": "main.js",
"size": 2418
}
],
"filteredAssets": 0,
"assetsSize": 2418,
"auxiliaryAssets": [],
"filteredAuxiliaryAssets": 0,
"auxiliaryAssetsSize": 0,
"children": {},
"childAssets": {},
"isOverSizeLimit": false
}
},
"namedChunkGroups": { //命名代码块组
"main": {
"name": "main",
"chunks": [
"main"
],
"assets": [
{
"name": "main.js",
"size": 2418
}
],
"filteredAssets": 0,
"assetsSize": 2418,
"auxiliaryAssets": [],
"filteredAuxiliaryAssets": 0,
"auxiliaryAssetsSize": 0,
"children": {},
"childAssets": {},
"isOverSizeLimit": false
}
},
"errors": [],
"errorsCount": 0,
"warnings": [],
"warningsCount": 0,
"children": []
}