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 fast incremental compiler #85

Merged
merged 46 commits into from
Feb 6, 2018
Merged

Conversation

dwickern
Copy link
Contributor

The idea here is to use tsc --watch instead of broccoli-typescript-compiler, since tsc has much faster incremental compile logic. There is no public API for the incremental compiler yet, but once microsoft/TypeScript#9282 is implemented, broccoli-typescript-compiler should be equally fast and this PR will be obsolete.

There are lots of issues with my hacked together implementation

  • the .ts are still being watched, so each change triggers two rebuilds. Fixing this should make it faster.
  • ember-cli will not watch a directory in tmp so I put the compiled js into an .e-c-ts directory
  • the incremental compiler should only be used in development, but I couldn't figure out how to check the environment
  • I couldn't figure out the broccoli hook to use to clean up temp files and child processes
  • I'm swallowing all the stdout from tsc since the error messages are nasty, e.g.

    tmp/ember_cli_typescript_fast_watcher-input_base_path- U6assc9z.tmp/web/tests/acceptance/search-test.ts(55,7): error TS2304: Cannot find name 'server'.

Copy link
Member

@chriskrycho chriskrycho left a comment

Choose a reason for hiding this comment

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

😍 – I'll see if I can take a stab at some of the other pieces, perhaps this afternoon or tomorrow.


function makeTempDir() {
// TODO: the directory won't get monitored if it's in tmp/
const dir = '.e-c-ts';
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a reasonable compromise to me; we'll just want to do two things if we run with it:

  1. Document it.
  2. Figure out how to automatically update .gitignore on installing the app, so that this directory is always excluded.

annotation: 'JS files',
});

// TODO: how to disable watching .ts files?
Copy link
Member

Choose a reason for hiding this comment

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

Once we figure out how to correctly identify the environment, it should either be available in the same way here or we can use it to set a flag on the this instance that we pass in so that we watch or not as appropriate for the environment.

Alternatively, maybe in the fast-compiler we intentionally just ignore these files entirely and only trigger on the files in the .e-c-ts directory changing?

@dwickern
Copy link
Contributor Author

I think the main problem are the two rebuilds

  1. I modify foo.ts
  2. ember-cli file watcher sees the change in foo.ts and triggers a rebuild
  3. rebuild copies foo.ts to tmp/.../foo.ts using broccoli-funnel
  4. tsc --watch file watcher sees the change in tmp/.../foo.ts
  5. tsc --watch recompiles and outputs .e-c-ts/foo.js
  6. ember-cli file watcher sees the change in foo.js and triggers a rebuild

It's a bit faster than broccoli-typescript-compiler but still not as fast as running tsc --watch to compile in-place. I think I need to take a different approach...

@chriskrycho
Copy link
Member

Ah, I misunderstood part of the flow in practice (a bit aspirationally I suppose). My assumption was (and I think if we could get there it would make sense if) it did:

  1. Modify foo.ts.
  2. tsc --watch recompiles and outputs .e-c-ts/foo.js.
  3. ember-cli file watcher sees change in foo.js and triggers rebuild, which funnels to correct tmp directory.

The main challenge I'd foresee with that approach would be getting the bits from .e-c-ts into the correct tmp directory with the funnel, but I think you could map it correctly? (My broccoli knowledge is… not large.)

@dwickern
Copy link
Contributor Author

The advantage of using the preprocessor hooks is they have already sorted out and merged all the trees.

In your step 2, ember-cli also sees the change to foo.ts and triggers the second recompile. I think the only way to avoid this will be using a custom command, e.g. ember serve-ts

@chriskrycho
Copy link
Member

Mmmm. 🤔 @rwjblue, any suggestions here?

@dwickern
Copy link
Contributor Author

I changed this to use a separate command instead so that I can tweak the watcher

ember serve-ts

It works! and it's really fast, as if you're not using typescript at all

@chriskrycho
Copy link
Member

chriskrycho commented Nov 25, 2017 via email

@chriskrycho
Copy link
Member

These test failures are incredibly annoying. Nothing changed on our end for them. 😑

@chriskrycho
Copy link
Member

Two quick notes:

  1. I pulled this down and tried to use it in our main app repository, and the TS watcher needs to exclude the blueprints directory. Otherwise, you end up with some nasty errors when using the substitutions that templates allow. We have a template that looks like this:

    import Component from '@ember/component';
    
    import { bem } from 'mobile-web/lib/utilities/bem';
    
    export const block = '<%= dasherizedModuleName %>';
    
    export default class <%= classifiedModuleName %> extends Component.extend({
      // `attributeBindings` and `classNameBindings` must be here until we have TS
      // support for the new decorator spec, as they need to be merged with other
      // items of the same name in the prototype chain.
      attributeBindings: [`elementId:data-test-${block}`],
    
      // You should remove `classNameBindings` and `tagClass` if you're going to
      // apply the BEM root to an element in the template explicitly.
      classNameBindings: ['tagClass'],
    }) {
    
      tagClass = bem({ block });
    }
    

    It's named blueprints/bem-component/files/app/components/__name__.ts (and there's a matching one for the integration test). Compilation falls over with this error:

    SyntaxError: mobile-web/blueprints/bem-component/files/app/components/__name__.js: Unexpected token (6:1)
      4 | export default class {
      5 | }
    > 6 |  %  > ;
        |  ^
      7 | Component.extend({
      8 |     // `attributeBindings` and `classNameBindings` must be here until we have TS
      9 |     // support for the new decorator spec, as they need to be merged with other
    

    It makes sense: it is a syntax error, but... we just need to exclude the blueprints directory.

  2. We need to make sure we always delete the .e-c-ts directory after a run. Otherwise, problems like the one above will stick around after renames, etc.

@chriskrycho
Copy link
Member

Next comment: holy smokes this is fast.

@chriskrycho
Copy link
Member

chriskrycho commented Nov 26, 2017

Played with it a good bit; other than needing that fix for the blueprints directory and getting rid of the .e-c-ts directory, it's amazing.

So, to ready this for shipping it:

  • add .e-c-ts to .gitignore on install
  • clean up by deleting .e-c-ts when shutting down
  • ignore the blueprints directory
  • document the serve-ts command

Edit: heh, and maybe also:

  • don't swallow type errors

@dwickern
Copy link
Contributor Author

Maybe blueprints/ should be excluded in tsconfig? Or better yet, we could use explicit includes. I had similar issues before I disabled allowJs because it was compiling stuff in server/

@dwickern
Copy link
Contributor Author

dwickern commented Nov 26, 2017

We have actually 2 separate builds: running tsc directly (with serve-ts or from the IDE) and the broccoli pipeline build which does a bunch of transformations first. And both builds need to compile...

@dwickern
Copy link
Contributor Author

I also figured out why I couldn't put .e-c-ts into tmp: the directory is completely ignored in the watchman config.

@chriskrycho
Copy link
Member

@dwickern do you know what we need to do so we're not swallowing type errors when doing these incremental builds? I'd like to knock this out and ship it, and I think I know how to tackle the rest of it, but am unsure why those are getting swallowed at present. 🤔 Hopefully we can ship this as 1.1 this week, and ship the addon support soon as well.

Implement `Command#onInterrupt` to remove directory during shutdown.
Also add prettier flag not to tweak the layout of tsc process fork.
Implement `Command#onInterrupt` to remove directory during shutdown.
Also add prettier-ignore for the tsc process creation, since arguments are
*not* like ordinary arrays.
@chriskrycho chriskrycho force-pushed the fast-incremental-compiler branch from 3f49802 to 2a2952b Compare December 18, 2017 00:38
@dwickern
Copy link
Contributor Author

I get an error on startup now

Error: process is not captured
    at Object.addHandler (/Users/dwickern/code/ember-cli-typescript/node_modules/ember-cli/lib/utilities/will-interrupt-process.js:82:13)
    at new Builder (/Users/dwickern/code/ember-cli-typescript/node_modules/ember-cli/lib/models/builder.js:37:30)
    at Class.run (/Users/dwickern/code/ember-cli-typescript/lib/serve-ts.js:41:21)
    at resolve (/Users/dwickern/code/ember-cli-typescript/node_modules/ember-cli/lib/models/command.js:317:20)
    at initializePromise (/Users/dwickern/code/ember-cli-typescript/node_modules/ember-cli/node_modules/rsvp/dist/rsvp.js:532:5)

@chriskrycho
Copy link
Member

chriskrycho commented Dec 18, 2017 via email

.travis.yml Outdated
@@ -3,7 +3,7 @@ language: node_js
node_js:
- "6"

sudo: false
sudo: required
Copy link
Member

Choose a reason for hiding this comment

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

Issue described here and here on Travis' end.

lib/serve-ts.js Outdated
this.tsc = child_process.fork(
'node_modules/typescript/bin/tsc',
[
'--watch',
Copy link
Member

Choose a reason for hiding this comment

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

@dwickern @dfreeman one thing I've been thinking is: we should be able to get and use the project's tsconfig, and then use that here (using -p <path to project>) except with overrides. I believe TS properly does the override sequence if you just hand it a project file and then a set of overrides.

Does that make sense to both of you?

Copy link
Member

Choose a reason for hiding this comment

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

Alternative approach: do the usual JSON.parse(tsConfig) dance and set only the properties we want to set here, e.g. sourceMap: <whatever is in the config That might be better.

Also, I don't think we need to worry about using a global install of tsc; we should only be using the local version, since that's what will be used for the final build, as well.

Copy link
Member

Choose a reason for hiding this comment

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

I like the idea of using -p <path-to-project> and overriding with the handful of flags we need — it feels simpler (and I also think tsc will actually let you get away with nonconformant JSON, so we'd need something fancier than JSON.parse for that approach)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it already picks up the tsconfig in the project root based on the cwd? We could pass the path explicitly though.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I just went back and read the docs, and you're both correct about those pieces. So here, we should just be able to override only what we need to – and presumably, other than outdir, that should largely match what we have in the overrides in the TypescriptPreprocessor class (starting here).

Note that that's assuming valid JSON. 🤔

@chriskrycho chriskrycho force-pushed the fast-incremental-compiler branch from 1b45638 to 0b4b4fe Compare January 24, 2018 20:44
@chriskrycho
Copy link
Member

Two things:

  1. Still seeing the problem of the tree getting pulled twice somehow. I end up seeing output like this for ember serve:

    WARNING: more-ts/Users/chris/dev/oss/more-ts/types/ember-cli-mirage/index.d.ts(220,16): error TS2451: Cannot redeclare block-scoped variable 'server'.
    types/ember-cli-mirage/index.d.ts(220,16): error TS2451: Cannot redeclare block-scoped variable 'server'.
    

    Best bet there is just that the treeForApp is getting something funky.

  2. When I run ember serve-ts locally, I see:

        /Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:58192
                    throw e;
                    ^
    
    TypeError: Cannot read property 'escapedText' of undefined
        at writeClassDeclaration (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:53824:84)
        at writeModuleElement (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:53428:28)
        at emitModuleElement (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:53389:17)
        at emitNode (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:54363:28)
        at emitLines (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:53136:17)
        at emitSourceFile (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:53335:13)
        at /Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:52928:17
        at Object.forEach (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:281:30)
        at emitDeclarations (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:52910:12)
        at Object.writeDeclarationFile (/Users/chris/dev/oss/more-ts/node_modules/typescript/lib/tsc.js:54418:37)
    

Just logging those here in case either of you gets to them ahead of me. I'll be at it again tomorrow.

@chriskrycho chriskrycho merged commit 3161a06 into master Feb 6, 2018
@chriskrycho chriskrycho deleted the fast-incremental-compiler branch February 6, 2018 15:33
dfreeman pushed a commit that referenced this pull request Aug 21, 2018
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.

3 participants