diff --git a/packages/ember-auto-import/ts/auto-import.ts b/packages/ember-auto-import/ts/auto-import.ts index b48ec339d..ff4a157e7 100644 --- a/packages/ember-auto-import/ts/auto-import.ts +++ b/packages/ember-auto-import/ts/auto-import.ts @@ -78,8 +78,12 @@ export default class AutoImport { let mappings = new Map(); for (let name of this.bundles.names) { - let target = this.bundles.bundleEntrypoint(name); - mappings.set(`entrypoints/${name}`, target); + let byType = new Map(); + mappings.set(`entrypoints/${name}`, byType); + for (let type of this.bundles.types) { + let target = this.bundles.bundleEntrypoint(name, type); + byType.set(type, target); + } } let passthrough = new Map(); diff --git a/packages/ember-auto-import/ts/broccoli-append.ts b/packages/ember-auto-import/ts/broccoli-append.ts index e7fbb65ca..52616eab8 100644 --- a/packages/ember-auto-import/ts/broccoli-append.ts +++ b/packages/ember-auto-import/ts/broccoli-append.ts @@ -1,5 +1,5 @@ import Plugin, { Tree } from 'broccoli-plugin'; -import { join } from 'path'; +import { join, extname } from 'path'; import walkSync, { WalkSyncEntry } from 'walk-sync'; import { unlinkSync, rmdirSync, mkdirSync, readFileSync, existsSync, writeFileSync, removeSync, readdirSync } from 'fs-extra'; import FSTree from 'fs-tree-diff'; @@ -17,10 +17,11 @@ import { insertBefore} from './source-map-url'; */ export interface AppendOptions { - // map from a directory in the appendedTree (like `entrypoints/app`) to a file - // that may exists in the upstreamTree (like `assets/vendor.js`). Appends the - // JS files in the directory to that file, when it exists. - mappings: Map; + // map from a directory in the appendedTree (like `entrypoints/app`) to a map + // keyed by file type (extension) containing file paths that may exists in the + // upstreamTree (like `assets/vendor.js`). Appends the JS/CSS files in the + // directory to that file, when it exists. + mappings: Map>; // map from a directory in the appendedTree (like `lazy`) to a directory where // we will output those files in the output (like `assets`). @@ -30,7 +31,7 @@ export interface AppendOptions { export default class Append extends Plugin { private previousUpstreamTree = new FSTree(); private previousAppendedTree = new FSTree(); - private mappings: Map; + private mappings: Map>; private reverseMappings: Map; private passthrough: Map; @@ -40,9 +41,13 @@ export default class Append extends Plugin { persistentOutput: true }); + // mappings maps entry points to maps that map file types to output files. + // reverseMappings maps output files back to entry points. let reverseMappings = new Map(); - for (let [key, value] of options.mappings.entries( )) { - reverseMappings.set(value, key); + for (let [key, map] of options.mappings.entries( )) { + for (let value of map.values( )) { + reverseMappings.set(value, key); + } } this.mappings = options.mappings; @@ -66,7 +71,10 @@ export default class Append extends Plugin { for (let [, relativePath] of patchset) { let match = findByPrefix(relativePath, this.mappings); if (match) { - changed.add(match.mapsTo); + let ext = extname(relativePath).slice(1); + if (match.mapsTo.has(ext)) { + changed.add(match.mapsTo.get(ext)); + } } } return { needsUpdate: changed, passthroughEntries }; @@ -152,6 +160,7 @@ export default class Append extends Plugin { private handleAppend(relativePath: string) { let upstreamPath = join(this.upstreamDir, relativePath); let outputPath = join(this.outputPath, relativePath); + let ext = extname(relativePath); if (!existsSync(upstreamPath)) { removeSync(outputPath); @@ -165,7 +174,7 @@ export default class Append extends Plugin { } let appendedContent = readdirSync(sourceDir).map(name => { - if (/\.js$/.test(name)) { + if (name.endsWith(ext)) { return readFileSync(join(sourceDir, name), 'utf8'); } }).filter(Boolean).join(";\n"); @@ -200,7 +209,7 @@ interface PassthroughEntry extends WalkSyncEntry { type AugmentedWalkSyncEntry = WalkSyncEntry | PassthroughEntry; -function findByPrefix(path: string, map: Map) { +function findByPrefix(path: string, map: Map) { let parts = path.split('/'); for (let i = 1; i < parts.length; i++) { let candidate = parts.slice(0, i).join('/'); diff --git a/packages/ember-auto-import/ts/bundle-config.ts b/packages/ember-auto-import/ts/bundle-config.ts index 435668ea6..60e44826c 100644 --- a/packages/ember-auto-import/ts/bundle-config.ts +++ b/packages/ember-auto-import/ts/bundle-config.ts @@ -15,13 +15,27 @@ export default class BundleConfig { return Object.freeze(['app', 'tests']); } + get types(): ReadonlyArray { + return Object.freeze(['js', 'css']); + } + // Which final JS file the given bundle's dependencies should go into. - bundleEntrypoint(name: string): string | undefined { + bundleEntrypoint(name: string, type: string): string | undefined { switch (name) { case 'tests': - return 'assets/test-support.js'; + switch (type) { + case 'js': + return 'assets/test-support.js'; + case 'css': + return 'assets/test-support.css'; + } case 'app': - return this.emberApp.options.outputPaths.vendor.js.replace(/^\//, ''); + switch (type) { + case 'js': + return this.emberApp.options.outputPaths.vendor.js.replace(/^\//, ''); + case 'css': + return this.emberApp.options.outputPaths.vendor.css.replace(/^\//, ''); + } } } @@ -36,6 +50,6 @@ export default class BundleConfig { } get lazyChunkPath() { - return dirname(this.bundleEntrypoint(this.names[0])!); + return dirname(this.bundleEntrypoint(this.names[0], 'js')!); } } diff --git a/packages/ember-auto-import/ts/tests/append-test.ts b/packages/ember-auto-import/ts/tests/append-test.ts index 6f8668ba5..1eef8787c 100644 --- a/packages/ember-auto-import/ts/tests/append-test.ts +++ b/packages/ember-auto-import/ts/tests/append-test.ts @@ -66,7 +66,9 @@ Qmodule('broccoli-append', function(hooks) { test('nothing to be appended', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); builder = makeBuilder({ mappings }); @@ -79,7 +81,9 @@ Qmodule('broccoli-append', function(hooks) { test('appended dir does not exist', async function(assert) { let mappings = new Map(); - mappings.set('other', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('other', byType); + byType.set('js', 'assets/vendor.js'); builder = makeBuilder({ mappings }); @@ -91,7 +95,9 @@ Qmodule('broccoli-append', function(hooks) { test('nothing to append to', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); builder = makeBuilder({ mappings }); @@ -103,26 +109,48 @@ Qmodule('broccoli-append', function(hooks) { test('all files appended', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); + byType.set('css', 'assets/vendor.css'); builder = makeBuilder({ mappings }); - let out = join(builder.outputPath, 'assets/vendor.js'); + + let outJs = join(builder.outputPath, 'assets/vendor.js'); outputFileSync(join(upstream, 'assets/vendor.js'), "hello"); outputFileSync(join(appended, 'app/1.js'), "one"); outputFileSync(join(appended, 'app/2.js'), "two"); outputFileSync(join(appended, 'tests/3.js'), "three"); + + let outCss = join(builder.outputPath, 'assets/vendor.css'); + outputFileSync(join(upstream, 'assets/vendor.css'), "hola"); + outputFileSync(join(appended, 'app/1.css'), "uno"); + outputFileSync(join(appended, 'app/2.css'), "dos"); + outputFileSync(join(appended, 'tests/3.css'), "tres"); + await builder.build(); - let content = readFileSync(out, 'utf8'); + + let content = readFileSync(outJs, 'utf8'); assert.ok(/^hello;\n/.test(content), 'original vendor.js and separator'); assert.ok(/\bone\b/.test(content), 'found one'); assert.ok(/\btwo\b/.test(content), 'found two'); assert.ok(!/\bthree\b/.test(content), 'did not find three'); + assert.ok(!/\buno\b/.test(content), 'did not find CSS'); + + content = readFileSync(outCss, 'utf8'); + assert.ok(/^hola;\n/.test(content), 'original vendor.css and separator'); + assert.ok(/\buno\b/.test(content), 'found uno'); + assert.ok(/\bdos\b/.test(content), 'found dos'); + assert.ok(!/\btres\b/.test(content), 'did not find tres'); + assert.ok(!/\bone\b/.test(content), 'did not find JS'); }); test('moves trailing sourceMappingURL', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); builder = makeBuilder({ mappings }); @@ -136,7 +164,9 @@ Qmodule('broccoli-append', function(hooks) { test('does not match non-trailing sourceMappingURL', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); builder = makeBuilder({ mappings }); @@ -150,28 +180,49 @@ Qmodule('broccoli-append', function(hooks) { test('upstream changed', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); + byType.set('css', 'assets/vendor.css'); builder = makeBuilder({ mappings }); - let out = join(builder.outputPath, 'assets/vendor.js'); + + let outJs = join(builder.outputPath, 'assets/vendor.js'); outputFileSync(join(upstream, 'assets/vendor.js'), "hello"); outputFileSync(join(appended, 'app/1.js'), "one"); outputFileSync(join(appended, 'app/2.js'), "two"); + + let outCss = join(builder.outputPath, 'assets/vendor.css'); + outputFileSync(join(upstream, 'assets/vendor.css'), "hola"); + outputFileSync(join(appended, 'app/1.css'), "uno"); + outputFileSync(join(appended, 'app/2.css'), "dos"); + await builder.build(); outputFileSync(join(upstream, 'assets/vendor.js'), "bonjour"); + outputFileSync(join(upstream, 'assets/vendor.css'), "gutentag"); + await builder.build(); - let content = readFileSync(out, 'utf8'); + let content = readFileSync(outJs, 'utf8'); assert.ok(/^bonjour;\n/.test(content), 'original vendor.js and separator'); assert.ok(/\bone\b/.test(content), 'found one'); assert.ok(/\btwo\b/.test(content), 'found two'); + assert.ok(!/\buno\b/.test(content), 'did not find CSS'); + + content = readFileSync(outCss, 'utf8'); + assert.ok(/^gutentag;\n/.test(content), 'original vendor.css and separator'); + assert.ok(/\buno\b/.test(content), 'found uno'); + assert.ok(/\bdos\b/.test(content), 'found dos'); + assert.ok(!/\bone\b/.test(content), 'did not find JS'); }); test('inner appended changed', async function(assert) { let mappings = new Map(); - mappings.set('app/inner', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app/inner', byType); + byType.set('js', 'assets/vendor.js'); builder = makeBuilder({ mappings }); @@ -192,108 +243,189 @@ Qmodule('broccoli-append', function(hooks) { test('appended changed', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); + byType.set('css', 'assets/vendor.css'); builder = makeBuilder({ mappings }); - let out = join(builder.outputPath, 'assets/vendor.js'); + + let outJs = join(builder.outputPath, 'assets/vendor.js'); outputFileSync(join(upstream, 'assets/vendor.js'), "hello"); outputFileSync(join(appended, 'app/1.js'), "one"); outputFileSync(join(appended, 'app/2.js'), "two"); + + let outCss = join(builder.outputPath, 'assets/vendor.css'); + outputFileSync(join(upstream, 'assets/vendor.css'), "hola"); + outputFileSync(join(appended, 'app/1.css'), "uno"); + outputFileSync(join(appended, 'app/2.css'), "dos"); + await builder.build(); outputFileSync(join(appended, 'app/1.js'), "updated"); + outputFileSync(join(appended, 'app/1.css'), "modified"); + await builder.build(); - let content = readFileSync(out, 'utf8'); + let content = readFileSync(outJs, 'utf8'); assert.ok(/^hello;\n/.test(content), 'original vendor.js and separator'); assert.ok(/\bupdated\b/.test(content), 'found updated'); assert.ok(/\btwo\b/.test(content), 'found two'); + + content = readFileSync(outCss, 'utf8'); + assert.ok(/^hola;\n/.test(content), 'original vendor.css and separator'); + assert.ok(/\bmodified\b/.test(content), 'found modified'); + assert.ok(/\bdos\b/.test(content), 'found dos'); }); test('upstream and appended changed', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); + byType.set('css', 'assets/vendor.css'); builder = makeBuilder({ mappings }); - let out = join(builder.outputPath, 'assets/vendor.js'); + + let outJs = join(builder.outputPath, 'assets/vendor.js'); outputFileSync(join(upstream, 'assets/vendor.js'), "hello"); outputFileSync(join(appended, 'app/1.js'), "one"); outputFileSync(join(appended, 'app/2.js'), "two"); + + let outCss = join(builder.outputPath, 'assets/vendor.css'); + outputFileSync(join(upstream, 'assets/vendor.css'), "hola"); + outputFileSync(join(appended, 'app/1.css'), "uno"); + outputFileSync(join(appended, 'app/2.css'), "dos"); + await builder.build(); - outputFileSync(join(upstream, 'assets/vendor.js'), "hola"); + outputFileSync(join(upstream, 'assets/vendor.js'), "bonjour"); outputFileSync(join(appended, 'app/1.js'), "updated"); + outputFileSync(join(upstream, 'assets/vendor.css'), "guten tag"); + outputFileSync(join(appended, 'app/1.css'), "modified"); await builder.build(); - let content = readFileSync(out, 'utf8'); - assert.ok(/^hola;\n/.test(content), 'original vendor.js and separator'); + let content = readFileSync(outJs, 'utf8'); + assert.ok(/^bonjour;\n/.test(content), 'original vendor.js and separator'); assert.ok(/\bupdated\b/.test(content), 'found updated'); assert.ok(/\btwo\b/.test(content), 'found two'); + + content = readFileSync(outCss, 'utf8'); + assert.ok(/^guten tag;\n/.test(content), 'original vendor.css and separator'); + assert.ok(/\bmodified\b/.test(content), 'found modified'); + assert.ok(/\bdos\b/.test(content), 'found dos'); }); test('additional appended file', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); + byType.set('css', 'assets/vendor.css'); builder = makeBuilder({ mappings }); - let out = join(builder.outputPath, 'assets/vendor.js'); + + let outJs = join(builder.outputPath, 'assets/vendor.js'); outputFileSync(join(upstream, 'assets/vendor.js'), "hello"); outputFileSync(join(appended, 'app/1.js'), "one"); outputFileSync(join(appended, 'app/2.js'), "two"); + + let outCss = join(builder.outputPath, 'assets/vendor.css'); + outputFileSync(join(upstream, 'assets/vendor.css'), "hola"); + outputFileSync(join(appended, 'app/1.css'), "uno"); + outputFileSync(join(appended, 'app/2.css'), "dos"); + await builder.build(); outputFileSync(join(appended, 'app/3.js'), "three"); + outputFileSync(join(appended, 'app/3.css'), "tres"); await builder.build(); - let content = readFileSync(out, 'utf8'); + let content = readFileSync(outJs, 'utf8'); assert.ok(/^hello;\n/.test(content), 'original vendor.js and separator'); assert.ok(/\bone\b/.test(content), 'found uno'); assert.ok(/\btwo\b/.test(content), 'found two'); assert.ok(/\bthree\b/.test(content), 'found three'); + + content = readFileSync(outCss, 'utf8'); + assert.ok(/^hola;\n/.test(content), 'original vendor.css and separator'); + assert.ok(/\buno\b/.test(content), 'found uno'); + assert.ok(/\bdos\b/.test(content), 'found dos'); + assert.ok(/\btres\b/.test(content), 'found tres'); }); test('removed appended file', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); + byType.set('css', 'assets/vendor.css'); builder = makeBuilder({ mappings }); - let out = join(builder.outputPath, 'assets/vendor.js'); + + let outJs = join(builder.outputPath, 'assets/vendor.js'); outputFileSync(join(upstream, 'assets/vendor.js'), "hello"); outputFileSync(join(appended, 'app/1.js'), "one"); outputFileSync(join(appended, 'app/2.js'), "two"); + + let outCss = join(builder.outputPath, 'assets/vendor.css'); + outputFileSync(join(upstream, 'assets/vendor.css'), "hola"); + outputFileSync(join(appended, 'app/1.css'), "uno"); + outputFileSync(join(appended, 'app/2.css'), "dos"); + await builder.build(); removeSync(join(appended, 'app/1.js')); + removeSync(join(appended, 'app/1.css')); + await builder.build(); - let content = readFileSync(out, 'utf8'); + let content = readFileSync(outJs, 'utf8'); assert.ok(/^hello;\n/.test(content), 'original vendor.js and separator'); assert.ok(!/\bone\b/.test(content), 'did not find one'); assert.ok(/\btwo\b/.test(content), 'found two'); + + content = readFileSync(outCss, 'utf8'); + assert.ok(/^hola;\n/.test(content), 'original vendor.css and separator'); + assert.ok(!/\buno\b/.test(content), 'did not find uno'); + assert.ok(/\bdos\b/.test(content), 'found dos'); }); test('removed upstream file', async function(assert) { let mappings = new Map(); - mappings.set('app', 'assets/vendor.js'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets/vendor.js'); + byType.set('css', 'assets/vendor.css'); builder = makeBuilder({ mappings }); - let out = join(builder.outputPath, 'assets/vendor.js'); + + let outJs = join(builder.outputPath, 'assets/vendor.js'); outputFileSync(join(upstream, 'assets/vendor.js'), "hello"); outputFileSync(join(appended, 'app/1.js'), "one"); outputFileSync(join(appended, 'app/2.js'), "two"); + + let outCss = join(builder.outputPath, 'assets/vendor.css'); + outputFileSync(join(upstream, 'assets/vendor.css'), "hola"); + outputFileSync(join(appended, 'app/1.css'), "uno"); + outputFileSync(join(appended, 'app/2.css'), "dos"); + await builder.build(); removeSync(join(upstream, 'assets/vendor.js')); + removeSync(join(upstream, 'assets/vendor.css')); + await builder.build(); - assert.ok(!existsSync(out), 'removed'); + assert.ok(!existsSync(outJs), 'removed js'); + assert.ok(!existsSync(outCss), 'removed css'); }); test('passthrough file created', async function(assert) { @@ -364,7 +496,9 @@ Qmodule('broccoli-append', function(hooks) { passthrough.set('lazy', 'assets'); let mappings = new Map(); - mappings.set('app', 'assets'); + let byType = new Map(); + mappings.set('app', byType); + byType.set('js', 'assets'); builder = makeBuilder({ mappings,