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
Conversation
}, function () { | ||
return new TypeScriptCompiler({ | ||
react: true, | ||
typescript: true, |
There was a problem hiding this comment.
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
.
a5a0079
to
01fb509
Compare
@benjamn glad you're working on this 😍 . I see you decided on implementing your own caching. At adornis, we were evaluating whether to implement support for incremental builds from typescript itself. Do you think that could have performance benefits over caching on a file by file basis? We've been using our predecessors caching and rebuild times get really long if a lot of files import that specific file which they don't (at least not in that extent) when using tsc. Haven't investigated this further, just random thoughts... EDIT: thanks for the shoutout :) |
@benjamn how do you do absolute imports? 'imports/client/my-component' results in module not found. The exact same code worked with our old plugin. We have like 200 absolute imports, please don't make us change them :D |
The nice thing about using Babel for part of the compilation pipeline is that we can use a Babel plugin to rewrite imported module specifiers. I think we can automate this better, but here's a custom plugin that I wrote for one of our internal application development teams: const fs = require('fs');
const { dirname, join } = require('path');
const appDir = dirname(dirname(__dirname));
const hasOwn = Object.prototype.hasOwnProperty;
// These directory names are computed only once per build process, so you
// may need to restart Meteor to pick up any changes.
const nodeModulesDirNames = Object.create(null);
fs.readdirSync(join(appDir, 'node_modules')).forEach((dir) => {
if (!dir.startsWith('.')) {
nodeModulesDirNames[dir] = true;
}
});
const topLevelDirNames = Object.create(null);
fs.readdirSync(appDir).forEach((item) => {
if (!item.startsWith('.') && item !== 'node_modules') {
const stat = fs.statSync(item);
if (stat.isDirectory()) {
topLevelDirNames[item] = stat;
}
}
});
// Babel plugin that rewrites import declarations like
//
// import foo from "imports/bar"
//
// to use properly absolute identifier strings like
//
// import foo from "/imports/bar"
//
// TypeScript can understand imports/bar thanks to the "paths" property in
// tsconfig.json, but Node and Meteor treat imports/bar as referring to a
// package in node_modules called "imports", which does not exist.
//
// If a directory name exists in both node_modules and the root
// application directory, the node_modules package will take precedence.
module.exports = function plugin(api) {
function helper(path) {
// An ImportDeclaration will always have a source, but an
// ExportAllDeclaration or ExportNamedDeclaration may not.
const { source } = path.node;
if (!source) return;
const sourceId = source.value;
const name = sourceId.split('/', 1)[0];
// If the first component of the sourceId is a top-level directory in
// the application, and not the name of a directory in node_modules,
// prepend a leading / to make it an absolute identifier that Meteor's
// module system can understand.
if (
hasOwn.call(topLevelDirNames, name) &&
!hasOwn.call(nodeModulesDirNames, name)
) {
path.get('source').replaceWith(api.types.stringLiteral(`/${sourceId}`));
}
}
return {
name: 'transform-non-relative-imports',
visitor: {
ImportDeclaration: helper,
ExportAllDeclaration: helper,
ExportNamedDeclaration: helper,
},
};
}; Until we implement this officially, you can put this code in a local file (one that is not eagerly loaded by Meteor), and then refer to it in the |
@benjamn @gerwinbrunner I must be missing something. I included the file in the babelrc (which is otherwise empty) and it seems like the paths are being replaced correctly. However, there are still exceptions failing to import This is my package file, am I missing something?
|
Does it help to specify the root path in the
More info on TS module resolution here: microsoft/TypeScript#5039 |
@coagmano thanks for the idea! Already have those options though |
@benjamn, regarding Would be great to have an option to use Vue+jsx with the official typescript plugin... |
Just wondering, this is probably not using TS 3.6's incremental compile API? Would probably have a good performance impact. Any plans on implementing? Are pull requests welcome for this sort of thing? Btw: Sorry never reported back, I just had no idea on how to make a babel plugin (you need a |
Conflict: Constraint errorsI tried to
Here are the conflicts prompted for the last two (those for modules are far longer):
I checked my global npm packages with |
@andrei-markeev If you put
@yorrd I am definitely open to moving the TypeScript-to-ES2019 compilation (and type checking) into the |
With the release of Meteor 1.8.2-rc.0 this morning, there's now a TypeScript skeleton app that you can create with the following command: meteor create --release 1.8.2-rc.0 --typescript new-typescript-app It's based on the I'm pretty happy with the absence of TypeScript errors and warnings in this project, though that depends partly on the use of One of the reasons I think the |
... So that is what I was doing wrong. Thank you!
Tried it! Awesome! :) |
@benjamn Can you elaborate on some issues that exist with the current type definitions in @types/meteor? I'm hoping to spur some community work on this. |
@benjamn we're celebrating this a lot in the office. Thanks. About the Greetings! |
Regarding @types/meteor. Would it make sense to focus on transforming the current meteor core packages to TS so that the definitions are implicit and there is no need to rely on |
@nicu-chiciuc I absolutely agree with that, and it's worth noting that we control the |
Is there a chance that we are going to be able to use the typescript version installed in the node_modules folder anytime soon? |
When I tested this by upgrading my project to 1.8.2, removing adornis:typescript and adding "typescript", I couldn't see any type errors in the console when I deliberately broke a thing or two. That is a vital part of my current workflow (to see what things break when meteor hot-compiles changes to files and their dependencies) so I wonder if I was doing something wrong or if this is currently by design. |
@perbergland That's by design. The new @MastroLindus We're following the precedent set by the Also, I worry about leading people to think any set of options in |
Thanks for the info, Ben. For my use case I think I would like to still have TS output in the console and also to fail builds that don't pass when running in build pipelines (circleci, github actions etc) and give proper errors there. TS is moving along very quickly so it seems essential to have the compiler support being able to release quickly after MS drops new versions. Therefore it's great that it's a separate package and not part of meteor core. As for settings, I rely on quite a few to be able to work. The most important being strictNullChecks=true (YMMV) but I also need a bunch of the npm import flags because npm imports are so hard to get right in Typescript. Below are my current settings for reference. I can't say I am certain I need all of them (and some are very poorly documented) but since my TS compile takes ~10min from scratch I don't often feel like experimenting when I get to a state that works :)
Another Very Important Attribute for build servers is to be able to cache TS results. For adornis:typescript, I cache the entire .meteor/local/.typescript-cache directory and this now keeps my bundle builds slightly below 10 minutes. It would be a non-starter for me if it caching/restoring partial ts results is not possible. My 10¢ |
@benjamn I really appreciate your feedback on your decisions regarding the typescript configuration, they are really understandable. Regarding the configuration, my dealBreakes/must haves are: I also use the exclude directive to exclude the folders .node_modules and .meteor the full config is the following, but the stuff above are really what I do need (also some of it might be outdated or not needed anymore, but I touch it rarely these days)
|
@MastroLindus regarding the option of viewing all the TS errors. We're using |
@nicu-chiciuc I used to have a similar approach (using tsc -watch to compile ts into js, and then let meteor pickup the js files) before I switched to adornis:typescript (that currently already provides me with compiler error output in the console, but soon will be deprecated in favor of the official package). Instead of running tsc --watch you could even just have it as a build task inside vs-code for what it is worth. Honestly I would rather either compiling the typescript by myself with tsc, or having it in meteor, but having it both it's not a solution I like. Second I don't see the point in compiling the same files twice, especially since there's no reason or benefit doing it so. |
As requested in meteor/meteor-feature-requests#285.
This plugin package enables the use of TypeScript 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, thetypescript
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:To add the
typescript
package to an existing package, include the statementapi.use('typescript');
in thePackage.onUse
callback in yourpackage.js
file: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 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 thetranspileModule
function, which means that certain cross-file analysis and compilation will not work. For example,export const enum {...}
is not fully supported, thoughconst 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 Meteortypescript
plugin from accepting configuration options fromtsconfig.json
, but for now we've kept things simple by avoiding configuration complexities. You may still want to have atsconfig.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 stillhas 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
.Use the version of
typescript
installed in the applicationnode_modules
directory, rather than the one bundled withmeteor-babel
. We will attempt to keepmeteor-babel
's version oftypescript
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 frommeteor/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 uptsconfig.json
files (similar to howbabel-compiler
looks up.babelrc
files) and obey (some of) the configuration options.