22
33const fs = require ( 'fs' ) ;
44const path = require ( 'path' ) ;
5- const RSVP = require ( 'rsvp ' ) ;
5+ const chalk = require ( 'chalk ' ) ;
66
77const najax = require ( 'najax' ) ;
88const SimpleDOM = require ( 'simple-dom' ) ;
@@ -11,6 +11,7 @@ const debug = require('debug')('fastboot:ember-app');
1111
1212const FastBootInfo = require ( './fastboot-info' ) ;
1313const Result = require ( './result' ) ;
14+ const FastBootSchemaVersions = require ( './fastboot-schema-versions' ) ;
1415
1516/**
1617 * @private
@@ -58,7 +59,6 @@ class EmberApp {
5859 * @param {Object } [sandboxGlobals={}] any additional variables to expose in the sandbox or override existing in the sandbox
5960 */
6061 buildSandbox ( distPath , sandboxClass , sandboxGlobals ) {
61- let Sandbox = sandboxClass || require ( './vm-sandbox' ) ;
6262 let sandboxRequire = this . buildWhitelistedRequire ( this . moduleWhitelist , distPath ) ;
6363 let config = this . appConfig ;
6464 function appConfig ( ) {
@@ -67,21 +67,16 @@ class EmberApp {
6767
6868 // add any additional user provided variables or override the default globals in the sandbox
6969 let globals = {
70- najax : najax ,
70+ najax,
7171 FastBoot : {
7272 require : sandboxRequire ,
7373 config : appConfig
7474 }
7575 } ;
76- for ( let key in sandboxGlobals ) {
77- if ( sandboxGlobals . hasOwnProperty ( key ) ) {
78- globals [ key ] = sandboxGlobals [ key ] ;
79- }
80- }
8176
82- return new Sandbox ( {
83- globals : globals
84- } ) ;
77+ globals = Object . assign ( globals , sandboxGlobals ) ;
78+
79+ return new sandboxClass ( { globals } ) ;
8580 }
8681
8782 /**
@@ -220,6 +215,10 @@ class EmberApp {
220215 * the app instance and then visits the given route and destroys the app instance
221216 * when the route is finished its render cycle.
222217 *
218+ * Ember apps can manually defer rendering in FastBoot mode if they're waiting
219+ * on something async the router doesn't know about. This function fetches
220+ * that promise for deferred rendering from the app.
221+ *
223222 * @param {string } path the URL path to render, like `/photos/1`
224223 * @param {Object } fastbootInfo An object holding per request info
225224 * @param {Object } bootOptions An object containing the boot options that are used by
@@ -238,18 +237,15 @@ class EmberApp {
238237
239238 return instance . boot ( bootOptions ) ;
240239 } )
241- . then ( ( ) => result . instanceBooted = true )
242240 . then ( ( ) => instance . visit ( path , bootOptions ) )
243- . then ( ( ) => waitForApp ( instance ) )
244- . then ( ( ) => {
245- return instance ;
246- } ) ;
241+ . then ( ( ) => fastbootInfo . deferredPromise )
242+ . then ( ( ) => instance ) ;
247243 }
248244
249245 /**
250246 * Creates a new application instance and renders the instance at a specific
251247 * URL, returning a promise that resolves to a {@link Result}. The `Result`
252- * givesg you access to the rendered HTML as well as metadata about the
248+ * gives you access to the rendered HTML as well as metadata about the
253249 * request such as the HTTP status code.
254250 *
255251 * If this call to `visit()` is to service an incoming HTTP request, you may
@@ -273,7 +269,7 @@ class EmberApp {
273269 let res = options . response ;
274270 let html = options . html || this . html ;
275271 let disableShoebox = options . disableShoebox || false ;
276- let destroyAppInstanceInMs = options . destroyAppInstanceInMs ;
272+ let destroyAppInstanceInMs = parseInt ( options . destroyAppInstanceInMs , 10 ) ;
277273
278274 let shouldRender = ( options . shouldRender !== undefined ) ? options . shouldRender : true ;
279275 let bootOptions = buildBootOptions ( shouldRender ) ;
@@ -292,23 +288,17 @@ class EmberApp {
292288 } ) ;
293289
294290 let destroyAppInstanceTimer ;
295- if ( parseInt ( destroyAppInstanceInMs , 10 ) > 0 ) {
291+ if ( destroyAppInstanceInMs > 0 ) {
296292 // start a timer to destroy the appInstance forcefully in the given ms.
297293 // This is a failure mechanism so that node process doesn't get wedged if the `visit` never completes.
298294 destroyAppInstanceTimer = setTimeout ( function ( ) {
299- if ( instance && ! result . instanceDestroyed ) {
300- result . instanceDestroyed = true ;
295+ if ( result . _destroyAppInstance ( ) ) {
301296 result . error = new Error ( 'App instance was forcefully destroyed in ' + destroyAppInstanceInMs + 'ms' ) ;
302- instance . destroy ( ) ;
303297 }
304298 } , destroyAppInstanceInMs ) ;
305299 }
306300
307- let instance ;
308301 return this . visitRoute ( path , fastbootInfo , bootOptions , result )
309- . then ( appInstance => {
310- instance = appInstance ;
311- } )
312302 . then ( ( ) => {
313303 if ( ! disableShoebox ) {
314304 // if shoebox is not disabled, then create the shoebox and send API data
@@ -318,10 +308,7 @@ class EmberApp {
318308 . catch ( error => result . error = error )
319309 . then ( ( ) => result . _finalize ( ) )
320310 . finally ( ( ) => {
321- if ( instance && ! result . instanceDestroyed ) {
322- result . instanceDestroyed = true ;
323- instance . destroy ( ) ;
324-
311+ if ( result . _destroyAppInstance ( ) ) {
325312 if ( destroyAppInstanceTimer ) {
326313 clearTimeout ( destroyAppInstanceTimer ) ;
327314 }
@@ -344,48 +331,61 @@ class EmberApp {
344331 }
345332
346333 let manifest ;
334+ let schemaVersion ;
347335 let pkg ;
348336
349337 try {
350338 pkg = JSON . parse ( file ) ;
351339 manifest = pkg . fastboot . manifest ;
340+ schemaVersion = pkg . fastboot . schemaVersion ;
352341 } catch ( e ) {
353342 throw new Error ( `${ pkgPath } was malformed or did not contain a manifest. Ensure that you have a compatible version of ember-cli-fastboot.` ) ;
354343 }
355344
356- var appFiles = [ ] ;
357- if ( manifest . appFiles ) {
358- debug ( "reading array of app file paths from manifest" ) ;
359- manifest . appFiles . forEach ( function ( appFile ) {
360- appFiles . push ( path . join ( distPath , appFile ) ) ;
361- } ) ;
362- } else if ( manifest . appFile ) {
363- // TODO : remove after Fastboot 1.0
364- debug ( "reading app file path from manifest" ) ;
365- appFiles = [ path . join ( distPath , manifest . appFile ) ] ;
345+ const currentSchemaVersion = FastBootSchemaVersions . latest ;
346+ // set schema version to 1 if not defined
347+ schemaVersion = schemaVersion || FastBootSchemaVersions . base ;
348+ debug ( 'Current schemaVersion from `ember-cli-fastboot` is %s while latest schema version is %s' , ( schemaVersion , currentSchemaVersion ) ) ;
349+
350+ if ( schemaVersion > currentSchemaVersion ) {
351+ let errorMsg = chalk . bold . red ( 'An incompatible version between `ember-cli-fastboot` and `fastboot` was found. Please update the version of fastboot library that is compatible with ember-cli-fastboot.' ) ;
352+ throw new Error ( errorMsg ) ;
366353 }
367354
368- var vendorFiles = [ ] ;
369- if ( manifest . vendorFiles ) {
370- debug ( "reading array of vendor file paths from manifest" ) ;
371- manifest . vendorFiles . forEach ( function ( vendorFile ) {
372- vendorFiles . push ( path . join ( distPath , vendorFile ) ) ;
373- } ) ;
374- } else if ( manifest . vendorFile ) {
375- // TODO : remove after Fastboot 1.0
376- debug ( "reading vendor file path from manifest" ) ;
377- vendorFiles = [ path . join ( distPath , manifest . vendorFile ) ] ;
355+ if ( schemaVersion < FastBootSchemaVersions . manifestFileArrays ) {
356+ // transform app and vendor file to array of files
357+ manifest = this . transformManifestFiles ( manifest ) ;
378358 }
379359
360+ debug ( "reading array of app file paths from manifest" ) ;
361+ var appFiles = manifest . appFiles . map ( function ( appFile ) {
362+ return path . join ( distPath , appFile ) ;
363+ } ) ;
364+
365+ debug ( "reading array of vendor file paths from manifest" ) ;
366+ var vendorFiles = manifest . vendorFiles . map ( function ( vendorFile ) {
367+ return path . join ( distPath , vendorFile ) ;
368+ } ) ;
369+
380370 return {
381- appFiles : appFiles ,
371+ appFiles : appFiles ,
382372 vendorFiles : vendorFiles ,
383373 htmlFile : path . join ( distPath , manifest . htmlFile ) ,
384374 moduleWhitelist : pkg . fastboot . moduleWhitelist ,
385375 hostWhitelist : pkg . fastboot . hostWhitelist ,
386376 appConfig : pkg . fastboot . appConfig
387377 } ;
388378 }
379+
380+ /**
381+ * Function to transform the manifest app and vendor files to an array.
382+ */
383+ transformManifestFiles ( manifest ) {
384+ manifest . appFiles = [ manifest . appFile ] ;
385+ manifest . vendorFiles = [ manifest . vendorFile ] ;
386+
387+ return manifest ;
388+ }
389389}
390390
391391/*
@@ -404,19 +404,6 @@ function buildBootOptions(shouldRender) {
404404 } ;
405405}
406406
407- /*
408- * Ember apps can manually defer rendering in FastBoot mode if they're waiting
409- * on something async the router doesn't know about. This function fetches
410- * that promise for deferred rendering from the app.
411- */
412- function waitForApp ( instance ) {
413- let fastbootInfo = instance . lookup ( 'info:-fastboot' ) ;
414-
415- return fastbootInfo . deferredPromise . then ( function ( ) {
416- return instance ;
417- } ) ;
418- }
419-
420407/*
421408 * Writes the shoebox into the DOM for the browser rendered app to consume.
422409 * Uses a script tag with custom type so that the browser will treat as plain
@@ -425,13 +412,14 @@ function waitForApp(instance) {
425412 * parse the specific item at the time it is needed instead of everything
426413 * all at once.
427414 */
415+ const hasOwnProperty = Object . prototype . hasOwnProperty ; // jshint ignore:line
416+
428417function createShoebox ( doc , fastbootInfo ) {
429418 let shoebox = fastbootInfo . shoebox ;
430- if ( ! shoebox ) { return RSVP . resolve ( ) ; }
419+ if ( ! shoebox ) { return ; }
431420
432421 for ( let key in shoebox ) {
433- if ( ! shoebox . hasOwnProperty ( key ) ) { continue ; }
434-
422+ if ( ! hasOwnProperty . call ( shoebox , key ) ) { continue ; } // TODO: remove this later #144, ember-fastboot/ember-cli-fastboot/pull/417
435423 let value = shoebox [ key ] ;
436424 let textValue = JSON . stringify ( value ) ;
437425 textValue = escapeJSONString ( textValue ) ;
@@ -444,8 +432,6 @@ function createShoebox(doc, fastbootInfo) {
444432 scriptEl . appendChild ( scriptText ) ;
445433 doc . body . appendChild ( scriptEl ) ;
446434 }
447-
448- return RSVP . resolve ( ) ;
449435}
450436
451437const JSON_ESCAPE = {
0 commit comments