предисловие
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.afterCompile
Hook , что означает завершение компиляции. - Обратный вызов окончательного возврата
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.environment
compiler.hooks.afterEnvironment
compiler.hooks.failed
compiler.hooks.shouldEmit
compilation.hooks.needAdditionalPass
compiler.hooks.beforeRun
compiler.hooks.run
compiler.hooks.beforeCompile
compiler.hooks.compile
compiler.hooks.thisCompilation
compiler.hooks.compilation
compiler.hooks.make
compiler.hooks.afterCompile
compiler.hooks.entryOption
compilation.hooks.seal
compilation.hooks.beforeChunks
compilation.hooks.afterChunks
compilation.createChunkAssets
(Я желаю тебе всего самого лучшего)