From f9032403eee0dd37e8cb6420f83f1674af8746cd Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Sat, 19 Oct 2019 12:44:20 -0400 Subject: [PATCH] Fix issues when using colocated component's with decorators. The changes made in f9053bc81 added the ability to detect `export { Foo as default }` along side the ability to detect `export default Foo`. When ran in isolation (or with the `@babel/plugin-transform-typescript`) this works fine, but in common scenarios without typescript support it failed. The cause of the failure is a bit difficult to grok, but _basically_ it is because when our `ExportDefaultDeclaration` hook is hit, we add the `setComponentTemplate` invocation. Then **subsequently** another plugin changes from `export default Foo` to `export { Foo as default }` (the combination of `@babel/plugin-proposal-decorators` and `@babel/plugin-proposal-class-fields` does this), and we run the new `ExportNamedDeclaration` which causes us to add `setComponentTemplate` invocation **_again_**. The fix added here ensures that _only **one**_ `setComponentTemplate` invocation will be added (even if we see both `export default Foo` and `export { Foo as default }` during traversal). --- lib/colocated-babel-plugin.js | 6 ++- node-tests/colocated-babel-plugin-test.js | 60 +++++++++++++++++++++++ package.json | 4 ++ yarn.lock | 21 +++++++- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/lib/colocated-babel-plugin.js b/lib/colocated-babel-plugin.js index d8280766..559c06af 100644 --- a/lib/colocated-babel-plugin.js +++ b/lib/colocated-babel-plugin.js @@ -22,10 +22,11 @@ module.exports = function(babel) { }, ExportDefaultDeclaration(path, state) { - if (!state.colocatedTemplateFound) { + if (state.colocatedTemplateFound !== true || state.setComponentTemplateInjected === true) { return; } + state.setComponentTemplateInjected = true; let defaultExportDeclaration = path.node.declaration; let setComponentTemplateMemberExpression = makeSetComponentTemplateMemberExpression(); let colocatedTemplateIdentifier = makeColocatedTemplateIdentifier(); @@ -63,12 +64,13 @@ module.exports = function(babel) { }, ExportNamedDeclaration(path, state) { - if (!state.colocatedTemplateFound) { + if (state.colocatedTemplateFound !== true || state.setComponentTemplateInjected === true) { return; } let defaultSpecifier = path.node.specifiers.find(spec => spec.exported.name === 'default'); if (defaultSpecifier) { + state.setComponentTemplateInjected = true; path.parent.body.push( t.expressionStatement( t.callExpression(makeSetComponentTemplateMemberExpression(), [ diff --git a/node-tests/colocated-babel-plugin-test.js b/node-tests/colocated-babel-plugin-test.js index 52a348b6..f438345d 100644 --- a/node-tests/colocated-babel-plugin-test.js +++ b/node-tests/colocated-babel-plugin-test.js @@ -4,10 +4,70 @@ const assert = require('assert'); const babel = require('@babel/core'); const { stripIndent } = require('common-tags'); const ColocatedBabelPlugin = require('../lib/colocated-babel-plugin'); +const DecoratorsPlugin = [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }]; +const ClassPropertiesPlugin = [ + require.resolve('@babel/plugin-proposal-class-properties'), + { loose: true }, +]; +const RuntimePlugin = [ + require.resolve('@babel/plugin-transform-runtime'), + { + version: require('@babel/runtime/package').version, + regenerator: false, + useESModules: true, + }, +]; describe('ColocatedBabelPlugin', function() { this.slow(500); + it('can be used with decorators', function() { + let { code } = babel.transformSync( + stripIndent` + import Component from '@glimmer/component'; + const __COLOCATED_TEMPLATE__ = 'ok'; + + export default class MyComponent extends Component { + @tracked data = null; + }; + `, + { plugins: [RuntimePlugin, ColocatedBabelPlugin, DecoratorsPlugin, ClassPropertiesPlugin] } + ); + + assert.strictEqual( + code, + stripIndent` + import _initializerDefineProperty from "@babel/runtime/helpers/esm/initializerDefineProperty"; + import _applyDecoratedDescriptor from "@babel/runtime/helpers/esm/applyDecoratedDescriptor"; + import _initializerWarningHelper from "@babel/runtime/helpers/esm/initializerWarningHelper"; + + var _class, _descriptor, _temp; + + import Component from '@glimmer/component'; + const __COLOCATED_TEMPLATE__ = 'ok'; + let MyComponent = (_class = (_temp = class MyComponent extends Component { + constructor(...args) { + super(...args); + + _initializerDefineProperty(this, "data", _descriptor, this); + } + + }, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "data", [tracked], { + configurable: true, + enumerable: true, + writable: true, + initializer: function () { + return null; + } + })), _class); + export { MyComponent as default }; + ; + + Ember._setComponentTemplate(__COLOCATED_TEMPLATE__, MyComponent); + ` + ); + }); + it('sets the template for non-class default exports', function() { let { code } = babel.transformSync( stripIndent` diff --git a/package.json b/package.json index ca15f1e4..8cb7ef04 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,11 @@ }, "devDependencies": { "@babel/core": "^7.6.4", + "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/plugin-proposal-decorators": "^7.6.0", + "@babel/plugin-transform-runtime": "^7.6.2", "@babel/plugin-transform-typescript": "^7.6.3", + "@babel/runtime": "^7.6.3", "@ember/optional-features": "^1.0.0", "babel-plugin-debug-macros": "^0.3.3", "broccoli-merge-trees": "^3.0.2", diff --git a/yarn.lock b/yarn.lock index e59e14f1..f6702d9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -273,7 +273,7 @@ "@babel/helper-remap-async-to-generator" "^7.1.0" "@babel/plugin-syntax-async-generators" "^7.2.0" -"@babel/plugin-proposal-class-properties@^7.1.0", "@babel/plugin-proposal-class-properties@^7.3.4": +"@babel/plugin-proposal-class-properties@^7.1.0", "@babel/plugin-proposal-class-properties@^7.3.4", "@babel/plugin-proposal-class-properties@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz#a974cfae1e37c3110e71f3c6a2e48b8e71958cd4" integrity sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A== @@ -281,7 +281,7 @@ "@babel/helper-create-class-features-plugin" "^7.5.5" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-proposal-decorators@^7.3.0": +"@babel/plugin-proposal-decorators@^7.3.0", "@babel/plugin-proposal-decorators@^7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.6.0.tgz#6659d2572a17d70abd68123e89a12a43d90aa30c" integrity sha512-ZSyYw9trQI50sES6YxREXKu+4b7MAg6Qx2cvyDDYjP2Hpzd3FleOUwC9cqn1+za8d0A2ZU8SHujxFao956efUg== @@ -597,6 +597,16 @@ resolve "^1.8.1" semver "^5.5.1" +"@babel/plugin-transform-runtime@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.6.2.tgz#2669f67c1fae0ae8d8bf696e4263ad52cb98b6f8" + integrity sha512-cqULw/QB4yl73cS5Y0TZlQSjDvNkzDbu0FurTZyHlJpWE5T3PCMdnyV+xXoH1opr1ldyHODe3QAX3OMAii5NxA== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + resolve "^1.8.1" + semver "^5.5.1" + "@babel/plugin-transform-shorthand-properties@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" @@ -731,6 +741,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.3.tgz#935122c74c73d2240cafd32ddb5fc2a6cd35cf1f" + integrity sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.1.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"