Цзяо Чуанкай, группа поддержки платформы отдела передовых технологий WeDoctor. Обучение также требует дыхания.
Во-первых, загрузчик
1.1 Что делает загрузчик?
webpack может понимать только файлы JavaScript и JSON, что является стандартной возможностью webpack. **загрузчик** позволяет веб-пакету обрабатывать другие типы файлов и преобразовывать их в действительныемодуль, который будет использоваться приложением и добавлен в граф зависимостей.
Другими словами, веб-пакет рассматривает любой файл как модуль, а загрузчик может импортировать любой тип модуля, но веб-пакет изначально не поддерживает синтаксический анализ, такой как файлы css, поэтому нам нужно использовать наш механизм загрузчика. Наш загрузчик в основном использует два атрибута, чтобы позволить нашему веб-пакету выполнять идентификацию связи:
- testсвойства, чтобы определить, какие файлы будут преобразованы.
- useсвойство, которое определяет, какой загрузчик следует использовать при выполнении преобразования.
Итак, вопрос в том, что вы должны знать, что вам нужно сделать, если вы хотите настроить загрузчик?
1.2 Руководство по разработке
Как говорится,Нет правил, нет стандартовРуководство по использованию(рекомендации), мы должны стандартизировать наш загрузчик в соответствии с этим набором рекомендаций при написании:
- Простой в использовании.
- использоватьцепьпередача. (Поскольку загрузчики могут быть объединены в цепочку, убедитесь, что каждый загрузчик несет одну ответственность)
- модульныйВыход.
- убедисьнет статуса. (Не сохраняйте предыдущее состояние в преобразовании загрузчика, каждый запуск должен быть независимым от других скомпилированных модулей и предыдущих результатов компиляции того же модуля)
- Используйте все возможности официальногоloader utilities.
- Зависимости загрузчика документов.
- Разрешить зависимости модуля.
В зависимости от типа модуля могут быть разные режимы указания зависимостей. Например, в CSS используйте операторы @import и url(...) для объявления зависимостей. Эти зависимости должны быть разрешены системой модулей.
Это можно сделать одним из двух способов:
- Превратив их в операторы require.
- Используйте функцию this.resolve для определения пути.
- Извлечь общий код.
- Избегайте абсолютного пути.
- Используйте одноранговые зависимости. Если ваш загрузчик просто обертывает другой пакет, вы должны импортировать этот пакет как peerDependency.
1.3 Начало работы
Загрузчик представляет собой модуль nodejs, он экспортирует функцию, эта функция имеет только один входной параметр, этот параметр представляет собой файл, содержащий содержимое файла ресурсов.нить, а возвращаемое значение функции — обработанный контент. То есть простейший загрузчик выглядит так:
module.exports = function (content) {
// content 就是传入的源内容字符串
return content
}
Когда используется загрузчик, он может получить только один входной параметр, представляющий собой строку, содержащую содержимое файла ресурсов. Да, пока один из простейших загрузчиков выполнен! Далее давайте посмотрим, как добавить ему богатые функции.
1.4 Четыре вида погрузчиков
Мы можем в основном разделить общие загрузчики на четыре типа:
- синхронизировать загрузчик
- асинхронный загрузчик
- "Raw" Loader
- Pitching loader
① Синхронный загрузчик и асинхронный загрузчик
Общее преобразование загрузчика является синхронным, мы можем вернуть результат нашей обработки, напрямую вернув результат, как указано выше:
module.exports = function (content) {
// 对 content 进行一些处理
const res = dosth(content)
return res
}
также можно использовать напрямуюthis.callback()
этот API, а затем скажите веб-пакету перейти непосредственно к **return undefined ** в концеthis.callback()
В поисках желаемого результата этот API принимает следующие параметры:
this.callback(
err: Error | null, // 一个无法正常编译时的 Error 或者 直接给个 null
content: string | Buffer,// 我们处理后返回的内容 可以是 string 或者 Buffer()
sourceMap?: SourceMap, // 可选 可以是一个被正常解析的 source map
meta?: any // 可选 可以是任何东西,比如一个公用的 AST 语法树
);
Вот пример:Обратите внимание здесь[this.getOptions()](https://webpack.docschina.org/api/loaders/#thisgetoptionsschema)
Может использоваться для получения параметров конфигурации
Начиная с веб-пакета 5, можно получить объект контекста загрузчика this.getOptions. Используется для замены изloader-utilsМетод getOptions.
module.exports = function (content) {
// 获取到用户传给当前 loader 的参数
const options = this.getOptions()
const res = someSyncOperation(content, options)
this.callback(null, res, sourceMaps);
// 注意这里由于使用了 this.callback 直接 return 就行
return
}
Вот такой синхронизированный загрузчик готов!
Давайте поговорим об этомасинхронный:
Разницу между синхронным и асинхронным легко понять.Как правило, наш процесс преобразования является синхронным, но когда мы сталкиваемся с такими сценариями, как сетевые запросы, чтобы избежать блокировки этапов построения, мы будем использовать асинхронную конструкцию.Для асинхронного загрузчика мы в основном нужно использоватьthis.async()
Чтобы сообщить веб-пакету, что эта операция сборки является асинхронной, не так уж и много глупостей, просто взгляните на код, чтобы понять:
module.exports = function (content) {
var callback = this.async()
someAsyncOperation(content, function (err, result) {
if (err) return callback(err)
callback(null, result, sourceMaps, meta)
})
}
② «Необработанный» загрузчик
По умолчанию файл ресурсов преобразуется в строку символов UTF-8, а затем передавал загрузчик. Предоставляя Roar True, погрузчик может получить оригинальный буфер. Каждый погрузчик может быть передан в виде его результата обработки или строкового буфера. Подробнее будет конвертировать между ними погрузчик. Знакомый файловой погрузчик должен использовать это.вкратце: вы добавляетеmodule.exports.raw = true;
То, что передается вам, — это Buffer, и тип, возвращаемый обработкой, не обязательно должен быть Buffer.Webpack не имеет ограничений.
module.exports = function (content) {
console.log(content instanceof Buffer); // true
return doSomeOperation(content)
}
// 划重点↓
module.exports.raw = true;
③ Питающий погрузчик
Каждый наш погрузчик может иметь одинpitch
метода, все мы знаем, что загрузчики вызываются по порядку справа налево, но на самом деле будетВыполнение каждого метода Loader Pitch слева направопроцесс.
Питч-метод имеет три параметра:
-
remainingRequest: Загрузчик и файл ресурсов загрузчика после него в цепочке загрузчикаабсолютный путьк
!
Строка, составленная как конкатенатор. -
precedingRequest: загрузчика, который предшествует самому себе в цепочке загрузчиковабсолютный путьк
!
Строка, составленная как конкатенатор. - data: Фиксированное поле, хранящееся в контексте в каждом загрузчике, которое можно использовать для передачи данных в загрузчик.
Данные, переданные в данные в поле, могут быть сохранены на последующем этапе выполнения вызова.this.data
получен из:
module.exports = function (content) {
return someSyncOperation(content, this.data.value);// 这里的 this.data.value === 42
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
data.value = 42;
};
Уведомление!Если метод pitch загрузчика возвращает значение, он будет напрямую "иду назад", пропустите следующие шаги, вот пример:Допустим, мы сейчас такие:use: ['a-loader', 'b-loader', 'c-loader'],
Тогда нормальная последовательность вызова такова:Теперь PITCH B-Loader изменен, чтобы иметь возвращаемое значение:
// b-loader.js
module.exports = function (content) {
return someSyncOperation(content);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
return "诶,我直接返回,就是玩儿~"
};
Тогда текущий вызов станет таким, прямо "оглянемся назад", остальные три шага пропустим:
1.5 Другие API
- this.addDependency: добавьте файл для мониторинга, как только файл изменится, загрузчик будет вызван снова для обработки
- this.cacheable: По умолчанию результат обработки загрузчика будет иметь эффект кэширования.Передача false этому методу может отключить этот эффект.
- this.clearDependencies: очистить все зависимости загрузчика
- this.context: каталог, в котором находится файл (без имени файла)
- this.data: объект, общий для фазы подачи и фазы обычного вызова.
- this.getOptions(schema): используется для получения параметров конфигурации загрузчика.
- Это. RESOLVELET: «Как требуется разборки выражения в качестве запроса.
resolve(context: string, request: string, callback: function(err, result: string))
- this.loaders: массив всех загрузчиков. Он доступен для записи во время фазы подачи.
- this.resource: Получить текущий путь запроса, включая параметры:
'/abc/resource.js?rrr'
- this.resourcePath: Путь без параметров:
'/abc/resource.js'
- this.sourceMap: логический тип, если будет сгенерирован sourceMap
Официальный также предоставляет много практических API, вот некоторые из часто используемых значений, вы можете щелкнуть ссылку, чтобы узнать больше 👇Подробнее Смотрите официальные ссылки
1.6 сделать простую практику
Реализация функции
Далее просто практикуемся делать два загрузчика, функции соответственно добавляются в скомпилированный код/** 公司@年份 */
Отформатированные комментарии и просто сделайте это, чтобы удалитьconsole.log
, и мы цепляем их:
company-loader.js
module.exports = function (source) {
const options = this.getOptions() // 获取 webpack 配置中传来的 option
this.callback(null, addSign(source, options.sign))
return
}
function addSign(content, sign) {
return `/** ${sign} */\n${content}`
}
console-loader.js
module.exports = function (content) {
return handleConsole(content)
}
function handleConsole(content) {
return content.replace(/console.log\(['|"](.*?)['|"]\)/, '')
}
вызвать тестовый метод
Функция реализована просто, здесь в основном речь идет оКак проверить вызов нашего местного загрузчика, есть два пути, один черезNpm linkКонкретное использование этого метода не детализировано, вы можете просто проверить его. Другой - передать его прямо в проектеконфигурация путикстати есть два случая:
- Чтобы сопоставить (протестировать) одиночный загрузчик, вы можете просто установить path.resolve в объекте правила, чтобы он указывал на этот локальный файл.
webpack.config.js
{
test: /\.js$/
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {/* ... */}
}
]
}
- Чтобы сопоставить (протестировать) несколько загрузчиков, вы можете использовать конфигурацию resolveLoader.modules, и webpack будет искать эти загрузчики в этих каталогах. Например, если в вашем проекте есть локальный каталог /loaders:
webpack.config.js
resolveLoader: {
// 这里就是说先去找 node_modules 目录中,如果没有的话再去 loaders 目录查找
modules: [
'node_modules',
path.resolve(__dirname, 'loaders')
]
}
Настроить с помощью
мы здеськонфигурация веб-пакетаСледующим образом:
module: {
rules: [
{
test: /\.js$/,
use: [
'console-loader',
{
loader: 'company-loader',
options: {
sign: 'we-doctor@2021',
},
},
],
},
],
},
index.js в проекте:
function fn() {
console.log("this is a message")
return "1234"
}
Запустите скомпилированный bundle.js: Видно, что функции двух загрузчиков отражены в скомпилированном файле.
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/** we-doctor@2021 */
function fn() {
return "1234"
}
/******/ })()
;
2. Плагин
Зачем нужен плагин
Плагин предоставляет гораздо больше полных функций, чем загрузчик.Он использует обратные вызовы поэтапной сборки.Webpack предоставляет нам множество крючков, позволяющих разработчикам свободно вводить свое собственное поведение на этапе сборки.
Базовая структура
Один из самых основных плагинов должен включать следующие части:
- класс JavaScript
- Один
apply
метод,apply
Метод Webpack вызывается при загрузке плагина и передаетcompiler
объект. -
callback
или черезPromise
путь (следуяЧасть асинхронной компиляциибуду уточнять)
class HelloPlugin{
apply(compiler){
compiler.hooks.<hookName>.tap(PluginName,(params)=>{
/** do some thing */
})
}
}
module.exports = HelloPlugin
Compiler and Compilation
Compiler 和 Compilation 是整个编写插件的过程中的**重! середина! Из! Тяжелый! **因为我们几乎所有的操作都会围绕他们。
compiler
Объект можно понимать как объект, привязанный к среде веб-пакета в целом, он содержит всю конфигурацию среды, включая параметры, загрузчик и плагин, когда веб-пакетзапускать, этот объект создается иглобально уникальныйДа, как мы упоминали вышеapply
Параметр, переданный методу, это он.
compilation
В процессе построения каждый ресурс будет создан из целевой производительности компиляции текущего модуля ресурсов, скомпилированных ресурсов, измененного файла и отслеживаемой зависимой информации о состоянии. Он также предлагает много крючков.
Compiler и CompiLation предоставляют нам множество хуков, которые позволяют нам получать различный контент в разное время в процессе построения.Официальный сайт директ.
В приведенной выше ссылке мы обнаружим, что существуют различные типы крючков, такие какSyncHook
,SyncBailHook
,AsyncParallelHook
,AsyncSeriesHook
tapable
предоставили нам, оtapable
Для получения подробной информации об использовании и анализе см.Строительство фронтальной колонны инструментовсерединаtapable
Специальное объяснение.
Основное использование:
compiler/compilation.hooks.<hookName>.tap/tapAsync/tapPromise(pluginName,(xxx)=>{/**dosth*/})
Tip: Ранее писалось как
compiler.plugin
, но может вызвать проблемы в последней версии webpack@5, см.webpack-4-migration-notes
Синхронный и асинхронный
Хуки плагина различают синхронные и асинхронные, в случае синхронизации мы используем<hookName>.tap
В асинхронном хуке мы можем выполнять некоторые асинхронные операции, и если есть асинхронные операции, пожалуйста, используйтеtapAsync
илиtapPromise
метод сообщить вебпаку, что контент здесь асинхронный, конечно, если внутри нет асинхронной операции, вы также можете использовать его в обычном режимеtap
.
tapAsync
использоватьtapAsync
callback
回调,并且在结束的时候一定要调用这个回调告知 webpack 这段异步操作结束了。 👇
Например:
class HelloPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync(HelloPlugin, (compilation, callback) => {
setTimeout(() => {
console.log('async')
callback()
}, 1000)
})
}
}
module.exports = HelloPlugin
tapPromise
когда используешьtapPromise
Promise
resolve
👇
class HelloPlugin {
apply(compiler) {
compiler.hooks.emit.tapPromise(HelloPlugin, (compilation) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('async')
resolve()
}, 1000)
})
})
}
}
module.exports = HelloPlugin
сделать практику
Далее мы сделаем плагин для сортировки общего процесса и разрозненных функциональных точек.Функция этого плагина состоит в том, чтобы после упаковки добавить в выходную папку еще один файл уценки, и зафиксировать момент времени упаковки, файл и вывод размера файла.
В первую очередь определяем нужные нам хуки согласно нашим потребностям.Так как нам нужно выводить файлы, то нужно использовать компиляторыemitAsset метод.
Во-вторых, поскольку активы необходимо обрабатывать, мы используемcompilation.hooks.processAssets
, потому что processAssets — это хук, отвечающий за обработку ассетов.
Таким образом, наша структура плагина выходит наружу.OutLogPlugin.js
class OutLogPlugin {
constructor(options) {
this.outFileName = options.outFileName
}
apply(compiler) {
// 可以从编译器对象访问 webpack 模块实例
// 并且可以保证 webpack 版本正确
const { webpack } = compiler
// 获取 Compilation 后续会用到 Compilation 提供的 stage
const { Compilation } = webpack
const { RawSource } = webpack.sources
/** compiler.hooks.<hoonkName>.tap/tapAsync/tapPromise */
compiler.hooks.compilation.tap('OutLogPlugin', (compilation) => {
compilation.hooks.processAssets.tap(
{
name: 'OutLogPlugin',
// 选择适当的 stage,具体参见:
// https://webpack.js.org/api/compilation-hooks/#list-of-asset-processing-stages
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
},
(assets) => {
let resOutput = `buildTime: ${new Date().toLocaleString()}\n\n`
resOutput += `| fileName | fileSize |\n| --------- | --------- |\n`
Object.entries(assets).forEach(([pathname, source]) => {
resOutput += `| ${pathname} | ${source.size()} bytes |\n`
})
compilation.emitAsset(
`${this.outFileName}.md`,
new RawSource(resOutput),
)
},
)
})
}
}
module.exports = OutLogPlugin
Настройте плагин:webpack.config.js
const OutLogPlugin = require('./plugins/OutLogPlugin')
module.exports = {
plugins: [
new OutLogPlugin({outFileName:"buildInfo"})
],
}
Упакованная структура каталогов:
dist
├─ buildInfo.md
├─ bundle.js
└─ bundle.js.map
buildInfo.md Вы можете видеть, что контент точно выводится в нужном нам формате, и такая простая функция плагина завершена!
Справочная статья
Writing a Loader | webpack Writing a Plugin | webpack Углубленный веб-пакет webpack/webpack | github
Полный код этой статьи — прямой поезд:github