Можно ли генерировать Typescript интерфейсы из файлов с помощью загрузчика webpack?

Я пытаюсь создать загрузчик webpack, который преобразует файл, содержащий описание структур данных API, в набор интерфейсов TypeScript.

В моем конкретном случае файл JSON, но это в конечном счете не имеет значения - файл является только общим источником данных, описывающих взаимодействие между бэкэнд-серверами веб-приложений и интерфейсами (-ами). В моем MCVE ниже вы можете видеть, что файл JSON содержит пустой объект, чтобы подчеркнуть, как тип и содержимое файла не имеют значения для проблемы.

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

[at-loader]: Child process failed to process the request:  Error: Could not find file: '/private/tmp/ts-loader/example.api'.
ERROR in ./example.api
Module build failed: Error: Final loader didn't return a Buffer or String

Как я могу сгенерировать TypeScript код с помощью загрузчика webpack?

package.json

{
  "name": "so-example",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack"
  },
  "dependencies": {
    "awesome-typescript-loader": "^3.2.3",
    "typescript": "^2.6.1",
    "webpack": "^3.8.1"
  }
}

webpack.config.js

const path = require('path');

module.exports = {
  entry: './index.ts',
  output: {
    filename: 'output.js',
  },
  resolveLoader: {
    alias: {
      'my-own-loader': path.resolve(__dirname, "my-own-loader.js"),
    },
  },
  module: {
    rules: [
      {
        test: /\.api$/,
        exclude: /node_modules/,
        use: ["awesome-typescript-loader", "my-own-loader"],
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: "awesome-typescript-loader",
      },
    ]
  },
};

My-собственность- loader.js

module.exports = function(source) {
  return `
interface DummyContent {
    name: string;
    age?: number;
}
`;
};

index.ts

import * as foo from './example';

console.log(foo);

example.api

{}

Я понимаю, что существуют другие методы генерации кода. Например, я мог бы конвертировать мои файлы JSON в TypeScript с помощью некоторого инструмента сборки и проверить их. Я ищу более динамическое решение.


my-own-loader.js не экспортирует json, а строку.

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

Почему вам нужно определить интерфейсы TypeScript из json файла? Используются ли интерфейсы для компиляции TypeScript?

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

Ответ 1

Во-первых, престиж для предоставления MCVE. Это действительно интересный вопрос. Код, с которым я работал, чтобы собрать этот ответ, основан на указанном MCVE, и доступен здесь.

Отсутствует файл?

Это действительно бесполезное сообщение об ошибке. Файл явно находится в этом месте, но TypeScript откажется загружать все, что не имеет приемлемого расширения.

Эта ошибка по существу скрывает ошибку real, которая

TS6054: File 'c:/path/to/project/example.api' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts', '.js', '.jsx'.

Это можно проверить, взломав typescript.js и вручную добавив файл. Это уродливо, так как детективная работа часто (начинается с строки 95141 в версии 2.6.1):

for (var _i = 0, rootFileNames_1 = rootFileNames; _i < rootFileNames_1.length; _i++) {
    var fileName = rootFileNames_1[_i];
    this.createEntry(fileName, ts.toPath(fileName, this.currentDirectory, getCanonicalFileName));
}
this.createEntry("c:/path/to/project/example.api", ts.toPath("c:/path/to/project/example.api", this.currentDirectory, getCanonicalFileName));

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

Возможное исправление

Я не видел способа сделать это с помощью awesome-typescript-loader, но если вы захотите использовать ts-loader, вы можете сгенерировать TypeScript из файлов с произвольными расширениями, скомпилировать этот TypeScript и ввести его в output.js.

ts-loader имеет параметр appendTsSuffixTo, который может использоваться для работы с известным боль в расширении файла. Конфигурация вашего webpack может выглядеть как-то как это, если вы пошли по этому маршруту:

rules: [
  {
    test: /\.api$/,
    exclude: /node_modules/,
    use: [
      { loader: "ts-loader" },
      { loader: "my-own-loader" }
    ]
  },
  {
    test: /\.tsx?$/,
    exclude: /node_modules/,
    loader: "ts-loader",
    options: {
      appendTsSuffixTo: [/\.api$/]
    }
  }
]

Примечание по интерфейсам и DX

Интерфейсы стираются компилятором. Это можно продемонстрировать, выполнив tsc против чего-то вроде this

interface DummyContent {
    name: string;
    age?: number;
}

против. this

interface DummyContent {
    name: string;
    age?: number;
}

class DummyClass {
    printMessage = () => {
        console.log("message");
    }
}

var dummy = new DummyClass();
dummy.printMessage();

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

Разработчики, вероятно, должны их выписать, поэтому их IDE есть что-то, чтобы утопить свои зубы. Вы можете добавить *.api.ts в .gitignore и оставить их вне репозитория, но я подозреваю, что они должны будут существовать в рабочих пространствах разработчиков.

Например, в моем образце repo новому разработчику придется запускать npm install (как обычно) и npm run build (для создания интерфейсов в их локальной среде), чтобы избавиться от все их красные squigglies.

Ответ 2

Если ваш API следует спецификации swagger, вы можете использовать пакет npm swagger-ts-generator для создания TypeScript файлов из него:

Генератор кодов TypeScript

Node для генерации кода TypeScript для Angular (2 и выше) на основе метаданных Webapi в формате Swagger v2.

В принципе, вы даете ему URL-адрес swagger, и он генерирует TypeScript. Примеры для Gulp, но они должны хорошо переноситься в WebPack:

var swagger = {
    url: 'http://petstore.swagger.io/v2/swagger.json',
    //url: 'http://127.0.0.1/ZIB.WebApi.v2/swagger/docs/v1',
    swaggerFile: folders.swaggerFolder + files.swaggerJson,
    swaggerFolder: folders.swaggerFolder,
    swaggerTSGeneratorOptions: {
        modelFolder: folders.srcWebapiFolder,
        enumTSFile: folders.srcWebapiFolder + 'enums.ts',
        enumLanguageFiles: [
            folders.srcLanguagesFolder + 'nl.json',
            folders.srcLanguagesFolder + 'en.json',
        ],
        modelModuleName: 'webapi.models',
        enumModuleName: 'webapi.enums',
        enumRef: './enums',
        namespacePrefixesToRemove: [
        ],
        typeNameSuffixesToRemove: [
        ]
    }
}