Skip to content

Commit

Permalink
perf: don't use middleware if has build
Browse files Browse the repository at this point in the history
`ember serve` and `ember test` has a `path` flag to specify
an existing ember build to use, without rebuilding. `ember-cli-typescript`
will add typechecking middleware regardless of ember using an existing
build, the typechecking middleware is not required. This can slow
down the initial request to express server significantly.

For instance one of my projects spends 49 secs for initial typecheck on
first request.

```sh
$ DEBUG=*:* ember s --path dist
...
– Serving on http://localhost:4200/admin/
...
ember-cli-typescript:typecheck-worker Typecheck complete (0 diagnostics) +49s
...
```
  • Loading branch information
mrloop committed May 12, 2020
1 parent 3b1cd07 commit 63d2691
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 17 deletions.
12 changes: 8 additions & 4 deletions ts/addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,16 @@ export default addon({
return `${__dirname}/blueprints`;
},

serverMiddleware({ app }) {
this._addTypecheckMiddleware(app);
serverMiddleware({ app, options }) {
if (!options || !options.path) {
this._addTypecheckMiddleware(app);
}
},

testemMiddleware(app) {
this._addTypecheckMiddleware(app);
testemMiddleware(app, options) {
if (!options || !options.path) {
this._addTypecheckMiddleware(app);
}
},

async postBuild() {
Expand Down
46 changes: 46 additions & 0 deletions ts/tests/acceptance/build-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,52 @@ describe('Acceptance: build', function() {
);
});

it("doesn't launch type checking when --path used", async () => {
await app.build();
let server = app.serve({
args: ['--path', 'dist'],
env: { DEBUG: 'ember-cli-typescript|express:*' },
});
let result = await server.raceForOutputs([
'ember-cli-typescript:typecheck-worker',
'Serving on',
]);
expect(result).to.include('Serving on');
});

it('does launch type checking when --path not used', async () => {
await app.build();
let server = app.serve({ env: { DEBUG: 'ember-cli-typescript|express:*' } });
let result = await server.raceForOutputs([
'ember-cli-typescript:typecheck-worker',
'Serving on',
]);
expect(result).to.include('ember-cli-typescript:typecheck-worker');
});

it("doesn't launch type checking when --path used for tests", async () => {
await app.build();
let test = app.test({
args: ['--path', 'dist'],
env: { DEBUG: 'ember-cli-typescript|express:*' },
});
let result = await test.raceForOutputs([
'ember-cli-typescript:typecheck-worker',
'express:application',
]);
expect(result).to.include('express:application');
});

it('does launch type checking when --path not used for tests', async () => {
await app.build();
let test = app.test({ env: { DEBUG: 'ember-cli-typescript|express:*' } });
let result = await test.raceForOutputs([
'ember-cli-typescript:typecheck-worker',
'express:application',
]);
expect(result).to.include('ember-cli-typescript:typecheck-worker');
});

it('fails the build when noEmitOnError is set and an error is emitted', async () => {
app.writeFile('app/app.ts', `import { foo } from 'nonexistent';`);

Expand Down
43 changes: 32 additions & 11 deletions ts/tests/helpers/skeleton-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ const getEmberPort = (() => {
return () => lastPort++;
})();

interface EmberCliOptions {
args?: string[];
env?: any;
}

export default class SkeletonApp {
port = getEmberPort();
watched: WatchedBuild | null = null;
watched: WatchedEmberProcess | null = null;
watchedTest: WatchedEmberProcess | null = null;
tmpDir = tmp.dirSync({
tries: 10,
unsafeCleanup: true,
Expand All @@ -31,17 +37,25 @@ export default class SkeletonApp {
}

build() {
return this._ember(['build']);
return this._ember({ args: ['build'] });
}

serve() {
serve(options: EmberCliOptions = { args: [], env: {} }) {
if (this.watched) {
throw new Error('Already serving');
}
return (this.watched = new WatchedBuild(
this._ember(['serve', '--port', `${this.port}`]),
this.port
));
options.args = options.args || [];
options.args = ['serve', '--port', `${this.port}`, ...options.args];
return (this.watched = new WatchedEmberProcess(this._ember(options), this.port));
}

test(options: EmberCliOptions = { args: [], env: {} }) {
if (this.watchedTest) {
throw new Error('Already testing');
}
options.args = options.args || [];
options.args = ['test', ...options.args];
return (this.watchedTest = new WatchedEmberProcess(this._ember(options)));
}

updatePackageJSON(callback: (arg: any) => any) {
Expand All @@ -68,17 +82,20 @@ export default class SkeletonApp {
if (this.watched) {
this.watched.kill();
}
if (this.watchedTest) {
this.watchedTest.kill();
}
this.tmpDir.removeCallback();
}

_ember(args: string[]) {
_ember(options: EmberCliOptions) {
let ember = require.resolve('ember-cli/bin/ember');
return execa.node(ember, args, { cwd: this.root, all: true });
return execa.node(ember, options.args, { cwd: this.root, all: true, env: options.env });
}
}

class WatchedBuild extends EventEmitter {
constructor(protected ember: execa.ExecaChildProcess, protected port: number) {
class WatchedEmberProcess extends EventEmitter {
constructor(protected ember: execa.ExecaChildProcess, protected port?: number) {
super();
this.ember.stdout.on('data', data => {
let output = data.toString();
Expand All @@ -102,6 +119,10 @@ class WatchedBuild extends EventEmitter {
return got(`http://localhost:${this.port}${path}`);
}

raceForOutputs(targets: string[]) {
return Promise.race(targets.map(target => this.waitForOutput(target)));
}

waitForOutput(target: string) {
return new Promise(resolve => {
let output = '';
Expand Down
11 changes: 9 additions & 2 deletions ts/types/ember-cli/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ declare module 'ember-cli/lib/models/addon' {
import UI from 'console-ui';
import { Application } from 'express';
import Project from 'ember-cli/lib/models/project';
import { TaskOptions } from 'ember-cli/lib/models/task';
import Command from 'ember-cli/lib/models/command';
import EmberApp from 'ember-cli/lib/broccoli/ember-app';
import PreprocessRegistry from 'ember-cli-preprocess-registry';
Expand All @@ -36,8 +37,8 @@ declare module 'ember-cli/lib/models/addon' {
includedCommands(): Record<string, typeof Command | ExtendOptions<Command>> | void;
shouldIncludeChildAddon(addon: Addon): boolean;
isDevelopingAddon(): boolean;
serverMiddleware(options: { app: Application }): void | Promise<void>;
testemMiddleware(app: Application): void;
serverMiddleware(options: { app: Application; options?: TaskOptions }): void | Promise<void>;
testemMiddleware(app: Application, options?: TaskOptions): void;
setupPreprocessorRegistry(type: 'self' | 'parent', registry: PreprocessRegistry): void;
}
}
Expand All @@ -49,6 +50,12 @@ declare module 'ember-cli/lib/models/blueprint' {
export = Blueprint;
}

declare module 'ember-cli/lib/models/task' {
export interface TaskOptions {
path?: string;
}
}

declare module 'ember-cli/lib/models/command' {
import CoreObject from 'core-object';
import UI from 'console-ui';
Expand Down

0 comments on commit 63d2691

Please sign in to comment.