Погружение вглубь Webpack — написание Loaders

API NPM CSS Webpack

Загрузчик похож на переводчик, который может конвертировать исходный файл и выводить новые результаты, а также файл может быть переведен несколькими переводчиками в цепочке.

В качестве примера возьмем обработку файлов SCSS:

  1. Исходный код SCSS сначала будет передан sass-loader для преобразования SCSS в CSS;
  2. Вывод CSS sass-loader передать в css-loader для обработки, узнать зависимые ресурсы в CSS, сжать CSS и т.д.;
  3. Передать CSS, выводимый css-loader, в style-loader для обработки и преобразовать его в код JavaScript, загружаемый скриптом;

Видно, что описанный выше процесс обработки требует последовательного выполнения цепочки, сначала sass-loader, затем css-loader, а затем style-loader. Обработанные выше конфигурации, связанные с Webpack, следующие:

module.exports = {
  module: {
    rules: [
      {
        // 增加对 SCSS 文件的支持
        test: /\.scss/,
        // SCSS 文件的处理顺序为先 sass-loader 再 css-loader 再 style-loader
        use: [
          'style-loader',
          {
            loader:'css-loader',
            // 给 css-loader 传入配置项
            options:{
              minimize:true, 
            }
          },
          'sass-loader'],
      },
    ]
  },
};

Обязанности грузчика

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

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

Основы загрузчика

Поскольку Webpack работает на Node.js, загрузчик на самом деле является модулем Node.js, которому необходимо экспортировать функцию. Задача этой экспортируемой функции — получить исходное содержимое перед обработкой, выполнить обработку исходного содержимого и вернуть обработанное содержимое.

Исходный код простейшего загрузчика выглядит следующим образом:

module.exports = function(source) {
  // source 为 compiler 传递给 Loader 的一个文件的原内容
  // 该函数需要返回处理后的内容,这里简单起见,直接把原内容返回了,相当于该 Loader 没有做任何转换
  return source;
};

Поскольку Loader работает в Node.js, вы можете вызывать любой API, поставляемый с Node.js, или устанавливать сторонние модули для вызова:

const sass = require('node-sass');
module.exports = function(source) {
  return sass(source);
};

Расширенный загрузчик

Выше приведен самый простой загрузчик. Webpack также предоставляет некоторые API для вызова загрузчика. Давайте представим их один за другим.

Получить параметры загрузчика

В верхней конфигурации Webpack, которая обрабатывает файлы SCSS, параметр options передается в css-loader для управления css-loader. Как получить параметры, переданные пользователем в загрузчике, написанные вами? Нужно сделать это:

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  // 获取到用户给当前 Loader 传入的 options
  const options = loaderUtils.getOptions(this);
  return source;
};

вернуть другие результаты

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

Например, используя babel-loader для преобразования кода ES6, ему также необходимо вывести исходную карту, соответствующую преобразованному коду ES5, чтобы облегчить отладку исходного кода. Чтобы вернуть исходную карту в Webpack вместе с кодом ES5, вы можете написать:

module.exports = function(source) {
  // 通过 this.callback 告诉 Webpack 返回的结果
  this.callback(null, source, sourceMaps);
  // 当你使用 this.callback 返回内容时,该 Loader 必须返回 undefined,
  // 以让 Webpack 知道该 Loader 返回的结果在 this.callback 中,而不是 return 中 
  return;
};

один из нихthis.callbackЭто API, который Webpack внедряет в Loader для облегчения связи между Loader и Webpack.this.callbackПодробное использование выглядит следующим образом:

this.callback(
    // 当无法转换原内容时,给 Webpack 返回一个 Error
    err: Error | null,
    // 原内容转换后的内容
    content: string | Buffer,
    // 用于把转换后的内容得出原内容的 Source Map,方便调试
    sourceMap?: SourceMap,
    // 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,
    // 以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
    abstractSyntaxTree?: AST
);

Генерация Source Maps занимает много времени Обычно Source Maps генерируются в среде разработки, а не генерируются в других средах для ускорения построения. Для этого Webpack предоставляет загрузчикthis.sourceMapAPI, чтобы сообщить загрузчику, нужна ли пользователю исходная карта в текущей среде сборки. Если вы пишете загрузчик, который генерирует исходные карты, примите это во внимание.

Синхронный и асинхронный

Загрузчики делятся на синхронные и асинхронные.Описанные выше Загрузчики являются синхронными Загрузчиками, потому что их процессы преобразования синхронны, и результаты возвращаются после завершения преобразования. Однако в некоторых сценариях этапы преобразования могут выполняться только асинхронно. Например, для получения результата необходимо сделать сетевой запрос. Если вы используете синхронный метод, сетевой запрос заблокирует всю сборку, что приведет к очень медленная сборка.

Когда шаг преобразования является асинхронным, вы можете сделать это:

module.exports = function(source) {
    // 告诉 Webpack 本次转换是异步的,Loader 会在 callback 中回调结果
    var callback = this.async();
    someAsyncOperation(source, function(err, result, sourceMaps, ast) {
        // 通过 callback 返回异步执行后的结果
        callback(err, result, sourceMaps, ast);
    });
};

Работа с бинарными данными

По умолчанию исходный контент, передаваемый Webpack в Loader, представляет собой строку, закодированную в формате UTF-8. Однако в некоторых сценариях загрузчик обрабатывает не текстовые файлы, а двоичные файлы, такие как загрузчик файлов, который требует, чтобы Webpack передал данные двоичного формата в загрузчик. Для этого нужно написать Loader так:

module.exports = function(source) {
    // 在 exports.raw === true 时,Webpack 传给 Loader 的 source 是 Buffer 类型的
    source instanceof Buffer === true;
    // Loader 返回的类型也可以是 Buffer 类型的
    // 在 exports.raw !== true 时,Loader 也可以返回 Buffer 类型的结果
    return source;
};
// 通过 exports.raw 属性告诉 Webpack 该 Loader 是否需要二进制数据 
module.exports.raw = true;

Самый важный код в приведенном выше коде — последняя строка.module.exports.raw = true;, без этой строки загрузчик может получить только строки.

ускорение кеша

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

Если вы хотите, чтобы Webpack не кэшировал результат обработки загрузчика, вы можете сделать это:

module.exports = function(source) {
  // 关闭该 Loader 的缓存功能
  this.cacheable(false);
  return source;
};

Другие API загрузчика

В дополнение к упомянутым выше API-интерфейсам Webpack, которые можно вызывать в загрузчике, существуют также следующие часто используемые API:

  • this.context: Каталог, в котором в данный момент обрабатывается файл, если файл, обрабатываемый в данный момент загрузчиком,/src/main.js,ноthis.contextэквивалентно/src.

  • this.resource: Полный путь запроса обрабатываемого в данный момент файла, включая строку запроса, например./src/main.js?name=1.

  • this.resourcePath: Путь к обрабатываемому в данный момент файлу, например./src/main.js.

  • this.resourceQuery: строка запроса обрабатываемого в данный момент файла.

  • this.target: равно Target в конфигурации Webpack, подробности см.2-7 Другие элементы конфигурации-Цель.

  • this.loadModule: Но когда загрузчик обрабатывает файл, если он зависит от результатов обработки других файлов для получения результата текущего файла, ты можешь пройтиthis.loadModule(request: string, callback: function(err, source, sourceMap, module))получитьrequestРезультат обработки соответствующего файла.

  • this.resolve:картинаrequireоператор, чтобы получить полный путь к указанному файлу, метод использования следующийresolve(context: string, request: string, callback: function(err, result: string)).

  • this.addDependency: добавить зависимый файл к текущему обрабатываемому файлу, чтобы при изменении зависимого файла снова вызывался загрузчик для обработки файла. Способ использованияaddDependency(file: string).

  • this.addContextDependency:а такжеaddDependencyпохоже, ноaddContextDependencyзаключается в добавлении всего каталога к зависимостям обрабатываемого в данный момент файла. Способ использованияaddContextDependency(directory: string).

  • this.clearDependencies: очищает все зависимости файла, который в данный момент обрабатывается, используя методclearDependencies().

  • this.emitFile: вывод файла, метод использованияemitFile(name: string, content: Buffer|string, sourceMap: {...}).

Другие не упомянутые API могут перейти кОфициальный сайт вебпакаПроверить.

Загрузить локальный загрузчик

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

module.exports = {
  module: {
    rules: [
      {
        test: /\.css/,
        use: ['style-loader'],
      },
    ]
  },
};

Если вы также воспользуетесь описанным выше методом для использования локально разработанного загрузчика, это будет очень хлопотно, потому что вам нужно убедиться, что исходный код загрузчика, который вы пишете, находится вnode_modulesПод содержанием. Для этого вам необходимо опубликовать написанный вами загрузчик в репозитории Npm, а затем установить его в свой локальный проект для использования.

Есть два удобных способа решения вышеуказанных проблем, они заключаются в следующем:

Npm link

Ссылка Npm специально используется для разработки и отладки локальных модулей Npm.Он может связать исходный код разрабатываемого локального модуля с проектом без выпуска модуля.node_modulesкаталог, чтобы проект мог напрямую использовать локальный модуль Npm. Так как это реализовано через софтлинки, код локального модуля Npm редактируется, и отредактированный код также можно использовать в проекте.

Шаги для завершения ссылки Npm следующие:

  1. Убедитесь, что разрабатываемый локальный модуль Npm (то есть разрабатываемый загрузчик)package.jsonбыл правильно настроен;
  2. Выполнить в локальном корневом каталоге модуля Npm.npm link, зарегистрируйте локальный модуль в глобальном;
  3. Выполнить в корневом каталоге проектаnpm link loader-name, свяжите локальный модуль Npm, зарегистрированный на шаге 2, с глобальной ссылкой на проектnode_moduelsниже, из нихloader-nameзначит на шаге 1package.jsonИмя модуля, настроенное в файле.

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

ResolveLoader

существует2-7 Другие элементы конфигурацииResolveLoader используется для настройки того, как Webpack ищет загрузчики. По умолчанию пойдет толькоnode_modulesПоиск в каталоге, чтобы Webpack мог загрузить Loader, размещенный в локальном проекте, его нужно модифицироватьresolveLoader.modules.

Если локальный загрузчик находится в каталоге проекта./loaders/loader-name, требуется следующая конфигурация:

module.exports = {
  resolveLoader:{
    // 去哪些目录下寻找 Loader,有先后顺序之分
    modules: ['node_modules','./loaders/'],
  }
}

После добавления вышеуказанной конфигурации Webpack будет идти первымnode_modulesИщите Loader под проектом, если не найдете, пойдете еще раз./loaders/Найдите в справочнике.

настоящий бой

Выше было рассмотрено много теорий, а дальше мы начнем с реальности и напишем Загрузчик, решающий практические задачи.

Загрузчик называется comment-require-loader, и его функция заключается в добавлении синтаксиса комментариев в код JavaScript.

// @require '../style/index.css'

Перевести в

require('../style/index.css');

Сценарий использования этого загрузчика заключается в корректной загрузке целиFis3Написанный JavaScript, который загружает зависимые файлы CSS через комментарии.

Использование этого загрузчика выглядит следующим образом:

module.exports = {
  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: ['comment-require-loader'],
        // 针对采用了 fis3 CSS 导入语法的 JavaScript 文件通过 comment-require-loader 去转换 
        include: [path.resolve(__dirname, 'node_modules/imui')]
      }
    ]
  }
};

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

function replace(source) {
    // 使用正则把 // @require '../style/index.css' 转换成 require('../style/index.css');  
    return source.replace(/(\/\/ *@require) +(('|").+('|")).*/, 'require($2);');
}

module.exports = function (content) {
    return replace(content);
};

этот примерПредоставьте полный код проекта

Ссылка для онлайн-чтения "Углубленный Webpack"

читать оригинал

Категории