предисловие
webpackЭто богатая тема. Возьмите каштан.
-
webpackизloaderа такжеpluginКакая разница? - написал
webpackизloaderилиplugin? -
webpackДля чего нужен процесс компиляции?
Тогда можно начинать бла-бла-бла-бла-бла-бла-бла-бла. С вами все в порядке, но время интервью ограничено, а в интернете так много ответов. Таким образом, такие вопросы, похоже, не являются первым выбором интервьюера.
Ну вы сталкивались или видели такую проблему.
В:webpackв процессе компиляцииhookЧто такое узлы?
В предыдущей статье я упомянул процесс компиляции webpack, и текстовое описание было очень подробным. Нажмите для конкретныхКак эти старшие/старшие фронтенды отвечают на вопросы интервью по JavaScript (2).
Начало вебпака
Давайте сначала посмотрим на webpack.config.js
const path = require('path')
module.exports = {
devtool: 'none',
mode: 'development',
context: process.cwd(),
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.resolve('dist')
},
module:{
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage"
}
],
"@babel/preset-react"
]
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
Подумайте, а зачем вам вообще писать webpack.config.js в проекте?
- имеют
entry(Вход) - имеют
loader - имеют
plugin - имеют
output
в конечном счетеmodule.exportsиз{}. Тогда вот вылитая спецификация commonJS{}Для кого он используется?
мы видимnode_modules/webpack-cli/bin/cli.jsчасть исходного кода
// cli.js …… 部分
const webpack = require("webpack");
let lastHash = null;
let compiler;
try {
compiler = webpack(options); // 1
} catch (err) {
if (err.name === "WebpackOptionsValidationError") {
if (argv.color) console.error(`\u001b[1m\u001b[31m${err.message}\u001b[39m\u001b[22m`);
else console.error(err.message);
// eslint-disable-next-line no-process-exit
process.exit(1);
}
throw err;
}
// ...
// ...
// ...
if (firstOptions.watch || options.watch) { // watch模式
const watchOptions =
irstOptions.watchOptions || options.watchOptions ||
firstOptions.watch || options.watch || {};
if (watchOptions.stdin) {
process.stdin.on("end", function(_) {
process.exit(); // eslint-disable-line
});
process.stdin.resume();
}
compiler.watch(watchOptions, compilerCallback);
if (outputOptions.infoVerbosity !== "none") console.error("\nwebpack is watching the files…\n");
} else {
// run 方法,
compiler.run((err, stats) => { // 2
if (compiler.close) {
ompiler.close(err2 => {
compilerCallback(err || err2, stats);
});
} else {
compilerCallback(err, stats);
}
});
}
// ...
См. 1 и 2 из них.
compiler = webpack(options);compiler.run
Итак, упомянутыйmodule.exportsиз{}, который находится в точке 1options. этоoptions= в вашем webpack.config.jsmodule.exports+ встроенная конфигурация по умолчанию для webpack. Ключевого момента, о котором я хочу рассказать, здесь нет, если у вас есть время, попросите своих друзей узнать о нем больше.
Глядя вниз, естьwebpack(options) , затем посмотрите наnode_modules/lib/webpack.js
/**
* @param {WebpackOptions} options options object
* @param {function(Error=, Stats=): void=} callback callback
* @returns {Compiler | MultiCompiler} the compiler object
*/
const webpack = (options, callback) => {
// code ...
if (Array.isArray(options)) {
// 加入 你 module.exports = [], 就进入到这。
// code ...
} else if (typeof options === "object") {
options = new WebpackOptionsDefaulter().process(options); // 获取webpack默认的内置好的配置信息
compiler = new Compiler(options.context);
compiler.options = options;
// NodeEnvironmentPlugin 赋予compiler文件读写的能力
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
compiler.hooks.environment.call(); // 遇见的第1个钩子
compiler.hooks.afterEnvironment.call(); // 遇见的第2 个钩子
// 下面这行代码很重要,后面会重点提及
compiler.options = new WebpackOptionsApply().process(options, compiler);
} else {
throw new Error("Invalid argument: options");
}
if (callback) {
// code ...
}
return compiler;
};
первый крючок, который я встретил
-
compiler.hooks.environment// Обнаружен 1-й хук -
compiler.hooks.afterEnvironment// Обнаружен второй хук
Вы хотите спросить, для чего эти два крючка?
Затем мы можем напрямую проследить исходный код до конкретного местоположения этих двух хуков:node_modules/lib/Compiler.js
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<string, Buffer>} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<NormalModuleFactory>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<ContextModuleFactory>} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<CompilationParams>} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<Compilation>} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook<string, string, any[]>} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
environment: new SyncHook([]),
/** @type {SyncHook} */
afterEnvironment: new SyncHook([]),
/** @type {SyncHook<Compiler>} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<string, Entry>} */
entryOption: new SyncBailHook(["context", "entry"])
};
// code ...
}
// code ...
}
Посмотрите на последние несколько строк в this.hooks:the following hooks are weirdly located here. move them for webpack 5. Это значит, что эти хуки немного странные, webpack5 их удалит. Поскольку webpack резервирует некоторые хуки, цель состоит в том, чтобы активировать эти хуки в разное время этапа. И эти два крючка на самом деле играют лишь роль сценических подсказок, и ничего особенного не делают.
Продолжай читатьcompiler.run метод.
// code...
run(callback) {
if (this.running) return callback(new ConcurrentCompilationError());
const finalCallback = (err, stats) => {
this.running = false;
if (err) {
this.hooks.failed.call(err);
}
if (callback !== undefined) return callback(err, stats);
};
const startTime = Date.now();
this.running = true;
const onCompiled = (err, compilation) => {
if (err) return finalCallback(err);
if (this.hooks.shouldEmit.call(compilation) === false) {
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
return;
}
this.emitAssets(compilation, err => {
if (err) return finalCallback(err);
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true;
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
this.hooks.additionalPass.callAsync(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
return;
}
this.emitRecords(err => {
if (err) return finalCallback(err);
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
});
});
};
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
}
Разобрать:
-
определить
finalCallback, метод сработаетthis.hooks.failedкрюк. В этот момент метод не вызывается. -
определяет
onCompiled, ожидая последующих вызовов в методе компиляции. Означает, что логика должна произойти после компиляции модуля.-
this.hooks.shouldEmitХук используется для определения компиляции текущего модуля (т.е.compilation) не завершена. Если завершено, выполните finalCallback напрямую. - Далее выполните
this.emitAssetsметод (который в конечном итоге будет обработан здесьchunkЗапись в указанный файл и вывод вdist) - вызывать
compilation.hooks.needAdditionalPassХук означает, что здесь нужно выполнить дополнительные условия, если нет, вернуть undefined, и программа завершается.- Буду
compilation.needAdditionalPassустановлен вtrue - Передать некоторые значения из объекта компиляции в статистику
- Активируйте хук this.hooks.done и выполните finalCallback.
- Буду
-
-
вызывать
this.hooks.beforeRunХук, если исключения нет, то он сработает асинхронноthis.hooks.runкрюк. -
передача
this.readRecordsметод (чтение файла), передать прочитанное содержимое вthis.compile, Тогда давай спустимся и посмотримcompileметод
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err => {
if (err) return callback(err);
compilation.finish(err => {
if (err) return callback(err);
compilation.seal(err => {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
}
// code ...
newCompilation(params) {
const compilation = this.createCompilation();
compilation.fileTimestamps = this.fileTimestamps;
compilation.contextTimestamps = this.contextTimestamps;
compilation.name = this.name;
compilation.records = this.records;
compilation.compilationDependencies = params.compilationDependencies;
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
}
- метод компиляции
- позвонив
this.newCompilationParamsМетод получает параметры инициализации для создания экземпляра компиляции. - вызывать
this.hooks.beforeCompileХук, если ненормальный, обратный вызов. - Сразу после срабатывания
this.hooks.compileкрюк - передача
this.newCompilationметод- Создайте экземпляр объекта:
compilation - вызывать
this.hooks.thisCompilationкрюк - вызывать
this.hooks.compilationкрюк - Получите созданный объект:
compilation
- Создайте экземпляр объекта:
- вызывать
this.hooks.makeХук, который возвращает обратный вызов, если возникает исключение. - передача
compilation.finishметод и возвращает обратный вызов, если возникает исключение. - передача
compilation.sealМетод (переработка куска), возврат обратного вызова, если ненормальный. - вызывать
this.hooks.afterCompileHook , что означает завершение компиляции. - Обратный вызов окончательного возврата
this.onCompiled. воплощать в жизньthis.onCompiled, и прошел вcompilation. назадШаг 2
- позвонив
Как насчет хука компиляции? ?
Вопрос: Куда вы пошли? Куда делись эти важные хуки объекта компиляции?
Ответ: при срабатыванииthis.hooks.makeКогда хук вызывается, он будет вызыватьсяcompilation.addEntryметод.
Спросите еще раз: как контролироватьmakeКрюка?
Ответ: Боюсь, вы забыли код в начале. . . .
compiler.options = new WebpackOptionsApply().process(options, compiler); // 忘记的童鞋请全局搜索这行代码
Что такое WebpackOptionsApply?node_modules/lib/WebpackOptionsApply.js
// 简写一下代码,因为真的太多了😂
//WebpackOptionsApply.js
const EntryOptionPlugin = require("./EntryOptionPlugin")
class WebpackOptionsApply {
process(options, compiler) {
new EntryOptionPlugin().apply(compiler)
compiler.hooks.entryOption.call(options.context, options.entry)
}
}
module.exports = WebpackOptionsApply
очевидно, зависимыйEntryOptionPlugin.js
// EntryOptionPlugin.js
const SingleEntryPlugin = require("./SingleEntryPlugin")
const itemToPlugin = function (context, item, name) {
return new SingleEntryPlugin(context, item, name)
}
class EntryOptionPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap('EntryOptionPlugin', (context, entry) => {
itemToPlugin(context, entry, "main").apply(compiler)
})
}
}
module.exports = EntryOptionPlugin
очевидно, зависимыйSingleEntryPlugin.js
// SingleEntryPlugin.js
class SingleEntryPlugin {
constructor(context, entry, name) {
this.context = context
this.entry = entry
this.name = name
}
apply(compiler) {
compiler.hooks.compilation.tap(
"SingleEntryPlugin",
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
SingleEntryDependency,
normalModuleFactory
);
}
);
compiler.hooks.make.tapAsync(
"SingleEntryPlugin",
(compilation, callback) => {
const { entry, name, context } = this;
const dep = SingleEntryPlugin.createDependency(entry, name);
compilation.addEntry(context, dep, name, callback);
}
);
}
}
module.exports = SingleEntryPlugin
Разобрать:
-
new WebpackOptionsApply().process(options, compiler)Смонтировать все встроенные плагины webpack (запись) - идти
WebpackOptionsApply.jsсреди-
processметод называетсяnew EntryOptionPlugin().apply(compiler)- идти
EntryOptionPlugin.jsсреди- вызов
itemToPlugin, при возвратеSingleEntryPluginЭкземпляр объекта , конструктор которого отвечает за получение вышеуказанногоcontext entry name -
entryOptionявляется экземпляром хука,entryOptionсуществуетEntryOptionPluginВнутреннийapplyметод называетсяtap(зарегистрированный прослушиватель событий)
- вызов
- идти
SingleEntryPlugin.js-
compilationпрослушиватель крючка -
makeКрюк слушателя.
-
- идти
- запущенный
compiler.hooks.entryOptionкрюк
-
Стучите по доске! Фокус! После запуска хука make будет выполнен его метод обратного вызова.compilation.addEntry, что означает завершение всех приготовлений перед компиляцией модуля.
-
addEntry->this._addModuleChain->this.createModule - Наконец,
compilerпередачаcompilation.sealметод-
вызывать
compilation.h- ooks.sealкрюк -
вызывать
compilation.hooks.beforeChunksкрюк -
вызывать
compilation.hooks.afterChunksкрюк -
передача
compilation.createChunkAssetsметод и, наконец, вызовите метод this.emitAssets, чтобы вывести файл на путь упаковки.
-
Резюме
В:webpackв процессе компиляцииhookЧто такое узлы?
отвечать:
compiler.hooks.environmentcompiler.hooks.afterEnvironmentcompiler.hooks.failedcompiler.hooks.shouldEmitcompilation.hooks.needAdditionalPasscompiler.hooks.beforeRuncompiler.hooks.runcompiler.hooks.beforeCompilecompiler.hooks.compilecompiler.hooks.thisCompilationcompiler.hooks.compilationcompiler.hooks.makecompiler.hooks.afterCompilecompiler.hooks.entryOptioncompilation.hooks.sealcompilation.hooks.beforeChunkscompilation.hooks.afterChunkscompilation.createChunkAssets
(Я желаю тебе всего самого лучшего)