Научите вас, как свернуть загрузчик Webpack

JavaScript Webpack
Научите вас, как свернуть загрузчик Webpack

Текст: Маленький мальчик (инженер веб-интерфейса в Шанхайской интернет-школе)

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

webpack

Студенты, которые часто посещают официальный сайт webpack, должны быть знакомы с этой картинкой. Как сообщается, webpack может упаковывать различные типы файлов слева (webpack называет их «модулями») в файлы справа, которые поддерживаются обычными браузерами. Вебпак похож на шляпу фокусника, надетый на шелковый шарф, он превращается в белого голубя. Как работает этот «магический» процесс? Сегодня мы находим ответ в одной из основных концепций вебпака — загрузчике, и приступаем к осуществлению этой «магии». Прочитав эту статью, вы сможете:

  • Знайте функцию и принцип работы загрузчика webpack.
  • Разработайте собственный загрузчик, соответствующий потребностям вашего бизнеса.

Что такое загрузчик?

Прежде чем создавать загрузчик, нам нужно знать, что это такое. По сути, загрузчик — это модуль узла, что соответствует идее «все является модулем» в webpack. Поскольку это модуль узла, он должен что-то экспортировать. В определении webpack загрузчик экспортирует функцию, которую загрузчик будет вызывать при преобразовании исходного модуля (ресурса). Внутри этой функции мы можем передатьthisКонтекст дает API-интерфейсу загрузчика возможность использовать их. Оглядываясь назад на модули в левой части заголовка, они являются так называемыми исходными модулями, которые будут преобразованы загрузчиком в общие файлы справа, поэтому мы также можем резюмировать функцию загрузчика: преобразовать исходный код. модули в общие модули.

Как пользоваться загрузчиком?

Зная его мощные функции, как мы можем использовать загрузчик?

1. Настройте файл конфигурации веб-пакета

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

Конфигурация одного загрузчика

Увеличиватьconfig.module.rulesОбъект правила в массиве.

let webpackConfig = {
    //...
    module: {
        rules: [{
            test: /\.js$/,
            use: [{
                //这里写 loader 的路径
                loader: path.resolve(__dirname, 'loaders/a-loader.js'), 
                options: {/* ... */}
            }]
        }]
    }
}

Конфигурация нескольких загрузчиков

Увеличиватьconfig.module.rulesобъекты правил в массиве иconfig.resolveLoader.

let webpackConfig = {
    //...
    module: {
        rules: [{
            test: /\.js$/,
            use: [{
                //这里写 loader 名即可
                loader: 'a-loader', 
                options: {/* ... */}
            }, {
                loader: 'b-loader', 
                options: {/* ... */}
            }]
        }]
    },
    resolveLoader: {
        // 告诉 webpack 该去那个目录下找 loader 模块
        modules: ['node_modules', path.resolve(__dirname, 'loaders')]
    }
}

Другая конфигурация

также черезnpm linkПодключиться к вашему проекту.Этот метод аналогичен разработке инструментов CLI узла.Он не посвящен модулям загрузчика.В этой статье он не будет рассматриваться далее.

2. Простота использования

После завершения настройки при импорте модуля в проект веб-пакета сопоставьте правило (например, приведенное выше/\.js$/) активирует соответствующий загрузчик (например, a-loader и b-loader выше). На этом этапе, предполагая, что мы являемся разработчиком a-loader, a-loader экспортирует функцию, которая принимает единственный параметр — строку, содержащую содержимое исходного файла. Давайте пока назовем его «источник».

Затем мы обрабатываем преобразование источника в функции и, наконец, возвращаем обработанное значение. Разумеется, количество возвращаемых значений и метод возврата определяются в соответствии с потребностями загрузчика. Как правило, черезreturnВозвращает значение, которое является преобразованным значением. Если вам нужно вернуть несколько параметров, вы должны вызватьthis.callback(err, values...)возвращаться. В асинхронных загрузчиках вы можете обрабатывать исключения, выдавая ошибки. Webpack предлагает возвращать от 1 до 2 параметров, первый параметр — это преобразованный источник, который может быть строкой или буфером. Второй параметр является необязательным и используется как объект SourceMap.

3. Расширенное использование

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

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

Возьмем пример:

webpack.config.js

    {
        test: /\.js/,
        use: [
            'bar-loader',
            'mid-loader',
            'foo-loader'
        ]
    }

В приведенной выше конфигурации:

  • Последовательность вызова загрузчика: foo-loader -> mid-loader -> bar-loader.
  • foo-loader получает исходник, передает JS-код в mid после обработки, mid получает «исходник», обработанный foo, затем обрабатывает его в bar, а затем отправляет его в webpack после обработки bar.
  • Наконец, bar-loader передает возвращаемое значение и исходную карту в webpack.

Разработайте Loader с правильной осанкой

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

1. Единственная ответственность

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

2. Комбинация цепей

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

Допустим, теперь мы хотим реализовать функцию рендеринга шаблонов через конфигурацию загрузчика и параметры запроса. Мы реализуем эту функцию в «apply-loader», который отвечает за компиляцию исходного шаблона и, наконец, выводит модуль, который экспортирует строки HTML. По правилам объединения цепочки мы можем объединить два других загрузчика с открытым исходным кодом:

  • jade-loaderПреобразуйте исходный файл шаблона в модуль, который экспортирует функцию.
  • apply-loaderПередайте параметры загрузчика вышеуказанной функции и выполните ее, вернув HTML-текст.
  • html-loaderПолучите текстовые файлы HTML и преобразуйте их в модули JS, на которые можно ссылаться.

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

3. Модульный

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

4. Без гражданства

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

5. Используйте утилиту загрузчика

Пожалуйста, используйте его хорошоloader-utilspackage, который предоставляет множество полезных инструментов, один из наиболее часто используемых — передача параметров в загрузчик. Кромеloader-utilsАутсорсинг такжеschema-utilsпакет, мы можем использоватьschema-utilsПредоставляет инструмент для получения констант схемы JSON, используемых для проверки параметров для проверки параметров загрузчика. Пример, приведенный ниже, кратко объединяет два инструментария, упомянутых выше:

import { getOptions } from 'loader-utils';
import { validateOptions } from 'schema-utils';

const schema = {
  type: object,
  properties: {
    test: {
      type: string
    }
  }
}

export default function(source) {
    const options = getOptions(this);

    validateOptions(schema, options, 'Example Loader');

    // 在这里写转换 source 的逻辑 ...
    return `export default ${ JSON.stringify(source) }`;
};

зависимости загрузчика

Если мы используем внешние ресурсы (то есть ресурсы, считанные из файловой системы) в загрузчике, мы должны объявить информацию об этих внешних ресурсах. Эта информация используется для проверки кэшируемых лодеров и перекомпиляции в режиме наблюдения. В следующем примере кратко показано, как использоватьaddDependencyметод, чтобы сделать вещи, упомянутые выше. загрузчик.js:

import path from 'path';

export default function(source) {
    var callback = this.async();
    var headerPath = path.resolve('header.js');

    this.addDependency(headerPath);

    fs.readFile(headerPath, 'utf-8', function(err, header) {
        if(err) return callback(err);
        //这里的 callback 相当于异步版的 return
        callback(null, header + "\n" + source);
    });
};

зависимости модуля

Различные модули определяют зависимости в разных формах. Например, в CSS мы используем@importиurl(...)объявление для завершения спецификации, и мы должны позволить модульной системе разрешить эти зависимости.

Как заставить модульную систему разрешать зависимости различных методов объявления? Ниже приведены два метода:

  • Унифицированное преобразование различных объявлений зависимостей вrequireутверждение.
  • пройти черезthis.resolveфункция для разрешения пути.

Для первого способа хорошим примером являетсяcss-loader. это ставит@importЗаявление переводится какrequireфайл таблицы стилей, поместитеurl(...)Заявление переводится какrequireссылочный файл.

Для второго метода вам нужно обратиться кless-loader. Так как мы хотим меньше отслеживать переменные и примеси, нам нужно поместить все.lessФайл компилируется сразу весь, поэтому нет возможности поместить каждый@importПреобразовать вrequire. следовательно,less-loaderДополняет компилятор less пользовательской логикой разрешения пути. Этот подход использует второй подход, который мы только что упомянули —this.resolveРазрешите зависимости с помощью webpack.

Если язык поддерживает только относительные пути (например,url(file)направление./file). ты можешь использовать это~Укажите относительный путь к уже установленному каталогу (например,node_modules), так что беритеurlНапример, это будет выглядеть так:url(~some-library/image.jpg).

общий код

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

абсолютный путь

Не указывайте абсолютные пути в модуле загрузчика, потому что при изменении корневого пути проекта эти пути будут мешать вычислению хэша веб-пакета (преобразование пути модуля в ссылочный идентификатор модуля).loader-utilsсуществует одинstringifyRequestметод, который преобразует абсолютные пути в относительные пути.

зависимость от сверстников

Если вы разрабатываете загрузчик, который просто обертывает другой пакет, вам следует установить этот пакет как peerDependency в package.json. Это позволяет разработчику приложения знать, какую конкретную версию указать. Например, как показано нижеsass-loaderбудетnode-sassУкажите в качестве сопутствующей зависимости:

"peerDependencies": {
  "node-sass": "^4.0.0"
}

Talk is cheep

Выше мы наточили нож для рубки дров, далее разработаем погрузчик.

Если мы хотим ссылаться на файлы шаблонов при разработке проекта, сжатие html является очень распространенным требованием. Чтобы разложить вышеперечисленные требования, шаблон парсинга и шаблон сжатия фактически можно разделить на два загрузчика (одна ответственность) Первый более сложен, поэтому мы вводим пакеты с открытым исходным кодом.html-loader, а последний мы используем на практике. Во-первых, давайте дадим ему громкое имя -html-minify-loader.

Далее, следуя шагам, представленным ранее, во-первых, мы должны настроитьwebpack.config.js, чтобы вебпак мог распознать наш загрузчик. Разумеется, в самом начале нам нужно создать файл загрузчика —src/loaders/html-minify-loader.js.

Итак, делаем это в конфигурационном файле:webpack.config.js

module: {
    rules: [{
        test: /\.html$/,
        use: ['html-loader', 'html-minify-loader'] // 处理顺序 html-minify-loader => html-loader => webpack
    }]
},
resolveLoader: {
    // 因为 html-loader 是开源 npm 包,所以这里要添加 'node_modules' 目录
    modules: [path.join(__dirname, './src/loaders'), 'node_modules']
}

Затем мы предоставляем образцы html и js для тестирования загрузчика:

src/example.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

src/app.js:

var html = require('./expamle.html');
console.log(html);

Хорошо, теперь мы приступаем к работеsrc/loaders/html-minify-loader.js. Как мы говорили ранее, загрузчик также является модулем узла, он экспортирует функцию, параметром этой функции является исходный модуль запроса, и после обработки источника возвращаемое значение передается следующему загрузчику. Таким образом, его «шаблон» должен выглядеть так:

module.exports = function (source) {
    // 处理 source ...
    return handledSource;
}

или

module.exports = function (source) {
    // 处理 source ...
    this.callback(null, handledSource)
    return handledSource;
}

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

// 处理顺序排在最后的 loader
module.exports = function (source) {
    // 这个 loader 的功能是把源模块转化为字符串交给 require 的调用方
    return 'module.exports = ' + JSON.stringify(source);
}

Весь процесс эквивалентен этому загрузчику, помещающему исходный файл

这里是 source 模块

превратиться в

// example.js
module.exports = '这里是 source 模块';

Затем передайте его вызывающему абоненту:

// applySomeModule.js
var source = require('example.js'); 

console.log(source); // 这里是 source 模块

В двух загрузчиках, которые мы подключили на этот раз, задача разбора html и преобразования его в JS для выполнения скрипта была переданаhtml-loaderТеперь займемся сжатием html.

Загрузчики как обычные модули узлов могут легко ссылаться на сторонние библиотеки. Мы используемminimizeЭта библиотека выполняет основные функции сжатия:

// src/loaders/html-minify-loader.js

var Minimize = require('minimize');

module.exports = function(source) {
    var minimize = new Minimize();
    return minimize.parse(source);
};

Конечно, библиотека минимизации поддерживает ряд параметров сжатия, таких как параметр comments, указывающий, следует ли сохранять комментарии. Мы не должны жестко прописывать эти конфигурации в загрузчике. Такloader-utilsПришло время играть:

// src/loaders/html-minify-loader.js
var loaderUtils = require('loader-utils');
var Minimize = require('minimize');

module.exports = function(source) {
    var options = loaderUtils.getOptions(this) || {}; //这里拿到 webpack.config.js 的 loader 配置
    var minimize = new Minimize(options);
    return minimize.parse(source);
};

Таким образом, мы можем указать в webpack.config.js, нужно ли сохранять аннотации после минификации:

    module: {
        rules: [{
            test: /\.html$/,
            use: ['html-loader', {
                loader: 'html-minify-loader',
                options: {
                    comments: false
                }
            }] 
        }]
    },
    resolveLoader: {
        // 因为 html-loader 是开源 npm 包,所以这里要添加 'node_modules' 目录
        modules: [path.join(__dirname, './src/loaders'), 'node_modules']
    }

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

var Minimize = require('minimize');
var loaderUtils = require('loader-utils');

module.exports = function(source) {
    var callback = this.async();
    if (this.cacheable) {
        this.cacheable();
    }
    var opts = loaderUtils.getOptions(this) || {};
    var minimize = new Minimize(opts);
    minimize.parse(source, callback);
};

ты сможешьэтот складСм. связанный код,npm startможно пойти позжеhttp://localhost:9000Откройте консоль, чтобы увидеть, что обработал загрузчик.

Суммировать

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

  • Создать каталог загрузчика и файл модуля
  • Настройте путь разбора правила и загрузчика в веб-пакете и обратите внимание на порядок загрузчика, чтобы вrequireПри указании типа файла мы можем сделать так, чтобы поток обработки проходил через указанный лаодер.
  • Проектируйте и разрабатывайте загрузчики, следуя принципам.

Наконец-то Talk взбодрится, поторопитесь и поиграйте с загрузчиком~

Ссылаться на

Writing a loader

Рекомендация: Самоотчет мастера переводческого проекта:

1. Галантерея|Каждый является мастером переводческих проектов

2. Обучение апплету WeChat от iKcamp состоит из 5 глав и 16 подразделов (включая видео).

3. Начать сериализацию бесплатно ~ 11 уроков iKcamp обновляются два раза в неделю | Создание практического проекта Node.js на основе Koa2 (включая видео) | Введение в план курса


В 2019 году оригинальная новая книга iKcamp «Практика разработки Koa и Node.js» была продана на JD.com, Tmall, Amazon и Dangdang!