Тип source: обработка текстовых ресурсов

Тип source в системе Asset Modules, появившейся в Webpack 5, предназначен для импорта содержимого файла в виде обычной строки. В отличие от типов asset/resource или asset/inline, здесь Webpack не генерирует отдельный файл и не преобразует данные в Base64. Вместо этого содержимое ресурса встраивается в JavaScript-бандл как текст.

Такой подход особенно полезен при работе с:

  • .txt файлами;
  • шаблонами;
  • SVG как строками;
  • Markdown-документами;
  • SQL-запросами;
  • GLSL-шейдерами;
  • XML;
  • CSV;
  • исходным кодом для последующего парсинга;
  • встроенными текстовыми конфигурациями.

Тип source заменяет старый raw-loader, который широко использовался в Webpack 4 и более ранних версиях.


Принцип работы asset/source

При использовании asset/source Webpack читает содержимое файла во время сборки и превращает его в строковый литерал внутри итогового JavaScript-кода.

Например, файл:

Hello Webpack

после импорта превращается примерно в:

export default "Hello Webpack";

Файл не копируется в dist, не получает хэш и не становится отдельным HTTP-ресурсом.


Базовая настройка

Структура проекта

src/
 ├── index.js
 └── message.txt

Файл message.txt

Webpack умеет импортировать текстовые файлы.

Конфигурация Webpack

module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/i,
        type: 'asset/source'
      }
    ]
  }
};

Импорт файла

import message fr om './message.txt';

console.log(message);

Результат

Webpack умеет импортировать текстовые файлы.

Что происходит во время сборки

Webpack:

  1. Находит импортируемый файл.
  2. Проверяет правила module.rules.
  3. Видит тип asset/source.
  4. Читает содержимое файла.
  5. Преобразует содержимое в JavaScript-строку.
  6. Встраивает строку в бандл.

В итоговой сборке файл отсутствует как физический ресурс.


Отличие от других типов Asset Modules

asset/resource

Создаёт отдельный файл.

import file from './data.txt';

Результат:

"/assets/data.a1b2c3.txt"

asset/inline

Преобразует содержимое в Data URL.

data:text/plain;base64,...

asset/source

Возвращает чистую строку.

"Текст файла"

Замена raw-loader

До появления Webpack 5 текстовые ресурсы обычно обрабатывались через raw-loader.

Старый подход

npm install raw-loader --save-dev
module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: 'raw-loader'
      }
    ]
  }
};

Современный подход

module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/,
        type: 'asset/source'
      }
    ]
  }
};

Преимущества нового механизма:

  • меньше зависимостей;
  • быстрее сборка;
  • встроенная поддержка;
  • единая система Asset Modules;
  • более простая конфигурация.

Импорт Markdown-файлов

Одно из наиболее популярных применений — загрузка Markdown как строки.

Конфигурация

module.exports = {
  module: {
    rules: [
      {
        test: /\.md$/,
        type: 'asset/source'
      }
    ]
  }
};

Файл article.md

# Заголовок

Текст статьи.

Импорт

import markdown from './article.md';

console.log(markdown);

Использование с Markdown-парсерами

Часто строка Markdown затем передаётся в библиотеку преобразования HTML.

Пример

import markdown from './article.md';
import { marked } from 'marked';

const html = marked(markdown);

document.body.innerHTML = html;

Webpack здесь используется только для получения содержимого файла.


Работа с SVG как текстом

SVG можно импортировать не как URL, а как строку.

Настройка

{
  test: /\.svg$/i,
  type: 'asset/source'
}

SVG-файл

<svg viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="40"/>
</svg>

Импорт

import svg from './icon.svg';

console.log(svg);

Вставка SVG в DOM

import icon from './icon.svg';

document.body.innerHTML = icon;

Преимущества SVG как строки

Такой подход позволяет:

  • динамически изменять SVG;
  • подменять цвета;
  • выполнять парсинг;
  • вставлять SVG без HTTP-запроса;
  • генерировать компоненты;
  • использовать шаблоны.

Работа с шаблонами

HTML-шаблоны

{
  test: /\.html$/i,
  type: 'asset/source'
}

Импорт HTML

import template from './card.html';

document.body.innerHTML = template;

Генерация шаблонов

<div class="card">
  {{title}}
</div>
import template from './card.html';

const result = template.replace('{{title}}', 'Webpack');

console.log(result);

Работа с SQL-файлами

Тип source активно используется в серверных проектах.

Пример SQL-файла

SEL ECT * FROM users WH ERE active = 1;

Импорт

import query fr om './users.sql';

console.log(query);

GLSL-шейдеры

В WebGL-проектах asset/source используется особенно часто.

Конфигурация

{
  test: /\.(glsl|vert|frag)$/i,
  type: 'asset/source'
}

Импорт шейдера

import fragmentShader from './shader.frag';

console.log(fragmentShader);

Пример содержимого шейдера

precision mediump float;

void main() {
  gl_FragColor = vec4(1.0);
}

Работа с XML

Настройка

{
  test: /\.xml$/i,
  type: 'asset/source'
}

Импорт XML

import xml from './data.xml';

const parser = new DOMParser();

const doc = parser.parseFromString(xml, 'application/xml');

console.log(doc);

CSV как текст

Конфигурация

{
  test: /\.csv$/i,
  type: 'asset/source'
}

Импорт

import csv from './users.csv';

console.log(csv);

Последующий парсинг CSV

const rows = csv
  .split('\n')
  .map(row => row.split(','));

console.log(rows);

Обработка JSON как строки

Обычно JSON импортируется как объект:

import data from './data.json';

Но иногда требуется именно исходный текст JSON.

Настройка

{
  test: /\.json$/i,
  type: 'asset/source'
}

Результат

import rawJson from './data.json';

console.log(typeof rawJson);
string

Комбинирование с resourceQuery

Webpack позволяет по-разному обрабатывать один и тот же тип файлов.

Конфигурация

module.exports = {
  module: {
    rules: [
      {
        resourceQuery: /raw/,
        type: 'asset/source'
      },
      {
        test: /\.svg$/i,
        type: 'asset/resource'
      }
    ]
  }
};

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

import iconUrl from './icon.svg';
import iconRaw from './icon.svg?raw';

Результат

console.log(iconUrl);
/assets/icon.svg
console.log(iconRaw);
<svg>...</svg>

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

Пример

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/i,
        oneOf: [
          {
            resourceQuery: /raw/,
            type: 'asset/source'
          },
          {
            type: 'asset/resource'
          }
        ]
      }
    ]
  }
};

Преимущества oneOf

oneOf позволяет:

  • избегать конфликтов правил;
  • ускорять обработку;
  • разделять режимы импорта;
  • создавать гибкие схемы загрузки.

Работа с Unicode

Webpack корректно обрабатывает Unicode-символы.

Файл

Привет мир
你好
こんにちは

Импорт

import text from './hello.txt';

console.log(text);

Кодировка файлов

Наиболее безопасный вариант — UTF-8.

Проблемы могут возникать при использовании:

  • Windows-1251;
  • KOI8-R;
  • ISO-8859;
  • UTF-16.

Webpack ожидает стандартную UTF-8-кодировку.


Размер бандла

asset/source встраивает содержимое прямо в JavaScript.

Это означает:

  • увеличение размера JS-файла;
  • рост времени парсинга;
  • увеличение памяти;
  • увеличение initial load.

Поэтому тип source подходит преимущественно для небольших текстовых файлов.


Когда asset/source использовать не стоит

Плохие сценарии:

  • огромные Markdown-файлы;
  • большие XML-документы;
  • длинные SQL-дампы;
  • крупные JSON;
  • большие SVG;
  • текстовые базы данных;
  • объёмные словари.

В подобных случаях лучше:

  • загружать файл отдельно;
  • использовать asset/resource;
  • выполнять HTTP-запрос;
  • использовать lazy loading.

Динамический импорт

Текстовые ресурсы можно загружать лениво.

Пример

async function loadArticle() {
  const article = await import('./article.md');

  console.log(article.default);
}

Что получает браузер

Webpack создаёт отдельный chunk:

article.chunk.js

Внутри которого хранится строковое содержимое файла.


Tree Shaking

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

Пример

import text from './text.txt';

Если переменная нигде не применяется, модуль потенциально исключается из бандла.


Совместимость с TypeScript

TypeScript требует деклараций модулей.

Пример global.d.ts

declare module '*.txt' {
  const content: string;
  export default content;
}

Декларации для Markdown

declare module '*.md' {
  const content: string;
  export default content;
}

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

Компонент

import article from './article.md';

export function Page() {
  return (
    <pre>{article}</pre>
  );
}

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

import template from './template.html';

export default {
  template
};

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

import content from './text.txt';
<p>{content}</p>

Использование в Node.js-сборках

Webpack может собирать серверные приложения.

Пример

import query from './query.sql';

database.execute(query);

Внутреннее представление в бандле

Webpack сериализует строку примерно так:

module.exports = "Hello World";

Спецсимволы автоматически экранируются:

\n
\t
\"
\\

Производительность

Плюсы

  • отсутствие HTTP-запросов;
  • мгновенный доступ к содержимому;
  • отсутствие сетевых задержек;
  • простота импорта.

Минусы

  • рост размера JavaScript;
  • увеличение времени компиляции;
  • увеличение времени парсинга;
  • больший объём памяти.

Сравнение source и inline

asset/source

"hello"

asset/inline

"dat a:text/plain;base64,aGVsbG8="

Основное различие

inline создаёт Data URL.

source возвращает оригинальное содержимое.


Использование генераторов

Тип source можно комбинировать с пользовательскими loader-цепочками.

Пример

{
  test: /\.md$/,
  use: [
    'markdown-loader'
  ],
  type: 'asset/source'
}

Порядок обработки

  1. Loader преобразует содержимое.
  2. asset/source возвращает итог как строку.

Ограничения

asset/source:

  • не создаёт файлов;
  • не поддерживает generator.filename;
  • не использует publicPath;
  • не генерирует URL;
  • не работает как файловый эмиттер.

Практические сценарии использования

Хранение email-шаблонов

import template from './welcome-email.html';

Импорт лицензий

import license from './LICENSE.txt';

Встроенные текстовые конфиги

import config from './default.conf';

Встроенные SVG-иконки

import icon from './check.svg';

GLSL/WebGL

import shader from './shader.frag';

Markdown CMS

import post from './post.md';

Типичные ошибки

Неверный тип

type: 'source'

Правильно:

type: 'asset/source'

Конфликт с loader

use: 'raw-loader',
type: 'asset/source'

Обычно raw-loader здесь уже не нужен.


Слишком большие файлы

Встраивание мегабайт текста резко увеличивает размер bundle.


Неправильная кодировка

Файл может импортироваться с повреждёнными символами.


Итоговая схема работы

Файл
   ↓
Webpack
   ↓
asset/source
   ↓
Строка JavaScript
   ↓
Встраивание в bundle