diff --git a/packages/typescript/README.md b/packages/typescript/README.md new file mode 100644 index 00000000000..406f24fa50e --- /dev/null +++ b/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. diff --git a/packages/typescript/package.js b/packages/typescript/package.js new file mode 100644 index 00000000000..ddec9d5b72f --- /dev/null +++ b/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"); +}); diff --git a/packages/typescript/plugin.js b/packages/typescript/plugin.js new file mode 100644 index 00000000000..35e4ee3a517 --- /dev/null +++ b/packages/typescript/plugin.js @@ -0,0 +1,19 @@ +Plugin.registerCompiler({ + extensions: ["ts", "tsx"], +}, function () { + return new TypeScriptCompiler({ + react: true, + typescript: true, + }); +}); + +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") + )); + } +} diff --git a/packages/typescript/tests.ts b/packages/typescript/tests.ts new file mode 100644 index 00000000000..5cf37303ac1 --- /dev/null +++ b/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"); +}); diff --git a/tools/static-assets/skel-bare/.meteor/packages b/tools/static-assets/skel-bare/.meteor/packages index 1dacd59dda4..62bedd2c004 100644 --- a/tools/static-assets/skel-bare/.meteor/packages +++ b/tools/static-assets/skel-bare/.meteor/packages @@ -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 diff --git a/tools/static-assets/skel-full/.meteor/packages b/tools/static-assets/skel-full/.meteor/packages index 31294f83115..f4a8c36f6fd 100644 --- a/tools/static-assets/skel-full/.meteor/packages +++ b/tools/static-assets/skel-full/.meteor/packages @@ -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 diff --git a/tools/static-assets/skel-minimal/.meteor/packages b/tools/static-assets/skel-minimal/.meteor/packages index 341bbfc93a3..8ddd70b7d25 100644 --- a/tools/static-assets/skel-minimal/.meteor/packages +++ b/tools/static-assets/skel-minimal/.meteor/packages @@ -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 diff --git a/tools/static-assets/skel-react/.meteor/packages b/tools/static-assets/skel-react/.meteor/packages index 1405595e1b9..3319ee0005f 100644 --- a/tools/static-assets/skel-react/.meteor/packages +++ b/tools/static-assets/skel-react/.meteor/packages @@ -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) diff --git a/tools/static-assets/skel/.meteor/packages b/tools/static-assets/skel/.meteor/packages index 18f8d45d89b..7bfecd24cec 100644 --- a/tools/static-assets/skel/.meteor/packages +++ b/tools/static-assets/skel/.meteor/packages @@ -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) diff --git a/tools/tests/apps/modules/.meteor/packages b/tools/tests/apps/modules/.meteor/packages index 7b80a7a68c3..b39115f0758 100644 --- a/tools/tests/apps/modules/.meteor/packages +++ b/tools/tests/apps/modules/.meteor/packages @@ -27,3 +27,4 @@ underscore@1.0.10 import-local-json-module akryum:vue-component dummy-compiler +typescript diff --git a/tools/tests/apps/modules/tests.js b/tools/tests/apps/modules/tests.js index 3f15c556fa9..5394a52b30c 100644 --- a/tools/tests/apps/modules/tests.js +++ b/tools/tests/apps/modules/tests.js @@ -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", () => { diff --git a/tools/tests/apps/modules/typescript.ts b/tools/tests/apps/modules/typescript.ts new file mode 100644 index 00000000000..72228b2f7d5 --- /dev/null +++ b/tools/tests/apps/modules/typescript.ts @@ -0,0 +1,3 @@ +export class Test { + constructor(public readonly value: T) {} +} diff --git a/tools/tool-testing/sandbox.js b/tools/tool-testing/sandbox.js index a024977037b..578b182d9ce 100644 --- a/tools/tool-testing/sandbox.js +++ b/tools/tool-testing/sandbox.js @@ -566,6 +566,8 @@ const ROOT_PACKAGES_TO_BUILD_IN_SANDBOX = [ "es5-shim", "shell-server", "modern-browsers", + "ecmascript", + "typescript", ]; function newSelfTestCatalog() {