Skip to content

Commit 8cb1542

Browse files
authored
Merge pull request #369 from kratiahuja/build-test
Refactor fastboot build to be more performant
2 parents c1186c0 + b4c105c commit 8cb1542

21 files changed

Lines changed: 6049 additions & 336 deletions

File tree

app/instance-initializers/browser/clear-double-boot.js renamed to app/instance-initializers/clear-double-boot.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@ export default {
1313
name: "clear-double-boot",
1414

1515
initialize: function(instance) {
16-
var originalDidCreateRootView = instance.didCreateRootView;
16+
if (typeof FastBoot === 'undefined') {
17+
var originalDidCreateRootView = instance.didCreateRootView;
1718

18-
instance.didCreateRootView = function() {
19-
let elements = document.querySelectorAll(instance.rootElement + ' .ember-view');
20-
for (let i = 0; i < elements.length; i++) {
21-
let element = elements[i];
22-
element.parentNode.removeChild(element);
23-
}
19+
instance.didCreateRootView = function() {
20+
let elements = document.querySelectorAll(instance.rootElement + ' .ember-view');
21+
for (let i = 0; i < elements.length; i++) {
22+
let element = elements[i];
23+
element.parentNode.removeChild(element);
24+
}
2425

25-
originalDidCreateRootView.apply(instance, arguments);
26-
};
26+
originalDidCreateRootView.apply(instance, arguments);
27+
};
28+
}
2729
}
2830
}

app-lt-2-9/initializers/fastboot/dom-helper-patches.js renamed to fastboot-app-lt-2-9/initializers/dom-helper-patches.js

File renamed without changes.
File renamed without changes.

index.js

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

4-
var path = require('path');
4+
const path = require('path');
5+
const fs = require('fs');
56

6-
var EventEmitter = require('events').EventEmitter;
7-
var mergeTrees = require('broccoli-merge-trees');
8-
var VersionChecker = require('ember-cli-version-checker');
9-
var FastBootExpressMiddleware = require('fastboot-express-middleware');
10-
var FastBoot = require('fastboot');
11-
var chalk = require('chalk');
7+
const EventEmitter = require('events').EventEmitter;
8+
const MergeTrees = require('broccoli-merge-trees');
9+
const FastBootExpressMiddleware = require('fastboot-express-middleware');
10+
const FastBoot = require('fastboot');
11+
const chalk = require('chalk');
1212

13-
var patchEmberApp = require('./lib/ext/patch-ember-app');
14-
var fastbootAppModule = require('./lib/utilities/fastboot-app-module');
13+
const fastbootAppModule = require('./lib/utilities/fastboot-app-module');
14+
const FastBootConfig = require('./lib/broccoli/fastboot-config');
15+
const migrateInitializers = require('./lib/build-utilities/migrate-initializers');
1516

16-
var filterInitializers = require('fastboot-filter-initializers');
17-
var FastBootBuild = require('./lib/broccoli/fastboot-build');
17+
const Concat = require('broccoli-concat');
18+
const Funnel = require('broccoli-funnel');
19+
const p = require('ember-cli-preprocess-registry/preprocessors');
20+
const existsSync = require('exists-sync');
1821

1922
/*
2023
* Main entrypoint for the Ember CLI addon.
2124
*/
22-
2325
module.exports = {
2426
name: 'ember-cli-fastboot',
2527

28+
2629
init() {
2730
this._super.init && this._super.init.apply(this, arguments);
2831

32+
// TODO remove once serve PR is checked in
2933
this.emitter = new EventEmitter();
3034
},
3135

36+
// TODO remove once serve PR is checked in
3237
includedCommands: function() {
3338
return {
3439
'fastboot': require('./lib/commands/fastboot')(this),
35-
36-
/* fastboot:build is deprecated and will be removed in a future version */
37-
'fastboot:build': require('./lib/commands/fastboot-build')
3840
};
3941
},
4042

43+
// TODO remove once serve PR is checked in
4144
on: function() {
4245
this.emitter.on.apply(this.emitter, arguments);
4346
},
4447

48+
// TODO remove once serve PR is checked in
4549
emit: function() {
4650
this.emitter.emit.apply(this.emitter, arguments);
4751
},
4852

4953
/**
5054
* Called at the start of the build process to let the addon know it will be
51-
* used. At this point, we can rely on the EMBER_CLI_FASTBOOT environment
52-
* variable being set.
55+
* used. Sets the auto run on app to be false so that we create and route app
56+
* automatically only in browser.
5357
*
54-
* Once we've determined which mode we're in (browser build or FastBoot build),
55-
* we mixin additional Ember addon hooks appropriate to the current build target.
58+
* See: https://ember-cli.com/user-guide/#integration
5659
*/
5760
included: function(app) {
58-
patchEmberApp(app);
59-
},
60-
61-
config: function() {
62-
if (this.app && this.app.options.__is_building_fastboot__) {
63-
return { APP: { autoboot: false } };
64-
}
61+
// set autoRun to false since we will conditionally include creating app when app files
62+
// is eval'd in app-boot
63+
app.options.autoRun = false;
64+
// get the app registry object and app name so that we can build the fastboot
65+
// tree
66+
this._appRegistry = app.registry;
67+
this._name = app.name;
68+
69+
// set a environment variable to allow addons to use `fastboot-filter-initializers`
70+
// for old versions.
71+
process.env.FASTBOOT_NEW_BUILD = true;
72+
73+
migrateInitializers(this.project);
6574
},
6675

6776
/**
@@ -79,83 +88,156 @@ module.exports = {
7988
}
8089

8190
if (type === 'app-boot') {
82-
return fastbootAppModule(config.modulePrefix);
91+
return fastbootAppModule(config.modulePrefix, JSON.stringify(config.APP || {}));
8392
}
8493

85-
if (type === 'config-module' && this.app.options.__is_building_fastboot__) {
86-
var linesToRemove = contents.length;
87-
while(linesToRemove) {
88-
// Clear out the default config from ember-cli
89-
contents.pop();
90-
linesToRemove--;
91-
}
92-
93-
return 'return FastBoot.config();';
94+
// if the fastboot addon is installed, we overwrite the config-module so that the config can be read
95+
// from meta tag for browser build and from Fastboot config for fastboot target
96+
if (type === 'config-module') {
97+
const emberCliPath = path.join(this.app.project.nodeModulesPath, 'ember-cli');
98+
contents.splice(0, contents.length);
99+
contents.push('if (typeof FastBoot !== \'undefined\') {');
100+
contents.push('return FastBoot.config();');
101+
contents.push('} else {');
102+
contents.push('var prefix = \'' + config.modulePrefix + '\';');
103+
contents.push(fs.readFileSync(path.join(emberCliPath, 'lib/broccoli/app-config-from-meta.js')));
104+
contents.push('}');
105+
return;
94106
}
95107
},
96108

97-
treeForApp: function(defaultTree) {
98-
var trees = [defaultTree];
109+
treeForFastBoot: function(tree) {
110+
let fastbootHtmlBarsTree;
99111

112+
// check the ember-cli version and conditionally patch the DOM api
100113
if (this._getEmberVersion().lt('2.10.0-alpha.1')) {
101-
trees.push(this.treeGenerator(path.resolve(this.root, 'app-lt-2-9')));
114+
fastbootHtmlBarsTree = this.treeGenerator(path.resolve(__dirname, 'fastboot-app-lt-2-9'));
115+
return tree ? new MergeTrees([tree, fastbootHtmlBarsTree]) : fastbootHtmlBarsTree;
102116
}
103117

104-
return mergeTrees(trees, { overwrite: true });
118+
return tree;
105119
},
106120

107121
/**
108-
* Filters out initializers and instance initializers that should only run in
109-
* browser mode.
122+
* Function that builds the fastboot tree from all fastboot complaint addons
123+
* and project and transpiles it into appname-fastboot.js
110124
*/
111-
preconcatTree: function(tree) {
112-
return filterInitializers(tree, this.app.name);
125+
_getFastbootTree: function() {
126+
const appName = this._name;
127+
const nodeModulesPath = this.project.nodeModulesPath;
128+
129+
let fastbootTrees = [];
130+
this.project.addons.forEach((addon) => {
131+
// walk through each addon and grab its fastboot tree
132+
const currentAddonFastbootPath = path.join(nodeModulesPath, addon.name, 'fastboot');
133+
let fastbootTree;
134+
if (existsSync(currentAddonFastbootPath)) {
135+
fastbootTree = this.treeGenerator(currentAddonFastbootPath);
136+
fastbootTrees.push(fastbootTree);
137+
}
138+
139+
// invoke addToFastBootTree for every addon
140+
if (addon.treeForFastBoot) {
141+
let additionalFastBootTree = addon.treeForFastBoot(fastbootTree);
142+
if (additionalFastBootTree) {
143+
fastbootTrees.push(additionalFastBootTree);
144+
}
145+
}
146+
});
147+
148+
// check the parent containing the fastboot directory
149+
const projectFastbootPath = path.join(this.project.root, 'fastboot');
150+
if (existsSync(projectFastbootPath)) {
151+
let fastbootTree = this.treeGenerator(projectFastbootPath);
152+
fastbootTrees.push(fastbootTree);
153+
}
154+
155+
// transpile the fastboot JS tree
156+
let mergedFastBootTree = new MergeTrees(fastbootTrees, {
157+
overwrite: true
158+
});
159+
let funneledFastbootTrees = new Funnel(mergedFastBootTree, {
160+
destDir: appName
161+
});
162+
const processExtraTree = p.preprocessJs(funneledFastbootTrees, '/', this._name, {
163+
registry: this._appRegistry
164+
});
165+
166+
let fileAppName = path.basename(this.app.options.outputPaths.app.js).split('.')[0];
167+
let finalFastbootTree = new Concat(processExtraTree, {
168+
outputFile: 'assets/' + fileAppName + '-fastboot.js'
169+
});
170+
171+
return finalFastbootTree;
172+
},
173+
174+
treeForPublic(tree) {
175+
let fastbootTree = this._getFastbootTree();
176+
let trees = [];
177+
if (tree) {
178+
trees.push(tree);
179+
}
180+
trees.push(fastbootTree);
181+
182+
let newTree = new MergeTrees(trees);
183+
184+
return newTree;
113185
},
114186

115187
/**
116188
* After the entire Broccoli tree has been built for the `dist` directory,
117189
* adds the `fastboot-config.json` file to the root.
118190
*
119-
* FASTBOOT_DISABLED is a pre 1.0 power user flag to
120-
* disable the fastboot build while retaining the fastboot service.
121191
*/
122192
postprocessTree: function(type, tree) {
123-
if (type === 'all' && !process.env.FASTBOOT_DISABLED) {
124-
var fastbootTree = this.buildFastBootTree();
193+
if (type === 'all') {
194+
let fastbootConfigTree = this._buildFastbootConfigTree(tree);
125195

126196
// Merge the package.json with the existing tree
127-
return mergeTrees([tree, fastbootTree], {overwrite: true});
197+
return new MergeTrees([tree, fastbootConfigTree], {overwrite: true});
128198
}
129199

130200
return tree;
131201
},
132202

133-
buildFastBootTree: function() {
134-
var fastbootBuild = new FastBootBuild({
135-
ui: this.ui,
203+
_buildFastbootConfigTree : function(tree) {
204+
let env = this.app.env;
205+
let config = this.project.config(env);
206+
let fastbootConfig = config.fastboot;
207+
// do not boot the app automatically in fastboot. The instance is booted and
208+
// lives for the lifetime of the request.
209+
if ('APP' in config) {
210+
config['APP']['autoboot'] = false;
211+
} else {
212+
config['APP'] = {
213+
'autoboot': false
214+
}
215+
}
216+
217+
return new FastBootConfig(tree, {
136218
assetMapPath: this.assetMapPath,
137219
project: this.project,
138-
app: this.app,
139-
parent: this.parent
220+
name: this.app.name,
221+
outputPaths: this.app.options.outputPaths,
222+
ui: this.ui,
223+
fastbootAppConfig: fastbootConfig,
224+
appConfig: config
140225
});
141-
142-
return fastbootBuild.toTree();
143226
},
144227

145228
serverMiddleware: function(options) {
146-
var emberCliVersion = this._getEmberCliVersion();
147-
var app = options.app;
148-
var options = options.options;
229+
let emberCliVersion = this._getEmberCliVersion();
230+
let app = options.app;
149231

150232
if (emberCliVersion.gte('2.12.0-beta.1')) {
151233
// only run the middleware when ember-cli version for app is above 2.12.0-beta.1 since
152234
// that version contains API to hook fastboot into ember-cli
153235

154236
app.use((req, resp, next) => {
155-
var fastbootQueryParam = (req.query.hasOwnProperty('fastboot') && req.query.fastboot === 'false') ? false : true;
156-
var enableFastBootServe = !process.env.FASTBOOT_DISABLED && fastbootQueryParam;
157-
var broccoliHeader = req.headers['x-broccoli'];
158-
var outputPath = broccoliHeader['outputPath'];
237+
const fastbootQueryParam = (req.query.hasOwnProperty('fastboot') && req.query.fastboot === 'false') ? false : true;
238+
const enableFastBootServe = !process.env.FASTBOOT_DISABLED && fastbootQueryParam;
239+
const broccoliHeader = req.headers['x-broccoli'];
240+
const outputPath = broccoliHeader['outputPath'];
159241

160242
if (broccoliHeader['url'] === req.serveUrl && enableFastBootServe) {
161243
// if it is a base page request, then have fastboot serve the base page
@@ -168,7 +250,7 @@ module.exports = {
168250
});
169251
}
170252

171-
var fastbootMiddleware = FastBootExpressMiddleware({
253+
let fastbootMiddleware = FastBootExpressMiddleware({
172254
fastboot: this.fastboot
173255
});
174256

@@ -181,10 +263,12 @@ module.exports = {
181263
}
182264
},
183265

266+
// TODO remove once serve PR is checked in
184267
outputReady: function() {
185268
this.emit('outputReady');
186269
},
187270

271+
// TODO remove once serve PR is checked in
188272
postBuild: function(result) {
189273
this.emit('postBuild');
190274
if (this.fastboot) {
@@ -199,16 +283,16 @@ module.exports = {
199283
},
200284

201285
_getEmberCliVersion: function() {
202-
var VersionChecker = require('ember-cli-version-checker');
203-
var checker = new VersionChecker(this);
286+
const VersionChecker = require('ember-cli-version-checker');
287+
const checker = new VersionChecker(this);
204288

205289
return checker.for('ember-cli', 'npm');
206290
},
207291

208292
_getEmberVersion: function() {
209-
var VersionChecker = require('ember-cli-version-checker');
210-
var checker = new VersionChecker(this);
211-
var emberVersionChecker = checker.for('ember-source', 'npm');
293+
const VersionChecker = require('ember-cli-version-checker');
294+
const checker = new VersionChecker(this);
295+
const emberVersionChecker = checker.for('ember-source', 'npm');
212296

213297
if (emberVersionChecker.version) {
214298
return emberVersionChecker;

0 commit comments

Comments
 (0)