Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide TypeScript compilation support when using ember-cli-typescript@4 or higher. #314

Merged
merged 27 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9e5baac
Include decorator and class field plugins after TypeScript, if present
dfreeman Dec 12, 2019
f602be4
feat: add TypeScript tranform when e-c-ts >= 4.0.0-alpha
jamescdavis Dec 19, 2019
e2680b1
tests: add tests for _addTypeScriptPlugins()
jamescdavis Dec 19, 2019
55ac0b2
tests: add (failing) test for _getExtensions
jamescdavis Dec 19, 2019
7f81c47
fix: don't add ts to user-configured extensions
jamescdavis Dec 19, 2019
5a1f5a8
refactor: simplify extension determination
jamescdavis Dec 19, 2019
e88eeb9
feat: added caching of ember-cli-typescript version check
jamescdavis Dec 19, 2019
b05c974
fix: conditionally apply class feature ordering restrictions
jamescdavis Dec 19, 2019
134b0a4
tests: test conditional class feature ordering restrictions
jamescdavis Dec 19, 2019
2e79c2f
fix: don't conditionally add optional chaining and nullish coalescing
jamescdavis Dec 19, 2019
14e7590
refactor: get e-c-ts version from parent.addons
jamescdavis Dec 20, 2019
24b8d09
tests: add test for _shouldHandleTypeScript
jamescdavis Dec 20, 2019
7c6db15
feat: add flag for manual override
jamescdavis Dec 20, 2019
304e67a
Merge branch 'master' into add_typescript_transform_plugin
jamescdavis Dec 20, 2019
ac58290
refactor: only enforce TS transform else case
jamescdavis Dec 30, 2019
21de796
feat: handle ts (in addition to js) by default
jamescdavis Dec 30, 2019
802323f
chore: s/\t/ /
jamescdavis Jan 8, 2020
5d7a817
chore: add ember-cli-typescript version to comment
jamescdavis Jan 8, 2020
5fb2aa0
docs: document enableTypeScriptTransforms
jamescdavis Jan 8, 2020
6393260
Revert "feat: handle ts (in addition to js) by default"
jamescdavis Jan 10, 2020
9bf6424
fix: e-c-ts was released as 4.0.0-alpha.1
jamescdavis Jan 18, 2020
f6adfe1
fix: don't emit modules for .d.ts files
dfreeman Jan 20, 2020
bcbddff
Merge pull request #1 from dfreeman/exclude-ts-declarations
jamescdavis Jan 20, 2020
11df8e5
Merge branch 'master' into add_typescript_transform_plugin
jamescdavis Jan 24, 2020
22480f1
Merge branch 'master' into add_typescript_transform_plugin
jamescdavis Jan 27, 2020
d0db857
chore: upgrade to latest @babel packages
jamescdavis Jan 27, 2020
4ab55d1
refactor: don't explicitly add optional-chaining/nullish coalescing
jamescdavis Jan 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ allow you to use latest Javascript in your Ember CLI project.
+ [Enabling Source Maps](#enabling-source-maps)
+ [Modules](#modules)
+ [Disabling Debug Tooling Support](#disabling-debug-tooling-support)
+ [Enabling TypeScript Transpilation](#enabling-typescript-transpilation)
* [Addon usage](#addon-usage)
+ [Adding Custom Plugins](#adding-custom-plugins)
+ [Additional Trees](#additional-trees)
Expand Down Expand Up @@ -121,6 +122,7 @@ interface EmberCLIBabelConfig {
disablePresetEnv?: boolean;
disableEmberModulesAPIPolyfill?: boolean;
disableDecoratorTransforms?: boolean;
enableTypeScriptTransform?: boolean;
extensions?: string[];
};
}
Expand Down Expand Up @@ -259,6 +261,38 @@ module.exports = function(defaults) {
}
```

#### Enabling TypeScript Transpilation

The transform plugin required for Babel to transpile TypeScript will
automatically be enabled when `ember-cli-typescript` >= 4.0 is installed.

You can enable the TypeScript Babel transform manually *without*
`ember-cli-typescript` by setting the `enableTypeScriptTransform` to `true`.

NOTE: Setting this option to `true` is not compatible with
`ember-cli-typescript` < 4.0 because of conflicting Babel plugin ordering
constraints and is unnecessary because `ember-cli-typescript` < 4.0 adds the
TypeScript Babel transform itself.

NOTE: Setting this option to `true` does *not* enable type-checking. For
integrated type-checking, you will need
[`ember-cli-typescript`](https://ember-cli-typescript.com).

In an app, manually enabling the TypeScript transform would look like:

```js
// ember-cli-build.js
module.exports = function(defaults) {
let app = new EmberApp(defaults, {
'ember-cli-babel': {
enableTypeScriptTransform: true
}
});

return app.toTree();
}
```

### Addon usage

#### Adding Custom Plugins
Expand Down
88 changes: 76 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ module.exports = {
return this._cachedDebugTree.apply(null, arguments);
},

transpileTree(inputTree, config) {
transpileTree(inputTree, _config) {
let config = _config || this._getAddonOptions();
let description = `000${++count}`.slice(-3);
let postDebugTree = this._debugTree(inputTree, `${description}:input`);

Expand All @@ -51,7 +52,15 @@ module.exports = {
output = postDebugTree;
} else {
let BabelTranspiler = require('broccoli-babel-transpiler');
output = new BabelTranspiler(postDebugTree, options);
let transpilationInput = postDebugTree;

if (this._shouldHandleTypeScript(config)) {
let Funnel = require('broccoli-funnel');
let inputWithoutDeclarations = new Funnel(transpilationInput, { exclude: ['**/*.d.ts'] });
transpilationInput = this._debugTree(inputWithoutDeclarations, `${description}:filtered-input`);
}

output = new BabelTranspiler(transpilationInput, options);
}

return this._debugTree(output, `${description}:output`);
Expand Down Expand Up @@ -254,14 +263,16 @@ module.exports = {
},

_getExtensions(config) {
let shouldHandleTypeScript = this._shouldHandleTypeScript(config);
let emberCLIBabelConfig = config['ember-cli-babel'] || {};
return emberCLIBabelConfig.extensions || ['js'];
return emberCLIBabelConfig.extensions || (shouldHandleTypeScript ? ['js', 'ts'] : ['js']);
rwjblue marked this conversation as resolved.
Show resolved Hide resolved
},

_getBabelOptions(config) {
let addonProvidedConfig = this._getAddonProvidedConfig(config);
let shouldCompileModules = this._shouldCompileModules(config);
let shouldIncludeHelpers = this._shouldIncludeHelpers(config);
let shouldHandleTypeScript = this._shouldHandleTypeScript(config);
let shouldIncludeDecoratorPlugins = this._shouldIncludeDecoratorPlugins(config);

let emberCLIBabelConfig = config['ember-cli-babel'];
Expand Down Expand Up @@ -292,8 +303,12 @@ module.exports = {
let userPlugins = addonProvidedConfig.plugins;
let userPostTransformPlugins = addonProvidedConfig.postTransformPlugins;

if (shouldHandleTypeScript) {
userPlugins = this._addTypeScriptPlugin(userPlugins.slice(), addonProvidedConfig.options);
}

if (shouldIncludeDecoratorPlugins) {
userPlugins = this._addDecoratorPlugins(userPlugins.slice(), addonProvidedConfig.options);
userPlugins = this._addDecoratorPlugins(userPlugins.slice(), addonProvidedConfig.options, config);
}

options.plugins = [].concat(
Expand All @@ -320,13 +335,63 @@ module.exports = {
return options;
},

_shouldHandleTypeScript(config) {
let emberCLIBabelConfig = config['ember-cli-babel'] || {};
if (typeof emberCLIBabelConfig.enableTypeScriptTransform === 'boolean') {
return emberCLIBabelConfig.enableTypeScriptTransform;
}
let typeScriptAddon = this.parent.addons
&& this.parent.addons.find(a => a.name === 'ember-cli-typescript');
return typeof typeScriptAddon !== 'undefined'
rwjblue marked this conversation as resolved.
Show resolved Hide resolved
&& semver.gte(typeScriptAddon.pkg.version, '4.0.0-alpha.1');
},

_buildClassFeaturePluginConstraints(constraints, config) {
// With versions of ember-cli-typescript < 4.0, class feature plugins like
// @babel/plugin-proposal-class-properties were run before the TS transform.
if (!this._shouldHandleTypeScript(config)) {
constraints.before = constraints.before || [];
constraints.before.push('@babel/plugin-transform-typescript');
}

return constraints;
},

_addTypeScriptPlugin(plugins) {
const { hasPlugin, addPlugin } = require('ember-cli-babel-plugin-helpers');

if (hasPlugin(plugins, '@babel/plugin-transform-typescript')) {
if (this.parent === this.project) {
this.project.ui.writeWarnLine(`${
this._parentName()
} has added the TypeScript transform plugin to its build, but ember-cli-babel provides this by default now when ember-cli-typescript >= 4.0 is installed! You can remove the transform, or the addon that provided it.`);
}
} else {
addPlugin(
plugins,
[
require.resolve('@babel/plugin-transform-typescript'),
{ allowDeclareFields: true },
],
{
before: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-private-methods',
'@babel/plugin-proposal-decorators',
]
}
);
}
return plugins;
},

_shouldIncludeDecoratorPlugins(config) {
let customOptions = config['ember-cli-babel'] || {};

return customOptions.disableDecoratorTransforms !== true;
},

_addDecoratorPlugins(plugins, options) {
_addDecoratorPlugins(plugins, options, config) {
const { hasPlugin, addPlugin } = require('ember-cli-babel-plugin-helpers');

if (hasPlugin(plugins, '@babel/plugin-proposal-decorators')) {
Expand All @@ -339,9 +404,9 @@ module.exports = {
addPlugin(
plugins,
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
{
before: ['@babel/plugin-proposal-class-properties', '@babel/plugin-transform-typescript']
}
this._buildClassFeaturePluginConstraints({
before: ['@babel/plugin-proposal-class-properties']
}, config)
);
}

Expand All @@ -356,10 +421,9 @@ module.exports = {
addPlugin(
plugins,
[require.resolve('@babel/plugin-proposal-class-properties'), { loose: options.loose || false }],
{
after: ['@babel/plugin-proposal-decorators'],
before: ['@babel/plugin-transform-typescript']
}
this._buildClassFeaturePluginConstraints({
after: ['@babel/plugin-proposal-decorators']
}, config)
);
}

Expand Down
131 changes: 128 additions & 3 deletions node-tests/addon-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,42 @@ describe('ember-cli-babel', function() {
}));
});

describe('TypeScript transpilation', function() {
beforeEach(function() {
this.addon.parent.addons.push({
name: 'ember-cli-typescript',
pkg: {
version: '4.0.0-alpha.1'
}
});
});

it('should transpile .ts files', co.wrap(function*() {
input.write({ 'foo.ts': `let foo: string = "hi";` });

subject = this.addon.transpileTree(input.path());
output = createBuilder(subject);

yield output.build();

expect(
output.read()
).to.deep.equal({
'foo.js': `define("foo", [], function () {\n "use strict";\n\n var foo = "hi";\n});`
});
}));

it('should exclude .d.ts files', co.wrap(function*() {
input.write({ 'foo.d.ts': `declare let foo: string;` });

subject = this.addon.transpileTree(input.path());
output = createBuilder(subject);

yield output.build();

expect(output.read()).to.deep.equal({});
}))
});

describe('_shouldDoNothing', function() {
it("will no-op if nothing to do", co.wrap(function* () {
Expand Down Expand Up @@ -746,9 +782,65 @@ describe('ember-cli-babel', function() {
});
});

describe('_shouldHandleTypeScript', function() {
it('should return false by default', function() {
expect(this.addon._shouldHandleTypeScript({})).to.be.false;
});
it('should return true when ember-cli-typescript >= 4.0.0-alpha.1 is installed', function() {
this.addon.parent.addons.push({
name: 'ember-cli-typescript',
pkg: {
version: '4.0.0-alpha.1',
},
});
expect(this.addon._shouldHandleTypeScript({})).to.be.true;
});
it('should return false when ember-cli-typescript < 4.0.0-alpha.1 is installed', function() {
this.addon.parent.addons.push({
name: 'ember-cli-typescript',
pkg: {
version: '3.0.0',
},
});
expect(this.addon._shouldHandleTypeScript({})).to.be.false;
});
it('should return true when the TypeScript transform is manually enabled', function() {
expect(this.addon._shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: true } })).to.be.true;
});
it('should return false when the TypeScript transforms is manually disabled', function() {
expect(this.addon._shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: false } })).to.be.false;
});
it('should return false when the TypeScript transform is manually disabled, even when ember-cli-typescript >= 4.0.0-alpha.1 is installed', function() {
this.addon.parent.addons.push({
name: 'ember-cli-typescript',
pkg: {
version: '4.0.0-alpha.1',
},
});
expect(this.addon._shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: false } })).to.be.false;
});
});

describe('_addTypeScriptPlugin', function() {
it('should warn and not add the TypeScript plugin if already added', function() {
this.addon.project.ui = {
writeWarnLine(message) {
expect(message).to.match(/has added the TypeScript transform plugin to its build/);
}
};

expect(
this.addon._addTypeScriptPlugin([
['@babel/plugin-transform-typescript']
],
{}
).length).to.equal(1, 'plugin was not added');
});
});

describe('_addDecoratorPlugins', function() {
it('should include babel transforms by default', function() {
expect(this.addon._addDecoratorPlugins([], {}).length).to.equal(2, 'plugins added correctly');
expect(this.addon._addDecoratorPlugins([], {}, {}).length).to.equal(2, 'plugins added correctly');
});

it('should include only fields if it detects decorators plugin', function() {
Expand All @@ -762,6 +854,7 @@ describe('ember-cli-babel', function() {
this.addon._addDecoratorPlugins([
['@babel/plugin-proposal-decorators']
],
{},
{}
).length).to.equal(2, 'plugins were not added');
});
Expand All @@ -778,20 +871,35 @@ describe('ember-cli-babel', function() {
[
['@babel/plugin-proposal-class-properties']
],
{},
{}
).length
).to.equal(2, 'plugins were not added');
});

it('should use babel options loose mode for class properties', function() {
let strictPlugins = this.addon._addDecoratorPlugins([], {});
let strictPlugins = this.addon._addDecoratorPlugins([], {}, {});

expect(strictPlugins[1][1].loose).to.equal(false, 'loose is false if no option is provided');

let loosePlugins = this.addon._addDecoratorPlugins([], { loose: true });
let loosePlugins = this.addon._addDecoratorPlugins([], { loose: true }, {});

expect(loosePlugins[1][1].loose).to.equal(true, 'loose setting added correctly');
});

it('should include class fields and decorators after typescript if handling typescript', function() {
this.addon._shouldHandleTypeScript = function() { return true; }
let plugins = this.addon._addDecoratorPlugins(['@babel/plugin-transform-typescript'], {}, {});
expect(plugins[0]).to.equal('@babel/plugin-transform-typescript', 'typescript still first');
expect(plugins.length).to.equal(3, 'class fields and decorators added');
});

it('should include class fields and decorators before typescript if not handling typescript', function() {
this.addon._shouldHandleTypeScript = function() { return false; }
let plugins = this.addon._addDecoratorPlugins(['@babel/plugin-transform-typescript'], {}, {});
expect(plugins.length).to.equal(3, 'class fields and decorators added');
expect(plugins[2]).to.equal('@babel/plugin-transform-typescript', 'typescript is now last');
});
});

describe('_shouldIncludeHelpers()', function() {
Expand Down Expand Up @@ -973,6 +1081,23 @@ describe('ember-cli-babel', function() {
});
});

describe('_getExtensions', function() {
it('defaults to js only', function() {
expect(this.addon._getExtensions({})).to.have.members(['js']);
});
it('adds ts automatically', function() {
this.addon._shouldHandleTypeScript = function() { return true; }
expect(this.addon._getExtensions({})).to.have.members(['js', 'ts']);
});
it('respects user-configured extensions', function() {
expect(this.addon._getExtensions({ 'ember-cli-babel': { extensions: ['coffee'] } })).to.have.members(['coffee']);
});
it('respects user-configured extensions even when adding TS plugin', function() {
this.addon._shouldHandleTypeScript = function() { return true; }
expect(this.addon._getExtensions({ 'ember-cli-babel': { extensions: ['coffee'] } })).to.have.members(['coffee']);
});
});

describe('buildBabelOptions', function() {
this.timeout(0);

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-transform-modules-amd": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/plugin-transform-typescript": "^7.8.3",
"@babel/polyfill": "^7.8.3",
"@babel/preset-env": "^7.8.3",
"@babel/runtime": "^7.8.3",
Expand Down
Loading