The below guide only applies to addons. If your app is using process.env.EMBER_CLI_FASTBOOT
, please create an in-repo addon and follow this guide.
When you make the changes to make sure your addons are backward compatible for upcoming FastBoot build changes, make sure to test all the usecases as follows:
- An app running the current FastBoot double builds. Your app should boot and function correctly in browser and FastBoot.
- An app running with the proposed FastBoot build changes here. Your app should boot and function correctly in browser and FastBoot.
- An app having
ember-cli-fastboot
not installed. Make sure the fastboot initializers or vendor files are not running in browser.
Following are the usecases where addons will break with the upcoming FastBoot build changes.
Note: One or more of the below usecases may apply for your addon. Make sure to fix for each usecase.
Example of current situation
Let's say you addon does the following during build time:
treeForVendor(defaultTree) {
var browserVendorLib = new Funnel(...);
return new mergeTrees([defaultTree, browserVendorLib]);
}
included() {
if (!process.env.EMBER_CLI_FASTBOOT) {
// only when it is not fastboot build
app.import('vendor/<browserLibName>.js');
}
}
What happens post FastBoot build issues are resolved?
This environment variable will no longer be available. Also FastBoot will load the same assets in sandbox that are sent to the browser in addition to some extra ones.
What should I do be backward compatible?
Instead of relying on process.env.EMBER_CLI_FASTBOOT
and conditionally importing do the following:
a. Your file that is being imported should be wrapped in an if (typeof FastBoot === 'undefined
) check
b. Remove the if (!process.env.EMBER_CLI_FASTBOOT) {..}
and simply import the vendor file
Example of proposed change
Using the above example, your addon code should be changed to:
var map = require('broccoli-stew').map;
treeForVendor(defaultTree) {
var browserVendorLib = new Funnel(...);
browserVendorLib = map(browserVendorLib, (content) => `if (typeof FastBoot === 'undefined') { ${content} }`);
return new mergeTrees([defaultTree, browserVendorLib]);
}
included() {
// this file will be loaded in FastBoot but will not be eval'd
app.import('vendor/<browserLibName>.js');
}
Example of current situation
Let's say you addon does the following during build time:
treeForVendor(defaultTree) {
var fastbootVendorLib = new Funnel(...);
return mergeTrees([defaultTree, fastbootVendorLib]);
}
included() {
if (process.env.EMBER_CLI_FASTBOOT) {
// only when it is not fastboot build
app.import('vendor/<fastbootLibName>.js');
}
}
What happens post FastBoot build issues are resolved?
This environment variable will no longer be available. Also FastBoot will load the same assets in sandbox that are sent to the browser in addition to some extra ones. You want this extra vendor file to continue loading and eval'd in the FastBoot sandbox.
What should I do be backward compatible?
Instead of relying on process.env.EMBER_CLI_FASTBOOT
and conditionally importing do the following:
a. [email protected] exposes an API called updatedFastBootManifest
that allows you to update additional vendorFiles to load.
b. Remove import conditionally and follow the API usage here
Example of proposed change
Using the above example, your addon code should be changed to:
treeForVendor(defaultTree) {
var fastbootVendorLib = new Funnel(...);
return mergeTrees([defaultTree, fastbootVendorLib]);
}
included() {
app.import(fastbootVendorLib, {outputFile: 'assets/fastboot-lib.js'});
}
updateFastBootManifest(manifest) {
// you can decide on the order of control (whether to push or unshift)
// once the build is done you will automically see `fastbootVendorLib` file under `dist/<addonname>` since the src library is in
// <addon>/public
manifest.vendorFiles.push('assets/fastboot-lib.js');
return manifest;
}
Example of current situation
Let's say your addon needs a initializer that only should run in the browser:
Your browser initializer will be in a directory structure as : app/(instance-)?initializers/browser/<someName>.js
and having content as follows:
export default {
name: 'someName',
initialize: function(app) {
// some initializer code
}
What happens post FastBoot build issues are resolved?
The new FastBoot build no longer has the notion of forking and creating assets for two different environments: browser and fastboot. Therefore, it is no longer going to filter these initializers and include them based on the which asset build is running. If you don't do the migration, your browser initializer will run the FastBoot environment and most likely your server side rendering would fail.
What should I do be backward compatible?
Instead of forking and filtering the intializers, do the following:
- Move the
app/(instance-)?initializer/browser/<someName>.js
->app/(instance-)?initializers/<someName>.js
- The
initialize
function should be wrapped withif (typeof FastBoot === 'undefined') {...}
check. See example below - If during the build you are calling
fastboot-filter-initializers
you no longer should call it since it will be a no-op.
Example of proposed change
Using the above proposed changes, your new initializer located at app/(instance-)?initializers/<someName>.js
should look as follows:
export default {
name: 'someName',
initialize: function(app) {
if (typeof FastBoot === 'undefined') {
// some initializer code
}
}
Example of current situation
Let's say your addon needs a initializer that only should run in fastboot:
Your fastboot initializer will be in a directory structure as : app/(instance-)?initializers/fastboot/<someName>.js
and having content as follows:
export default {
name: 'someName',
initialize: function(app) {
// some initializer code
}
What happens post FastBoot build issues are resolved?
The new FastBoot build no longer has the notion of forking and creating assets for two different environments: browser and fastboot. Therefore, it is no longer going to filter these initializers and include them based on the which asset build is running. If you don't do the migration, your fastboot initializer will end up running in the browser app and breaking your app.
What should I do to be backward compatible?
You don't need to do much except:
- If your addon is not calling
fastboot-filter-initializers
, please go ahead and use it to make sure it gets filtered by your addon.
NOTE: We didn't ask you to move this initializer as in the pervious usecase. The reason for this is we will do the auto-migration for you. Eventually once FastBoot 1.0 or 2.0 is released, you will need to move it to fastboot/(instance-)?intializers/<somename>.js
since we will drop the auto-migration at some point (TBD).
When your addon only wants to support FastBoot 1.0, you will need to copy over your existing app/(instance-)?intializers/<somename>.js
to fastboot/(instance-)?intializers/<somename>.js
.
< TODO add more details with new API and backward compatible changes>
Hey, I need to update a number of addons to have FB 1.0 compatibility so I've been researching how Ember CLI addons work as well as the new FastBoot dependency approach. I'm playing catch-up with regard to the internals of Ember CLI as well as FastBoot in general so I was hoping I could get some clarification on my understanding of the changes.
From what I understand, Fastboot previously had two build targets: one for the browser (
dist
folder) and another for node/FastBoot (fastboot-dist
folder). That meant it was easy to control an addon's dependencies: you could use theprocess.env.FastBoot
variable during the application's build time to include/exclude dependencies depending on whether the build was for the browser or node.Now, in FastBoot 1.0 there is only one build for both the browser and node. This means that we have to include all of the dependencies at build time. This is obviously a problem as, when we run the application in FastBoot, any incompatible browser-base dependencies will throw errors. So to get around this, at build time we wrap the dependencies code in a conditional statement that means that at run time the dependency will be included/excluded depending on whether it's being run by node or the browser.
From an Ember CLI perspective, we use the
app.import
in theincluded
hook of our addon to tell Ember CLI that we want that path added to the Broccoli tree. When the tree is actually being built (afterapp.toTree
is called), we then make use of thetreeFor
hooks to intercept our dependency import and wrap it in a conditional statement. TheFastboot
variable is now a run-time variable as opposed to a build-time environment variable.Is this all a correct interpretation of what's going on? I'd particularly like to confirm the use of the
treeFor
hooks and that their purpose is essentially to intercept and wrap the previous import as I found this very confusing when first trying to get up to speed.Thanks!