Сохранение метаданных с помощью Typescript, Babel 7, декораторов

Я использую TypeORM с Babel 7 и Typescript, и кажется, что метаданные отсутствуют в скомпилированном коде. Можно ли что-то с этим сделать или это ограничение использования Babel?

Ошибка

ColumnTypeUndefinedError: Column type for Photo#isPublished is not defined and cannot be guessed. Make sure you have turned on an "emitDecoratorMetadata": true option in tsconfig.json. Also make sure you have imported "reflect-metadata" on top of the main entry file in your application (before any entity imported).If you are using JavaScript instead of TypeScript you must explicitly provide a column type. at new ColumnTypeUndefinedError

Photo.js

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'

@Entity()
export class Photo {

    @PrimaryGeneratedColumn()
    id: number


    @Column()
    isPublished: boolean
}

код orm

import 'reflect-metadata'
import {createConnection} from 'typeorm'
import {Photo} from './entities/Photo'

createConnection({
    type: 'postgres',
    host: 'localhost',
    port: 5432,
    username: 'postgres',
    password: 'root',
    database: 'test',
    entities: [
        Photo
    ],
    synchronize: true,
    logging: false
}).then(connection => {
    // here you can start to work with your entities
}).catch(error => console.info(error))

package.json

{
    "name": "typescript-babel-node",
    "version": "0.1.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "dev": "run-p -r dev:run type-check:watch",
        "dev:run": "nodemon --exec babel-node --extensions '.ts,.js' src/index.js",
        "build": "babel src -d build --extensions '.ts,.js' src/index.js",
        "start": "node build/index.js",
        "type-check:watch": "tsc --watch",
        "test": "jest --watch"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "@types/node": "^10.12.0",
        "express": "^4.16.4",
        "pg": "^7.6.0",
        "ramda": "^0.25.0",
        "reflect-metadata": "^0.1.12",
        "typeorm": "^0.2.8"
    },
    "devDependencies": {
        "@babel/cli": "^7.1.2",
        "@babel/core": "^7.1.2",
        "@babel/node": "^7.0.0",
        "@babel/plugin-proposal-class-properties": "^7.1.0",
        "@babel/plugin-proposal-decorators": "^7.1.2",
        "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
        "@babel/plugin-syntax-import-meta": "^7.0.0",
        "@babel/preset-env": "^7.1.0",
        "@babel/preset-typescript": "^7.1.0",
        "@types/express": "^4.16.0",
        "@types/jest": "^23.3.7",
        "@types/ramda": "^0.25.39",
        "babel-core": "^7.0.0-bridge.0",
        "babel-jest": "^23.6.0",
        "babel-plugin-module-resolver": "^3.1.1",
        "dot-env": "0.0.1",
        "eslint": "^5.7.0",
        "eslint-config-standard": "^12.0.0",
        "eslint-plugin-import": "^2.14.0",
        "eslint-plugin-node": "^7.0.1",
        "eslint-plugin-promise": "^4.0.1",
        "eslint-plugin-standard": "^4.0.0",
        "jest": "^23.6.0",
        "nodemon": "^1.18.5",
        "npm-run-all": "^4.1.3",
        "regenerator-runtime": "^0.12.1",
        "source-map-loader": "^0.2.4",
        "ts-jest": "^23.10.4",
        "typescript": "^3.1.3",
        "webpack": "^4.23.0",
        "webpack-cli": "^3.1.2"
    }
}

Обновлено: Подойдя к этой проблеме через некоторое время, я смог найти для этого еще один плагин Babel, кроме упомянутого в принятом ответе: babel-plugin-transform-typescript-metadata Кажется, работает с TypeORM и Nest.

разве ваш основной входной файл не index.js? Это должно быть то место, где "отражение-метаданные" импортируются в

Oluwafemi Sule 26.10.2018 22:28

Это невозможно, поскольку реализован текущий плагин babel, он просто удаляет типы. Вы бы открыли собственный плагин babel? (Не говорю, что напишу один, но было бы интересно попробовать :))

Titian Cernicova-Dragomir 21.01.2019 11:31

@ TitianCernicova-Dragomir: Я не вижу причин, почему бы не использовать собственный плагин babel. Однако не следует изобретать велосипед и заново реализовывать всю поддержку TS ... :)

Daniel Hilgarth 21.01.2019 14:00

@DanielHilgarth немного поигрался с этим, у меня почти есть доказательство концепции, с некоторыми оговорками. Информация о типе отсутствует, поэтому, если мы хотим передать тип, требуется аннотация (если она отсутствует, может возникнуть ошибка). Декораторы параметров недоступны (вавилонская версия декораторов не поддерживает это, насколько я могу судить). Поскольку нет возможности отличить класс от интерфейса, мне нужно выдать что-то вроде TypeAnnotationType || Object. Если TypeAnnotationType является интерфейсом, он не будет определен во время выполнения и будет использоваться Object. давайте поговорим подробнее о gitter.

Titian Cernicova-Dragomir 22.01.2019 14:02
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
15
4
4 234
2

Ответы 2

К сожалению, я достаточно уверен, что это ограничение использования Babel с Typescript. Что делает Babel, так это просто убирает типизацию машинописного текста, а затем обрабатывает код как JavaScript. Это означает, что babel не заботится о вашем tsconfig.jsonвообще, а значит, и о emitDecoratorMetadata.

Так что, к сожалению, если вам нужны метаданные декоратора, вам придется придерживаться tsc.

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

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

Ограничения:

  • Если аннотации типа нет, у нас нет типа для записи
  • Если у нас есть ссылка на тип, мы можем использовать только имя типа, мы не можем проверить, относится ли ссылка к интерфейсу, как псевдоним типа, класс или перечисление. На практике это означает, что:
    • Если тип является интерфейсом или псевдонимом типа, имя типа будет undefined во время выполнения, чтобы избежать неопределенного, мы можем сделать type || Object по умолчанию для объекта, если тип не имеет связанного значения времени выполнения.
    • Если тип является перечислением, Typescript будет записывать Number или String в метаданные, в зависимости от типа перечисления. Поскольку мы записываем имя типа в метаданные, это означает, что вы получите объект-контейнер перечисления внутри метаданных.

Сериализация типов может быть скопирована из самого компилятора машинописного текста с минимальными изменениями (а на самом деле это всего две функции serializeTypeNode и serializeTypeList, всего около 150 строк кода).

Для этого примера класса мы получаем следующие результаты:

declare var decorator: any;

interface ISampleInterface {

}

enum Flags { One }
class OtherClass {}


type ArrayAlias = number[]

class Test {
    @decorator
    untypedProp; // no design:type
    @decorator
    nrProp: number // Number as expected
    @decorator
    strProp: string // String as expected
    @decorator
    boolProp: boolean // Boolean as expected

    @decorator
    nrPropUndefined: number | undefined // Number as expected
    @decorator
    strPropUndefined: string | undefined // String as expected
    @decorator
    boolPropUndefined: boolean | undefined // Boolean as expected

    @decorator
    arrayProp: number[]

    // Type references
    @decorator
    classRefProp: OtherClass; // OtherClass || Object  = Object since OtherClass is still a class at runtime

    @decorator
    interfaceRefProp: ISampleInterface;  // ISampleInterface || Object  = Object since ISampleInterface is undefined at runtime

    @decorator
    enumRefProp: Flags; // Flags || Object = Flags since Flags exists as a value at runtime, here TS would have written Number/String

    @decorator
    typeAliasProp: ArrayAlias; // ArrayAlias || Object = Object since ArrayAlias does not exist t runtime and in Babel swe have no idea ArrayAlias is actually an array

    @decorator
    selfClassRefProp: Test; // Test || Object  = Object since Babel puts decorators instantiation before class definition, this is a quirk, this may be fixable
}

Фактический код плагина не очень большой (за вычетом методов, скопированных из версии TS):

export default declare((api: typeof import('@babel/core'), { jsxPragma = "React" }): PluginObj => {
  api.assertVersion(7);

  return {
    name: "transform-typescript-decorator-metadata",
    inherits: syntaxTypeScript,

    visitor: {
      ClassDeclaration(path) {
        var node = path.node;
        for (const field of node.body.body) {
          if (field.type !== "ClassProperty") continue;

          if (field.typeAnnotation && field.typeAnnotation.type === "TSTypeAnnotation" && field.decorators && field.decorators.length > 0) {
            const key = field.key as t.Identifier;
            const serializedType = serializeTypeNode(field.typeAnnotation.typeAnnotation);
            field.decorators.push(decorator(
              t.callExpression(
                t.memberExpression(t.identifier("Reflect"), t.identifier("metadata")), [
                  t.stringLiteral("design:type"),
                  t.logicalExpression("||", serializedType, createIdentifier("Object"))
                ])
            ))
          }
        }
      },
    }
  };
});

Вы можете найти полный код плагина и рабочий образец здесь

С другой стороны, порядок плагинов имеет значение. Если @babel/plugin-proposal-class-properties появится перед нашим плагином, он сотрет все свойства, и у нашего плагина больше не будет информации для генерации декораторов. Это .babelrc, с которым я тестировал, и он работает, мне не удалось его использовать с какой-либо другой конфигурацией (но я не могу сказать, что я так старался)

  {
    "env": {},
    "ignore": [],
    "plugins": [
      "../plugin/plugin.js",
      ["@babel/plugin-proposal-decorators", { "legacy": true }],
      ["@babel/plugin-proposal-class-properties", { "loose": true }],
      "babel-plugin-transform-es2015-modules-commonjs"
    ],
    "presets": [
      "@babel/preset-typescript"
    ]
  }

Спасибо за отличный ответ! Исходя из вашей идеи, я также разработал плагин, пытаясь заполнить пробел (используя typeof, поскольку Type || Object небезопасен, если включено использование строгого, добавляя декораторы параметров, гарантируя, что операторы импорта не удаляются). Здесь вы можете найти плагин babel

leonardfactory 25.03.2019 17:52

@leonardfactory, этот плагин - чертовски отличная работа ?

Michal 25.07.2019 03:04

Другие вопросы по теме