Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
FastBoot 1.0 migration cheatsheet

Pre-req

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.

Testing guidelines

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:

  1. An app running the current FastBoot double builds. Your app should boot and function correctly in browser and FastBoot.
  2. An app running with the proposed FastBoot build changes here. Your app should boot and function correctly in browser and FastBoot.
  3. An app having ember-cli-fastboot not installed. Make sure the fastboot initializers or vendor files are not running in browser.

Use cases

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.

Using process.env.EMBER_CLI_FASTBOOT to run import in browser build

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');
}

Using process.env.EMBER_CLI_FASTBOOT to run import in fastboot build

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. ember-cli-fastboot@1.0.0-beta.18 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;
}

Addon contains browser based initializers as app/(instance-)?initializers/browser/*js

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:

  1. Move the app/(instance-)?initializer/browser/<someName>.js -> app/(instance-)?initializers/<someName>.js
  2. The initialize function should be wrapped with if (typeof FastBoot === 'undefined') {...} check. See example below
  3. 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
    }
  }

Addon containing fastboot based initializers as app/(instance-)?initializers/fastboot/*.js

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:

  1. 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.

Addon contains app-lt-2-9 structure and imports initializers based on ember version

< TODO add more details with new API and backward compatible changes>

timmyomahony commented Jun 11, 2017 edited

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 the process.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 the included 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 (after app.toTree is called), we then make use of the treeFor hooks to intercept our dependency import and wrap it in a conditional statement. The Fastboot 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!

From reading a bit more, I think I'm wrong with regard to the treeFor hooks. The treeForVendor is not intercepting our asset from app.import(), instead it is actually adding a new tree node that will be added to the other vendor trees, and this is what is being references from our app.import('vendor/...') (noting the use of the vendor path as opposed to bower_components or otherwise)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment