UNPKG

26.3 kBMarkdownView Raw
1# Ember FastBoot
2
3[![Greenkeeper badge](https://badges.greenkeeper.io/ember-fastboot/ember-cli-fastboot.svg)](https://greenkeeper.io/)
4[![npm version](https://badge.fury.io/js/ember-cli-fastboot.svg)](https://badge.fury.io/js/ember-cli-fastboot)
5[![Build Status](https://travis-ci.org/ember-fastboot/ember-cli-fastboot.svg?branch=master)](https://travis-ci.org/ember-fastboot/ember-cli-fastboot)
6[![Build status](https://ci.appveyor.com/api/projects/status/6qcpp4ndy3ao4yv8/branch/master?svg=true)](https://ci.appveyor.com/project/embercli/ember-cli-fastboot/branch/master)
7
8An Ember CLI addon that allows you to render and serve Ember.js apps on
9the server. Using FastBoot, you can serve rendered HTML to browsers and
10other clients without requiring them to download JavaScript assets.
11
12Currently, the set of Ember applications supported is extremely limited.
13As we fix more issues, we expect that set to grow rapidly. See [Known
14Limitations](#known-limitations) below for a full-list.
15
16The bottom line is that you should not (yet) expect to install this add-on in
17your production app and have FastBoot work.
18
19## Introduction Video
20
21[![Introduction to Ember FastBoot](https://i.vimeocdn.com/video/559399270_640x360.jpg)](https://vimeo.com/157688134)
22
23## Installation
24
25FastBoot requires Ember 2.3 or higher. It is also preferable that your app is running `ember-cli` 2.12.0 and higher.
26
27From within your Ember CLI application, run the following command:
28
29```
30ember install ember-cli-fastboot
31```
32
33## Running
34
35If your app is running `ember-cli` 2.12.0-beta.1+ you can run as follows:
36
37* `ember serve`
38* Visit your app at `http://localhost:4200`
39
40You may be shocked to learn that minified code runs faster in Node than
41non-minified code, so you will probably want to run the production
42environment build for anything "serious."
43
44```
45ember serve --environment production
46```
47
48You can also specify the port (default is 4200):
49
50```
51ember serve --port 8088
52```
53
54See `ember help` for more.
55
56### Disabling FastBoot with `ember serve`
57
58Optionally you can even disable the fastboot serving at runtime using the `fastboot` query parameter. Example to turn off fastboot serving,
59visit your app at `http://localhost:4200/?fastboot=false`. If you want to turn on fastboot serving again, simply visit at `http://localhost:4200/?fastboot=true` or `http://localhost:4200/`.
60
61You can even disable serving fastboot with `ember serve` using an environment flag: `FASTBOOT_DISABLED=true ember serve`. If you have disabled building fastboot assets using the same flag as described [here](https://github.com/ember-fastboot/ember-cli-fastboot#double-build-times-and-no-incremental-builds), remember to also disable serving fastboot assets when using `ember serve`.
62
63### FastBoot Configuration
64
65When running locally using `ember serve` you can pass options into FastBoot instance via `config/fastboot.js` file. The configuration file is applicable only for applications, addons are not supported.
66
67```js
68module.exports = function(environment) {
69 let myGlobal = environment === 'production' ? process.env.MY_GLOBAL : 'testing';
70
71 return {
72 sandboxGlobals: {
73 myGlobal;
74 }
75 };
76}
77```
78
79There are several options available, see FastBoot's [README](https://github.com/ember-fastboot/fastboot/tree/v2.0.3#usage) for more information, but be aware that `distPath` is provided internally by `ember-cli-fastboot`, hence it can not be modified by this file.
80
81### FastBoot App Server Configuration
82
83When using FastBoot App Server for production environment you have to manually pass options from `config/fastboot.js` file.
84
85```js
86const FastBootAppServer = require('fastboot-app-server');
87const config = require('./config/fastboot')(process.env.NODE_ENV);
88
89let server = new FastBootAppServer({
90 distPath: 'dist',
91 ...config,
92});
93
94server.start();
95```
96
97## Using Node/npm Dependencies
98
99### Whitelisting Packages
100
101When your app is running in FastBoot, it may need to use Node packages
102to replace features that are available only in the browser.
103
104For security reasons, your Ember app running in FastBoot can only access
105packages that you have explicitly whitelisted.
106
107To allow your app to require a package, add it to the
108`fastbootDependencies` array in your app's `package.json`:
109
110```js
111{
112 "name": "my-sweet-app",
113 "version": "0.4.2",
114 "devDependencies": {
115 // ...
116 },
117 "dependencies": {
118 // ...
119 },
120 "fastbootDependencies": [
121 "rsvp",
122 "path"
123 ]
124}
125```
126
127The `fastbootDependencies` in the above example means the only node
128modules your Ember app can use are `rsvp` and `path`.
129
130If the package you are using is not built-in to Node, **you must also
131specify the package and a version in the `package.json` `dependencies`
132hash.** Built-in modules (`path`, `fs`, etc.) only need to be added to
133`fastbootDependencies`.
134
135### Using Dependencies
136
137From your Ember.js app, you can run `FastBoot.require()` to require a
138package. This is identical to the CommonJS `require` except it checks
139all requests against the whitelist first.
140
141```js
142let path = FastBoot.require('path');
143let filePath = path.join('tmp', session.getID());
144```
145
146If you attempt to require a package that is not in the whitelist,
147FastBoot will raise an exception.
148
149Note that the `FastBoot` global is **only** available when running in
150FastBoot mode. You should either guard against its presence or only use
151it in FastBoot-only initializers.
152
153## FastBoot Service
154
155FastBoot registers the `fastboot` service. This service allows you to
156check if you are running within FastBoot by checking
157`fastboot.isFastBoot`. There is also a request object under
158`fastboot.request` which exposes details about the current request being
159handled by FastBoot
160
161### Delaying the server response
162
163By default, FastBoot waits for the `beforeModel`, `model`, and
164`afterModel` hooks to resolve before sending the response back to the
165client. If you have asynchrony that runs outside of those contexts, your
166response may not reflect the state that you want.
167
168To solve this, the `fastboot` service has `deferRendering` method that accepts
169a promise. It will chain all promises passed to it, and the FastBoot server will
170wait until all of these promises resolve before sending the response to
171the client. These promises must be chained before the rendering is
172complete after the model hooks. For example, if a component that is
173rendered into the page makes an async call for data, registering a
174promise to be resolved in its `init` hook would allow the component to
175defer the rendering of the page.
176
177The following example demonstrates how the `deferRendering` method can be
178used to ensure posts data has been loaded asynchronously by a component before
179rendering the entire page. Note how the call should be wrapped in a `fastboot.isFastBoot`
180check since the method will throw an exception outside of that context:
181
182```js
183import Ember from 'ember';
184
185export default Ember.Component.extend({
186 fastboot: Ember.inject.service(),
187 model: Ember.inject.service(),
188
189 init() {
190 this._super(...arguments);
191
192 let promise = this.get('store').findAll('post').then((posts) => {
193 this.set('posts', posts);
194 });
195
196 if (this.get('fastboot.isFastBoot')) {
197 this.get('fastboot').deferRendering(promise);
198 }
199 }
200});
201```
202
203### Cookies
204
205You can access cookies for the current request via `fastboot.request`
206in the `fastboot` service.
207
208```js
209export default Ember.Route.extend({
210 fastboot: Ember.inject.service(),
211
212 model() {
213 let authToken = this.get('fastboot.request.cookies.auth');
214 // ...
215 }
216});
217```
218
219The service's `cookies` property is an object containing the request's
220cookies as key/value pairs.
221
222### Headers
223
224You can access the headers for the current request via `fastboot.request`
225in the `fastboot` service. The `headers` object implements part of the
226[Fetch API's Headers
227class](https://developer.mozilla.org/en-US/docs/Web/API/Headers), the
228functions available are
229[`has`](https://developer.mozilla.org/en-US/docs/Web/API/Headers/has),
230[`get`](https://developer.mozilla.org/en-US/docs/Web/API/Headers/get), and
231[`getAll`](https://developer.mozilla.org/en-US/docs/Web/API/Headers/getAll).
232
233```js
234export default Ember.Route.extend({
235 fastboot: Ember.inject.service(),
236
237 model() {
238 let headers = this.get('fastboot.request.headers');
239 let xRequestHeader = headers.get('X-Request');
240 // ...
241 }
242});
243```
244
245### Host
246
247You can access the host of the request that the current FastBoot server
248is responding to via `fastboot.request` in the `fastboot` service. The
249`host` property will return the host (`example.com` or `localhost:3000`).
250
251```js
252export default Ember.Route.extend({
253 fastboot: Ember.inject.service(),
254
255 model() {
256 let host = this.get('fastboot.request.host');
257 // ...
258 }
259});
260```
261
262To retrieve the host of the current request, you must specify a list of
263hosts that you expect in your `config/environment.js`:
264
265```js
266module.exports = function(environment) {
267 var ENV = {
268 modulePrefix: 'host',
269 environment: environment,
270 baseURL: '/',
271 locationType: 'auto',
272 EmberENV: {
273 // ...
274 },
275 APP: {
276 // ...
277 },
278
279 fastboot: {
280 hostWhitelist: ['example.com', 'subdomain.example.com', /^localhost:\d+$/]
281 }
282 };
283 // ...
284};
285```
286
287The `hostWhitelist` can be a string or RegExp to match multiple hosts.
288Care should be taken when using a RegExp, as the host function relies on
289the `Host` HTTP header, which can be forged. You could potentially allow
290a malicious request if your RegExp is too permissive when using the `host`
291when making subsequent requests.
292
293Retrieving `host` will error on 2 conditions:
294
295 1. you do not have a `hostWhitelist` defined
296 2. the `Host` header does not match an entry in your `hostWhitelist`
297
298### Query Parameters
299
300You can access query parameters for the current request via `fastboot.request`
301in the `fastboot` service.
302
303```js
304export default Ember.Route.extend({
305 fastboot: Ember.inject.service(),
306
307 model() {
308 let authToken = this.get('fastboot.request.queryParams.auth');
309 // ...
310 }
311});
312```
313
314The service's `queryParams` property is an object containing the request's
315query parameters as key/value pairs.
316
317### Path
318
319You can access the path (`/` or `/some-path`) of the request that the
320current FastBoot server is responding to via `fastboot.request` in the
321`fastboot` service.
322
323```js
324export default Ember.Route.extend({
325 fastboot: Ember.inject.service(),
326
327 model() {
328 let path = this.get('fastboot.request.path');
329 // ...
330 }
331});
332```
333
334### Protocol
335
336You can access the protocol (`http:` or `https:`) of the request that the
337current FastBoot server is responding to via `fastboot.request` in the
338`fastboot` service.
339
340```js
341export default Ember.Route.extend({
342 fastboot: Ember.inject.service(),
343
344 model() {
345 let protocol = this.get('fastboot.request.protocol');
346 // ...
347 }
348});
349```
350
351### The Shoebox
352
353You can pass application state from the FastBoot rendered application
354to the browser rendered application using a feature called the "Shoebox".
355This allows you to leverage server API calls made by the FastBoot
356rendered application on the browser rendered application. Thus preventing
357you from duplicating work that the FastBoot application is performing.
358This should result in a performance benefit for your browser application,
359as it does not need to issue server API calls whose results are available
360from the Shoebox.
361
362The contents of the Shoebox are written to the HTML as strings within
363`<script>` tags by the server rendered application, which are then
364consumed by the browser rendered application.
365
366This looks like:
367```html
368.
369.
370<script type="fastboot/shoebox" id="shoebox-main-store">
371{"data":[{"attributes":{"name":"AEC Professionals"},"id":106,"type":"audience"},
372{"attributes":{"name":"Components"},"id":111,"type":"audience"},
373{"attributes":{"name":"Emerging Professionals"},"id":116,"type":"audience"},
374{"attributes":{"name":"Independent Voters"},"id":2801,"type":"audience"},
375{"attributes":{"name":"Staff"},"id":141,"type":"audience"},
376{"attributes":{"name":"Students"},"id":146,"type":"audience"}]}
377</script>
378.
379.
380```
381
382You can add items into the shoebox with `shoebox.put`, and you can retrieve
383items from the shoebox using `shoebox.retrieve`. In the example below we use
384an object, `shoeboxStore`, that acts as our store of objects that reside in
385the shoebox. We can then add/remove items from the `shoeboxStore` in the
386FastBoot rendered application as we see fit. Then in the browser rendered
387application, it will grab the `shoeboxStore` from the shoebox and retrieve
388the record necessary for rendering this route.
389
390```js
391export default Ember.Route.extend({
392 fastboot: Ember.inject.service(),
393
394 model(params) {
395 let shoebox = this.get('fastboot.shoebox');
396 let shoeboxStore = shoebox.retrieve('my-store');
397
398 if (this.get('fastboot.isFastBoot')) {
399 return this.store.findRecord('post', params.post_id).then(post => {
400 if (!shoeboxStore) {
401 shoeboxStore = {};
402 shoebox.put('my-store', shoeboxStore);
403 }
404 shoeboxStore[post.id] = post.toJSON();
405 });
406 } else {
407 return shoeboxStore && shoeboxStore[params.post_id];
408 }
409 }
410});
411```
412
413### Think out of the Shoebox
414
415Shoebox gives you great capabilities, but using it in the real app is pretty rough. Have you ever thought that such kind of logic should be done behind the scenes? In a large codebase, defining `fastboot.isFastBoot` conditionals can be a daunting task. Furthermore, it generates a lot of boilerplate code, which obscures the solution. Sooner or later coupling with `shoebox` will spread over all routes.
416
417#### Solution: Application Adapter
418
419One way to abstract the shoebox data storage mechanics is to move the logic into
420the Application Adapter as shown below.
421
422```
423export default class ApplicationAdapter extends JSONAPIAdapter.extend(
424 // ...snip...
425
426 cacheKeyFor([, model, id]) {
427 return (model.modelName && id) ? `${model.modelName}-${id}` : 'default-store';
428 }
429
430 async findRecord() {
431 const key = this.cacheKeyFor(arguments);
432
433 if (this.fastboot.isFastBoot) {
434 let result = await super.findRecord(...arguments);
435
436 // must deep-copy for clean serialization.
437 result = JSON.parse(JSON.stringify(result));
438
439 this.fastboot.shoebox.put(key, result);
440
441 return result;
442 }
443
444 let result = this.fastboot.shoebox.retrieve(key);
445
446 if (!result) {
447 result = await super.findRecord(...arguments);
448 }
449
450 // must deep-copy for clean serialization.
451 return JSON.parse(JSON.stringify(result));
452 }
453}
454```
455With this strategy, any time an ember-data `findRecord` request happens while in
456Fastboot mode, the record will be put into the shoebox cache and returned. When
457subsequent calls are made for that record in the hydrated application, it will
458first check the shoebox data.
459
460#### Solution: Use an Addon (ember-storefront)
461
462Additionally, there is an addon called [ember-data-storefront](https://embermap.github.io/ember-data-storefront/) that can help to alleviate this pain, thanks to its Fastboot mixin: https://embermap.github.io/ember-data-storefront/docs/guides/fastboot.
463
464After installing the addon and applying the mixin, your routes can look like this:
465
466`app/routes/my-route.js`:
467
468```javascript
469import Route from '@ember/routing/route';
470
471export default Route.extend({
472 model() {
473 // first call in a server makes actual ajax request.
474 // second call in a browser serves cached response
475 return this.store.findAll('posts')
476 }
477})
478```
479And they still take advantage of caching in the `shoebox`. No more redundant AJAX for already acquired data. Installation details are available in the addon [documentation](https://embermap.github.io/ember-data-storefront/docs).
480
481### Rehydration
482
483What is Rehydration?
484
485The rehydration feature means that the Glimmer VM can take a DOM tree
486created using Server Side Rendering (SSR) and use it as the starting
487point for the append pass.
488
489See details here:
490
491https://github.com/glimmerjs/glimmer-vm/commit/316805b9175e01698120b9566ec51c88d075026a
492
493In order to utilize rehydration in Ember.js applications we need to ensure that
494both server side renderers (like fastboot) properly encode the DOM they send to
495the browser with the serialization format (introduced in the commit above) AND
496that the browser instantiated Ember.js application knows to use the rehydration
497builder to consume that DOM.
498
499Rehydration is 100% opt-in, if you do not specify the environment flag your
500application will behave as it did before!
501
502We can opt-in to the rehydration filter by setting the following environment
503flag:
504
505```
506EXPERIMENTAL_RENDER_MODE_SERIALIZE=true
507```
508
509This flag is read by Ember CLI Fastboot's dependency; fastboot to alert it to
510produce DOM with the glimmer-vm's serialization element builder. This addon
511(Ember CLI Fastboot) then uses a utility function from glimmer-vm that allows
512it to know whether or not the DOM it received in the browser side was generated
513by the serialization builder. If it was, it tells the Ember.js Application to
514use the rehydration builder and your application will be using rehydration.
515
516Rehydration is only compatible with fastboot > 1.1.4-beta.1, and Ember.js > 3.2.
517
518## Build Hooks for FastBoot
519
520### Disabling incompatible dependencies
521
522There are two places where the inclusion of incompatible JavaScript libraries could
523occur:
524
525#### `app.import` in the application's `ember-cli-build.js`
526
527If your Ember application is importing an incompatible Javascript library,you can use `app.import` with the `using` API.
528
529```js
530app.import('vendor/fastboot-incompatible.js', {
531 using: [
532 {
533 transformation: 'fastbootShim'
534 }
535 ]
536});
537```
538#### `app.import` in an addon's `included` hook
539
540You can include the incompatible Javascript libraries by wrapping them with a `FastBoot` variable check. In the browser, `FastBoot` global variable is not defined.
541
542```js
543var map = require('broccoli-stew').map;
544
545treeForVendor(defaultTree) {
546 var browserVendorLib = new Funnel(...);
547
548 browserVendorLib = map(browserVendorLib, (content) => `if (typeof FastBoot === 'undefined') { ${content} }`);
549
550 return new mergeTrees([defaultTree, browserVendorLib]);
551}
552
553included() {
554 // this file will be loaded in FastBoot but will not be eval'd
555 app.import('vendor/<browserLibName>.js');
556}
557```
558
559*Note*: `ember-cli-fastboot` will no longer provide the `EMBER_CLI_FASTBOOT` environment variable to differentiate browser and fastboot builds with rc builds and FastBoot 1.0 and above.
560
561### Loading additional assets in FastBoot environment
562
563Often addons require to load libraries that are specific to the FastBoot environment and only need to be loaded on the server side. This can include loading
564libraries before or after the vendor file is loaded in the sandbox and/or before or after the app file is loaded in the sandbox. Since the FastBoot manifest defines
565an array of vendor and app files to load in the sandbox, an addon can define additional vendor/app files to load in the sandbox as well.
566
567If your addon requires to load something in the sandbox: you can define the `updateFastBootManifest` hook from your addon (in `index.js`):
568
569```js
570updateFastBootManifest(manifest) {
571 /**
572 * manifest is an object containing:
573 * {
574 * vendorFiles: [<path of the vendor file to load>, ...],
575 * appFiles: [<path of the app file to load>, ...],
576 * htmlFile: '<path of the base page that should be served by FastBoot>'
577 * }
578 */
579
580 // This will load the foo.js before vendor.js is loaded in sandbox
581 manifest.vendorFiles.unshift('<path to foo.js under dist>');
582 // This will load bar.js after app.js is loaded in the sandbox
583 manifest.appFiles.push('<path to bar.js under dist>');
584
585 // remember to return the updated manifest, otherwise your build will fail.
586 return manifest;
587}
588```
589
590*Note*: `process.env.EMBER_CLI_FASTBOOT` will be removed in RC builds and FastBoot 1.0.
591Therefore, if you are relying on this environment variable to import something in the fastboot environment, you should instead use `updateFastBootManifest` hook.
592
593### Conditionally include assets in FastBoot asset
594
595Often your addon may need to conditionally include additional app trees based on ember version. Example, Ember changed an API and in order to have your addon be backward compatible for the API changes you want to include an asset when the ember version is x. For such usecases you could define the `treeForFastBoot` hook in your addon's `index.js` as below:
596
597```js
598treeForFastBoot: function(tree) {
599 let fastbootHtmlBarsTree;
600
601 // check the ember version and conditionally patch the DOM api
602 if (this._getEmberVersion().lt('2.10.0-alpha.1')) {
603 fastbootHtmlBarsTree = this.treeGenerator(path.resolve(__dirname, 'fastboot-app-lt-2-9'));
604 return tree ? new MergeTrees([tree, fastbootHtmlBarsTree]) : fastbootHtmlBarsTree;
605 }
606
607 return tree;
608},
609```
610
611The `tree` is the additional fastboot asset that gets generated and contains the fastboot overrides.
612
613### Providing additional config
614
615By default `ember-cli-fastboot` reads the app's config and provides it in the FastBoot sandbox as a JSON object. For the app in browser, it respects `storeConfigInMeta` and either reads it from the config meta tag or inlines it as JSON object in the `app-name/config/environment` AMD module.
616
617Addons like ember-engines may split the app in different bundles that are loaded asynchronously. Since each bundle is loaded asynchronously, it can have its own configuration as well. In order to allow FastBoot to provide this config in the sandbox, it exposes a `fastbootConfigTree` build hook.
618
619Addons wishing to use this hook simply need to return a unique identifier for the configuration with the configuration.
620
621```js
622fastbootConfigTree() {
623 return {
624 '<engine-name>': {
625 'foo': 'bar'
626 }
627 }
628}
629```
630
631The above configuration will be available in Node via the `FastBoot.config()` function. Therefore, in order to get the above config, the addon/app can call `FastBoot.config('<engine-name>')`.
632
633## Known Limitations
634
635While FastBoot is under active development, there are several major
636restrictions you should be aware of. Only the most brave should even
637consider deploying this to production.
638
639### No `didInsertElement`
640
641Since `didInsertElement` hooks are designed to let your component
642directly manipulate the DOM, and that doesn't make sense on the server
643where there is no DOM, we do not invoke either `didInsertElement` or
644`willInsertElement` hooks. The only component lifecycle hooks called in
645FastBoot are `init`, `didReceiveAttrs`, `didUpdateAttrs`, `willRender`, `didRender`, and `willDestroy`.
646
647### No jQuery
648
649Running most of jQuery requires a full DOM. Most of jQuery will just not be
650supported when running in FastBoot mode. One exception is network code for
651fetching models, which we intended to support, but doesn't work at
652present.
653
654### Prototype extensions
655
656Prototype extensions do not currently work across node "realms." Fastboot
657applications operate in two realms, a normal node environment and a [virtual machine](https://nodejs.org/api/vm.html). Passing objects that originated from the normal realm will not contain the extension methods
658inside of the sandbox environment. For this reason, it's encouraged to [disable prototype extensions](https://guides.emberjs.com/v2.4.0/configuring-ember/disabling-prototype-extensions/).
659
660## Troubleshooting
661
662Because your app is now running in Node.js, not the browser, you'll
663need a new set of tools to diagnose problems when things go wrong. Here
664are some tips and tricks we use for debugging our own apps.
665
666### Verbose Logging
667
668Enable verbose logging by running the FastBoot server with the following
669environment variables set:
670
671```sh
672DEBUG=ember-cli-fastboot:* ember serve
673```
674
675PRs adding or improving logging facilities are very welcome.
676
677### Developer Tools
678
679Thanks to recent improvements in NodeJS it is now possible to get a
680debugging environment that you can connect to with Chrome DevTools (version 55+).
681You can find more information on the new debugging method on Node's
682[official documentation](https://nodejs.org/en/docs/inspector/) but here is a quick-start guide:
683
684First let's start up the FastBoot server with Node in debug mode. One thing
685about debug mode: it makes everything much slower.
686
687```sh
688node --inspect-brk ./node_modules/.bin/ember serve
689```
690
691This starts the FastBoot server in debug mode. Note that the `--inspect-brk` flag will cause your
692app to start paused to give you a chance to open the debugger.
693
694Once you see the output `Debugger listening on ws://127.0.0.1:<port>/<guid>`, open Chrome
695and visit [chrome://inspect](chrome://inspect). Once it loads you should see an Ember target
696with a link "inspect" underneath. Click inspect and it should pop up a Chrome inspector
697window and you can click the ▶︎ icon to let FastBoot continue loading.
698
699Assuming your app loads without an exception, after a few seconds you
700will see a message that FastBoot is listening on port 3000. Once you see
701that, you can open a connection; any exceptions should be logged in the
702console, and you can use the tools you'd expect such as `console.log`,
703`debugger` statements, etc.
704
705#### Note Regarding Node Versions
706
707The above method only started working for the v8.x track of Node after version v8.4.0,
708which has a fix to [this issue](https://github.com/nodejs/node/issues/7593). If you
709are using any versions between v8.0 and v8.4 we would recommend upgrading to at least v8.4.0
710
711For any versions prior to 6.4 the previous version of this documentation is still valid.
712Please follow those instructions [here](https://github.com/ember-fastboot/ember-cli-fastboot/tree/v1.0.4#developer-tools)
713
714## Tests
715
716Run the automated tests by running `npm test`.
717
718Note that the integration tests create new Ember applications via `ember
719new` and thus have to run an `npm install`, which can take several
720minutes, particularly on slow connections.
721
722To speed up test runs you can run `npm run test:precook` to "precook" a
723`node_modules` directory that will be reused across test runs.
724
725### Debugging Integration Tests
726
727Run the tests with the `DEBUG` environment variable set to
728`fastboot-test` to see verbose debugging output.
729
730```sh
731DEBUG=fastboot-test npm test
732```
733
734### Questions
735
736Reach out to us in Ember community slack in the `#-fastboot` channel.