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

Implement an official TypeScript compiler plugin. #10610

Merged
merged 2 commits into from Jul 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
98 changes: 98 additions & 0 deletions packages/typescript/README.md
@@ -0,0 +1,98 @@
# typescript
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/typescript) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/typescript)
***

This plugin package enables the use of
[TypeScript](https://www.typescriptlang.org) modules with `.ts` or `.tsx`
file extensions in Meteor applications and packages, alongside `.js`
modules (or whatever other types of modules you might be using).

## Usage

The `typescript` package registers a compiler plugin that transpiles
TypeScript to plain ECMAScript, which is then compiled by Babel for
multiple targets (server, modern browsers, legacy browsers, cordova).
By default, the `typescript` package is included in the `.meteor/packages`
file of all new Meteor applications.

To add the `typescript` package to an existing app, run the following
command from your app directory:

```bash
meteor add typescript
```

To add the `typescript` package to an existing package, include the
statement `api.use('typescript');` in the `Package.onUse` callback in your
`package.js` file:

```js
Package.onUse(function (api) {
api.use('typescript');
});
```

## Supported TypeScript features

Almost all TypeScript syntax is supported, though this plugin currently
does not attempt to provide type checking (just compilation).

Since the Meteor `typescript` package runs the official TypeScript
compiler before running Babel, it does not suffer from the same
[caveats](https://babeljs.io/docs/en/babel-plugin-transform-typescript#caveats)
known to affect the Babel TypeScript transform. In particular,
`namespace`s are fully supported.

However, as of this writing, the Meteor `typescript` package compiles
TypeScript modules individually, using the `transpileModule` function,
which means that certain cross-file analysis and compilation will not
work. For example, `export const enum {...}` is not fully supported,
though `const enum {...}` works when confined to a single module.

Unlike the Babel implementation of TypeScript, there is nothing
fundamentally preventing Meteor from compiling all TypeScript modules
together, rather than individually, which would enable full support for
features like `export const enum`. That's an area for future improvement,
though we will have to weigh the performance implications carefully.

As of this writing, `tsconfig.json` files are ignored by the plugin.
There's nothing fundamentally preventing the Meteor `typescript` plugin
from accepting configuration options from `tsconfig.json`, but for now
we've kept things simple by avoiding configuration complexities. You may
still want to have a `tsconfig.json` file in your application root
directory to configure the behavior of editors like VSCode, but it will
not be respected by Meteor.

Finally, since the TypeScript compiler runs first, syntax that is not
understood by the TypeScript compiler, such as experimental ECMAScript
proposals, may cause the TypeScript parser to reject your code. You can
use `.babelrc` files to configure Babel compilation, but TypeScript still
has to accept the code first.

## Areas for improvement

* Compile all TypeScript modules at the same time, rather than compiling
them individually, to enable cross-module analysis and compilation. In
the meantime, if you need this behavior, consider using
[`adornis:typescript`](https://atmospherejs.com/adornis/typescript).

* Use the version of `typescript` installed in the application
`node_modules` directory, rather than the one bundled with
`meteor-babel`. We will attempt to keep `meteor-babel`'s version of
`typescript` up-to-date, but it would be better to leave this decision
to the application developer.

* Generate `.d.ts` declaration files that can be consumed by external
tools. In particular, a Meteor package that uses TypeScript could
generate `.d.ts` files in the `/node_modules/meteor/package-name/`
directory, which would allow tools like VSCode to find the right types
for the package when importing from `meteor/package-name`.

* Cache the output of the TypeScript compiler separately from the output
of Meteor's Babel compiler pipeline. The TypeScript compiler does not
compile code differently for different targets (server, modern, legacy,
etc.), so its results could theoretically be reused for all targets.

* Make the `typescript` plugin look up `tsconfig.json` files (similar to
how `babel-compiler` looks up `.babelrc` files) and obey (some of) the
configuration options.
31 changes: 31 additions & 0 deletions packages/typescript/package.js
@@ -0,0 +1,31 @@
Package.describe({
name: "typescript",
version: "3.5.2",
summary: "Compiler plugin that compiles TypeScript and ECMAScript in .ts and .tsx files",
documentation: "README.md"
});

Package.registerBuildPlugin({
name: "compile-typescript",
use: ["babel-compiler"],
sources: ["plugin.js"]
});

Package.onUse(function (api) {
api.use("isobuild:compiler-plugin@1.0.0");
// The following api.imply calls should match those in
// ../ecmascript/package.js.
api.imply("modules");
api.imply("ecmascript-runtime");
api.imply("babel-runtime");
api.imply("promise");
// Runtime support for Meteor 1.5 dynamic import(...) syntax.
api.imply("dynamic-import");
});

Package.onTest(function (api) {
api.use("tinytest");
api.use("es5-shim");
api.use("typescript");
api.mainModule("tests.ts");
});
19 changes: 19 additions & 0 deletions packages/typescript/plugin.js
@@ -0,0 +1,19 @@
Plugin.registerCompiler({
extensions: ["ts", "tsx"],
}, function () {
return new TypeScriptCompiler({
react: true,
typescript: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

See meteor/babel#25 for details about how this is implemented in meteor-babel.

});
});

class TypeScriptCompiler extends BabelCompiler {
processFilesForTarget(inputFiles) {
return super.processFilesForTarget(inputFiles.filter(
// TypeScript .d.ts declaration files look like .ts files, but it's
// important that we do not compile them using the TypeScript
// compiler, as it will fail with a cryptic error message.
file => ! file.getPathInPackage().endsWith(".d.ts")
));
}
}
39 changes: 39 additions & 0 deletions packages/typescript/tests.ts
@@ -0,0 +1,39 @@
import { Tinytest } from "meteor/tinytest";

Tinytest.add("TypeScript - basics", test => {
test.equal(2 + 2, 4);
});

Tinytest.add("TypeScript - const enum", test => {
const enum Kind {
NORMAL,
WEIRD,
}
test.equal(typeof Kind.NORMAL, "number");
test.equal(typeof Kind.WEIRD, "number");
test.equal(Kind.NORMAL + 1, Kind.WEIRD);
});

Tinytest.add("TypeScript - constructor member parameters", test => {
class Test {
constructor(
private a: number,
public b: string,
) {}
}
const t = new Test(1234, "asdf");
test.equal((t as any).a, 1234);
test.equal(t.b, "asdf");
});

Tinytest.add("TypeScript - namespaces", test => {
function foo() {
return foo.bar;
}

namespace foo {
export const bar = "oyez";
}

test.equal(foo(), "oyez");
});
1 change: 1 addition & 0 deletions tools/static-assets/skel-bare/.meteor/packages
Expand Up @@ -15,4 +15,5 @@ standard-minifier-css # CSS minifier run for production mode
standard-minifier-js # JS minifier run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers
ecmascript # Enable ECMAScript2015+ syntax in app code
typescript # Enable TypeScript syntax in .ts and .tsx modules
shell-server # Server-side component of the `meteor shell` command
1 change: 1 addition & 0 deletions tools/static-assets/skel-full/.meteor/packages
Expand Up @@ -15,6 +15,7 @@ standard-minifier-css # CSS minifier run for production mode
standard-minifier-js # JS minifier run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers
ecmascript # Enable ECMAScript2015+ syntax in app code
typescript # Enable TypeScript syntax in .ts and .tsx modules
shell-server # Server-side component of the `meteor shell` command

kadira:flow-router # FlowRouter is a very simple router for Meteor
Expand Down
1 change: 1 addition & 0 deletions tools/static-assets/skel-minimal/.meteor/packages
Expand Up @@ -10,6 +10,7 @@ standard-minifier-css # CSS minifier run for production mode
standard-minifier-js # JS minifier run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers
ecmascript # Enable ECMAScript2015+ syntax in app code
typescript # Enable TypeScript syntax in .ts and .tsx modules
shell-server # Server-side component of the `meteor shell` command
webapp # Serves a Meteor app over HTTP
server-render # Support for server-side rendering
1 change: 1 addition & 0 deletions tools/static-assets/skel-react/.meteor/packages
Expand Up @@ -13,6 +13,7 @@ standard-minifier-css # CSS minifier run for production mode
standard-minifier-js # JS minifier run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers
ecmascript # Enable ECMAScript2015+ syntax in app code
typescript # Enable TypeScript syntax in .ts and .tsx modules
shell-server # Server-side component of the `meteor shell` command

autopublish # Publish all data to the clients (for prototyping)
Expand Down
1 change: 1 addition & 0 deletions tools/static-assets/skel/.meteor/packages
Expand Up @@ -15,6 +15,7 @@ standard-minifier-css # CSS minifier run for production mode
standard-minifier-js # JS minifier run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers
ecmascript # Enable ECMAScript2015+ syntax in app code
typescript # Enable TypeScript syntax in .ts and .tsx modules
shell-server # Server-side component of the `meteor shell` command

autopublish # Publish all data to the clients (for prototyping)
Expand Down
1 change: 1 addition & 0 deletions tools/tests/apps/modules/.meteor/packages
Expand Up @@ -27,3 +27,4 @@ underscore@1.0.10
import-local-json-module
akryum:vue-component
dummy-compiler
typescript
8 changes: 8 additions & 0 deletions tools/tests/apps/modules/tests.js
Expand Up @@ -708,6 +708,14 @@ describe("ecmascript miscellany", () => {
});
});

describe("TypeScript", () => {
it("can be imported", () => {
const { Test } = require("./typescript");
assert.strictEqual(typeof Test, "function");
assert.strictEqual(new Test(1234).value, 1234);
});
});

Meteor.isClient &&
describe("client/compatibility directories", () => {
it("should contain bare files", () => {
Expand Down
3 changes: 3 additions & 0 deletions tools/tests/apps/modules/typescript.ts
@@ -0,0 +1,3 @@
export class Test<T> {
constructor(public readonly value: T) {}
}
2 changes: 2 additions & 0 deletions tools/tool-testing/sandbox.js
Expand Up @@ -566,6 +566,8 @@ const ROOT_PACKAGES_TO_BUILD_IN_SANDBOX = [
"es5-shim",
"shell-server",
"modern-browsers",
"ecmascript",
"typescript",
];

function newSelfTestCatalog() {
Expand Down