Как эти старшие/старшие фронтенды отвечают на вопросы интервью по JavaScript (4)

внешний интерфейс JavaScript

предисловие

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 из них.

  1. compiler = webpack(options);
  2. 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);
				});
			});
		});
	}

Разобрать:

  1. определитьfinalCallback, метод сработаетthis.hooks.failedкрюк. В этот момент метод не вызывается.

  2. определяетonCompiled, ожидая последующих вызовов в методе компиляции. Означает, что логика должна произойти после компиляции модуля.

    • this.hooks.shouldEmitХук используется для определения компиляции текущего модуля (т.е.compilation) не завершена. Если завершено, выполните finalCallback напрямую.
    • Далее выполнитеthis.emitAssetsметод (который в конечном итоге будет обработан здесьchunkЗапись в указанный файл и вывод вdist)
    • вызыватьcompilation.hooks.needAdditionalPassХук означает, что здесь нужно выполнить дополнительные условия, если нет, вернуть undefined, и программа завершается.
      • Будуcompilation.needAdditionalPassустановлен вtrue
      • Передать некоторые значения из объекта компиляции в статистику
      • Активируйте хук this.hooks.done и выполните finalCallback.
  3. вызыватьthis.hooks.beforeRunХук, если исключения нет, то он сработает асинхронноthis.hooks.runкрюк.

  4. передача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;
  }
  1. метод компиляции
    • позвонив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

Разобрать:

  1. new WebpackOptionsApply().process(options, compiler)Смонтировать все встроенные плагины webpack (запись)
  2. идти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, что означает завершение всех приготовлений перед компиляцией модуля.

  1. addEntry -> this._addModuleChain -> this.createModule
  2. Наконец,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

(Я желаю тебе всего самого лучшего)