Both the interview and the internal implementation of webpack are important to understand, and I recently learned about it, so I’ll record my experience and hopefully help other students who are interested in learning about it!

First we can break it down into several steps to make it easier to remember

  1. Initialization parameters
  2. Initialize the Compiler object
  3. Get all plugins and execute them
  4. Start compilation and execute the run method of the Compiler object
  5. Get all the entry files according to the configuration file
  6. Call all loaders to compile the module based on the entry files
  7. Find the modules on which the entry files depend, recursively until all the entry dependencies are processed in this step
  8. Assemble chunks according to the dependencies of the entry files and modules
  9. Add the chunk to the output list by generating the corresponding file
  10. Determine the content of the output, determine the path and file name of the output file according to the configuration, and write the file to the file system

webpack.js

  1. Initialization parameters
  2. Initialize the Compiler object
  3. Load all the plugins and pass the compiler object to the form parameter of apply
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const Compiler = require('./Compiler);

function webpack(options) {
const argv = process.argv.slice(2);
const shellOptions = argv.reduce((shellOptions, option) => {
const [key, value] = option.split('=');
shellOptions[key] = value;
return shellOptions
}, {});

const finalOptions = {
...options,
...shellOptions
};

const compiler = new Compiler(finalOptions);

finalOptions.plugins.forEach(plugin => plugin.apply(compiler));
};

module.exports = webpack;

Compiler.js

  1. Execute the run method of the object to start compilation
  2. Determine the output content, determine the output path and file name according to the configuration, and write the file to the file system
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const fs = require('fs');
const { SyncHook } = require('tapable');
const Compilcation = require('./Compilcation');

class Compiler {
constructor(options) {
this.options = options;
this.hooks = {
run: new SyncHook(),
done: new SyncHook()
}
}

run(callback) {
this.hooks.run.call();
function compiled(err, state, fileDependencies) {

for (let filename in stats.assets) {

let filePath = path.join(this.options.output.path, filename);

fs.writeFileSync(filePath, stats.assets[filename], "utf8");
}

callback(err, {
toJson: () => stats,
});


fileDependencies.forEach(file => {
fs.watch(file, () => {
this.compiler(compiled)
});
});
};

this.compiler(compiled);
this.hooks.done.call();
}

compiler(callback) {
const complication = new Compilcation(this.options);
complication.build(callback)
}
}

module.exports = Compiler;

Compilcation.js

  1. Find all the entry files according to the entry in the configuration
  2. Based on the entry files, call all Loader configurations to compile the module
  3. Then find the modules that the module depends on, recursively until all the modules that the entry file depends on have been processed in this step
  4. Assemble the entry and module dependencies into a chunk containing multiple modules
    Convert each chunk into a separate file and add it to the output list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
const path = require('path');
const baseDir = process.cwd();
const fs = require('fs')
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generatar = require('@babel/generator').default;

class Compilcation {
constructor(options) {
this.options = options;
this.fileDependencies = [];
this.modules = [];
this.chunks = [];
this.assets = {};
}

build(callback) {

let entry = {};
if (typeof this.options.entry === 'string') {
entry.main = this.options.entry;
} else {
entry = this.options.entry;
};

for (let entryName in entry) {
let entryPath = path.posix.join(baseDir, entry[entryName]);
this.fileDependencies.push(entryPath);

const entryModule = this.buildModule(entryName, entryPath);

let chunk = {
name: entryName,
entryModule,
modules: this.modules.filter((item) => item.names.includes(entryName))
};
this.chunks.push(chunk);
}

this.chunks.forEach(chunk => {
let filename = this.options.output.filename.replace("[name]", chunk.name);
this.assets[filename] = getSource(chunk);
});

callback(null, {
chunks: this.chunks,
modules: this.modules,
assets: this.assets,
}, this.fileDependencies);
}

buildModule(name, modulePath) {
let sourceCode = fs.readFileSync(modulePath, 'utf8');

const { rules } = this.options.module;

let loaders = [];
rules.forEach(rule => {
if (modulePath.match(rule.test)) {
loaders.push(...rule.use);
}
});

sourceCode = loaders.reduceRight((sourceCode, loader) => {
return require(loader)(sourceCode);
}, sourceCode);

let moduleId = './' + path.posix.relative(baseDir, modulePath);
let moudle = {
id: moduleId,
dependencies: [],
names: [name]
};

let ast = parser.parse(sourceCode, {sourceType: "module"})
traverse(ast, {
CallExpression: ({ node }) => {
if (node.callee.name === "require") {
let depModuleName = node.arguments[0].value; // ./title

let dirname = path.posix.dirname(modulePath); //src

//C:\aproject\zhufengwebpack202108\4.flow\src\title.js
let depModulePath = path.posix.join(dirname, depModuleName);

let extensions = this.options.resolve.extensions;
depModulePath = tryExtensions(depModulePath, extensions);

this.fileDependencies.push(depModulePath);

let depModuleId = "./" + path.posix.relative(baseDir, depModulePath);
node.arguments = [types.stringLiteral(depModuleId)]; // ./title => ./src/title.js

module.dependencies.push({ depModuleId, depModulePath });
}
}
});

let { code } = generator(ast);

module._source = code;


module.dependencies.forEach(({depModuleId, depModulePath}) => {
let existModule = this.modules.find(module => module.id === depModuleId);
if (existModule) {
existModule.names.push(name);
} else {
let depModule = this.buildModule(name, depModulePath);
this.modules.push(depModule)
};
});
}
}

function getSource(chunk) {
return `
(() => {
var modules = {
${chunk.modules.map(
(module) => `
"${module.id}": (module) => {
${module._source}
},
`
)}
};
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}
})();
`;
}

module.exports = Compilcation;