Как правильно экспортировать SVG в библиотеку компонентов React с помощью TypeScript

Я занимаюсь извлечением основных компонентов нашего приложения React в отдельную библиотеку компонентов для использования в других клиентских приложениях. Эти компоненты используют значки SVG, которые уже работают в оригинальном приложении с помощью babel-loader.

Однако, поскольку компоненты написаны на Typescript, то, насколько я понял, мне нужно ts-loader для корректной работы библиотеки, хотя проверить это я не смог, т.к. не могу даже скомпилировать библиотеку. Чтобы иметь возможность экспортировать SVG, я дополнительно включил babel-loader с babel-plugin-named-asset-import.

Это моя структура проекта:

.
├── config
│   └── webpack.config.js
├── global.d.ts
├── index.ts
├── package.json
├── package-lock.json
├── src
│   ├── assets
│   │   └── icons
│   │       ├── index.d.ts
│   │       ├── index.ts
│   │       └── upload.svg
│   ├── bar
│   │   └── index.ts
│   └── foo
│       ├── index.ts
│       └── Stuff.ts
└── tsconfig.json

веб-пакет.config.json:

const path = require("path");

module.exports = {
  entry: "./index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: {
          loader: "ts-loader",
        },
        exclude: /node_modules/,
      },
      {
        test: /\.(js|mjs|jsx|ts|tsx)$/,
        loader: require.resolve("babel-loader"),
        options: {
          customize: require.resolve(
            "babel-preset-react-app/webpack-overrides"
          ),

          plugins: [
            [
              require.resolve("babel-plugin-named-asset-import"),
              {
                loaderMap: {
                  svg: {
                    ReactComponent: "@svgr/webpack?-svgo,+ref![path]",
                  },
                },
              },
            ],
          ],
        },
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
};

tsconfig.json:

{
  "include": ["./global.d.ts", "./index.ts", "src/**/*"],

  "compilerOptions": {
    "allowJs": false,
    "jsx": "react",
    "esModuleInterop": true,
    "downlevelIteration": true,
    "declaration": true,
    "noEmit": false,
    "outDir": "dist",
    "moduleResolution": "node",
    "module": "commonjs",
    "target": "es5",
    "lib": ["es5", "es6", "es7", "es2017", "dom"],
    "sourceMap": true,
    "typeRoots": ["./node_modules/@types", "./@types"],
    "resolveJsonModule": true
  }
}

global.d.ts — я выяснил, что часть declare module здесь точно не действует, я получаю тот же результат и с ней, и без нее.

import 'jest-dom/extend-expect';

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

./src/assets/icons/index.ts должен отвечать за экспорт всех доступных значков в другие файлы для их импорта.

export { ReactComponent as Add } from "./add.svg";

./src/demo/index.ts содержит код, который «использует» этот SVG:

import { Add } from "../assets/icons";

export default {
  Add,
};

index.ts в корневом каталоге содержит тот же контент, чтобы продемонстрировать несоответствие моей проблемы:

import { Add } from "../assets/icons";

export default {
  Add,
};

Пока все хорошо, подумал я, поэтому я запустил webpack с npx webpack --config ./config/webpack.config.js только для того, чтобы все это вышло из строя с этим сообщением об ошибке:

ERROR in C:\work\tstest\src\demo\index.ts
[tsl] ERROR in C:\work\tstest\src\demo\index.ts(1,21)
      TS2306: File 'C:/work/tstest/src/assets/icons/index.ts' is not a module.

Это оставляет меня совершенно сбитым с толку несколькими вопросами:

  1. Как рассматриваемый файл не является модулем? Было бы здорово узнать, что мешает ts-loader правильно его распознать.
  2. ./src/demo/index.ts и ./index.ts на 100% идентичны по содержанию. Тем не менее, компиляция работает для одного файла, но не для другого. Почему так и где такое поведение задокументировано?
  3. Что я делаю не так?
  4. Как это правильнее осуществить?

Я предполагаю, что это происходит потому, что после того, как babel-loader сделал свое дело, ./src/assets/icons/index.ts выглядит не так, как хочет ts-loader, но это не совсем объясняет, как два файла с одинаковым содержимым обрабатываются по-разному.


person TheKvist    schedule 19.05.2020    source источник


Ответы (1)


Это был типичный случай, когда я не понимал используемый инструментарий. Проблема была в tsconfig.json по адресу:

"include": ["./global.d.ts", "./index.ts", "src/**/*"]

Что включает в себя все исходные файлы проекта при запуске tsc. Это привело к тому, что tsc пытался скомпилировать все исходные файлы одновременно всякий раз, когда веб-пакет вызывал ts-loader, включая те файлы, которые еще не были обработаны @svgr/webpack, что приводило к ошибке.

Решение было только

"include": ["./global.d.ts", "src/**/*.d.ts"]

Таким образом, webpack отвечает за компиляцию отдельных исходных файлов, включенных из точки входа, в соответствии с настроенной логикой, а tsc видит только те файлы определений, которые были обработаны webpack.

person TheKvist    schedule 20.05.2020