Skip to content

Commit 5c468af

Browse files
authored
Merge pull request #199 from typed-ember/mu
Initial MU support
2 parents cb3f7df + a5c8908 commit 5c468af

File tree

15 files changed

+246
-20
lines changed

15 files changed

+246
-20
lines changed

blueprints/ember-cli-typescript/index.js

+35-9
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ module.exports = {
3232
let inRepoAddons = (this.project.pkg['ember-addon'] || {}).paths || [];
3333
let hasMirage = 'ember-cli-mirage' in (this.project.pkg.devDependencies || {});
3434
let isAddon = this.project.isEmberCLIAddon();
35-
let includes = ['app', isAddon && 'addon', 'tests', 'types'].concat(inRepoAddons).filter(Boolean);
35+
let isMU = this._detectMU();
36+
let includes = isMU ? ['src'] : ['app', isAddon && 'addon'].filter(Boolean);
37+
38+
includes = includes.concat(['tests', 'types']).concat(inRepoAddons);
3639

3740
// Mirage is already covered for addons because it's under `tests/`
3841
if (hasMirage && !isAddon) {
@@ -51,15 +54,24 @@ module.exports = {
5154
paths[`${appName}/mirage/*`] = [`${isAddon ? 'tests/dummy/' : ''}mirage/*`];
5255
}
5356

54-
if (isAddon) {
55-
paths[`${appName}/*`] = ['tests/dummy/app/*', 'app/*'];
57+
if (isMU) {
58+
if (isAddon) {
59+
paths[`${appName}/src/*`] = ['tests/dummy/src/*'];
60+
paths[`${dasherizedName}/src/*`] = ['src/*'];
61+
} else {
62+
paths[`${appName}/src/*`] = ['src/*'];
63+
}
5664
} else {
57-
paths[`${appName}/*`] = ['app/*'];
58-
}
59-
60-
if (isAddon) {
61-
paths[dasherizedName] = ['addon'];
62-
paths[`${dasherizedName}/*`] = ['addon/*'];
65+
if (isAddon) {
66+
paths[`${appName}/*`] = ['tests/dummy/app/*', 'app/*'];
67+
} else {
68+
paths[`${appName}/*`] = ['app/*'];
69+
}
70+
71+
if (isAddon) {
72+
paths[dasherizedName] = ['addon'];
73+
paths[`${dasherizedName}/*`] = ['addon/*'];
74+
}
6375
}
6476

6577
for (let addon of inRepoAddons) {
@@ -79,11 +91,21 @@ module.exports = {
7991
},
8092

8193
fileMapTokens(/*options*/) {
94+
let isMU = this._detectMU();
95+
8296
// Return custom tokens to be replaced in your files.
8397
return {
8498
__app_name__(options) {
8599
return options.inAddon ? 'dummy' : options.dasherizedModuleName;
86100
},
101+
102+
__config_root__(options) {
103+
if (isMU) {
104+
return options.inAddon ? 'tests/dummy' : '.';
105+
} else {
106+
return options.inAddon ? 'tests/dummy/app' : 'app';
107+
}
108+
}
87109
};
88110
},
89111

@@ -138,6 +160,10 @@ module.exports = {
138160
return files;
139161
},
140162

163+
_detectMU() {
164+
return this.project.isModuleUnification && this.project.isModuleUnification();
165+
},
166+
141167
_installPrecompilationHooks() {
142168
let pkgPath = `${this.project.root}/package.json`;
143169
let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));

index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = {
2929
shouldIncludeChildAddon(addon) {
3030
// For testing, we have dummy in-repo addons set up, but e-c-ts doesn't depend on them;
3131
// its dummy app does. Otherwise we'd have a circular dependency.
32-
return addon.name !== 'in-repo-a' && addon.name !== 'in-repo-b';
32+
return !['in-repo-a', 'in-repo-b', 'in-repo-c'].includes(addon.name);
3333
},
3434

3535
setupPreprocessorRegistry(type, registry) {

lib/commands/precompile.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,12 @@ module.exports = Command.extend({
4545
let compiled = declSource.replace(/\.d\.ts$/, '.js');
4646
this._copyFile(output, `${outDir}/${compiled}`, compiled);
4747

48-
// We can only do anything meaningful with declarations for files in addon/
48+
// We can only do anything meaningful with declarations for files in addon/ or src/
4949
if (this._isAddonFile(declSource)) {
5050
let declDest = declSource.replace(/^addon\//, '');
5151
this._copyFile(output, `${outDir}/${declSource}`, declDest);
52+
} else if (this._isSrcFile(declSource)) {
53+
this._copyFile(output, `${outDir}/${declSource}`, declSource);
5254
}
5355
}
5456
}
@@ -60,7 +62,9 @@ module.exports = Command.extend({
6062
},
6163

6264
_shouldCopy(source) {
63-
return this._isAppFile(source) || this._isAddonFile(source);
65+
return this._isAppFile(source)
66+
|| this._isAddonFile(source)
67+
|| this._isSrcFile(source);
6468
},
6569

6670
_isAppFile(source) {
@@ -71,6 +75,10 @@ module.exports = Command.extend({
7175
return source.indexOf('addon') === 0;
7276
},
7377

78+
_isSrcFile(source) {
79+
return source.indexOf('src') === 0;
80+
},
81+
7482
_copyFile(output, source, dest) {
7583
let segments = dest.split(/\/|\\/);
7684

lib/incremental-typescript-compiler/index.js

+30-6
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,22 @@ module.exports = class IncrementalTypescriptCompiler {
3333
}
3434

3535
treeForHost() {
36-
let appTree = new TypescriptOutput(this, {
37-
[`${this._relativeAppRoot()}/app`]: 'app',
38-
});
36+
let appRoot = `${this._relativeAppRoot()}/app`;
37+
let srcRoot = `${this._relativeAppRoot()}/src`;
38+
39+
let trees = {};
40+
if (fs.existsSync(appRoot)) {
41+
trees[appRoot] = 'app';
42+
}
3943

44+
if (fs.existsSync(srcRoot)) {
45+
// MU apps currently include tests in production builds, and it's not yet clear
46+
// how those will be filtered out in the future. We may or may not wind up needing
47+
// to do that filtering here.
48+
trees[srcRoot] = 'app/src';
49+
}
50+
51+
let appTree = new TypescriptOutput(this, trees);
4052
let mirage = this._mirageDirectory();
4153
let mirageTree = mirage && new TypescriptOutput(this, {
4254
[mirage]: 'app/mirage',
@@ -63,7 +75,16 @@ module.exports = class IncrementalTypescriptCompiler {
6375
treeForAddons() {
6476
let paths = {};
6577
for (let addon of this.addons) {
66-
paths[`${this._relativeAddonRoot(addon)}/addon`] = addon.name;
78+
let absoluteRoot = this._addonRoot(addon);
79+
let relativeRoot = this._relativeAddonRoot(addon);
80+
81+
if (fs.existsSync(`${absoluteRoot}/addon`)) {
82+
paths[`${relativeRoot}/addon`] = addon.name;
83+
}
84+
85+
if (fs.existsSync(`${absoluteRoot}/src`)) {
86+
paths[`${relativeRoot}/src`] = `${addon.name}/src`;
87+
}
6788
}
6889
return new TypescriptOutput(this, paths);
6990
}
@@ -187,16 +208,19 @@ module.exports = class IncrementalTypescriptCompiler {
187208
}
188209
}
189210

190-
_relativeAddonRoot(addon) {
211+
_addonRoot(addon) {
191212
let addonRoot = addon.root;
192213
if (addonRoot.indexOf(this.project.root) !== 0) {
193214
let packagePath = resolve.sync(`${addon.pkg.name}/package.json`, {
194215
basedir: this.project.root,
195216
});
196217
addonRoot = path.dirname(packagePath);
197218
}
219+
return addonRoot;
220+
}
198221

199-
return addonRoot.replace(this.project.root, '');
222+
_relativeAddonRoot(addon) {
223+
return this._addonRoot(addon).replace(this.project.root, '');
200224
}
201225
};
202226

node-tests/blueprints/ember-cli-typescript-test.js

+103
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const path = require('path');
55
const helpers = require('ember-cli-blueprint-test-helpers/helpers');
66
const chaiHelpers = require('ember-cli-blueprint-test-helpers/chai');
77
const Blueprint = require('ember-cli/lib/models/blueprint');
8+
const Project = require('ember-cli/lib/models/project');
89

910
const ects = require('../../blueprints/ember-cli-typescript');
1011

@@ -130,6 +131,108 @@ describe('Acceptance: ember-cli-typescript generator', function() {
130131
});
131132
});
132133

134+
describe('module unification', function() {
135+
const originalIsMU = Project.prototype.isModuleUnification;
136+
137+
beforeEach(function() {
138+
Project.prototype.isModuleUnification = () => true;
139+
});
140+
141+
afterEach(function() {
142+
Project.prototype.isModuleUnification = originalIsMU;
143+
});
144+
145+
it('basic app', function() {
146+
const args = ['ember-cli-typescript'];
147+
148+
return helpers
149+
.emberNew()
150+
.then(() => helpers.emberGenerate(args))
151+
.then(() => {
152+
const pkg = file('package.json');
153+
expect(pkg).to.exist;
154+
155+
const pkgJson = JSON.parse(pkg.content);
156+
expect(pkgJson.scripts.prepublishOnly).to.be.undefined;
157+
expect(pkgJson.scripts.postpublish).to.be.undefined;
158+
expect(pkgJson.devDependencies).to.include.all.keys('ember-data');
159+
expect(pkgJson.devDependencies).to.include.all.keys('@types/ember-data');
160+
expect(pkgJson.devDependencies).to.include.all.keys('ember-cli-qunit');
161+
expect(pkgJson.devDependencies).to.include.all.keys('@types/ember-qunit', '@types/qunit');
162+
expect(pkgJson.devDependencies).to.not.have.any.keys('@types/ember-mocha', '@types/mocha');
163+
164+
const tsconfig = file('tsconfig.json');
165+
expect(tsconfig).to.exist;
166+
167+
const tsconfigJson = JSON.parse(tsconfig.content);
168+
expect(tsconfigJson.compilerOptions.paths).to.deep.equal({
169+
'my-app/tests/*': ['tests/*'],
170+
'my-app/src/*': ['src/*'],
171+
'*': ['types/*'],
172+
});
173+
174+
expect(tsconfigJson.compilerOptions.inlineSourceMap).to.equal(true);
175+
expect(tsconfigJson.compilerOptions.inlineSources).to.equal(true);
176+
177+
expect(tsconfigJson.include).to.deep.equal(['src/**/*', 'tests/**/*', 'types/**/*']);
178+
179+
const projectTypes = file('types/my-app/index.d.ts');
180+
expect(projectTypes).to.exist;
181+
expect(projectTypes).to.include(ects.APP_DECLARATIONS);
182+
183+
const environmentTypes = file('config/environment.d.ts');
184+
expect(environmentTypes).to.exist;
185+
186+
const emberDataCatchallTypes = file('types/ember-data.d.ts');
187+
expect(emberDataCatchallTypes).to.exist;
188+
});
189+
});
190+
191+
it('basic addon', function() {
192+
const args = ['ember-cli-typescript'];
193+
194+
return helpers
195+
.emberNew({ target: 'addon' })
196+
.then(() => helpers.emberGenerate(args))
197+
.then(() => {
198+
const pkg = file('package.json');
199+
expect(pkg).to.exist;
200+
201+
const pkgJson = JSON.parse(pkg.content);
202+
expect(pkgJson.scripts.prepublishOnly).to.equal('ember ts:precompile');
203+
expect(pkgJson.scripts.postpublish).to.equal('ember ts:clean');
204+
expect(pkgJson.devDependencies).to.not.have.any.keys('ember-data');
205+
expect(pkgJson.devDependencies).to.not.have.any.keys('@types/ember-data');
206+
expect(pkgJson.devDependencies).to.include.all.keys('ember-cli-qunit');
207+
expect(pkgJson.devDependencies).to.include.all.keys('@types/ember-qunit', '@types/qunit');
208+
expect(pkgJson.devDependencies).to.not.have.any.keys('@types/ember-mocha', '@types/mocha');
209+
210+
const tsconfig = file('tsconfig.json');
211+
expect(tsconfig).to.exist;
212+
213+
const tsconfigJson = JSON.parse(tsconfig.content);
214+
expect(tsconfigJson.compilerOptions.paths).to.deep.equal({
215+
'dummy/tests/*': ['tests/*'],
216+
'dummy/src/*': ['tests/dummy/src/*'],
217+
'my-addon/src/*': ['src/*'],
218+
'*': ['types/*'],
219+
});
220+
221+
expect(tsconfigJson.include).to.deep.equal(['src/**/*', 'tests/**/*', 'types/**/*']);
222+
223+
const projectTypes = file('types/dummy/index.d.ts');
224+
expect(projectTypes).to.exist;
225+
expect(projectTypes).not.to.include(ects.APP_DECLARATIONS);
226+
227+
const environmentTypes = file('tests/dummy/config/environment.d.ts');
228+
expect(environmentTypes).to.exist;
229+
230+
const emberDataCatchallTypes = file('types/ember-data.d.ts');
231+
expect(emberDataCatchallTypes).not.to.exist;
232+
});
233+
});
234+
});
235+
133236
it('in-repo addons', function() {
134237
const args = ['ember-cli-typescript'];
135238

node-tests/commands/precompile-test.js

+22
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,26 @@ describe('Acceptance: ts:precompile command', function() {
4747
expect(transpiled.content.trim()).to.equal(`export const testString = 'hello';`);
4848
});
4949
});
50+
51+
describe('module unification', function() {
52+
it('generates .js and .d.ts files from the src tree', function() {
53+
fs.ensureDirSync('src');
54+
fs.writeFileSync('src/test-file.ts', `export const testString: string = 'hello';`);
55+
56+
let tsconfig = fs.readJSONSync('tsconfig.json');
57+
tsconfig.include.push('src');
58+
fs.writeJSONSync('tsconfig.json', tsconfig);
59+
60+
return ember(['ts:precompile'])
61+
.then(() => {
62+
let declaration = file('src/test-file.d.ts');
63+
expect(declaration).to.exist;
64+
expect(declaration.content.trim()).to.equal(`export declare const testString: string;`);
65+
66+
let transpiled = file('src/test-file.js');
67+
expect(transpiled).to.exist;
68+
expect(transpiled.content.trim()).to.equal(`export const testString = 'hello';`);
69+
});
70+
});
71+
});
5072
});

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@
120120
],
121121
"paths": [
122122
"tests/dummy/lib/in-repo-a",
123-
"tests/dummy/lib/in-repo-b"
123+
"tests/dummy/lib/in-repo-b",
124+
"tests/dummy/lib/in-repo-c"
124125
]
125126
},
126127
"prettier": {

tests/dummy/lib/in-repo-c/index.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* eslint-env node */
2+
'use strict';
3+
4+
module.exports = {
5+
name: 'in-repo-c',
6+
7+
isDevelopingAddon() {
8+
return true;
9+
}
10+
};
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "in-repo-c",
3+
"keywords": [
4+
"ember-addon"
5+
],
6+
"dependencies": {
7+
"ember-cli-babel": "*"
8+
},
9+
"devDependencies": {
10+
"ember-cli-typescript": "*"
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// This should wind up in the addon tree
2+
const value: string = 'in-repo-c/src/test-file';
3+
4+
export default value;

tests/dummy/src/test-file.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// This should wind up in the app's src tree
2+
const value: string = 'dummy/src/test-file';
3+
4+
export default value;

tests/dummy/src/ui/styles/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)