Skip to content

Commit 6226644

Browse files
committed
Increment schema to latest 5
html-oriented manifest format ----------------------------- In the new schema format, which is defined in ember-fastboot/fastboot@3fd5bc9 the manifest is written into HTML and later extracted by fastboot on server side instead of previously reading from dist/package.json Note: The new schema in fastboot does not handle fastboot config https://github.com/ember-fastboot/ember-cli-fastboot/tree/e4d0b7c7bcdf82def0dc8726835b49d707673f41#providing-additional-config This commit changes to read Fastboot.config from dist/package.json instead of ignoring it Allow to require module path from whitelisted dependency ------------------------------------------------------- Incrementing schema to 5 also included the changes in schema 4 strictWhitelist See ember-fastboot/fastboot#200
1 parent 33fa701 commit 6226644

File tree

8 files changed

+179
-66
lines changed

8 files changed

+179
-66
lines changed

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,16 @@
99
"packages/*",
1010
"test-packages/*"
1111
],
12+
"scripts": {
13+
"test": "npm-run-all test:*",
14+
"test:ember-cli-fastboot": "yarn workspace ember-cli-fastboot test:ember",
15+
"test:fastboot": "yarn workspace fastboot test",
16+
"test:fastboot-express-middleware": "yarn workspace fastboot-express-middleware test",
17+
"test:fastboot-app-server": "yarn workspace fastboot-app-server test:mocha",
18+
"test:integration": "yarn workspace integration-tests test"
19+
},
1220
"devDependencies": {
21+
"npm-run-all": "^4.1.5",
1322
"release-it": "^14.2.2",
1423
"release-it-lerna-changelog": "^3.1.0",
1524
"release-it-yarn-workspaces": "^2.0.0"

packages/ember-cli-fastboot/.editorconfig

Lines changed: 0 additions & 20 deletions
This file was deleted.

packages/ember-cli-fastboot/index.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const chalk = require('chalk');
1111

1212
const fastbootAppBoot = require('./lib/utilities/fastboot-app-boot');
1313
const FastBootConfig = require('./lib/broccoli/fastboot-config');
14+
const HTMLWriter = require('./lib/broccoli/html-writer');
1415
const fastbootAppFactoryModule = require('./lib/utilities/fastboot-app-factory-module');
1516
const migrateInitializers = require('./lib/build-utilities/migrate-initializers');
1617
const SilentError = require('silent-error');
@@ -177,7 +178,6 @@ module.exports = {
177178
*/
178179
_getFastbootTree() {
179180
const appName = this._name;
180-
const isModuleUnification = this._isModuleUnification();
181181

182182
let fastbootTrees = [];
183183

@@ -222,6 +222,7 @@ module.exports = {
222222
return finalFastbootTree;
223223
},
224224

225+
// Note: this hook is ignored when built with embroider
225226
treeForPublic(tree) {
226227
let fastbootTree = this._getFastbootTree();
227228
let trees = [];
@@ -232,7 +233,7 @@ module.exports = {
232233

233234
let newTree = new MergeTrees(trees);
234235

235-
let fastbootConfigTree = this._buildFastbootConfigTree(newTree);
236+
let fastbootConfigTree = (this._fastbootConfigTree = this._buildFastbootConfigTree(newTree));
236237

237238
// Merge the package.json with the existing tree
238239
return new MergeTrees([newTree, fastbootConfigTree], {overwrite: true});
@@ -309,6 +310,28 @@ module.exports = {
309310
});
310311
},
311312

313+
/**
314+
* Write fastboot-script tags to the html file
315+
*/
316+
postprocessTree(type, tree) {
317+
this._super(...arguments);
318+
if (type === 'all') {
319+
let { fastbootConfig, appName, manifest } = this._fastbootConfigTree;
320+
let fastbootHTMLTree = new HTMLWriter(tree, {
321+
annotation: 'FastBoot HTML Writer',
322+
fastbootConfig,
323+
appName,
324+
manifest,
325+
appJsPath: this.app.options.outputPaths.app.js,
326+
});
327+
328+
// Merge the package.json with the existing tree
329+
return new MergeTrees([tree, fastbootHTMLTree], { overwrite: true });
330+
}
331+
332+
return tree;
333+
},
334+
312335
serverMiddleware(options) {
313336
let emberCliVersion = this._getEmberCliVersion();
314337
let app = options.app;

packages/ember-cli-fastboot/lib/broccoli/fastboot-config.js

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

4-
const fs = require('fs');
5-
const fmt = require('util').format;
6-
const uniq = require('ember-cli-lodash-subset').uniq;
7-
const merge = require('ember-cli-lodash-subset').merge;
4+
const fs = require('fs');
5+
const fmt = require('util').format;
6+
const uniq = require('ember-cli-lodash-subset').uniq;
7+
const merge = require('ember-cli-lodash-subset').merge;
88
const md5Hex = require('md5-hex');
9-
const path = require('path');
9+
const path = require('path');
1010
const Plugin = require('broccoli-plugin');
1111

1212
const stringify = require('json-stable-stringify');
1313

14-
const LATEST_SCHEMA_VERSION = 3;
14+
const LATEST_SCHEMA_VERSION = 5;
1515

1616
module.exports = class FastBootConfig extends Plugin {
1717
constructor(inputNode, options) {
@@ -37,25 +37,29 @@ module.exports = class FastBootConfig extends Plugin {
3737
} else {
3838
this.htmlFile = 'index.html';
3939
}
40-
40+
41+
this.prepareConfig();
42+
this.prepareDependencies();
4143
}
4244

43-
4445
/**
4546
* The main hook called by Broccoli Plugin. Used to build or
4647
* rebuild the tree. In this case, we generate the configuration
4748
* and write it to `package.json`.
4849
*/
4950
build() {
50-
this.buildConfig();
51-
this.buildDependencies();
52-
this.buildManifest();
5351
this.buildHostWhitelist();
54-
5552
let outputPath = path.join(this.outputPath, 'package.json');
5653
this.writeFileIfContentChanged(outputPath, this.toJSONString());
5754
}
5855

56+
get manifest() {
57+
if (!this._manifest) {
58+
this._manifest = this.buildManifest();
59+
}
60+
return this._manifest;
61+
}
62+
5963
writeFileIfContentChanged(outputPath, content) {
6064
let previous = this._fileToChecksumMap[outputPath];
6165
let next = md5Hex(content);
@@ -66,11 +70,11 @@ module.exports = class FastBootConfig extends Plugin {
6670
}
6771
}
6872

69-
buildConfig() {
73+
prepareConfig() {
7074
// we only walk the host app's addons to grab the config since ideally
7175
// addons that have dependency on other addons would never define
7276
// this advance hook.
73-
this.project.addons.forEach((addon) => {
77+
this.project.addons.forEach(addon => {
7478
if (addon.fastbootConfigTree) {
7579
let configFromAddon = addon.fastbootConfigTree();
7680

@@ -83,7 +87,7 @@ module.exports = class FastBootConfig extends Plugin {
8387
});
8488
}
8589

86-
buildDependencies() {
90+
prepareDependencies() {
8791
let dependencies = {};
8892
let moduleWhitelist = [];
8993
let ui = this.ui;
@@ -97,7 +101,10 @@ module.exports = class FastBootConfig extends Plugin {
97101

98102
if (dep in dependencies) {
99103
version = dependencies[dep];
100-
ui.writeLine(fmt("Duplicate FastBoot dependency %s. Versions may mismatch. Using range %s.", dep, version), ui.WARNING);
104+
ui.writeLine(
105+
fmt('Duplicate FastBoot dependency %s. Versions may mismatch. Using range %s.', dep, version),
106+
ui.WARNING
107+
);
101108
return;
102109
}
103110

@@ -129,7 +136,7 @@ module.exports = class FastBootConfig extends Plugin {
129136
}
130137

131138
updateFastBootManifest(manifest) {
132-
this.project.addons.forEach(addon =>{
139+
this.project.addons.forEach(addon => {
133140
if (addon.updateFastBootManifest) {
134141
manifest = addon.updateFastBootManifest(manifest);
135142

@@ -157,7 +164,7 @@ module.exports = class FastBootConfig extends Plugin {
157164
htmlFile: this.htmlFile
158165
};
159166

160-
this.manifest = this.updateFastBootManifest(manifest);
167+
return this.updateFastBootManifest(manifest);
161168
}
162169

163170
buildHostWhitelist() {
@@ -167,17 +174,21 @@ module.exports = class FastBootConfig extends Plugin {
167174
}
168175

169176
toJSONString() {
170-
return stringify({
171-
dependencies: this.dependencies,
172-
fastboot: {
173-
moduleWhitelist: this.moduleWhitelist,
174-
schemaVersion: LATEST_SCHEMA_VERSION,
175-
manifest: this.manifest,
176-
hostWhitelist: this.normalizeHostWhitelist(),
177-
config: this.fastbootConfig,
178-
appName: this.appName,
179-
}
180-
}, null, 2);
177+
return stringify(
178+
{
179+
name: this.appName,
180+
dependencies: this.dependencies,
181+
fastboot: {
182+
moduleWhitelist: this.moduleWhitelist,
183+
schemaVersion: LATEST_SCHEMA_VERSION,
184+
hostWhitelist: this.normalizeHostWhitelist(),
185+
config: this.fastbootConfig,
186+
htmlEntrypoint: this.manifest.htmlFile
187+
}
188+
},
189+
null,
190+
2
191+
);
181192
}
182193

183194
normalizeHostWhitelist() {
@@ -194,7 +205,7 @@ module.exports = class FastBootConfig extends Plugin {
194205
}
195206
});
196207
}
197-
}
208+
};
198209

199210
function eachAddonPackage(project, cb) {
200211
project.addons.map(addon => cb(addon.pkg));
@@ -207,7 +218,11 @@ function getFastBootDependencies(pkg) {
207218
}
208219

209220
if (addon.fastBootDependencies) {
210-
throw new SilentError('ember-addon.fastBootDependencies has been replaced with ember-addon.fastbootDependencies [addon: ' + pkg.name + ']')
221+
throw new SilentError(
222+
'ember-addon.fastBootDependencies has been replaced with ember-addon.fastbootDependencies [addon: ' +
223+
pkg.name +
224+
']'
225+
);
211226
}
212227

213228
return addon.fastbootDependencies;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
3+
const Filter = require('broccoli-persistent-filter');
4+
const { JSDOM } = require('jsdom');
5+
6+
module.exports = class BasePageWriter extends Filter {
7+
constructor(inputNodes, { annotation, fastbootConfig, appName, manifest, appJsPath }) {
8+
super(inputNodes, {
9+
annotation,
10+
extensions: ['html'],
11+
targetExtension: 'html',
12+
});
13+
this._manifest = manifest;
14+
this._rootURL = getRootURL(fastbootConfig, appName);
15+
this._appJsPath = appJsPath;
16+
}
17+
18+
getDestFilePath() {
19+
let filteredRelativePath = super.getDestFilePath(...arguments);
20+
21+
return filteredRelativePath === this._manifest.htmlFile ? filteredRelativePath : null;
22+
}
23+
24+
processString(content) {
25+
let dom = new JSDOM(content);
26+
let scriptTags = dom.window.document.querySelectorAll('script');
27+
28+
// In fastboot-config.js the paths are transformed with stripLeadingSlash
29+
// do we need to concat rootURL here?
30+
let rootURL = this._rootURL;
31+
32+
let scriptSrcs = [];
33+
for (let element of scriptTags) {
34+
scriptSrcs.push(urlWithin(element.getAttribute('src'), rootURL));
35+
}
36+
37+
let fastbootScripts = this._manifest.vendorFiles
38+
.concat(this._manifest.appFiles)
39+
.map(src => urlWithin(src, rootURL))
40+
.filter(src => !scriptSrcs.includes(src));
41+
42+
let appJsTag = findAppJsTag(scriptTags, this._appJsPath, rootURL);
43+
let range = new NodeRange(appJsTag);
44+
45+
for (let src of fastbootScripts) {
46+
range.insertAsScriptTag(src);
47+
}
48+
49+
return dom.serialize();
50+
}
51+
};
52+
53+
function getRootURL(appName, config) {
54+
let rootURL = (config[appName] && config[appName].rootURL) || '/';
55+
if (!rootURL.endsWith('/')) {
56+
rootURL = rootURL + '/';
57+
}
58+
return rootURL;
59+
}
60+
61+
function urlWithin(candidate, root) {
62+
let candidateURL = new URL(candidate, 'http://_the_current_origin_');
63+
let rootURL = new URL(root, 'http://_the_current_origin_');
64+
if (candidateURL.href.startsWith(rootURL.href)) {
65+
return candidateURL.href.slice(rootURL.href.length);
66+
}
67+
}
68+
69+
function findAppJsTag(scriptTags, appJsPath, rootURL) {
70+
appJsPath = urlWithin(appJsPath, rootURL);
71+
for (let e of scriptTags) {
72+
if (urlWithin(e.getAttribute('src'), rootURL) === appJsPath) {
73+
return e;
74+
}
75+
}
76+
}
77+
78+
class NodeRange {
79+
constructor(initial) {
80+
this.start = initial.ownerDocument.createTextNode('');
81+
initial.parentElement.insertBefore(this.start, initial);
82+
this.end = initial;
83+
}
84+
85+
insertAsScriptTag(src) {
86+
let newTag = this.end.ownerDocument.createElement('fastboot-script');
87+
newTag.setAttribute('src', src);
88+
this.insertNode(newTag);
89+
}
90+
91+
insertNode(node) {
92+
this.end.parentElement.insertBefore(node, this.end);
93+
}
94+
}

packages/ember-cli-fastboot/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"fastboot-express-middleware": "3.2.0-beta.2",
3838
"fastboot-transform": "^0.1.3",
3939
"fs-extra": "^7.0.0",
40+
"jsdom": "^16.2.2",
4041
"json-stable-stringify": "^1.0.1",
4142
"md5-hex": "^2.0.0",
4243
"recast": "^0.19.1",

packages/fastboot/src/fastboot-schema.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ function loadConfig(distPath) {
7979
({ appName, config, html, scripts } = loadManifest(distPath, pkg.fastboot, schemaVersion));
8080
} else {
8181
appName = pkg.name;
82-
({ config, html, scripts } = htmlEntrypoint(appName, distPath, pkg.fastboot.htmlEntrypoint));
82+
config = pkg.fastboot.config;
83+
({ html, scripts } = htmlEntrypoint(appName, distPath, pkg.fastboot.htmlEntrypoint, config));
8384
}
8485

8586
let sandboxRequire = buildWhitelistedRequire(

0 commit comments

Comments
 (0)