Демистификация рабочего процесса и принципов плагинов веб-пакетов

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

webpack series 1: краткий анализ исходного кода распространенного загрузчика и практическая реализация md2html-загрузчика

Webpack Series 2: демистификация работы плагинов webpack

Webpack, серия 3: чтение исходного кода основного процесса webpack и реализация webpack

предисловие

С помощью плагинов мы можем расширитьwebpack, проходя в нужное времяWebpackПредоставленный API изменяет вывод так, чтоwebpackМожет выполнять более широкий спектр задач и иметь более сильные возможности сборки. В этой статье мы попытаемся изучитьwebpackРабочий процесс плагина, а затем демистификация того, как он работает. Также нужно, чтобы выwebpackИ строить что-то лежащие в основе процессов есть определенное понимание.

Чтобы понять механизм плагинов webpack, вам необходимо понять следующие моменты:

  1. Состав простого плагина
  2. webpackпроцесс сборки
  3. Tapableкак соединить плагины между собой
  4. compilerтак же какcompilationИспользование объектов и соответствующих им обработчиков событий.

Базовая структура плагина

pluginsможет использовать свой собственный метод-прототипapplyобъект для создания экземпляра.applyтолько после установки плагинаWebpack compilerВыполнить один раз.applyметод проходит черезwebpck compilerссылка для доступа к обратным вызовам компилятора.

Простая структура плагина:

class HelloPlugin{
  // 在构造函数中获取用户给该插件传入的配置
  constructor(options){
  }
  // Webpack 会调用 HelloPlugin 实例的 apply 方法给插件实例传入 compiler 对象
  apply(compiler) {
    // 在emit阶段插入钩子函数,用于特定时机处理额外的逻辑;
    compiler.hooks.emit.tap('HelloPlugin', (compilation) => {
      // 在功能流程完成后可以调用 webpack 提供的回调函数;
    });
    // 如果事件是异步的,会带两个参数,第二个参数为回调函数,在插件处理完任务时需要调用回调函数通知webpack,才会进入下一个处理流程。
    compiler.plugin('emit',function(compilation, callback) {
      // 支持处理逻辑
      // 处理完毕后执行 callback 以通知 Webpack 
      // 如果不执行 callback,运行流程将会一直卡在这不往下执行 
      callback();
    });
  }
}

module.exports = HelloPlugin;

При установке плагина вам нужно всего лишь поместить его экземпляр вWebpack config pluginsВнутри массива:

const HelloPlugin = require('./hello-plugin.js');
var webpackConfig = {
  plugins: [
    new HelloPlugin({options: true})
  ]
};

Давайте сначала проанализируем принцип работы плагина webpack.

  1. Сначала будет выполнен процесс чтения конфигурацииnew HelloPlugin(options)инициализироватьHelloPluginполучить его экземпляр.
  2. инициализацияcompilerвызывается после объектаHelloPlugin.apply(compiler)Передайте экземпляр плагинаcompilerобъект.
  3. Экземпляр плагина получаетcompilerобъект, вы можете пройтиcompiler.plugin(事件名称, 回调函数)Слушайте события, транслируемые Webpack. и может пройтиcompilerобъект для работыWebpack.

процесс сборки вебпака

Прежде чем писать плагин, нужно знать еще несколько вещей.Webpackпроцесс сборки, чтобы вставить правильную логику плагина в нужное время.

Основной процесс сборки Webpack выглядит следующим образом:

  1. Проверьте файл конфигурации: прочитайте ввод командной строки илиwebpack.config.jsфайл для инициализации параметров конфигурации для этой сборки
  2. генерироватьCompilerОбъект: выполняет оператор создания экземпляра плагина в файле конфигурации.new MyWebpackPlugin(),дляwebpackПоток событий зависает на пользовательскомhooks
  3. ВойтиentryOptionсцена:webpackначать чтение конфигурацииEntries, рекурсивно просмотреть все входные файлы
  4. run/watch: если работаетwatchрежим выполненwatchметод, иначе выполнитьrunметод
  5. compilation:СоздайтеCompilationобратный вызов объектаcompilationСвязанные хуки, вводите каждый входной файл по очереди (entry), используйте загрузчик для компиляции файла. пройти черезcompilationя могу читатьmoduleизresource(путь ресурса),loaders(загрузчик используется) и т. д. Затем используйте содержимое скомпилированного файлаacornАнализ для создания статического синтаксического дерева AST. Затем выполните этот процесс рекурсивно и многократно, После того, как все модули и зависимости проанализированы, выполнитеcompilationизsealМетод организует, оптимизирует и инкапсулирует каждый фрагмент__webpack_require__для имитации модульных операций.
  6. emit: Выполнена компиляция и конвертация всех файлов, включая финальные выходные ресурсы, мы можем отзвонить событие во входящем событииcompilation.assetsПолучить необходимые данные, в том числе информацию о ресурсах для вывода, блоках кода Chunk и т.д.
// 修改或添加资源
compilation.assets['new-file.js'] = {
  source() {
    return 'var a=1';
  },
  size() {
    return this.source().length;
  }
};
  1. afterEmit: файл был полностью записан на диск
  2. done: полная компиляция

Предложите часть блога Didi CloudWebPackСкомпилируйте блок-схему, если вам не нравится читать текстовое объяснение, вы можете прочитать блок-схему, чтобы понять память

Блок-схема компиляции WebPackИсходное изображение из:blog.Brother Yoon.com/index.PHP/2…

После прочтения, если вы все еще не можете понять или понять процесс создания веб-пакета, рекомендуется прочитать полный текст, а затем вернуться к прочтению этого отрывка, я думаю, у вас будет более глубокое понимание процесса создания веб-пакета.

Понимание механизма потока событий Tapable

webpackПо сути, это механизм потока событий, и его рабочий процесс заключается в последовательном подключении различных подключаемых модулей, и ядром всего этого является Tapable.

WebpackизTapableМеханизм потока событий обеспечивает упорядоченность плагинов, подключая каждый плагин последовательно. Webpack будет транслировать события во время запущенного процесса. Плагину нужно только прослушивать события, которые его интересуют, а затем его можно добавлен к этому механизму webapck для изменения webapck.Операция делает всю систему хорошо расширяемой.

TapableТакже небольшая библиотека, даWebpackосновной инструмент. похожий наnodeсерединаeventsОсновным принципом библиотеки является модель публикации по подписке. Роль заключается в предоставлении аналогичного интерфейса подключаемого модуля.

Ядро webpack отвечает за компиляциюCompilerи отвечает за создание пакетовCompilationявляются экземплярами Tapable, которые можно использовать непосредственно вCompilerа такжеCompilationПередайте и прослушайте события на объекте, методы следующие:

/**
* 广播事件
* event-name 为事件名称,注意不要和现有的事件重名
*/
compiler.apply('event-name',params);
compilation.apply('event-name',params);
/**
* 监听事件
*/
compiler.plugin('event-name',function(params){});
compilation.plugin('event-name', function(params){});

Tapableкласс выставленtap,tapAsyncа такжеtapPromiseметод, вы можете выбрать логику внедрения функции в соответствии с синхронным/асинхронным режимом хука.

tapСинхронизирующий хук

compiler.hooks.compile.tap('MyPlugin', params => {
  console.log('以同步方式触及 compile 钩子。')
})

tapAsyncАсинхронные хуки черезcallbackобратный вызов сообщаетWebpackАсинхронное выполнение завершеноtapPromiseАсинхронный хук, возвращаетPromiseРассказыватьWebpackАсинхронное выполнение завершено

compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => {
  console.log('以异步方式触及 run 钩子。')
  callback()
})

compiler.hooks.run.tapPromise('MyPlugin', compiler => {
  return new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
    console.log('以具有延迟的异步方式触及 run 钩子')
  })
})

Использование Tapable

const {
 SyncHook,
 SyncBailHook,
 SyncWaterfallHook,
 SyncLoopHook,
 AsyncParallelHook,
 AsyncParallelBailHook,
 AsyncSeriesHook,
 AsyncSeriesBailHook,
 AsyncSeriesWaterfallHook
 } = require("tapable");
tapable
tapable

Простая реализация SyncHook

class Hook{
    constructor(args){
        this.taps = []
        this.interceptors = [] // 这个放在后面用
        this._args = args 
    }
    tap(name,fn){
        this.taps.push({name,fn})
    }
}
class SyncHook extends Hook{
    call(name,fn){
        try {
            this.taps.forEach(tap => tap.fn(name))
            fn(null,name)
        } catch (error) {
            fn(error)
        }

    }
}

tapableкак будетwebapck/webpackСвязано с плагином?

Compiler.js

const { AsyncSeriesHook ,SyncHook } = require("tapable");
//创建类
class Compiler {
    constructor() {
        this.hooks = {
           run: new AsyncSeriesHook(["compiler"]), //异步钩子
           compile: new SyncHook(["params"]),//同步钩子
        };
    },
    run(){
      //执行异步钩子
      this.hooks.run.callAsync(this, err => {
         this.compile(onCompiled);
      });
    },
    compile(){
      //执行同步钩子 并传参
      this.hooks.compile.call(params);
    }
}
module.exports = Compiler

MyPlugin.js

const Compiler = require('./Compiler')

class MyPlugin{
    apply(compiler){//接受 compiler参数
        compiler.hooks.run.tap("MyPlugin", () => console.log('开始编译...'));
        compiler.hooks.complier.tapAsync('MyPlugin', (name, age) => {
          setTimeout(() => {
            console.log('编译中...')
          }, 1000)
        });
    }
}

//这里类似于webpack.config.js的plugins配置
//向 plugins 属性传入 new 实例

const myPlugin = new MyPlugin();

const options = {
    plugins: [myPlugin]
}
let compiler = new Compiler(options)
compiler.run()

хочу узнать большеtapableВ статье можно посмотреть эту статью:

webpack4основной модульtapableАнализ исходного кода: https://www.cnblogs.com/tugenhua0707/p/11317557.html

Понимание компилятора (отвечает за компиляцию)

Первое, что нужно знать при разработке плагинаcompilerа такжеcompilationчто делает объект

Compilerобъект содержит текущий запущенныйWebpackконфигурация, в том числеentry、output、loadersДождитесь настройки, этот объект запускаетсяWebpackсоздается и глобально уникален.PluginИнформация о конфигурации Webpack может быть получена через этот объект для обработки.

Если вы читаете это, вы все еще не понимаетеcompilerЧто он делает, не бойтесь, продолжайте смотреть. бегатьnpm run build,ПучокcompilerВся информация выводится на консольconsole.log(Compiler).

compiler
compiler
// 为了能更直观的让大家看清楚compiler的结构,里面的大量代码使用省略号(...)代替。
Compiler {
  _pluginCompat: SyncBailHook {
    ...
  },
  hooks: {
    shouldEmit: SyncBailHook {
     ...
    },
    done: AsyncSeriesHook {
     ...
    },
    additionalPass: AsyncSeriesHook {
     ...
    },
    beforeRun: AsyncSeriesHook {
     ...
    },
    run: AsyncSeriesHook {
     ...
    },
    emit: AsyncSeriesHook {
     ...
    },
    assetEmitted: AsyncSeriesHook {
     ...
    },
    afterEmit: AsyncSeriesHook {
     ...
    },
    thisCompilation: SyncHook {
     ...
    },
    compilation: SyncHook {
     ...
    },
    normalModuleFactory: SyncHook {
     ...
    },
    contextModuleFactory: SyncHook {
     ...
    },
    beforeCompile: AsyncSeriesHook {
      ...
    },
    compile: SyncHook {
     ...
    },
    make: AsyncParallelHook {
     ...
    },
    afterCompile: AsyncSeriesHook {
     ...
    },
    watchRun: AsyncSeriesHook {
     ...
    },
    failed: SyncHook {
     ...
    },
    invalid: SyncHook {
     ...
    },
    watchClose: SyncHook {
     ...
    },
    infrastructureLog: SyncBailHook {
     ...
    },
    environment: SyncHook {
     ...
    },
    afterEnvironment: SyncHook {
     ...
    },
    afterPlugins: SyncHook {
     ...
    },
    afterResolvers: SyncHook {
     ...
    },
    entryOption: SyncBailHook {
     ...
    },
    infrastructurelog: SyncBailHook {
     ...
    }
  },
  ...
  outputPath: '',//输出目录
  outputFileSystem: NodeOutputFileSystem {
   ...
  },
  inputFileSystem: CachedInputFileSystem {
    ...
  },
  ...
  options: {
    //Compiler对象包含了webpack的所有配置信息,entry、module、output、resolve等信息
    entry: [
      'babel-polyfill',
      '/Users/frank/Desktop/fe/fe-blog/webpack-plugin/src/index.js'
    ],
    devServer: { port: 3000 },
    output: {
      ...
    },
    module: {
      ...
    },
    plugins: [ MyWebpackPlugin {} ],
    mode: 'production',
    context: '/Users/frank/Desktop/fe/fe-blog/webpack-plugin',
    devtool: false,
    ...
    performance: {
      maxAssetSize: 250000,
      maxEntrypointSize: 250000,
      hints: 'warning'
    },
    optimization: {
      ... 
    },
    resolve: {
      ...
    },
    resolveLoader: {
      ...
    },
    infrastructureLogging: { level: 'info', debug: false }
  },
  context: '/Users/frank/Desktop/fe/fe-blog/webpack-plugin',//上下文,文件目录
  requestShortener: RequestShortener {
    ...
  },
  ...
  watchFileSystem: NodeWatchFileSystem {
    //监听文件变化列表信息
     ...
  }
}

Исходный код компилятора упрощает анализ кода версии

Адрес исходного кода (строка 948): https://github.com/webpack/webpack/blob/master/lib/Compiler.js.

const { SyncHook, SyncBailHook, AsyncSeriesHook } = require("tapable");
class Compiler {
  constructor() {
    // 1. 定义生命周期钩子
    this.hooks = Object.freeze({
      // ...只列举几个常用的常见钩子,更多hook就不列举了,有兴趣看源码
      done: new AsyncSeriesHook(["stats"]),//一次编译完成后执行,回调参数:stats
      beforeRun: new AsyncSeriesHook(["compiler"]),
      run: new AsyncSeriesHook(["compiler"]),//在编译器开始读取记录前执行
      emit: new AsyncSeriesHook(["compilation"]),//在生成文件到output目录之前执行,回调参数: compilation
      afterEmit: new AsyncSeriesHook(["compilation"]),//在生成文件到output目录之后执行
      compilation: new SyncHook(["compilation", "params"]),//在一次compilation创建后执行插件
      beforeCompile: new AsyncSeriesHook(["params"]),
      compile: new SyncHook(["params"]),//在一个新的compilation创建之前执行
      make:new AsyncParallelHook(["compilation"]),//完成一次编译之前执行
      afterCompile: new AsyncSeriesHook(["compilation"]),
      watchRun: new AsyncSeriesHook(["compiler"]),
      failed: new SyncHook(["error"]),
      watchClose: new SyncHook([]),
      afterPlugins: new SyncHook(["compiler"]),
      entryOption: new SyncBailHook(["context", "entry"])
    });
    // ...省略代码
  }
  newCompilation() {
    // 创建Compilation对象回调compilation相关钩子
    const compilation = new Compilation(this);
    //...一系列操作
    this.hooks.compilation.call(compilation, params); //compilation对象创建完成 
    return compilation
  }
  watch() {
    //如果运行在watch模式则执行watch方法,否则执行run方法
    if (this.running) {
      return handler(new ConcurrentCompilationError());
    }
    this.running = true;
    this.watchMode = true;
    return new Watching(this, watchOptions, handler);
  }
  run(callback) {
    if (this.running) {
      return callback(new ConcurrentCompilationError());
    }
    this.running = true;
    process.nextTick(() => {
      this.emitAssets(compilation, err => {
        if (err) {
          // 在编译和输出的流程中遇到异常时,会触发 failed 事件 
          this.hooks.failed.call(err)
        };
        if (compilation.hooks.needAdditionalPass.call()) {
          // ...
          // done:完成编译
          this.hooks.done.callAsync(stats, err => {
            // 创建compilation对象之前   
            this.compile(onCompiled);
          });
        }
        this.emitRecords(err => {
          this.hooks.done.callAsync(stats, err => {

          });
        });
      });
    });

    this.hooks.beforeRun.callAsync(this, err => {
      this.hooks.run.callAsync(this, err => {
        this.readRecords(err => {
          this.compile(onCompiled);
        });
      });
    });

  }
  compile(callback) {
    const params = this.newCompilationParams();
    this.hooks.beforeCompile.callAsync(params, err => {
      this.hooks.compile.call(params);
      const compilation = this.newCompilation(params);
      //触发make事件并调用addEntry,找到入口js,进行下一步
      this.hooks.make.callAsync(compilation, err => {
        process.nextTick(() => {
          compilation.finish(err => {
            // 封装构建结果(seal),逐次对每个module和chunk进行整理,每个chunk对应一个入口文件
            compilation.seal(err => {
              this.hooks.afterCompile.callAsync(compilation, err => {
                // 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,
                // 不然运行流程将会一直卡在这不往下执行
                return callback(null, compilation);
              });
            });
          });
        });
      });
    });
  }
  emitAssets(compilation, callback) {
    const emitFiles = (err) => {
      //...省略一系列代码
      // afterEmit:文件已经写入磁盘完成
      this.hooks.afterEmit.callAsync(compilation, err => {
        if (err) return callback(err);
        return callback();
      });
    }

    // emit 事件发生时,可以读取到最终输出的资源、代码块、模块及其依赖,并进行修改(这是最后一次修改最终文件的机会)
    this.hooks.emit.callAsync(compilation, err => {
      if (err) return callback(err);
      outputPath = compilation.getPath(this.outputPath, {});
      mkdirp(this.outputFileSystem, outputPath, emitFiles);
    });
  }
  // ...省略代码
}

applyОбщая форма вставки хука в метод выглядит следующим образом:

// compiler提供了compiler.hooks,可以根据这些不同的时刻去让插件做不同的事情。
compiler.hooks.阶段.tap函数('插件名称', (阶段回调参数) => {

});
compiler.run(callback)

Понимание компиляции (отвечает за создание пакетов)

CompilationОбъект представляет сборку версии ресурса. при бегеwebpackПри разработке ПО промежуточного слоя всякий раз, когда обнаруживается изменение файла, создается новыйcompilation, что приводит к новому набору скомпилированных ресурсов. ОдинCompilationОбъект представляет текущие ресурсы модуля, скомпилированные и сгенерированные ресурсы, измененные файлы и информацию о состоянии отслеживаемых зависимостей.CompilationОбъект также предоставляет обратные вызовы для подключаемых модулей, которым требуются пользовательские функции, чтобы подключаемые модули могли использовать расширения при выполнении пользовательской обработки.

Проще говоря,CompilationОтветственность за создание модулей и чанков, а также за использование плагинов для оптимизации процесса сборки.

а такжеCompilerТо же использование, разные типы хуков, также доступные на некоторых хукахtapAsyncа такжеtapPromise。

консольный выводconsole.log(compilation) compilation

пройти черезCompilationтакже может читатьCompilerобъект.

Там более 2000 строк исходного кода, я больше не могу его читать -- -- Если вам интересно, можете сами глянуть. https://github.com/webpack/webpack/blob/master/lib/Compilation.js

Познакомить с несколькими часто используемыми хуками компиляции.

крюк Типы когда звонить
buildModule SyncHook Запускается перед началом компиляции модуля, может использоваться для модификации модуля
succeedModule SyncHook Этот хук выполняется, когда модуль успешно скомпилирован
finishModules AsyncSeriesHook Вызывается, когда все модули успешно скомпилированы
seal SyncHook когда однаждыcompilationЗапускается, когда вы перестаете получать новые модули
optimizeDependencies SyncBailHook Выполнить в начале оптимизации зависимостей
optimize SyncHook Выполняется в начале фазы оптимизации
optimizeModules SyncBailHook Выполняется в начале фазы оптимизации модуля, плагин может выполнять оптимизацию модуля в этом хуке, параметры обратного вызова:modules
optimizeChunks SyncBailHook Выполняется в начале фазы оптимизации блока кода, плагин может выполнять оптимизацию блока кода в этом хуке, параметры обратного вызова:chunks
optimizeChunkAssets AsyncSeriesHook Оптимизируйте любые ресурсы блока кода, которые хранятся вcompilation.assetsначальство. У чанка есть свойство files, которое указывает на все файлы, созданные чанком. Любые дополнительные ресурсы фрагмента хранятся вcompilation.additionalChunkAssetsначальство. Параметры обратного вызова:chunks
optimizeAssets AsyncSeriesHook Оптимизировать все, что хранится вcompilation.assetsвсех ресурсов. Параметры обратного вызова:assets

Разница между компилятором и компиляцией

Compilerпредставляет весьWebpackжизненный цикл от запуска до закрытия, в то время какCompilationОн просто представляет собой новую компиляцию, пока файл изменяется,compilationбудет воссоздан.

Общий API

Плагины можно использовать для изменения выходных файлов, добавления выходных файлов и даже улучшенияWebpackпроизводительность и т. д. Короче говоря, плагин вызываетWebpackкоторый предоставилAPIМожет добиться многого. из-заWebpackкоторый предоставилAPIочень много, есть многоAPIРедко используемые, плюс ограниченное пространство, вот некоторые часто используемые API.

Чтение выходных ресурсов, блоков кода, модулей и их зависимостей

Некоторые плагины, возможно, потребуется прочитатьWebpack, такие как вывод ресурсов, блоков кода, модулей и их зависимостей для дальнейшей обработки. Когда происходит событие emit, преобразование и сборка исходного файла завершены.Здесь вы можете прочитать окончательный выходной ресурс, блок кода, модуль и его зависимости, а также изменить содержимое выходного ресурса. Код плагина следующий:

class Plugin {
  apply(compiler) {
    compiler.plugin('emit', function (compilation, callback) {
      // compilation.chunks 存放所有代码块,是一个数组
      compilation.chunks.forEach(function (chunk) {
        // chunk 代表一个代码块
        // 代码块由多个模块组成,通过 chunk.forEachModule 能读取组成代码块的每个模块
        chunk.forEachModule(function (module) {
          // module 代表一个模块
          // module.fileDependencies 存放当前模块的所有依赖的文件路径,是一个数组
          module.fileDependencies.forEach(function (filepath) {
          });
        });

        // Webpack 会根据 Chunk 去生成输出的文件资源,每个 Chunk 都对应一个及其以上的输出文件
        // 例如在 Chunk 中包含了 CSS 模块并且使用了 ExtractTextPlugin 时,
        // 该 Chunk 就会生成 .js 和 .css 两个文件
        chunk.files.forEach(function (filename) {
          // compilation.assets 存放当前所有即将输出的资源
          // 调用一个输出资源的 source() 方法能获取到输出资源的内容
          let source = compilation.assets[filename].source();
        });
      });

      // 这是一个异步事件,要记得调用 callback 通知 Webpack 本次事件监听处理结束。
      // 如果忘记了调用 callback,Webpack 将一直卡在这里而不会往后执行。
      callback();
    })
  }
}

Отслеживание изменений файлов

WebpackНачиная с настроенного модуля входа, он по очереди находит все зависимые модули, при изменении модуля входа или зависимых от него модулей срабатывает новый.Compilation.

При разработке плагинов часто необходимо знать, какой файл был изменен, что привело к появлению новогоCompilation, вы можете использовать для этого следующий код:

// 当依赖的文件发生变化时会触发 watch-run 事件
compiler.hooks.watchRun.tap('MyPlugin', (watching, callback) => {
  // 获取发生变化的文件列表
  const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes;
  // changedFiles 格式为键值对,键为发生变化的文件路径。
  if (changedFiles[filePath] !== undefined) {
    // filePath 对应的文件发生了变化
  }
  callback();
});

по умолчаниюWebpackНа наличие изменений отслеживаются только запись и зависимые от нее модули.В некоторых случаях в проекте может потребоваться введение новых файлов, например введениеHTMLдокумент. из-заJavaScriptфайл не будет импортированHTMLдокумент,Webpackне будет контролироватьHTMLизменения файлов, редактированиеHTMLфайл не будет перезапускать новыйCompilation. контролироватьHTMLизменения файла, нам нужно поставитьHTMLФайл добавляется в список зависимостей, для чего можно использовать следующий код:

compiler.hooks.afterCompile.tap('MyPlugin', (compilation, callback) => {
  // 把 HTML 文件添加到文件依赖列表,好让 Webpack 去监听 HTML 模块文件,在 HTML 模版文件发生变化时重新启动一次编译
  compilation.fileDependencies.push(filePath);
  callback();
});

Изменить выходной ресурс

В некоторых сценариях подключаемым модулям необходимо изменять, добавлять и удалять выходные ресурсы.Для этого необходимо отслеживатьemitсобытие, потому чтоemitКогда происходит событие, файлы, соответствующие преобразованию всех модулей и блоков кода, уже сгенерированы, и ресурсы для вывода вот-вот будут выведены, поэтому событие эммита — это последний раз, когда нужно изменить выходные ресурсы Webpack.

Все ресурсы, которые необходимо вывести, будут храниться вcompilation.assetsсередина,compilation.assetsЭто пара ключ-значение, ключ — это имя файла для вывода, а значение — это содержимое, соответствующее файлу.

настраиватьcompilation.assetsКод выглядит следующим образом:

// 设置名称为 fileName 的输出资源
  compilation.assets[fileName] = {
    // 返回文件内容
    source: () => {
      // fileContent 既可以是代表文本文件的字符串,也可以是代表二进制文件的 Buffer
      return fileContent;
      },
    // 返回文件大小
      size: () => {
      return Buffer.byteLength(fileContent, 'utf8');
    }
  };
  callback();

Определите, какие плагины использует webpack

// 判断当前配置使用使用了 ExtractTextPlugin,
// compiler 参数即为 Webpack 在 apply(compiler) 中传入的参数
function hasExtractTextPlugin(compiler) {
  // 当前配置所有使用的插件列表
  const plugins = compiler.options.plugins;
  // 去 plugins 中寻找有没有 ExtractTextPlugin 的实例
  return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null;
}

Вышеупомянутые 4 метода взяты из статьи: [Обучающий плагин Webpack]: http://wushaobin.top/2019/03/15/webpackPlugin/

Управление предупреждениями и ошибками

проведите эксперимент, если выapplyВставить в функциюthrow new Error("Message"), что происходит, терминал печатаетUnhandled rejection Error: Message. Затем выполнение веб-пакета прерывается. Чтобы не повлиятьwebpackРеализация, чтобы выдать пользователю предупреждение или сообщение об ошибке во время компиляции, вы должны использовать компиляцию.предупреждения и компиляция.ошибки.

compilation.warnings.push("warning");
compilation.errors.push("error");

Отображение демо-кода кейса в статье

GitHub.com/6Fed com/share-…

Как отладить процесс упаковки веб-пакета или код плагина?

  1. В текущей папке проекта проекта webpack выполните командную строку:
node --inspect-brk ./node_modules/webpack/bin/webpack.js --inline --progress

Параметр --inspect-brk запускает узел в режиме отладки:

Терминал выдаст:

Debugger listening on ws://127.0.0.1:9229/1018c03f-7473-4d60-b62c-949a6404c81d
For help, see: https://nodejs.org/en/docs/inspector
  1. Google Chrome типа chrome://inspect/#devices
点击inspect
нажмите проверить
  1. Затем нажмите «Продолжить» в отладчике Chrome, и точка останова останется в точке останова отладчика, которую мы установили в плагине.
debugger
debugger