Skip to content

Commit

Permalink
Move fastboot specific config to a meta tag; keep manifest in
Browse files Browse the repository at this point in the history
package.json for broccoli-asset-rev support

This commit builds fastboot specific config (available via
FastBoot.config()) to meta tag, which will be extracted by fastboot
when served.

The fastboot config meta has the name ending with
"config/fastboot-environement" to distinguish from regular
"config/environement" meta tags. If the new meta tags are not found,
which happens in embroider builds, we fallback to find regular meta
tags.
  • Loading branch information
xg-wang committed Aug 8, 2021
1 parent 3b74b03 commit 78c8ec2
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 84,249 deletions.
4 changes: 3 additions & 1 deletion packages/ember-cli-fastboot/lib/broccoli/fastboot-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,9 @@ module.exports = class FastBootConfig extends Plugin {
moduleWhitelist: this.moduleWhitelist,
schemaVersion: LATEST_SCHEMA_VERSION,
hostWhitelist: this.normalizeHostWhitelist(),
config: this.fastbootConfig,
// We can't drop manifest until broccoli-asset-rev also supports v5 HTML based manifest
// https://github.com/ember-cli/broccoli-asset-rev/blob/78f6047c15acb3bd348611f658b03bdd1041911f/lib/fastboot-manifest-rewrite.js
manifest: this.manifest,
htmlEntrypoint: this.manifest.htmlFile
}
},
Expand Down
52 changes: 50 additions & 2 deletions packages/ember-cli-fastboot/lib/broccoli/html-writer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = class BasePageWriter extends Filter {
});
this._manifest = manifest;
this._rootURL = getRootURL(fastbootConfig, appName);
this._fastbootConfig = fastbootConfig;
this._appJsPath = outputPaths.app.js;
this._expectedFiles = expectedFiles(outputPaths);
}
Expand All @@ -24,15 +25,46 @@ module.exports = class BasePageWriter extends Filter {

processString(content) {
let dom = new JSDOM(content);
this._handleConfig(dom);
this._handleScripts(dom);
return dom.serialize();
}

_handleConfig(dom) {
function findFistConfigMeta(dom) {
let metaTags = dom.window.document.querySelectorAll('meta');
for (let element of metaTags) {
let name = element.getAttribute('name');
if (name && name.endsWith('/config/environment')) {
return element;
}
}
}
let firstConfigMeta;
if (firstConfigMeta) {
firstConfigMeta = findFistConfigMeta(dom);
} else {
firstConfigMeta = dom.window.document.createTextNode('\n');
dom.window.document.head.appendChild(firstConfigMeta);
}
let nodeRange = new NodeRange(firstConfigMeta);
for (let [name, options] of Object.entries(this._fastbootConfig)) {
nodeRange.insertJsonAsMetaTag(`${name}/config/fastboot-environment`, options);
}
}

_handleScripts(dom) {
let scriptTags = dom.window.document.querySelectorAll('script');

this._ignoreUnexpectedScripts(scriptTags);

let fastbootScripts = this._findFastbootScriptToInsert(scriptTags);
let appJsTag = findAppJsTag(scriptTags, this._appJsPath, this._rootURL);
insertFastbootScriptsBeforeAppJsTags(fastbootScripts, appJsTag);
if (!appJsTag) {
throw new Error('ember-cli-fastboot cannot find own app script tag');
}

return dom.serialize();
insertFastbootScriptsBeforeAppJsTags(fastbootScripts, appJsTag);
}

_findFastbootScriptToInsert(scriptTags) {
Expand Down Expand Up @@ -79,6 +111,10 @@ function getRootURL(appName, config) {
}

function urlWithin(candidate, root) {
// this is a null or relative path
if (!candidate || !candidate.startsWith('/')) {
return candidate;
}
let candidateURL = new URL(candidate, 'http://_the_current_origin_');
let rootURL = new URL(root, 'http://_the_current_origin_');
if (candidateURL.href.startsWith(rootURL.href)) {
Expand Down Expand Up @@ -114,6 +150,18 @@ class NodeRange {
let newTag = this.end.ownerDocument.createElement('fastboot-script');
newTag.setAttribute('src', src);
this.insertNode(newTag);
this.insertNewLine();
}

insertJsonAsMetaTag(name, content) {
let newTag = this.end.ownerDocument.createElement('meta');
newTag.setAttribute('name', name);
newTag.setAttribute('content', encodeURIComponent(JSON.stringify(content)));
this.insertNode(newTag);
this.insertNewLine();
}

insertNewLine() {
this.insertNode(this.end.ownerDocument.createTextNode('\n'));
}

Expand Down
3 changes: 1 addition & 2 deletions packages/fastboot/src/fastboot-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ function loadConfig(distPath) {
({ appName, config, html, scripts } = loadManifest(distPath, pkg.fastboot, schemaVersion));
} else {
appName = pkg.name;
config = pkg.fastboot.config;
({ html, scripts } = htmlEntrypoint(appName, distPath, pkg.fastboot.htmlEntrypoint, config));
({ config, html, scripts } = htmlEntrypoint(appName, distPath, pkg.fastboot.htmlEntrypoint));
}

let sandboxRequire = buildWhitelistedRequire(
Expand Down
30 changes: 27 additions & 3 deletions packages/fastboot/src/html-entrypoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,35 @@ const { JSDOM } = require('jsdom');
const fs = require('fs');
const path = require('path');

function htmlEntrypoint(appName, distPath, htmlPath, config = {}) {
function htmlEntrypoint(appName, distPath, htmlPath) {
let html = fs.readFileSync(path.join(distPath, htmlPath), 'utf8');
let dom = new JSDOM(html);
let scripts = [];

function mergeContent(metaElement, config, configName) {
let name = metaElement.getAttribute('name');
if (name && name.endsWith(configName)) {
let content = JSON.parse(decodeURIComponent(metaElement.getAttribute('content')));
content.APP = Object.assign({ autoboot: false }, content.APP);
config[name.slice(0, -1 * configName.length)] = content;
return true;
}
return false;
}
let fastbootConfig = {};
let config = {};
for (let element of dom.window.document.querySelectorAll('meta')) {
mergeContent(element, config, '/config/environment');
let fastbootMerged = mergeContent(element, fastbootConfig, '/config/fastboot-environment');
if (fastbootMerged) {
element.remove();
}
}
let isFastbootConfigBuilt = Object.keys(fastbootConfig).length > 0;
if (isFastbootConfigBuilt) {
config = fastbootConfig;
}

let scripts = [];
let rootURL = getRootURL(appName, config);

for (let element of dom.window.document.querySelectorAll('script,fastboot-script')) {
Expand All @@ -24,7 +48,7 @@ function htmlEntrypoint(appName, distPath, htmlPath, config = {}) {
}
}

return { html: dom.serialize(), scripts };
return { config, html: dom.serialize(), scripts };
}

function extractSrc(element) {
Expand Down
14 changes: 0 additions & 14 deletions packages/fastboot/test/fastboot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,18 +455,4 @@ describe('FastBoot', function() {
usedPrebuiltSandbox: true,
});
});

it('htmlEntrypoint works', function() {
var fastboot = new FastBoot({
distPath: fixture('html-entrypoint'),
});

return fastboot
.visit('/')
.then(r => r.html())
.then(html => {
expect(html).to.match(/Welcome to Ember/);
expect(html).to.not.match(/fastboot-script/);
});
});
});
Loading

0 comments on commit 78c8ec2

Please sign in to comment.