Skip to content

Commit 3d0fb1f

Browse files
authored
Merge pull request #172 from typed-ember/programmatic-compilation
Invoke tsc programmatically
2 parents 38c9d35 + 1661c54 commit 3d0fb1f

File tree

10 files changed

+222
-117
lines changed

10 files changed

+222
-117
lines changed

appveyor.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ install:
1818
- rename C:\Python27 Python27hidden
1919
# Typical npm stuff.
2020
- md C:\nc
21-
- yarn --ignore-optional
21+
- yarn
2222

2323
cache:
24-
- "%LOCALAPPDATA%\\Yarn"
24+
# - "%LOCALAPPDATA%\\Yarn"
2525

2626
# Post-install test scripts.
2727
test_script:

ember-cli-build.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
44

55
module.exports = function(defaults) {
66
let app = new EmberAddon(defaults, {
7-
// Add options here
7+
babel: {
8+
sourceMaps: 'inline'
9+
}
810
});
911

1012
/*

lib/commands/precompile.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
'use strict';
33

44
const tmpdir = require('../utilities/tmpdir');
5+
const execa = require('execa');
56
const fs = require('fs-extra');
67
const path = require('path');
78
const walkSync = require('walk-sync');
89
const mkdirp = require('mkdirp');
910
const Command = require('ember-cli/lib/models/command'); // eslint-disable-line node/no-unpublished-require
10-
const compile = require('../utilities/compile');
1111

1212
const PRECOMPILE_MANIFEST = 'tmp/.ts-precompile-manifest';
1313

@@ -26,13 +26,20 @@ module.exports = Command.extend({
2626

2727
// prettier-ignore
2828
let flags = [
29+
'--outDir', outDir,
30+
'--rootDir', project.root,
31+
'--allowJs', 'false',
32+
'--noEmit', 'false',
2933
'--declaration',
3034
'--sourceMap', 'false',
3135
'--inlineSourceMap', 'false',
3236
'--inlineSources', 'false',
3337
];
3438

35-
return compile({ project, outDir, flags }).then(() => {
39+
// Ensure the output directory is created even if no files are generated
40+
mkdirp.sync(outDir);
41+
42+
return execa('tsc', flags).then(() => {
3643
let output = [];
3744
for (let declSource of walkSync(outDir, { globs: ['**/*.d.ts'] })) {
3845
if (this._shouldCopy(declSource)) {

lib/incremental-typescript-compiler.js

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const path = require('path');
1111
const fs = require('fs');
1212
const resolve = require('resolve');
1313
const compile = require('./utilities/compile');
14+
const ts = require('typescript');
1415

1516
const debugCompiler = require('debug')('ember-cli-typescript:compiler');
1617
const debugAutoresolve = require('debug')('ember-cli-typescript:autoresolve');
@@ -29,7 +30,7 @@ module.exports = class IncrementalTypescriptCompiler {
2930
this.project = project;
3031
this.addons = this._discoverAddons(project, []);
3132
this.maxBuildCount = 1;
32-
this.autoresolveThreshold = 500;
33+
this.autoresolveThreshold = 250;
3334

3435
this._buildDeferred = RSVP.defer();
3536
this._isSynced = false;
@@ -106,43 +107,41 @@ module.exports = class IncrementalTypescriptCompiler {
106107

107108
let project = this.project;
108109
let outDir = this.outDir();
109-
let flags = ['--watch'];
110-
let tsc = compile({ project, outDir, flags });
111-
112-
tsc.stdout.on('data', data => {
113-
let text = data
114-
.toString()
115-
.trim()
116-
// tsc screen-clearing:
117-
.replace(/\u001bc/g, ''); // eslint-disable-line no-control-regex
118-
119-
if (text) {
120-
this.project.ui.writeLine(text);
121-
}
122110

123-
if (data.indexOf('Starting incremental compilation') !== -1) {
124-
debugCompiler('tsc detected a file change');
125-
this.willRebuild();
126-
clearTimeout(this._pendingAutoresolve);
127-
}
111+
compile(project, { outDir, watch: true }, {
112+
reportWatchStatus: (diagnostic) => {
113+
let text = diagnostic.messageText;
128114

129-
if (data.indexOf('Compilation complete') !== -1) {
130-
debugCompiler('rebuild completed');
115+
if (text.indexOf('Starting incremental compilation') !== -1) {
116+
debugCompiler('tsc detected a file change');
117+
this.willRebuild();
118+
clearTimeout(this._pendingAutoresolve);
119+
}
131120

132-
this.didSync();
121+
if (text.indexOf('Compilation complete') !== -1) {
122+
debugCompiler('rebuild completed');
133123

134-
if (this._didAutoresolve) {
135-
this._touchRebuildTrigger();
136-
this.maxBuildCount++;
137-
}
124+
this.didSync();
138125

139-
clearTimeout(this._pendingAutoresolve);
140-
this._didAutoresolve = false;
141-
}
142-
});
126+
if (this._didAutoresolve) {
127+
this._touchRebuildTrigger();
128+
this.maxBuildCount++;
129+
}
143130

144-
tsc.stderr.on('data', data => {
145-
this.project.ui.writeError(data.toString().trim());
131+
clearTimeout(this._pendingAutoresolve);
132+
this._didAutoresolve = false;
133+
}
134+
},
135+
136+
reportDiagnostic: (diagnostic) => {
137+
if (diagnostic.category !== 2) {
138+
this.project.ui.write(ts.formatDiagnostic(diagnostic, {
139+
getCanonicalFileName: path => path,
140+
getCurrentDirectory: ts.sys.getCurrentDirectory,
141+
getNewLine: () => ts.sys.newLine,
142+
}));
143+
}
144+
}
146145
});
147146
}
148147

lib/utilities/compile.js

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,65 @@
11
/* eslint-env node */
22
'use strict';
33

4-
const execa = require('execa');
54
const mkdirp = require('mkdirp');
5+
const ts = require('typescript');
6+
const chokidar = require('chokidar');
7+
const fs = require('fs-extra');
68

7-
module.exports = function compile(options) {
9+
module.exports = function compile(project, tsOptions, callbacks) {
810
// Ensure the output directory is created even if no files are generated
9-
mkdirp.sync(options.outDir);
10-
11-
// argument sequence here is meaningful; don't apply prettier.
12-
// prettier-ignore
13-
let args = [
14-
'--outDir', options.outDir,
15-
'--rootDir', options.project.root,
16-
'--allowJs', 'false',
17-
'--noEmit', 'false'
18-
];
19-
20-
return execa('tsc', args.concat(options.flags));
11+
mkdirp.sync(tsOptions.outDir);
12+
13+
let fullOptions = Object.assign({
14+
rootDir: project.root,
15+
allowJs: false,
16+
noEmit: false
17+
}, tsOptions);
18+
19+
let configPath = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json');
20+
let createProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram;
21+
let host = ts.createWatchCompilerHost(
22+
configPath,
23+
fullOptions,
24+
buildWatchHooks(ts.sys),
25+
createProgram,
26+
callbacks.reportDiagnostic,
27+
callbacks.reportWatchStatus
28+
);
29+
30+
return ts.createWatchProgram(host);
2131
};
32+
33+
function buildWatchHooks(sys) {
34+
let watchedFiles = new Map();
35+
36+
return Object.assign({}, sys, {
37+
watchFile(file, callback) {
38+
watchedFiles.set(file, callback);
39+
40+
return {
41+
close() {
42+
watchedFiles.delete(file);
43+
}
44+
};
45+
},
46+
47+
watchDirectory(dir, callback) {
48+
if (!fs.existsSync(dir)) return;
49+
50+
let ignored = /\/(\..*?|dist|node_modules|tmp)\//;
51+
let watcher = chokidar.watch(dir, { ignored });
52+
53+
watcher.on('all', (type, path) => {
54+
path = path.replace(/\\/g, '/'); // Normalize Windows
55+
if (type === 'add') {
56+
callback(path);
57+
} else if (watchedFiles.has(path)) {
58+
watchedFiles.get(path)(path, type === 'change' ? 1 : 2);
59+
}
60+
});
61+
62+
return watcher;
63+
}
64+
});
65+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"broccoli-plugin": "^1.2.1",
3737
"broccoli-stew": "^1.4.0",
3838
"chalk": "^2.3.0",
39+
"chokidar": "^2.0.3",
3940
"debug": "^3.1.0",
4041
"ember-cli": "*",
4142
"ember-cli-get-component-path-option": "^1.0.0",

tests/dummy/app/controllers/application.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ import Ember from 'ember';
33
export default Ember.Controller.extend({
44
// Just a very roundabout way of using some ES6 features
55
value: ((test = 'Test') => `${test} ${'Value'}`)(),
6+
foo: 'hello'
67
});

tests/dummy/app/helpers/typed-help.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Ember from 'ember';
12
import { helper } from '@ember/component/helper';
23

34
export function typedHelp(/*params, hash*/) {

tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"allowJs": false,
55
"moduleResolution": "node",
66
"noEmitOnError": true,
7+
"inlineSourceMap": true,
8+
"inlineSources": true,
79
"baseUrl": ".",
810
"paths": {
911
"dummy/tests/*": ["tests/*"],

0 commit comments

Comments
 (0)