Микрофронтенд и Module Federation

год назад·6 мин. на чтение

Module Federation - это плагин для Webpack. С его помощью можно разбивать приложение на микрофронтенды, подключив их в хост приложение.

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

Низкоуровневые концепции

Различаются два вида модулей - локальные и удаленные модули. Локальные модули — это обычные модули, которые являются частью текущей сборки. Удаленные модули (remote modules) — это модули, которые не являются частью текущей сборки, но загружаются во время выполнения из удаленного контейнера. Загрузка удаленных модулей считается асинхронной операцией. Невозможно использовать удаленный модуль без загрузки его чанка. Операция загрузки чанка обычно является вызовом import(), но также поддерживаются более старые конструкции, такие как require.ensure или require([...])). Контейнер создается с помощью точки входа контейнера, которая предоставляет асинхронный доступ к определенным модулям. Доступ к предоставляемым (expose) моделям разделен на два этапа:
  1. загрузка модуля (асинхронная)
  2. выполнение модуля (синхронная).
Шаг 1 будет выполнен во время загрузки чанка. Шаг 2 будет выполнен во время выполнения модуля, чередующегося с другими (локальными и удаленными) модулями. Таким образом, порядок выполнения не зависит от преобразования модуля из локального в удаленный или наоборот. Возможно вложение контейнеров. Контейнеры могут использовать модули из других контейнеров. Также возможны циклические зависимости между контейнерами.

Высокоуровневые концепции

Каждая сборка действует как контейнер, а также потребляет другие сборки в качестве контейнеров. Таким образом, каждая сборка может получить доступ к любому другому предоставленному модулю, загрузив его из своего контейнера. Общие модули — это модули, которые являются переопределяемыми и предоставляются в качестве переопределений для вложенных контейнеров. Они обычно указывают на один и тот же модуль в каждой сборке, например, на одну и ту же библиотеку. Параметр packageName позволяет задать имя пакета для поиска requiredVersion. Он автоматически выводится для запросов модуля по умолчанию. Установите requiredVersion в false, когда автоматический вывод должен быть отключен.

Основные части

ContainerPlugin (низкий уровень)

Этот плагин создает дополнительную запись контейнера с указанными открытыми модулями.

ContainerReferencePlugin (низкий уровень)

Этот плагин добавляет конкретные ссылки на контейнеры как внешние и позволяет импортировать удаленные модули из этих контейнеров. Он также вызывает API override этих контейнеров для предоставления им переопределений. Локальные переопределения (через __webpack_override__ или override API когда сборка является контейнером) и указанные переопределения предоставляются всем контейнерам, на которые имеются ссылки.

ModuleFederationPlugin (высокий уровень)

ModuleFederationPlugin объединяет ContainerPlugin и ContainerReferencePlugin.

Какие цели преследует Module Federation

  • Должна быть возможность предоставлять и использовать любой тип модуля, поддерживаемый webpack.
  • При загрузке чанков должно загружаться все необходимое параллельно (за один запрос к серверу).
  • Управление от потребителя к контейнеру
    • Переопределение модулей представляет собой однонаправленную операцию.
    • Родственные контейнеры не могут переопределять модули друг друга.
  • Концепция должна быть независимой от среды.
    • Можно использовать в web, Node.js и т. д.
  • Относительный и абсолютный запрос в shared:
    • Всегда будет предоставлен, даже если не используется.
    • Будет разрешаться относительно config.context.
    • Не использует requiredVersion по умолчанию.
  • Запросы модулей в shared:
    • Предоставляются только тогда, когда они используются.
    • Будет соответствовать всем используемым равным запросам модулей в вашей сборке.
    • Предоставит все соответствующие модули.
    • Будет извлекать requiredVersion из package.json в этой позиции в графе.
    • Может предоставлять и использовать несколько различных версий при наличии вложенных node_modules.
  • Запросы модулей с / в shared будут сопоставлять все запросы модулей с этим префиксом.

Примеры использования

Отдельные сборки для каждой страницы

Каждая страница одностраничного приложения предоставляется из контейнерной сборки в отдельной сборке. Оболочка приложения (application shell) также представляет собой отдельную сборку, ссылающуюся на все страницы как на удаленные модули. Таким образом, каждая страница может быть развернута отдельно. Оболочка приложения заново развертывается при обновлении маршрутов или добавлении новых маршрутов. Оболочка приложения определяет часто используемые библиотеки как разделяемые модули (shared), чтобы избежать их дублирования в сборках страниц.

Библиотека компонентов в качестве контейнера

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

Динамические удаленные контейнеры

Интерфейс контейнера поддерживает методы get и init. init — это async метод, который вызывается с одним аргументом: объектом общей области (shared scope). Этот объект используется в качестве общей области в удаленном контейнере и заполняется предоставленными модулями от хоста. Его можно использовать для динамического подключения удаленных контейнеров к контейнеру хоста во время выполнения.
// init.js

(async () => {
  // Инициализирует общую область. Заполняет его предоставленными модулями 
  // из текущего билда и из удаленных билдов
  await __webpack_init_sharing__('default');
  const container = window.someContainer; // или получить контейнер откуда-либо еще
  // Проинициализируйте контейнер, он может предоставлять общие модули
  await container.init(__webpack_share_scopes__.default);
  const module = await container.get('./module');
})();
Контейнер пытается предоставить общие модули, но если общий модуль уже использовался, предупреждение и предоставленный общий модуль будут проигнорированы. Контейнер может по-прежнему использовать его в качестве запасного варианта. Таким образом, вы можете динамически загружать A/B-тест, который предоставляет другую версию общего модуля. Убедитесь, что контейнер загружен, прежде чем пытаться динамически подключить удаленный контейнер. Пример:
// init.js

function loadComponent(scope, module) {
  return async () => {
    // Инициализирует общую область. Заполняет его предоставленными модулями 
    // из текущего билда и из удаленных билдов
    await __webpack_init_sharing__('default');
    const container = window[scope]; // или получить контейнер откуда-либо еще
    // Проинициализируйте контейнер, он может предоставлять общие модули
    await container.init(__webpack_share_scopes__.default);
    const factory = await window[scope].get(module);
    const Module = factory();
    return Module;
  };
}

loadComponent('abtests', 'test123');

Динамические удаленные модули на основе промисов

Как правило, удаленные модули настраиваются с использованием URL-адресов, как в этом примере:
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
      },
    }),
  ],
};
Но вы также можете передать промис в этот модуль, который будет зарезолвлен во время выполнения. Вы должны зарезолвить этот промис с помощью любого модуля, который соответствует интерфейсу get/init, описанному выше. Например, если вы хотите передать, какую версию fedarated модуля вы должны использовать, с помощью параметра запроса вы можете сделать что-то вроде следующего:
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        app1: `promise new Promise(resolve => {
      const urlParams = new URLSearchParams(window.location.search)
      const version = urlParams.get('app1VersionParam')
      // Эта часть зависит от того как вы планируете хостить
      // и версионировать ваши federated модули
      const remoteUrlWithVersion = 'http://localhost:3001/' + version + '/remoteEntry.js'
      const script = document.createElement('script')
      script.src = remoteUrlWithVersion
      script.onload = () => {
        // внедренный скрипт загружен и доступен через объект window
        // теперь можно зарезолвить Promise
        const proxy = {
          get: (request) => window.app1.get(request),
          init: (arg) => {
            try {
              return window.app1.init(arg)
            } catch(e) {
              console.log('remote container already initialized')
            }
          }
        }
        resolve(proxy)
      }
      // внедрим этот скрипт с src с версионированным remoteEntry.js
      document.head.appendChild(script);
    })
    `,
      },
      // ...
    }),
  ],
};
Обратите внимание, что при использовании этого API необходимо зарезолвить объект, содержащий API get/init.

Динамический publicPath

Установка publicPath

Можно разрешить хосту задавать publicPath удаленного модуля во время выполнения, предоставляя метод из этого удаленного модуля. Этот подход особенно полезен при подключении независимо развернутых дочерних приложений по подпути домена узла. Сценарий: У вас есть хост приложение, размещенное на https://my-host.com/app/*, и дочернее приложение, размещенное на https://foo-app.com. Дочернее приложение также монтируется на хост-домене, следовательно, ожидается, https://foo-app.com будет доступно через https://my-host.com/app/foo-app, а запросы https://my-host.com/app/foo-app/* перенаправляются https://foo-app.com/* через прокси-сервер. Пример:
// webpack.config.js (удаленный)

module.exports = {
  entry: {
    remote: './public-path',
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote', // это имя должно совпадать с именем точки входа
      exposes: ['./public-path'],
      // ...
    }),
  ],
};
public-path.js (remote)

export function set(value) {
  __webpack_public_path__ = value;
}
// src/index.js (host)

const publicPath = await import('remote/public-path');
publicPath.set('/your-public-path');

//bootstrap app  e.g. import('./bootstrap.js')

Вывод publicPath из скрипта

Можно вывести publicPath из тега script из document.currentScript.src и задать его с переменной __webpack_public_path__ во время выполнения. Пример:
// webpack.config.js (удаленный)

module.exports = {
  entry: {
    remote: './setup-public-path',
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote', // this name needs to match with the entry name
      // ...
    }),
  ],
};
setup-public-path.js (удаленный)

// вычислите publicPath и установите его в  __webpack_public_path__
__webpack_public_path__ = document.currentScript.src + '/../';
Существует также значение 'auto', доступное для output.publicPath, которое автоматически определяет publicPath для вас.

Как настроить Webpack, TypeScript и ts-loader

3 месяца назад·8 мин. на чтение

Здесть вы узнаете, как правильно настроить Webpack с использованием TypeScript и ts-loader. Шаг за шагом мы проведем вас через процесс интеграции TypeScript в ваш проект с помощью ts-loader, расскажем об основных конфигурационных параметрах и поделимся советами по оптимизации сборки.

Используемый во многих современных проектах, Webpack, является инструментом, который оптимизирует ресурсы приложения, чтобы они работали более эффективно на любом устройстве. Webpack помогает компилировать и объединять модули в единый файл, уменьшая количество HTTP-запросов и, как следствие, повышая производительность приложения. С помощью Webpack код TypeScript компилируется в файл JavaScript, который удобен для браузера. С помощью загрузчиков (loaders) Webpack вы также можете конвертировать файлы Sass и Less в один CSS файл. В этой статье мы узнаем, как использовать Webpack для компиляции TypeScript в JavaScript, объединять исходный код в один JavaScript файл и использовать source map исходного кода для отладки. Мы также рассмотрим, как использовать плагины Webpack. Чтобы следовать инструкциям в этом руководстве, вам потребуется следующее:
  • npm
  • Node.js (≥v8.x)
  • Редактор кода на ваш выбор (например, Visual Studio Code)
  • Базовые знания TypeScript

Содержание

  • Загрузчики Webpack
  • Настройка Webpack и TypeScript
  • Конфигурация Webpack
  • Конфигурация TypeScript
  • Конфигурация пакета
  • Создание HTML-страниц с помощью HtmlWebpackPlugin
  • Объединение CSS с MiniCssExtractPlugin
  • Минимизация CSS
  • Минификация JavaScript
  • Использование CopyWebpackPlugin
  • Отладка с помощью source map

Загрузчики Webpack

По умолчанию Webpack понимает только файлы JavaScript, рассматривая каждый импортированный файл как модуль. Webpack не может компилировать или объединять файлы, отличные от JavaScript, поэтому он использует загрузчики. Загрузчики сообщают Webpack, как компилировать и объединять статические ресурсы. Они используются для компиляции модулей TypeScript в JavaScript, обработки стилей приложений и даже линтинга кода с помощью ESLint. Некоторые загрузчики Webpack включают ts-loader, css-loader, style-loader и другие. Мы обсудим их позже в этом руководстве.

Настройка Webpack и TypeScript

Начнем с настройки нашего проекта. Во-первых, на вашем компьютере должен быть установлен TypeScript. Чтобы установить TypeScript глобально, используйте следующую команду:
npm install -g typescript
Глобальная установка TypeScript избавляет от необходимости устанавливать TypeScript каждый раз, когда вы начинаете новый проект. Далее мы установим пакеты webpack и ts-loader в качестве зависимостей в нашем проекте:
npm init -y
npm install -D webpack webpack-cli ts-loader webpack-dev-server

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

По умолчанию Webpack не нуждается в конфигурационном файле. Предполагается, что точкой входа для вашего проекта является src/index.js и выведет минимизированный и оптимизированный результат в dist/main.js. Если вы хотите использовать плагины или загрузчики, то вам нужно будет использовать конфигурационный файл Webpack, позволяющий указать, как Webpack будет работать с вашим проектом, какие файлы компилировать и где будет находиться выходной файл. Давайте добавим конфигурационный файл Webpack в наш проект. В корневой папке проекта создайте webpack.config.js со следующими конфигурациями:
const path = require('path');

module.exports = {
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.ts?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  devServer: {
    static: path.join(__dirname, "dist"),
    compress: true,
    port: 4000,
  },
};
Давайте рассмотрим некоторые параметры конфигурации Webpack. Во-первых, опция entry является отправной точкой для приложения, где Webpack начинает строить граф зависимостей. Webpack перейдет к другим модулям в зависимости от входного файла. Опция output указывает Webpack, куда сохранять бандлы (результаты сборки), и позволяет присвоить файлу имя. Наконец, опция module указывает Webpack, как обрабатывать модули с определенными правилами с помощью загрузчиков.

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

Конфигурационный файл TypeScript определяет, как TypeScript будет компилироваться в JavaScript, и определяет различные параметры компилятора, необходимые для транспиляции TypeScript. В корневой папке проекта создайте tsconfig.json и добавьте следующие конфигурации:
{
    "compilerOptions": {
        "noImplicitAny": true,
        "target": "ES5",
        "module": "ES2015"
    }
}
target — это версия JavaScript, в которую вы хотите транспилировать TypeScript, а module — это формат используемого оператора импорта. Вы можете установить модуль на CommonJS, ES6 или UMD, так как Webpack будет работать со всеми системами модулей.

Конфигурация проекта

Теперь нам нужно добавить сценарий Webpack, который будет запускать webpack.config.js файл для нас. Чтобы добавить сценарий Webpack, откройте package.json и добавьте следующие скрипты в опцию script:
  • "dev": "webpack-dev-server --mode development",
  • "build" : "webpack --mode production"
Файл package.json теперь будет содержать следующие параметры конфигурации:
{
  "name": "webpack-setup",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --mode development",
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^6.7.1",
    "html-webpack-plugin": "^5.5.0",
    "mini-css-extract-plugin": "^2.6.1",
    "ts-loader": "^9.4.1",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.11.1"
  }
}
Теперь давайте создадим простую программу TypeScript, которая будет вычитать два числа. Внутри папки src создайте index.ts и добавьте следующий код TypeScript:
import { subtract } from "./app";

function init() {
    const form = document.querySelector("form");
    form?.addEventListener("submit", submitHandler);
}

function submitHandler(e: Event) {
    e.preventDefault();
    const num1 = document.querySelector("input[name='firstnumber']") as HTMLInputElement;
    const num2 = document.querySelector("input[name='secondnumber']") as HTMLInputElement;
    const result = subtract(Number(num1.value), Number(num2.value));
    const resultElement = document.querySelector("p");
    if (resultElement) {
      resultElement.textContent = result.toString();
    }
}

init();
Затем создайте еще один файл app.ts и добавьте следующий код:
export function subtract(firstnumber: number, secondnumber: number): number {
  return firstnumber - secondnumber;
}
Запуск скрипта dev запустит приложение в режиме разработки:
npm run develop 
Запуск скрипта build запустит приложение в режиме для продакшен сборки:
npm run build
После выполнения команды build Webpack транспилирует два файла TypeScript в код JavaScript и сгенерирует bundle.js в папке dist.

Создание HTML-страниц с помощью HtmlWebpackPlugin

HtmlWebpackPlugin позволяет Webpack генерировать стандартную HTML-страницу, которая будет обслуживать сгенерированные файлы пакета. Когда имя файла пакета изменяется или хэшируется, HTMLWebpackPlugin обновляет имена файлов на HTML-странице. Во-первых, чтобы установить HtmlWebpackPlugin, выполните следующую команду:
npm install html-webpack-plugin --save-dev
Далее нам нужно импортировать и добавить HtmlWebpackPlugin в опцию плагина конфигурации Webpack следующим образом:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');

module.exports = {
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.ts?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      }
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new HtmlWebpackPlugin({
        title: 'our project', 
        template: 'src/custom.html' }) 
  ],
  devServer: {
    static: path.join(__dirname, "dist"),
    compress: true,
    port: 4000,
  },
};
Шаблон представляет собой пользовательский HTML-файл, сгенерированный HtmlWebpackPlugin для вставки в HTML-страницу. Чтобы создать пользовательский HTML-код, внутри папки src создайте custom.html и добавьте следующий HTML-код:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div class="cal">
      <center>
     <form><br>
      <p>Result : <span id="display"></span></p>
      <input type="number" class="input" placeholder="Enter first number" name="firstnumber" value="1" min="1" min="9" /><br>
      <input type="number" class="input" placeholder="Enter second number" name="secondnumber" value="1" min="1" min="9" /><br><br>
      <button type="submit" class="button">Subtract</button>
    </form>
  </center>
  </div>
  </body>
</html>
Вам не нужно включать скрипт или теги ссылок в пользовательский HTML. HtmlWebpackPlugin позаботится об этом, связав URL-адрес файла пакета со сгенерированной страницей. При запуске приложения в продакшен режиме файл index.html появится внутри папки dist.

Собираем CSS с MiniCSSExtractPlugin

css-loader подсказывает Webpack, как работать с CSS. Он интерпретирует @import и URL как import/require и резолвит их. css-loader позволяет Webpack скомпилировать все CSS файлы и конвертировать их в формат JavaScript. Объединение CSS-файлов с загрузчиком стилей приводит к тому, что стили HTML-страниц не отвечают на запросы до тех пор, пока bundle.js полностью не загружен. Загрузчик стилей внедряет CSS в DOM, но собранный JavaScript файл должен быть полностью загружен до внедрения стилей. Чтобы решить эту проблему, мы можем использовать MiniCssExtractPlugin. MiniCssExtractPlugin извлекает файлы CSS и объединяет их в один bundle.css файл. Это полезно для уменьшения размера ресурсов CSS и помогает избежать ненужных HTTP-запросов для их загрузки. Мы можем установить css-loader и MiniCssExtractPlugin, выполнив в терминале следующие команды:
npm install css-loader --save-dev
npm install mini-css-extract-plugin --save-dev
Теперь давайте добавим css-loader и MiniCssExtractPlugin в webpack.config.js файл. В верхней части webpack.config.js импортируйте модуль MiniCssExtractPlugin, используя приведенный ниже код:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
Затем мы добавим новое правило в свойство rules следующим образом:
…
{
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"]
}
…
Когда css-loader компилирует все CSS файлы в JavaScript, MiniCssExtractPlugin.loader загружает CSS в CSS бандл. Далее мы добавим MiniCssExtractPlugin в опцию плагина следующим образом:
plugins: [
  new HtmlWebpackPlugin({
    title: 'our project',
    template: 'src/custom.html'
  }),
  new MiniCssExtractPlugin({
    filename:"bundle.css"
  })
]
Теперь, когда мы настроили css-loader и MiniCssExtractPlugin, давайте создадим CSS-файл и импортируем его в index.ts. Внутри папки src создайте index.css и добавьте следующий CSS-код:
form {
    background-color: pink;
    margin-top: 100px;
    border-radius: 40px;
}
.cal {
    width: 550px;
    height: 300px;
    margin-left: 400px;
}
.button {
    border-radius: 10px;
    margin-top: 20px;
    margin-bottom: 20px;
}
.input {
    border-radius: 10px;
    margin-top: 40px;
}
Импортируйте CSS-стиль в index.ts следующим образом:
import styles "./main.css"
Запуск npm run build объединит CSS и применит его к index.html. Когда вы запускаете приложение в режиме разработки и открываете http://localhost:4000.

Минимизация CSS

Мы можем использовать css-minimizer-webpack-plugin, чтобы уменьшить размер файлов CSS, удалив неиспользуемые правила CSS и оставив только необходимые. css-minimizer-webpack-plugin находит все неиспользуемые стили. Затем этот плагин удалит эти неиспользуемые стили из вашего окончательного файла CSS, тем самым уменьшив его размер. Выполните приведенную ниже команду установки, чтобы установить css-minimizer-webpack-plugin:
npm install css-minimizer-webpack-plugin --save-dev
Добавим css-minimizer-webpack-plugin в конфигурацию Webpack. Во-первых, импортируйте плагин следующим образом:
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
Затем мы добавим новое свойство optimization в конфигурацию Webpack следующим образом:
optimization: {
  minimizer: [
    new CssMinimizerPlugin()
  ],
}
Когда мы запускаем команду npm run build, bundle.css будет минифицироваться, но bundle.js не будет. Стандартная минификация для bundle.js была переопределена параметром minimizer, который мы установили. Чтобы решить эту проблему, нам нужно минифицировать JavaScript с помощью TerserWebpackPlugin.

Минификация JavaScript

В текущей версии Webpack (на момент написания статьи 5.74.0) и более поздних, вам не нужно устанавливать TerserWebpackPlugin, так как он включен из коробки. Во-первых, мы должны импортировать TerserWebpackPlugin:
const TerserPlugin = require("terser-webpack-plugin");
Затем добавьте TerserPlugin в опцию минимизации следующим образом:
optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserPlugin()
    ],
  }
Если вы запустите скрипт npm run build и посмотрите на файлы в папке dist, вы увидите, что и JavaScript, и CSS минифицированы.

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

Мы можем настроить Webpack для копирования ресурсов приложения из папки c исходными файлами в папку сборки dist с помощью CopyWebpackPlugin. Этот плагин может копировать такие файлы, как изображения, видео и другие ресурсы, в папку dist. Установите CopyWebpackPlugin с помощью следующей команды:
npm install copy-webpack-plugin --save-dev
Теперь добавим CopyWebpackPlugin в конфигурацию Webpack. Импортируйте плагин следующим образом:
const CopyPlugin = require("copy-webpack-plugin");
Далее мы добавим CopyWebpackPlugin в опцию плагина. Свойство from — это папка, из которой мы будем копировать, а to — это папка в каталоге dist, в которую нужно скопировать все файлы:
// ...
plugins: [
  new HtmlWebpackPlugin({
    title: 'our project',
    template: 'src/custom.html'
  }),
  new MiniCssExtractPlugin({
    filename: "bundle.css"
  }),
  new CopyPlugin({
    patterns: [
      { from: "src/img", to: "img" }
    ]
  }),
]
// ...
Создайте новую папку img и добавьте в нее изображения. После выполнения команды npm run build образы будут скопированы в dist/img.

Отладка с помощью source map

Когда мы собираем пакет путем компиляции файлов TypeScript в файлы JavaScript (npm run build), нам может потребоваться отладить и протестировать код с помощью DevTools нашего браузера. При отладке кода инструментам разработки браузера вы заметите, что отображаются только собранные файлы. Всякий раз, когда в нашем коде TypeScript есть ошибка, она будет указана только в собранном файле, что затрудняет отслеживание ошибок в TypeScript для исправления. Тем не менее, с source map кода мы можем легко отлаживать TypeScript с помощью DevTools. Source map кода отображают исходный файл, что упрощает отладку TypeScript и исправление кода и минимизированного кода JavaScript. Файлы .map содержат сведения как об исходных файлах, так и о собранных файлах. DevTools использует этот файл для сопоставления исходного файла с собранным файлом. Чтобы сгенерировать .map для файлов пакета, нам нужно настроить как Webpack, так и TypeScript. В конфигурационном файле TypeScript добавьте sourceMap к параметру компилятора и присвойте ему значение true:
{
    "compilerOptions": {
        "noImplicitAny": true,
        "target": "ES5",
        "module": "ES2015",
        "sourceMap": true
    }
}
Далее мы добавим свойство devtool в конфигурацию Webpack и установим его в true, указав Webpack сгенерировать соответствующую карту исходного кода для каждого собранного файла:
module.exports = {
  devtool: 'source-map',
  // ...
}
Выполнив команду npm run build, вы сможете отлаживать исходный код напрямую.

Итоги

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