解决TypeScript装饰器(decorators)和JavaScript装饰器编译出的代码不同

389 阅读2分钟

Input Code

function observable() {}

class Example {
    @observable()
    toolbar = 'example';
}

export default Example;

JavaScript output

function observable() {}

var Example = (_dec = observable(), (_class = (_temp = function Example() {
  (0, _classCallCheck2["default"])(this, Example);
  (0, _initializerDefineProperty2["default"])(this, "toolbar", _descriptor, this);
}, _temp), (_descriptor = (0, _applyDecoratedDescriptor2["default"])(_class.prototype, "toolbar", [_dec], {
  configurable: true,
  enumerable: true,
  writable: true,
  initializer: function initializer() {
    return 'example';
  }
})), _class));

exports["default"] = Example;

TypeScript output

function observable() {}

var Example = function Example() {
  (0, _classCallCheck2["default"])(this, Example);
  this.toolbar = 'example';
};

__decorate([observable(), __metadata("design:type", Object)], Example.prototype, "toolbar", void 0);

exports["default"] = Example;

我们发现两者编译出的装饰器属性代码并不相同。

现在我们创建一个javascript类ExampleEnhance,继承typescript类Example,并重写装饰器属性toolbar

// This is javascript code, but class Example is typescript
class ExampleEnhance extends Example {
    @observable()
    toolbar = 'YY';
}

这时,我们期望toolbar的值为'YY',但是我们发现它的值仍然是'example'。 怎么办? 我们有没有办法让TypeScriptJavaScript装饰器属性编译出来的代码相同呢?

我们从TypeScript官方得到的回复是

TypeScript's decorators currently work differently from ES decorators, hence the experimental flag. If you need ES decorators you'll need to continue to process the code with Babel.

Because Typescript's decorator specification is not quite the same as anyone else's, they are always transformed.

You cannot do this right now unless you remove Typescript from the chain entirely. It always transforms decorators, leaving nothing left for Babel to transform.

因此,要让两者编译出相同的代码,我们必须要把typescript loader(比如awesome-typescript-loader, ts-loader)移除,而只使用babel来转换这两类代码。这里我们需要引入@babel/plugin-transform-typescript插件来处理TypeScript

#配置babel

Install

npm install @babel/plugin-transform-typescript

babel.config.js

module.exports = {
    presets: ['@babel/preset-typescript', '@babel/preset-react', '@babel/preset-env', 'mobx'],
    plugins: [
        ['@babel/plugin-transform-typescript', { allowNamespaces: true }],
        // ... other
    ]
}

webpack.config.js

module.exports = {
    // ...
    resolve: {
        // Add '.ts' and '.tsx' as resolvable extensions.
        extensions: ['.js', '.ts', '.tsx']
    },

    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: 'babel-loader',
            },
        ]
    }
};

那么,如果改成这样了,TypeScript的类型检测怎么办呢?不是相当于废了吗? 你可以使用 TypeScript-Babel-Starterfork-ts-checker-webpack-plugin来启用TypeScript类型检测。

我们这里来说明一下如何使用fork-ts-checker-webpack-plugin

#配置TypeScript类型检查器

Install

npm install fork-ts-checker-webpack-plugin fork-ts-checker-notifier-webpack-plugin

webpack.config.js

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');

module.exports = {
    // ...
    plugins: [
        new ForkTsCheckerWebpackPlugin({
            // 将async设为false,可以阻止Webpack的emit以等待类型检查器/linter,并向Webpack的编译添加错误。
            async: false
        }),
        // 将TypeScript类型检查错误以弹框提示
        // 如果fork-ts-checker-webpack-plugin的async为false时可以不用
        // 否则建议使用,以方便发现错误
        new ForkTsCheckerNotifierWebpackPlugin({
            title: 'TypeScript',
            excludeWarnings: true,
            skipSuccessful: true,
        }),
    ]
};