@embroider/core
Advanced tools
Comparing version 0.0.4 to 0.0.5
{ | ||
"name": "@embroider/core", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"description": "A build system for EmberJS applications.", | ||
@@ -30,2 +30,3 @@ "main": "src/index.js", | ||
"debug": "^3.1.0", | ||
"fast-sourcemap-concat": "^1.4.0", | ||
"fs-extra": "^7.0.1", | ||
@@ -32,0 +33,0 @@ "fs-tree-diff": "0.5.7", |
@@ -34,2 +34,3 @@ import { OutputPaths } from './wait-for-trees'; | ||
private readonly babelConfig; | ||
private appJSAsset; | ||
private insertEmberApp; | ||
@@ -40,6 +41,9 @@ private appDiffer; | ||
private prepareAssets; | ||
private assetIsValid; | ||
private updateOnDiskAsset; | ||
private updateInMemoryAsset; | ||
private updateBuiltEmberAsset; | ||
private updateConcatenatedAsset; | ||
private updateAssets; | ||
private gatherAssets; | ||
build(inputPaths: OutputPaths<TreeNames>): Promise<void>; | ||
@@ -49,3 +53,2 @@ private combineExternals; | ||
private addBabelConfig; | ||
private addEmberEnv; | ||
private javascriptEntrypoint; | ||
@@ -52,0 +55,0 @@ private testJSEntrypoint; |
227
src/app.js
@@ -24,2 +24,3 @@ "use strict"; | ||
const assert_never_1 = __importDefault(require("assert-never")); | ||
const fast_sourcemap_concat_1 = __importDefault(require("fast-sourcemap-concat")); | ||
class ParsedEmberAsset { | ||
@@ -44,2 +45,12 @@ constructor(asset) { | ||
} | ||
class ConcatenatedAsset { | ||
constructor(relativePath, sources) { | ||
this.relativePath = relativePath; | ||
this.sources = sources; | ||
this.kind = 'concatenated-asset'; | ||
} | ||
get sourcemapPath() { | ||
return this.relativePath.replace(/\.js$/, '') + '.map'; | ||
} | ||
} | ||
class AppBuilder { | ||
@@ -74,8 +85,19 @@ constructor(root, app, adapter) { | ||
} | ||
impliedAssets(type) { | ||
let result = this.impliedAddonAssets(type); | ||
// This file gets created by addEmberEnv(). We need to insert it at the | ||
// beginning of the scripts. | ||
if (type === "implicit-scripts") { | ||
result.unshift(path_1.join(this.root, "_ember_env_.js")); | ||
impliedAssets(type, emberENV) { | ||
let result = this.impliedAddonAssets(type).map((sourcePath) => { | ||
let stats = fs_extra_1.statSync(sourcePath); | ||
return { | ||
kind: 'on-disk', | ||
relativePath: path_1.relative(this.root, sourcePath), | ||
sourcePath, | ||
mtime: stats.mtimeMs, | ||
size: stats.size, | ||
}; | ||
}); | ||
if (type === 'implicit-scripts') { | ||
result.unshift({ | ||
kind: 'in-memory', | ||
relativePath: '_ember_env_.js', | ||
source: `window.EmberENV=${JSON.stringify(emberENV, null, 2)};`, | ||
}); | ||
} | ||
@@ -108,3 +130,3 @@ return result; | ||
} | ||
insertEmberApp(asset, appFiles, prepared) { | ||
appJSAsset(appFiles, prepared) { | ||
let appJS = prepared.get(`assets/${this.app.name}.js`); | ||
@@ -115,10 +137,24 @@ if (!appJS) { | ||
} | ||
return appJS; | ||
} | ||
insertEmberApp(asset, appFiles, prepared, emberENV) { | ||
let html = asset.html; | ||
html.insertScriptTag(html.javascript, appJS.relativePath, { type: 'module' }); | ||
// our tests entrypoint already includes a correct module dependency on the | ||
// app, so we only insert the app when we're not inserting tests | ||
if (!asset.fileAsset.includeTests) { | ||
let appJS = this.appJSAsset(appFiles, prepared); | ||
html.insertScriptTag(html.javascript, appJS.relativePath, { type: 'module' }); | ||
} | ||
html.insertStyleLink(html.styles, `assets/${this.app.name}.css`); | ||
for (let script of this.impliedAssets("implicit-scripts")) { | ||
html.insertScriptTag(html.implicitScripts, path_1.relative(this.root, script)); | ||
let implicitScripts = this.impliedAssets("implicit-scripts", emberENV); | ||
if (implicitScripts.length > 0) { | ||
let vendorJS = new ConcatenatedAsset('assets/vendor.js', implicitScripts); | ||
prepared.set(vendorJS.relativePath, vendorJS); | ||
html.insertScriptTag(html.implicitScripts, vendorJS.relativePath); | ||
} | ||
for (let style of this.impliedAssets("implicit-styles")) { | ||
html.insertStyleLink(html.implicitStyles, path_1.relative(this.root, style)); | ||
let implicitStyles = this.impliedAssets("implicit-styles"); | ||
if (implicitStyles.length > 0) { | ||
let vendorCSS = new ConcatenatedAsset('assets/vendor.css', implicitStyles); | ||
prepared.set(vendorCSS.relativePath, vendorCSS); | ||
html.insertStyleLink(html.implicitStyles, vendorCSS.relativePath); | ||
} | ||
@@ -128,11 +164,17 @@ if (asset.fileAsset.includeTests) { | ||
if (!testJS) { | ||
testJS = this.testJSEntrypoint(appFiles); | ||
testJS = this.testJSEntrypoint(appFiles, prepared); | ||
prepared.set(testJS.relativePath, testJS); | ||
} | ||
html.insertScriptTag(html.testJavascript, testJS.relativePath, { type: 'module' }); | ||
for (let script of this.impliedAssets("implicit-test-scripts")) { | ||
html.insertScriptTag(html.implicitTestScripts, path_1.relative(this.root, script)); | ||
let implicitTestScripts = this.impliedAssets("implicit-test-scripts"); | ||
if (implicitTestScripts.length > 0) { | ||
let testSupportJS = new ConcatenatedAsset('assets/test-support.js', implicitTestScripts); | ||
prepared.set(testSupportJS.relativePath, testSupportJS); | ||
html.insertScriptTag(html.implicitTestScripts, testSupportJS.relativePath); | ||
} | ||
for (let style of this.impliedAssets("implicit-test-styles")) { | ||
html.insertStyleLink(html.implicitTestStyles, path_1.relative(this.root, style)); | ||
let implicitTestStyles = this.impliedAssets("implicit-test-styles"); | ||
if (implicitTestStyles.length > 0) { | ||
let testSupportCSS = new ConcatenatedAsset('assets/test-support.css', implicitTestStyles); | ||
prepared.set(testSupportCSS.relativePath, testSupportCSS); | ||
html.insertStyleLink(html.implicitTestStyles, testSupportCSS.relativePath); | ||
} | ||
@@ -148,3 +190,3 @@ } | ||
} | ||
prepareAsset(asset, appFiles, prepared) { | ||
prepareAsset(asset, appFiles, prepared, emberENV) { | ||
if (asset.kind === 'ember') { | ||
@@ -161,3 +203,3 @@ let prior = this.assets.get(asset.relativePath); | ||
} | ||
this.insertEmberApp(parsed, appFiles, prepared); | ||
this.insertEmberApp(parsed, appFiles, prepared, emberENV); | ||
prepared.set(asset.relativePath, new BuiltEmberAsset(parsed)); | ||
@@ -169,14 +211,31 @@ } | ||
} | ||
prepareAssets(requestedAssets, appFiles) { | ||
prepareAssets(requestedAssets, appFiles, emberENV) { | ||
let prepared = new Map(); | ||
for (let asset of requestedAssets) { | ||
this.prepareAsset(asset, appFiles, prepared); | ||
this.prepareAsset(asset, appFiles, prepared, emberENV); | ||
} | ||
return prepared; | ||
} | ||
updateOnDiskAsset(asset, prior) { | ||
if (prior && prior.kind === 'on-disk' && prior.size === asset.size && prior.mtime === asset.mtime) { | ||
// prior was already valid | ||
return; | ||
assetIsValid(asset, prior) { | ||
if (!prior) { | ||
return false; | ||
} | ||
switch (asset.kind) { | ||
case 'on-disk': | ||
return prior.kind === 'on-disk' && prior.size === asset.size && prior.mtime === asset.mtime; | ||
case 'in-memory': | ||
return prior.kind === 'in-memory' && stringOrBufferEqual(prior.source, asset.source); | ||
case 'built-ember': | ||
return prior.kind === 'built-ember' && prior.source === asset.source; | ||
case 'concatenated-asset': | ||
return prior.kind === 'concatenated-asset' && | ||
prior.sources.length === asset.sources.length && | ||
prior.sources.every((priorFile, index) => { | ||
let newFile = asset.sources[index]; | ||
return this.assetIsValid(newFile, priorFile); | ||
}); | ||
} | ||
assert_never_1.default(asset); | ||
} | ||
updateOnDiskAsset(asset) { | ||
let destination = path_1.join(this.root, asset.relativePath); | ||
@@ -186,7 +245,3 @@ fs_extra_1.ensureDirSync(path_1.dirname(destination)); | ||
} | ||
updateInMemoryAsset(asset, prior) { | ||
if (prior && prior.kind === 'in-memory' && stringOrBufferEqual(prior.source, asset.source)) { | ||
// prior was already valid | ||
return; | ||
} | ||
updateInMemoryAsset(asset) { | ||
let destination = path_1.join(this.root, asset.relativePath); | ||
@@ -196,8 +251,3 @@ fs_extra_1.ensureDirSync(path_1.dirname(destination)); | ||
} | ||
updateBuiltEmberAsset(asset, prior) { | ||
if (prior && prior.kind === 'built-ember' && | ||
prior.source === asset.source) { | ||
// prior was already valid | ||
return; | ||
} | ||
updateBuiltEmberAsset(asset) { | ||
let destination = path_1.join(this.root, asset.relativePath); | ||
@@ -207,16 +257,45 @@ fs_extra_1.ensureDirSync(path_1.dirname(destination)); | ||
} | ||
updateAssets(requestedAssets, appFiles) { | ||
let assets = this.prepareAssets(requestedAssets, appFiles); | ||
async updateConcatenatedAsset(asset) { | ||
let concat = new fast_sourcemap_concat_1.default({ | ||
outputFile: path_1.join(this.root, asset.relativePath), | ||
mapCommentType: asset.relativePath.endsWith('.js') ? 'line' : 'block', | ||
baseDir: this.root, | ||
}); | ||
for (let source of asset.sources) { | ||
switch (source.kind) { | ||
case 'on-disk': | ||
concat.addFile(path_1.relative(this.root, source.sourcePath)); | ||
break; | ||
case 'in-memory': | ||
if (typeof source.source !== 'string') { | ||
throw new Error(`attempted to concatenated a Buffer-backed in-memory asset`); | ||
} | ||
concat.addSpace(source.source); | ||
break; | ||
default: | ||
assert_never_1.default(source); | ||
} | ||
} | ||
await concat.end(); | ||
} | ||
async updateAssets(requestedAssets, appFiles, emberENV) { | ||
let assets = this.prepareAssets(requestedAssets, appFiles, emberENV); | ||
for (let asset of assets.values()) { | ||
let prior = this.assets.get(asset.relativePath); | ||
if (this.assetIsValid(asset, this.assets.get(asset.relativePath))) { | ||
continue; | ||
} | ||
messages_1.debug('rebuilding %s', asset.relativePath); | ||
switch (asset.kind) { | ||
case 'on-disk': | ||
this.updateOnDiskAsset(asset, prior); | ||
this.updateOnDiskAsset(asset); | ||
break; | ||
case 'in-memory': | ||
this.updateInMemoryAsset(asset, prior); | ||
this.updateInMemoryAsset(asset); | ||
break; | ||
case 'built-ember': | ||
this.updateBuiltEmberAsset(asset, prior); | ||
this.updateBuiltEmberAsset(asset); | ||
break; | ||
case 'concatenated-asset': | ||
await this.updateConcatenatedAsset(asset); | ||
break; | ||
default: | ||
@@ -232,16 +311,44 @@ assert_never_1.default(asset); | ||
this.assets = assets; | ||
return [...assets.values()]; | ||
} | ||
gatherAssets(inputPaths) { | ||
// first gather all the assets out of addons | ||
let assets = []; | ||
for (let pkg of this.activeAddonDescendants) { | ||
if (pkg.meta['public-assets']) { | ||
for (let [filename, appRelativeURL] of Object.entries(pkg.meta['public-assets'])) { | ||
assets.push({ | ||
kind: 'on-disk', | ||
sourcePath: path_1.join(pkg.root, filename), | ||
relativePath: appRelativeURL, | ||
mtime: 0, | ||
size: 0 | ||
}); | ||
} | ||
} | ||
} | ||
// and finally tack on the ones from our app itself | ||
return assets.concat(this.adapter.assets(inputPaths)); | ||
} | ||
async build(inputPaths) { | ||
let appFiles = this.updateAppJS(this.adapter.appJSSrcDir(inputPaths)); | ||
let emberENV = this.adapter.emberENV(); | ||
let assets = this.adapter.assets(inputPaths); | ||
this.updateAssets(assets, appFiles); | ||
let assets = this.gatherAssets(inputPaths); | ||
let finalAssets = await this.updateAssets(assets, appFiles, emberENV); | ||
this.addTemplateCompiler(emberENV); | ||
this.addBabelConfig(); | ||
this.addEmberEnv(emberENV); | ||
let externals = this.combineExternals(); | ||
let assetPaths = assets.map(asset => asset.relativePath); | ||
for (let asset of finalAssets) { | ||
// our concatenated assets all have map files that ride along. Here we're | ||
// telling the final stage packager to be sure and serve the map files | ||
// too. | ||
if (asset.kind === 'concatenated-asset') { | ||
assetPaths.push(asset.sourcemapPath); | ||
} | ||
} | ||
let meta = { | ||
version: 2, | ||
externals, | ||
assets: assets.map(a => a.relativePath), | ||
assets: assetPaths, | ||
["template-compiler"]: "_template_compiler_.js", | ||
@@ -298,10 +405,2 @@ ["babel-config"]: "_babel_config_.js", | ||
} | ||
// this is stuff that needs to get set globally before Ember loads. In classic | ||
// Ember CLI it was "vendor-prefix" content that would go at the start of the | ||
// vendor.js. We are going to make sure it's the first plain <script> in the | ||
// HTML that we hand to the final stage packager. | ||
addEmberEnv(config) { | ||
let content = `window.EmberENV=${JSON.stringify(config, null, 2)};`; | ||
fs_extra_1.writeFileSync(path_1.join(this.root, "_ember_env_.js"), content, "utf8"); | ||
} | ||
javascriptEntrypoint(name, appFiles) { | ||
@@ -340,3 +439,4 @@ let modulePrefix = this.adapter.modulePrefix(); | ||
} | ||
testJSEntrypoint(appFiles) { | ||
testJSEntrypoint(appFiles, prepared) { | ||
const myName = 'assets/test.js'; | ||
let testModules = [...appFiles].map(relativePath => { | ||
@@ -347,2 +447,8 @@ if (relativePath.startsWith("tests/") && relativePath.endsWith('-test.js')) { | ||
}).filter(Boolean); | ||
// tests necessarily also include the app. This is where we account for | ||
// that. The classic solution was to always include the app's separate | ||
// script tag in the tests HTML, but that isn't as easy for final stage | ||
// packagers to understand. It's better to express it here as a direct | ||
// module dependency. | ||
testModules.unshift('./' + path_1.relative(path_1.dirname(myName), this.appJSAsset(appFiles, prepared).relativePath)); | ||
let lazyModules = []; | ||
@@ -360,3 +466,3 @@ // this is a backward-compatibility feature: addons can force inclusion of | ||
source, | ||
relativePath: 'assets/test.js' | ||
relativePath: myName | ||
}; | ||
@@ -434,2 +540,11 @@ } | ||
{{#if testSuffix ~}} | ||
{{!- TODO: both of these suffixes should get dynamically generated so they incorporate | ||
any content-for added by addons. -}} | ||
{{!- this is the traditional test-support-suffix.js -}} | ||
runningTests = true; | ||
if (window.Testem) { | ||
window.Testem.hookIntoTestFramework(); | ||
} | ||
{{!- this is the traditional tests-suffix.js -}} | ||
@@ -436,0 +551,0 @@ r('../tests/test-helper'); |
247
src/app.ts
@@ -8,5 +8,5 @@ import { AppMeta } from './metadata'; | ||
import { Memoize } from "typescript-memoize"; | ||
import { writeFileSync, ensureDirSync, copySync, unlinkSync } from 'fs-extra'; | ||
import { writeFileSync, ensureDirSync, copySync, unlinkSync, statSync } from 'fs-extra'; | ||
import { join, dirname, relative } from 'path'; | ||
import { todo, unsupported } from './messages'; | ||
import { todo, unsupported, debug } from './messages'; | ||
import cloneDeep from 'lodash/cloneDeep'; | ||
@@ -18,2 +18,3 @@ import AppDiffer from './app-differ'; | ||
import assertNever from 'assert-never'; | ||
import SourceMapConcat from 'fast-sourcemap-concat'; | ||
@@ -121,4 +122,12 @@ export type EmberENV = unknown; | ||
type InternalAsset = OnDiskAsset | InMemoryAsset | BuiltEmberAsset; | ||
class ConcatenatedAsset { | ||
kind: 'concatenated-asset' = 'concatenated-asset'; | ||
constructor(public relativePath: string, public sources: (OnDiskAsset | InMemoryAsset)[]){} | ||
get sourcemapPath() { | ||
return this.relativePath.replace(/\.js$/, '') + '.map'; | ||
} | ||
} | ||
type InternalAsset = OnDiskAsset | InMemoryAsset | BuiltEmberAsset | ConcatenatedAsset; | ||
export class AppBuilder<TreeNames> { | ||
@@ -159,9 +168,19 @@ // for each relativePath, an Asset we have already emitted | ||
private impliedAssets(type: keyof ImplicitAssetPaths): any { | ||
let result = this.impliedAddonAssets(type); | ||
// This file gets created by addEmberEnv(). We need to insert it at the | ||
// beginning of the scripts. | ||
if (type === "implicit-scripts") { | ||
result.unshift(join(this.root, "_ember_env_.js")); | ||
private impliedAssets(type: keyof ImplicitAssetPaths, emberENV?: EmberENV): (OnDiskAsset | InMemoryAsset)[] { | ||
let result: (OnDiskAsset | InMemoryAsset)[] = this.impliedAddonAssets(type).map((sourcePath: string): OnDiskAsset => { | ||
let stats = statSync(sourcePath); | ||
return { | ||
kind: 'on-disk', | ||
relativePath: relative(this.root, sourcePath), | ||
sourcePath, | ||
mtime: stats.mtimeMs, | ||
size: stats.size, | ||
}; | ||
}); | ||
if (type === 'implicit-scripts') { | ||
result.unshift({ | ||
kind: 'in-memory', | ||
relativePath: '_ember_env_.js', | ||
source: `window.EmberENV=${JSON.stringify(emberENV, null, 2)};`, | ||
}); | ||
} | ||
@@ -171,3 +190,3 @@ return result; | ||
private impliedAddonAssets(type: keyof ImplicitAssetPaths): any { | ||
private impliedAddonAssets(type: keyof ImplicitAssetPaths): string[] { | ||
let result = []; | ||
@@ -206,3 +225,3 @@ for (let addon of sortBy( | ||
private insertEmberApp(asset: ParsedEmberAsset, appFiles: Set<string>, prepared: Map<string, InternalAsset>) { | ||
private appJSAsset(appFiles: Set<string>, prepared: Map<string, InternalAsset>): InternalAsset { | ||
let appJS = prepared.get(`assets/${this.app.name}.js`); | ||
@@ -213,12 +232,29 @@ if (!appJS) { | ||
} | ||
return appJS; | ||
} | ||
private insertEmberApp(asset: ParsedEmberAsset, appFiles: Set<string>, prepared: Map<string, InternalAsset>, emberENV: EmberENV) { | ||
let html = asset.html; | ||
html.insertScriptTag(html.javascript, appJS.relativePath, { type: 'module' }); | ||
// our tests entrypoint already includes a correct module dependency on the | ||
// app, so we only insert the app when we're not inserting tests | ||
if (!asset.fileAsset.includeTests) { | ||
let appJS = this.appJSAsset(appFiles, prepared); | ||
html.insertScriptTag(html.javascript, appJS.relativePath, { type: 'module' }); | ||
} | ||
html.insertStyleLink(html.styles, `assets/${this.app.name}.css`); | ||
for (let script of this.impliedAssets("implicit-scripts")) { | ||
html.insertScriptTag(html.implicitScripts, relative(this.root, script)); | ||
let implicitScripts = this.impliedAssets("implicit-scripts", emberENV); | ||
if (implicitScripts.length > 0) { | ||
let vendorJS = new ConcatenatedAsset('assets/vendor.js', implicitScripts); | ||
prepared.set(vendorJS.relativePath, vendorJS); | ||
html.insertScriptTag(html.implicitScripts, vendorJS.relativePath); | ||
} | ||
for (let style of this.impliedAssets("implicit-styles")) { | ||
html.insertStyleLink(html.implicitStyles, relative(this.root, style)); | ||
let implicitStyles = this.impliedAssets("implicit-styles"); | ||
if (implicitStyles.length > 0) { | ||
let vendorCSS = new ConcatenatedAsset('assets/vendor.css', implicitStyles); | ||
prepared.set(vendorCSS.relativePath, vendorCSS); | ||
html.insertStyleLink(html.implicitStyles, vendorCSS.relativePath); | ||
} | ||
@@ -229,11 +265,19 @@ | ||
if (!testJS) { | ||
testJS = this.testJSEntrypoint(appFiles); | ||
testJS = this.testJSEntrypoint(appFiles, prepared); | ||
prepared.set(testJS.relativePath, testJS); | ||
} | ||
html.insertScriptTag(html.testJavascript, testJS.relativePath, { type: 'module' }); | ||
for (let script of this.impliedAssets("implicit-test-scripts")) { | ||
html.insertScriptTag(html.implicitTestScripts, relative(this.root, script)); | ||
let implicitTestScripts = this.impliedAssets("implicit-test-scripts"); | ||
if (implicitTestScripts.length > 0) { | ||
let testSupportJS = new ConcatenatedAsset('assets/test-support.js', implicitTestScripts); | ||
prepared.set(testSupportJS.relativePath, testSupportJS); | ||
html.insertScriptTag(html.implicitTestScripts, testSupportJS.relativePath); | ||
} | ||
for (let style of this.impliedAssets("implicit-test-styles")) { | ||
html.insertStyleLink(html.implicitTestStyles, relative(this.root, style)); | ||
let implicitTestStyles = this.impliedAssets("implicit-test-styles"); | ||
if (implicitTestStyles.length > 0) { | ||
let testSupportCSS = new ConcatenatedAsset('assets/test-support.css', implicitTestStyles); | ||
prepared.set(testSupportCSS.relativePath, testSupportCSS); | ||
html.insertStyleLink(html.implicitTestStyles, testSupportCSS.relativePath); | ||
} | ||
@@ -253,3 +297,3 @@ } | ||
private prepareAsset(asset: Asset, appFiles: Set<string>, prepared: Map<string, InternalAsset>) { | ||
private prepareAsset(asset: Asset, appFiles: Set<string>, prepared: Map<string, InternalAsset>, emberENV: EmberENV) { | ||
if (asset.kind === 'ember') { | ||
@@ -265,3 +309,3 @@ let prior = this.assets.get(asset.relativePath); | ||
} | ||
this.insertEmberApp(parsed, appFiles, prepared); | ||
this.insertEmberApp(parsed, appFiles, prepared, emberENV); | ||
prepared.set(asset.relativePath, new BuiltEmberAsset(parsed)); | ||
@@ -273,6 +317,6 @@ } else { | ||
private prepareAssets(requestedAssets: Asset[], appFiles: Set<string>): Map<string, InternalAsset> { | ||
private prepareAssets(requestedAssets: Asset[], appFiles: Set<string>, emberENV: EmberENV): Map<string, InternalAsset> { | ||
let prepared: Map<string, InternalAsset> = new Map(); | ||
for (let asset of requestedAssets) { | ||
this.prepareAsset(asset, appFiles, prepared); | ||
this.prepareAsset(asset, appFiles, prepared, emberENV); | ||
} | ||
@@ -282,7 +326,25 @@ return prepared; | ||
private updateOnDiskAsset(asset: OnDiskAsset, prior: InternalAsset | undefined) { | ||
if (prior && prior.kind === 'on-disk' && prior.size === asset.size && prior.mtime === asset.mtime) { | ||
// prior was already valid | ||
return; | ||
private assetIsValid(asset: InternalAsset, prior: InternalAsset | undefined): boolean { | ||
if (!prior) { | ||
return false; | ||
} | ||
switch(asset.kind) { | ||
case 'on-disk': | ||
return prior.kind === 'on-disk' && prior.size === asset.size && prior.mtime === asset.mtime; | ||
case 'in-memory': | ||
return prior.kind === 'in-memory' && stringOrBufferEqual(prior.source, asset.source); | ||
case 'built-ember': | ||
return prior.kind === 'built-ember' && prior.source === asset.source; | ||
case 'concatenated-asset': | ||
return prior.kind === 'concatenated-asset' && | ||
prior.sources.length === asset.sources.length && | ||
prior.sources.every((priorFile, index) => { | ||
let newFile = asset.sources[index]; | ||
return this.assetIsValid(newFile, priorFile); | ||
}); | ||
} | ||
assertNever(asset); | ||
} | ||
private updateOnDiskAsset(asset: OnDiskAsset) { | ||
let destination = join(this.root, asset.relativePath); | ||
@@ -293,7 +355,3 @@ ensureDirSync(dirname(destination)); | ||
private updateInMemoryAsset(asset: InMemoryAsset, prior: InternalAsset | undefined) { | ||
if (prior && prior.kind === 'in-memory' && stringOrBufferEqual(prior.source, asset.source)) { | ||
// prior was already valid | ||
return; | ||
} | ||
private updateInMemoryAsset(asset: InMemoryAsset) { | ||
let destination = join(this.root, asset.relativePath); | ||
@@ -304,10 +362,3 @@ ensureDirSync(dirname(destination)); | ||
private updateBuiltEmberAsset(asset: BuiltEmberAsset, prior: InternalAsset | undefined) { | ||
if ( | ||
prior && prior.kind === 'built-ember' && | ||
prior.source === asset.source | ||
) { | ||
// prior was already valid | ||
return; | ||
} | ||
private updateBuiltEmberAsset(asset: BuiltEmberAsset) { | ||
let destination = join(this.root, asset.relativePath); | ||
@@ -318,16 +369,46 @@ ensureDirSync(dirname(destination)); | ||
private updateAssets(requestedAssets: Asset[], appFiles: Set<string>) { | ||
let assets = this.prepareAssets(requestedAssets, appFiles); | ||
private async updateConcatenatedAsset(asset: ConcatenatedAsset) { | ||
let concat = new SourceMapConcat({ | ||
outputFile: join(this.root, asset.relativePath), | ||
mapCommentType: asset.relativePath.endsWith('.js') ? 'line' : 'block', | ||
baseDir: this.root, | ||
}); | ||
for (let source of asset.sources) { | ||
switch (source.kind) { | ||
case 'on-disk': | ||
concat.addFile(relative(this.root, source.sourcePath)); | ||
break; | ||
case 'in-memory': | ||
if (typeof source.source !== 'string') { | ||
throw new Error(`attempted to concatenated a Buffer-backed in-memory asset`); | ||
} | ||
concat.addSpace(source.source); | ||
break; | ||
default: | ||
assertNever(source); | ||
} | ||
} | ||
await concat.end(); | ||
} | ||
private async updateAssets(requestedAssets: Asset[], appFiles: Set<string>, emberENV: EmberENV) { | ||
let assets = this.prepareAssets(requestedAssets, appFiles, emberENV); | ||
for (let asset of assets.values()) { | ||
let prior = this.assets.get(asset.relativePath); | ||
if (this.assetIsValid(asset, this.assets.get(asset.relativePath))) { | ||
continue; | ||
} | ||
debug('rebuilding %s', asset.relativePath); | ||
switch (asset.kind) { | ||
case 'on-disk': | ||
this.updateOnDiskAsset(asset, prior); | ||
this.updateOnDiskAsset(asset); | ||
break; | ||
case 'in-memory': | ||
this.updateInMemoryAsset(asset, prior); | ||
this.updateInMemoryAsset(asset); | ||
break; | ||
case 'built-ember': | ||
this.updateBuiltEmberAsset(asset, prior); | ||
this.updateBuiltEmberAsset(asset); | ||
break; | ||
case 'concatenated-asset': | ||
await this.updateConcatenatedAsset(asset); | ||
break; | ||
default: | ||
@@ -343,20 +424,50 @@ assertNever(asset); | ||
this.assets = assets; | ||
return [...assets.values()]; | ||
} | ||
private gatherAssets(inputPaths: OutputPaths<TreeNames>): Asset[] { | ||
// first gather all the assets out of addons | ||
let assets: Asset[] = []; | ||
for (let pkg of this.activeAddonDescendants) { | ||
if (pkg.meta['public-assets']) { | ||
for (let [filename, appRelativeURL] of Object.entries(pkg.meta['public-assets'])) { | ||
assets.push({ | ||
kind: 'on-disk', | ||
sourcePath: join(pkg.root, filename), | ||
relativePath: appRelativeURL, | ||
mtime: 0, | ||
size: 0 | ||
}); | ||
} | ||
} | ||
} | ||
// and finally tack on the ones from our app itself | ||
return assets.concat(this.adapter.assets(inputPaths)); | ||
} | ||
async build(inputPaths: OutputPaths<TreeNames>) { | ||
let appFiles = this.updateAppJS(this.adapter.appJSSrcDir(inputPaths)); | ||
let emberENV = this.adapter.emberENV(); | ||
let assets = this.adapter.assets(inputPaths); | ||
let assets = this.gatherAssets(inputPaths); | ||
this.updateAssets(assets, appFiles); | ||
let finalAssets = await this.updateAssets(assets, appFiles, emberENV); | ||
this.addTemplateCompiler(emberENV); | ||
this.addBabelConfig(); | ||
this.addEmberEnv(emberENV); | ||
let externals = this.combineExternals(); | ||
let assetPaths = assets.map(asset => asset.relativePath); | ||
for (let asset of finalAssets) { | ||
// our concatenated assets all have map files that ride along. Here we're | ||
// telling the final stage packager to be sure and serve the map files | ||
// too. | ||
if (asset.kind === 'concatenated-asset') { | ||
assetPaths.push(asset.sourcemapPath); | ||
} | ||
} | ||
let meta: AppMeta = { | ||
version: 2, | ||
externals, | ||
assets: assets.map(a => a.relativePath), | ||
assets: assetPaths, | ||
["template-compiler"]: "_template_compiler_.js", | ||
@@ -431,11 +542,2 @@ ["babel-config"]: "_babel_config_.js", | ||
// this is stuff that needs to get set globally before Ember loads. In classic | ||
// Ember CLI it was "vendor-prefix" content that would go at the start of the | ||
// vendor.js. We are going to make sure it's the first plain <script> in the | ||
// HTML that we hand to the final stage packager. | ||
private addEmberEnv(config: EmberENV) { | ||
let content = `window.EmberENV=${JSON.stringify(config, null, 2)};`; | ||
writeFileSync(join(this.root, "_ember_env_.js"), content, "utf8"); | ||
} | ||
private javascriptEntrypoint(name: string, appFiles: Set<string>): InternalAsset { | ||
@@ -480,3 +582,4 @@ let modulePrefix = this.adapter.modulePrefix(); | ||
private testJSEntrypoint(appFiles: Set<string>): InternalAsset { | ||
private testJSEntrypoint(appFiles: Set<string>, prepared: Map<string, InternalAsset>): InternalAsset { | ||
const myName = 'assets/test.js'; | ||
let testModules = [...appFiles].map(relativePath => { | ||
@@ -488,2 +591,9 @@ if (relativePath.startsWith("tests/") && relativePath.endsWith('-test.js')) { | ||
// tests necessarily also include the app. This is where we account for | ||
// that. The classic solution was to always include the app's separate | ||
// script tag in the tests HTML, but that isn't as easy for final stage | ||
// packagers to understand. It's better to express it here as a direct | ||
// module dependency. | ||
testModules.unshift('./' + relative(dirname(myName), this.appJSAsset(appFiles, prepared).relativePath)); | ||
let lazyModules: { runtime: string, buildtime: string }[] = []; | ||
@@ -503,3 +613,3 @@ // this is a backward-compatibility feature: addons can force inclusion of | ||
source, | ||
relativePath: 'assets/test.js' | ||
relativePath: myName | ||
}; | ||
@@ -575,2 +685,11 @@ } | ||
{{#if testSuffix ~}} | ||
{{!- TODO: both of these suffixes should get dynamically generated so they incorporate | ||
any content-for added by addons. -}} | ||
{{!- this is the traditional test-support-suffix.js -}} | ||
runningTests = true; | ||
if (window.Testem) { | ||
window.Testem.hookIntoTestFramework(); | ||
} | ||
{{!- this is the traditional tests-suffix.js -}} | ||
@@ -577,0 +696,0 @@ r('../tests/test-helper'); |
@@ -22,3 +22,3 @@ import { JSDOM } from "jsdom"; | ||
// | ||
// Do not confus these with controlling whether or not we will insert tests. | ||
// Do not confuse these with controlling whether or not we will insert tests. | ||
// That is separately controlled via `includeTests`. | ||
@@ -25,0 +25,0 @@ testJavascript?: Node; |
declare type filename = string; | ||
declare type appRelativeURL = string; | ||
export interface AppMeta { | ||
@@ -12,2 +13,5 @@ version: 2; | ||
externals?: string[]; | ||
'public-assets'?: { | ||
[filename: string]: appRelativeURL; | ||
}; | ||
"implicit-scripts"?: filename[]; | ||
@@ -14,0 +18,0 @@ "implicit-test-scripts"?: filename[]; |
type filename = string; | ||
type appRelativeURL = string; | ||
@@ -18,2 +19,5 @@ // This describes the ember-specific parts of package.json of an app after the | ||
externals?: string[]; | ||
'public-assets'?: { | ||
[filename: string]: appRelativeURL; | ||
}; | ||
"implicit-scripts"?: filename[]; | ||
@@ -20,0 +24,0 @@ "implicit-test-scripts"?: filename[]; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
165439
3181
16
+ Addedfast-sourcemap-concat@^1.4.0
+ Addedamdefine@1.0.1(transitive)
+ Addedansi-styles@3.2.1(transitive)
+ Addedchalk@2.4.2(transitive)
+ Addedcolor-convert@1.9.3(transitive)
+ Addedcolor-name@1.1.3(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedfast-sourcemap-concat@1.4.0(transitive)
+ Addedfs-extra@5.0.0(transitive)
+ Addedhas-flag@3.0.0(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedjsesc@0.3.0(transitive)
+ Addedlodash._reinterpolate@3.0.0(transitive)
+ Addedlodash.foreach@4.5.0(transitive)
+ Addedlodash.template@4.5.0(transitive)
+ Addedlodash.templatesettings@4.2.0(transitive)
+ Addedmemory-streams@0.1.3(transitive)
+ Addedmkdirp@0.5.6(transitive)
+ Addedreadable-stream@1.0.34(transitive)
+ Addedsource-map@0.1.430.4.4(transitive)
+ Addedsource-map-url@0.3.0(transitive)
+ Addedsourcemap-validator@1.1.1(transitive)
+ Addedstring_decoder@0.10.31(transitive)
+ Addedsupports-color@5.5.0(transitive)