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

Add support for TypeScript without precompiling #1122

Closed
wants to merge 32 commits into from
Closed

Add support for TypeScript without precompiling #1122

wants to merge 32 commits into from

Conversation

huan
Copy link

@huan huan commented Nov 21, 2016

Follow #1109

Notice that this PR has to work together with avajs/ava-files#9

So unit testing in CI will expect failure before we merge&update AvaFiles NPM.

In my development environment with npm link ava-files, all unit tests had passed.

@sindresorhus sindresorhus force-pushed the master branch 2 times, most recently from 8610723 to 4acb78f Compare November 22, 2016 17:46
"xo": "^0.17.0",
"zen-observable": "^0.3.0"
},
"xo": {
"rules": {
"import/newline-after-import": "off",
"no-use-extend-native/no-use-extend-native": "off"
"no-use-extend-native/no-use-extend-native": "off",
"no-useless-escape": "off"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the issue instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. fixed.

but this part looks to be a little weird:

  var isBabelPath = /^babel-runtime[\\/]?/.test(path.node.value);

Ref: http://stackoverflow.com/a/40315390/1123955

And a question: How did the AVA pass the CI test? I just clone the AVA repo, run npm install && npm t, XO will throw no-useless-escape error.

Copy link
Member

@sindresorhus sindresorhus Nov 23, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's weird, I don't get that failure locally either. Just fix it though.

@@ -0,0 +1,6 @@
import { test } from 'ava'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import {test} from 'ava';

And use semi-colons.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -89,7 +89,7 @@
"arr-flatten": "^1.0.1",
"array-union": "^1.0.1",
"array-uniq": "^1.0.2",
"arrify": "^1.0.0",
"arrify": "^1.0.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do unrelated changes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

@@ -154,7 +154,7 @@
"resolve-cwd": "^1.0.0",
"semver": "^5.3.0",
"set-immediate-shim": "^1.0.1",
"source-map-support": "^0.4.0",
"source-map-support": "^0.4.5",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just run an npm install arrify source-map-support --save by mistake

Restored old version.

@@ -0,0 +1,116 @@
'use strict';
/**
* AVA File Extention for typescript
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extention => Extension

typescript => TypeScript

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

extTs.register();
break;
default:
// not supported ext?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should throw an useful error here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

        throw new Error('`--extension ' + ext + '` is not supported by AVA.')

runOnlyExclusive: runOnlyExclusive
}).then(function (runStatus) {
};
if (extensions) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an internal API. We can just make passing the extensions argument required and drop this if-statement.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -100,6 +103,11 @@ exports.run = function () {
throw new Error(colors.error(figures.cross) + ' The --require and -r flags are deprecated. Requirements should be configured in package.json - see documentation.');
}

if (cli.flags.extension) {
cli.flags.extension = arrify(cli.flags.extension);
cli.flags.extension.push('js');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed in avajs/ava-files#9 (comment), the option should override js.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to this:

  if (cli.flags.extension) {
    cli.flags.extension = arrify(cli.flags.extension);
  } else {
    cli.flags.extension = ['js'];
  }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you just do this?

cli.flags.extension = arrify(cli.flags.extension || 'js');

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jedmao great idea, thanks!

@@ -43,6 +43,7 @@ exports.run = function () {
' --source, -S Pattern to match source files so tests can be re-run (Can be repeated)',
' --timeout, -T Set global timeout',
' --concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)',
' --extension, -e Set test file extention. `ts` is for Typescript',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test file extension, for example `ts` for TypeScript (Can be repeated)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// here for other exts in the future

default:
// not supported(yet)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should throw an useful error here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

throws the same error as the above.

@sindresorhus sindresorhus changed the title Typescript without compiling Add support for TypeScript without precompiling Nov 22, 2016
@sindresorhus
Copy link
Member

I don't think you pushed your changes.

Merge conflict also needs fixing.

@huan
Copy link
Author

huan commented Nov 23, 2016

@sindresorhus Yes, I'm still fighting with the unit tests.

Because of the new extensions option parameter, some unit tests broken.

Will push after I fixed them.

@huan
Copy link
Author

huan commented Nov 23, 2016

Just finished all the unit tests with the latest code in this PR branch.

ava-files has updated PR to latest version as well.

Please let me know if I missed anything. :)

};
}

var extTs = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just export this directly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

require('source-map-support').install({
hookRequire: true
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't ts-node already do this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes and no.

ts-node will do this if we use transpile function from ts-node. However, the current version of lib/ext/ts.js is not using the ts-node by default, it use the raw typescript module, so we have to do this by ourselves.

I leave the ts-node in lib/ext/ts.js is because at the beginning I'm not sure which one is better, so I implement both, but at last I decided to use pure typescript by default:

module.exports = module.exports.default = {
  register: typescriptRegister,
  transpile: typescriptTranspile,

Do you think we should use ts-node, or I need to get rid of the additional ts-node code in lib/ext/ts.js?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like ts-node is closer to babel-node than babel-core (and ts-node/register is equivalent to babel-core/register). IMO this module should just use typescript to transpile the code, and no other functionality is required.

For consistency we should not automatically transpile source files, at least until we have a better answer for the .js use case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zixia per the above discusison, could you simplify this module to just the transpile function?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @novemberborn ,

I had simplified this module, removed all the ts-node parts, cleaned the code, but not just the transpile.

The register() function has to be kept because if we run test written in TypeScript, the lib/test-worker.js must has the ability to transpile TypeScript, which is enabled by extTs.register() at here: https://github.com/zixia/ava/blob/typescript/lib/test-worker.js#L38

*
* Ts-Node version
*
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Copy link
Member

@novemberborn novemberborn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zixia very cool!

There's some merge conflicts now, probably because we changed the code to use more ES2015 features. Sorry about that, though on the plus side you get to use ES2015!

@@ -47,8 +48,29 @@ CachingPrecompiler.prototype._init = function () {
CachingPrecompiler.prototype._transform = function (code, filePath, hash) {
code = code.toString();

var options = babelConfigHelper.build(this.babelConfig, this.powerAssert, filePath, code);
var result = this.babel.transform(code, options);
var fileExt = path.extname(filePath).replace(/^\./, '');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path.extname(filePath).slice(1) seems cleaner.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

var fileExt = path.extname(filePath).replace(/^\./, '');
var isCustomExt = this.extensions.filter(function (ext) {
return (ext !== 'js' && ext === fileExt);
}).length > 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be simplified to:

var isCustomExt = fileExt !== 'js' && this.extensions.indexOf(fileExt) > -1

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

}
}

if (!result) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should just be an else, assuming the body for if (isCustomExt) assigns result or throws.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

typescript = require('typescript');
} catch (err) {
console.error('AVA: `--extensions=ts` require TypeScript to be installed.');
throw err;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than logging and then rethrowing the original error construct a new error like in

ava/lib/cli.js

Line 99 in 0f04001

throw new Error(colors.error(figures.cross) + ' The TAP reporter is not available when using watch mode.');
and throw that. It'll be logged correctly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

require('source-map-support').install({
hookRequire: true
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like ts-node is closer to babel-node than babel-core (and ts-node/register is equivalent to babel-core/register). IMO this module should just use typescript to transpile the code, and no other functionality is required.

For consistency we should not automatically transpile source files, at least until we have a better answer for the .js use case.

@huan
Copy link
Author

huan commented Dec 4, 2016

@novemberborn I love ES2015 too. Had just followed your suggestions and resolved all the conflicts.

var result;

if (isCustomExt) {
switch (fileExt) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zixia could you change this to an if statement?

break;

default:
throw new Error('`--extension ' + fileExt + '` is not supported by AVA.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's check for allowed extensions in lib/cli instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had changed to if.

Where are the allowed extensions in cli.js? Did you mean we should save all supported extensions to an array in cli.js, then reference it in other js files?

require('source-map-support').install({
hookRequire: true
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zixia per the above discusison, could you simplify this module to just the transpile function?

};
}

module.exports = module.exports.default = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use exports.transpile = typescriptTranspile.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to keep register() with tranpile(), as I described here: #1122 (comment)

If there has any alternative way to do this, please let me know, thanks.

throw new Error('AVA: `--extension=' + ext + '` is not supported by AVA.');
}
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these changes. Right now I'd prefer our TypeScript support is consistent with our Babel support, where dependencies are not automatically transpiled.

I know that's not ideal but we can revisit it later.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the TypeScript support would still work after we removed those changes. Because if no extTs.register(), then test-worker.js will not work with .ts test files.

This TypeScript support will only be enabled when we specify --extension ts arg with AVA, which is the case we need to automatically transpile all TypeScript file with extension: .ts

And about the dependencies, I think for TypeScript is different as for JavaScript, because:

  1. There are almost no TypeScript dependencies in NPM(node_modules)
  2. The .ts extension is not the default JavaScript file extension, so it's almost only in our development environment.

Please correct me if I'm wrong, or missed anything. Thanks.

@huan
Copy link
Author

huan commented Dec 6, 2016

Thank you guys for all the replies of this PR.

Please let me know if I missed any important replies because this conversation is growing fast. ;-p

@novemberborn
Copy link
Member

@zixia could you explain why you think register is needed in the workers? The CachingPrecompiler already compiles the test files, and the workers load the resulting code. On-the-fly compilation would only apply to files required by the test file. I'm arguing that, though not ideal, it's better to be consistent with our Babel implementation and not automatically transpile such dependencies. Thus, register would not be needed.

Where are the allowed extensions in cli.js? Did you mean we should save all supported extensions to an array in cli.js, then reference it in other js files?

There are none, currently, since .js is hardcoded in a few places. I'm suggesting you check for .ts after the hasFlag tests, before API is instantiated. Around here.

Zooming out a bit, CachingPrecompiler was really written for Babel. It uses the Babel config to salt the cache and it automatically loads Babel when it needs to compile something. Your proposed changes still cause the Babel code paths to execute and don't allow the TypeScript compiler to be configured. I think that's OK at this stage but we'll have to solve those issues at a later stage. @avajs/core thoughts?


@jedmao perhaps we should revisit .tsx once this has landed? There's a parallel with .jsx which currently doesn't work either. Maybe we can treat these as aliases and just assume the existing .js / .ts setup supports JSX. It's complicated though because JSX doesn't necessarily mean React.

@huan
Copy link
Author

huan commented Dec 8, 2016

@novemberborn got it.

The reason I think register is needed in the workers is because if I do not do this, my test will not work anymore.

I believe that's because my tests have to import pure TypeScript module file: there is all kind of TypeScript files in my source tree, and I never compile them, just import them in my tests.

Will check them and reply you with the details later.

@novemberborn
Copy link
Member

@zixia:

The reason I think register is needed in the workers is because if I do not do this, my test will not work anymore.

I believe that's because my tests have to import pure TypeScript module file: there is all kind of TypeScript files in my source tree, and I never compile them, just import them in my tests.

Will check them and reply you with the details later.

Right, that's the same issue we have with our Babel support: source files are not automatically transpiled so users need to add babel-core/register to AVA's require configuration.

This is by no means ideal, but automatically including babel-core/register has a measurable impact on performance for those users who do not need it. I imagine the same applies to TypeScript.

I've been playing with performance optimizations in this area (e.g. #1082). Really I'd like to have users opt-in to a particular behavior for their tests and source files, and then have everything be automatic.

For now though I'm suggesting we do not automatically load TypeScript when running tests. The behavior would be the same as for users relying on babel-core/register.

This PR already introduces many subtle changes to AVA's behavior that should be factored out. That's a larger task and I think this PR provides enough value as it stands today, so we should try and land it. But not with automatic TypeScript loading.

@huan
Copy link
Author

huan commented Dec 10, 2016

Hi @novemberborn ,

Got your idea, and thanks for the explanations.

There are none, currently, since .js is hardcoded in a few places. I'm suggesting you check for .ts after the hasFlag tests, before API is instantiated. Around here.

done.

Right, that's the same issue we have with our Babel support: source files are not automatically transpiled so users need to add babel-core/register to AVA's require

done.

I had removed extTs.register() part from lib/test-worker.js, and add the following json part to package.json, TypeScript work without any problem again.

  "ava": {
    "require": [
      "ts-node/register"
    ]
  },

For more about TypeScript register, here's some discussions about it: microsoft/TypeScript#1823

Have a good weekend!

@huan
Copy link
Author

huan commented Dec 10, 2016

I found a strange unit test failure in test/cli.js at here: https://github.com/zixia/ava/blob/d7bfa76fb708b6d918f1c821df0417d79e96cde4/test/cli.js#L383

We run cli.js in cwd, however, we pass resolveTestsFrom to AvaFiles at here, which is /tmp/ instead of /tmp/xxxx/, as the result, test fail.

I have no idea of it because I did not touch this part this week and everything is ok before. Will check it later.

@asvetliakov
Copy link

Any news about PR ? I'd like to adopt ava in my project, but separate TS precompilation (and as i understand, the ava now just doesn't work with file extensions rather than .js) is huge stopper for me.

@huan
Copy link
Author

huan commented Dec 16, 2016

@asvetliakov Before this PR be merged, you can try this new feature by clone the source with npm link:

$ git clone https://github.com/zixia/ava-files.git
$ cd ava-files
$ npm install && npm link
$ cd ..

$ git clone https://github.com/zixia/ava.git
$ cd ava
$ git checkout typescript
$ npm install & npm link ava-files && npm link

Then you can use ava with TypeScripe in your project like:

$ cd my-project
$ npm link ava
$ ./node_modules/.bin/ava --extension=ts

Please let me know if there's any issue with your project if you have time to try it.

BTW: I love your Huject.

Good luck! :)

@novemberborn
Copy link
Member

Hi @zixia, I have some concerns with the direction we're taking here.

As already mentioned it's not clear how this would work for source files without running into the same performance issues we already see with Babel. The TypeScript test files won't get enhanced assertions or protection against improper t.throws() usage either. It's hard to communicate this to users when the way TypeScript support is enabled is to specify an extensions option.

I've been doing a lot of thinking on how we can better solve these problems. Not only for TypeScript but for Babel-based projects as well. I've started writing an RFC with my specific proposal but I'm not ready to share it yet. I'm currently on holiday, and the weather is awfully nice (except when there's ⛈ like right now), so I can't commit to any particular publication date.

I didn't want to leave you hanging though. As I see it right now I don't think we can land this pull request. I'm really sorry about that, since you've put in a lot of work already.

I hope you'll give my proposal a chance. It should enable us to make AVA's TypeScript support really awesome.

Thanks.

@huan
Copy link
Author

huan commented Dec 19, 2016

@novemberborn Understand, and please let me know when you made new progress on your RFC. :)

Have a nice holiday!

@novemberborn
Copy link
Member

@zixia see #1159.

@novemberborn
Copy link
Member

We'll be taking a different direction with regards to TypeScript support, see RFC001. @zixia I'll let you know when the Babel side of that has stabilized sufficiently for you (or somebody else) to take on the TypeScript implementation.

@huan
Copy link
Author

huan commented Jan 17, 2017

@novemberborn No problem, looking forward hearing from you.

@jednano jednano mentioned this pull request Feb 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants