typed-ember / ember-cli-typescript

Use TypeScript in your Ember.js apps!
https://docs.ember-cli-typescript.com
MIT License
363 stars 99 forks source link

Component disappear silently when using decorators #906

Closed HenryVonfire closed 5 years ago

HenryVonfire commented 5 years ago

ember version

ember-cli: 3.13.1
node: 10.11.0 os: win32 x64

tsc version

Version 3.1.1

ember-cli-typescript and ember-cli-typescript-blueprints versions

ember-cli-typescript@3.0.0 ember-cli-typescript-blueprints@3.0.0

Reproduction Case

As @dfreeman suggested me, here is a reproduction of the unexpected behaviour I encountered with a new octane app:

Link: repository

The repository in the link has the bare minimum to reproduce the unexpected behaviour. It's a new octane project with a glimmer component using colocation. The component works as long as no decorators are used inside. If a single decorator is used (for example it has a @tracked property), then the component is not rendered without throwing any error.

Removing the files and packages added by ember-cli-typescript makes the component work with decorators.

dfreeman commented 5 years ago

Thanks so much for putting together that reproduction, @HenryVonfire! I'm traveling the next few days, but will pull it down and take a look as soon as I get the opportunity.

dfreeman commented 5 years ago

Ok, this is pretty interesting:

Output without @babel/plugin-transform-typescript ```js define("colocation-bug/components/test-comp", ["exports", "@glimmer/component"], function (_exports, _component) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; var _class, _descriptor, _temp; function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; } function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and set to use loose mode. ' + 'To use proposal-class-properties in spec mode with decorators, wait for ' + 'the next major version of decorators in stage 2.'); } const __COLOCATED_TEMPLATE__ = Ember.HTMLBars.template( /* Hi! I'm TestComp */ { id: "Fz8j9CRQ", block: "{\"symbols\":[],\"statements\":[[0,\"Hi! I'm TestComp\"]],\"hasEval\":false}", meta: { moduleName: "colocation-bug/components/test-comp.hbs" } }); let TestCompComponent = (_class = (_temp = class TestCompComponent extends _component.default { constructor(...args) { super(...args); _initializerDefineProperty(this, "testProperty", _descriptor, this); } }, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "testProperty", [Ember._tracked], { configurable: true, enumerable: true, writable: true, initializer: null })), _class); _exports.default = TestCompComponent; Ember._setComponentTemplate(__COLOCATED_TEMPLATE__, TestCompComponent); }); ```
Output with @babel/plugin-transform-typescript ```js define("colocation-bug/components/test-comp", ["exports", "@glimmer/component"], function (_exports, _component) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; var _class, _descriptor, _temp; function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; } function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and set to use loose mode. ' + 'To use proposal-class-properties in spec mode with decorators, wait for ' + 'the next major version of decorators in stage 2.'); } const __COLOCATED_TEMPLATE__ = Ember.HTMLBars.template( /* Hi! I'm TestComp */ { id: "Fz8j9CRQ", block: "{\"symbols\":[],\"statements\":[[0,\"Hi! I'm TestComp\"]],\"hasEval\":false}", meta: { moduleName: "colocation-bug/components/test-comp.hbs" } }); let TestCompComponent = (_class = (_temp = class TestCompComponent extends _component.default { constructor(...args) { super(...args); _initializerDefineProperty(this, "testProperty", _descriptor, this); } }, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "testProperty", [Ember._tracked], { configurable: true, enumerable: true, writable: true, initializer: null })), _class); _exports.default = TestCompComponent; }); ```

The only difference I see is the _setComponentTemplate line. I can't think of any reason the TS syntax plugin should be affecting whether colocated-babel-plugin would find the default export (since it doesn't touch module syntax at all, afaik). @rwjblue do you have any thoughts on what might be happening?

dfreeman commented 5 years ago

Well. 🤦‍♂

Stopped in the debugger after the file's been processed we have this:

var _class, _descriptor, _temp;

const __COLOCATED_TEMPLATE__ = Ember.HTMLBars.template(/* snip */);

import Component from "@glimmer/component";
let TestCompComponent = (_class = (_temp = class TestCompComponent extends Component {
  /* snip */
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "testProperty", [Ember._tracked], {
  configurable: true,
  enumerable: true,
  writable: true,
  initializer: null
})), _class);
export { TestCompComponent as default };

That last line would explain why the ExportDefaultDeclaration visitor is never hit 😭

rwjblue commented 5 years ago

Great work @HenryVonfire & @dfreeman, we can fix things up in the babel transform. Will try to get that done today...

dfreeman commented 5 years ago

@rwjblue I'm working on it now—just getting some tests in place 🙂

rwjblue commented 5 years ago

OK, awesome thank you!

HenryVonfire commented 5 years ago

@dfreeman thanks a lot for looking into this. It's amazing how fast you found the bug!

rwjblue commented 5 years ago

Thanks to @dfreeman, @HenryVonfire, and @josemarluedke, this should now be fixed and released in v4.0.7.

Please test / confirm...

josemarluedke commented 5 years ago

I just confirmed that v4.0.7 did fix the issues I have been seeing. Thanks, @dfreeman, @HenryVonfire, @rwjblue.

HenryVonfire commented 5 years ago

I can confirm it works too. I'll close this issue. Thanks @dfreeman, @rwjblue, and @josemarluedke