Skip to content

Commit 63d2691

Browse files
committed
perf: don't use middleware if has build
`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 ... ```
1 parent 3b1cd07 commit 63d2691

File tree

4 files changed

+95
-17
lines changed

4 files changed

+95
-17
lines changed

ts/addon.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,16 @@ export default addon({
4646
return `${__dirname}/blueprints`;
4747
},
4848

49-
serverMiddleware({ app }) {
50-
this._addTypecheckMiddleware(app);
49+
serverMiddleware({ app, options }) {
50+
if (!options || !options.path) {
51+
this._addTypecheckMiddleware(app);
52+
}
5153
},
5254

53-
testemMiddleware(app) {
54-
this._addTypecheckMiddleware(app);
55+
testemMiddleware(app, options) {
56+
if (!options || !options.path) {
57+
this._addTypecheckMiddleware(app);
58+
}
5559
},
5660

5761
async postBuild() {

ts/tests/acceptance/build-test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,52 @@ describe('Acceptance: build', function() {
6969
);
7070
});
7171

72+
it("doesn't launch type checking when --path used", async () => {
73+
await app.build();
74+
let server = app.serve({
75+
args: ['--path', 'dist'],
76+
env: { DEBUG: 'ember-cli-typescript|express:*' },
77+
});
78+
let result = await server.raceForOutputs([
79+
'ember-cli-typescript:typecheck-worker',
80+
'Serving on',
81+
]);
82+
expect(result).to.include('Serving on');
83+
});
84+
85+
it('does launch type checking when --path not used', async () => {
86+
await app.build();
87+
let server = app.serve({ env: { DEBUG: 'ember-cli-typescript|express:*' } });
88+
let result = await server.raceForOutputs([
89+
'ember-cli-typescript:typecheck-worker',
90+
'Serving on',
91+
]);
92+
expect(result).to.include('ember-cli-typescript:typecheck-worker');
93+
});
94+
95+
it("doesn't launch type checking when --path used for tests", async () => {
96+
await app.build();
97+
let test = app.test({
98+
args: ['--path', 'dist'],
99+
env: { DEBUG: 'ember-cli-typescript|express:*' },
100+
});
101+
let result = await test.raceForOutputs([
102+
'ember-cli-typescript:typecheck-worker',
103+
'express:application',
104+
]);
105+
expect(result).to.include('express:application');
106+
});
107+
108+
it('does launch type checking when --path not used for tests', async () => {
109+
await app.build();
110+
let test = app.test({ env: { DEBUG: 'ember-cli-typescript|express:*' } });
111+
let result = await test.raceForOutputs([
112+
'ember-cli-typescript:typecheck-worker',
113+
'express:application',
114+
]);
115+
expect(result).to.include('ember-cli-typescript:typecheck-worker');
116+
});
117+
72118
it('fails the build when noEmitOnError is set and an error is emitted', async () => {
73119
app.writeFile('app/app.ts', `import { foo } from 'nonexistent';`);
74120

ts/tests/helpers/skeleton-app.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ const getEmberPort = (() => {
1515
return () => lastPort++;
1616
})();
1717

18+
interface EmberCliOptions {
19+
args?: string[];
20+
env?: any;
21+
}
22+
1823
export default class SkeletonApp {
1924
port = getEmberPort();
20-
watched: WatchedBuild | null = null;
25+
watched: WatchedEmberProcess | null = null;
26+
watchedTest: WatchedEmberProcess | null = null;
2127
tmpDir = tmp.dirSync({
2228
tries: 10,
2329
unsafeCleanup: true,
@@ -31,17 +37,25 @@ export default class SkeletonApp {
3137
}
3238

3339
build() {
34-
return this._ember(['build']);
40+
return this._ember({ args: ['build'] });
3541
}
3642

37-
serve() {
43+
serve(options: EmberCliOptions = { args: [], env: {} }) {
3844
if (this.watched) {
3945
throw new Error('Already serving');
4046
}
41-
return (this.watched = new WatchedBuild(
42-
this._ember(['serve', '--port', `${this.port}`]),
43-
this.port
44-
));
47+
options.args = options.args || [];
48+
options.args = ['serve', '--port', `${this.port}`, ...options.args];
49+
return (this.watched = new WatchedEmberProcess(this._ember(options), this.port));
50+
}
51+
52+
test(options: EmberCliOptions = { args: [], env: {} }) {
53+
if (this.watchedTest) {
54+
throw new Error('Already testing');
55+
}
56+
options.args = options.args || [];
57+
options.args = ['test', ...options.args];
58+
return (this.watchedTest = new WatchedEmberProcess(this._ember(options)));
4559
}
4660

4761
updatePackageJSON(callback: (arg: any) => any) {
@@ -68,17 +82,20 @@ export default class SkeletonApp {
6882
if (this.watched) {
6983
this.watched.kill();
7084
}
85+
if (this.watchedTest) {
86+
this.watchedTest.kill();
87+
}
7188
this.tmpDir.removeCallback();
7289
}
7390

74-
_ember(args: string[]) {
91+
_ember(options: EmberCliOptions) {
7592
let ember = require.resolve('ember-cli/bin/ember');
76-
return execa.node(ember, args, { cwd: this.root, all: true });
93+
return execa.node(ember, options.args, { cwd: this.root, all: true, env: options.env });
7794
}
7895
}
7996

80-
class WatchedBuild extends EventEmitter {
81-
constructor(protected ember: execa.ExecaChildProcess, protected port: number) {
97+
class WatchedEmberProcess extends EventEmitter {
98+
constructor(protected ember: execa.ExecaChildProcess, protected port?: number) {
8299
super();
83100
this.ember.stdout.on('data', data => {
84101
let output = data.toString();
@@ -102,6 +119,10 @@ class WatchedBuild extends EventEmitter {
102119
return got(`http://localhost:${this.port}${path}`);
103120
}
104121

122+
raceForOutputs(targets: string[]) {
123+
return Promise.race(targets.map(target => this.waitForOutput(target)));
124+
}
125+
105126
waitForOutput(target: string) {
106127
return new Promise(resolve => {
107128
let output = '';

ts/types/ember-cli/index.d.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ declare module 'ember-cli/lib/models/addon' {
1111
import UI from 'console-ui';
1212
import { Application } from 'express';
1313
import Project from 'ember-cli/lib/models/project';
14+
import { TaskOptions } from 'ember-cli/lib/models/task';
1415
import Command from 'ember-cli/lib/models/command';
1516
import EmberApp from 'ember-cli/lib/broccoli/ember-app';
1617
import PreprocessRegistry from 'ember-cli-preprocess-registry';
@@ -36,8 +37,8 @@ declare module 'ember-cli/lib/models/addon' {
3637
includedCommands(): Record<string, typeof Command | ExtendOptions<Command>> | void;
3738
shouldIncludeChildAddon(addon: Addon): boolean;
3839
isDevelopingAddon(): boolean;
39-
serverMiddleware(options: { app: Application }): void | Promise<void>;
40-
testemMiddleware(app: Application): void;
40+
serverMiddleware(options: { app: Application; options?: TaskOptions }): void | Promise<void>;
41+
testemMiddleware(app: Application, options?: TaskOptions): void;
4142
setupPreprocessorRegistry(type: 'self' | 'parent', registry: PreprocessRegistry): void;
4243
}
4344
}
@@ -49,6 +50,12 @@ declare module 'ember-cli/lib/models/blueprint' {
4950
export = Blueprint;
5051
}
5152

53+
declare module 'ember-cli/lib/models/task' {
54+
export interface TaskOptions {
55+
path?: string;
56+
}
57+
}
58+
5259
declare module 'ember-cli/lib/models/command' {
5360
import CoreObject from 'core-object';
5461
import UI from 'console-ui';

0 commit comments

Comments
 (0)