diff --git a/packages/subapp-server/src/index-page.jsx b/packages/subapp-server/src/index-page.jsx index 54e934c9d..32e3cae36 100644 --- a/packages/subapp-server/src/index-page.jsx +++ b/packages/subapp-server/src/index-page.jsx @@ -1,6 +1,7 @@ /* @jsx createElement */ import { IndexPage, createElement, Token, Require } from "../template"; +import { ReserveSpot } from "subapp-web"; const RenderSubApps = (props, context) => { const { routeOptions } = context.user; @@ -46,6 +47,7 @@ const Template = ( + diff --git a/packages/subapp-web/lib/ReserveSpot.js b/packages/subapp-web/lib/ReserveSpot.js new file mode 100644 index 000000000..200deaccf --- /dev/null +++ b/packages/subapp-web/lib/ReserveSpot.js @@ -0,0 +1,10 @@ +"use strict"; + +const ReserveSpot = (props, context) => { + if (props.saveId) { + const spot = context.output.reserve(); + context.user[props.saveId] = spot; + } +}; + +module.exports = ReserveSpot; diff --git a/packages/subapp-web/lib/index.js b/packages/subapp-web/lib/index.js index e64b93717..daf23cb99 100644 --- a/packages/subapp-web/lib/index.js +++ b/packages/subapp-web/lib/index.js @@ -8,6 +8,7 @@ const { default: makeSubAppSpec } = require("../dist/node/make-subapp-spec"); const { setupFramework } = require("./util"); const lazyLoadSubApp = () => {}; +const ReserveSpot = require("./ReserveSpot"); module.exports = { // isomorphic functions @@ -29,5 +30,6 @@ module.exports = { init: require("./init"), load: require("./load"), start: require("./start"), + ReserveSpot, xarc: { IS_BROWSER: false, HAS_WINDOW: false } // no xarc client lib on the server }; diff --git a/packages/subapp-web/lib/load.js b/packages/subapp-web/lib/load.js index 37e7fc8c3..312bfdaf8 100644 --- a/packages/subapp-web/lib/load.js +++ b/packages/subapp-web/lib/load.js @@ -145,6 +145,7 @@ Response: ${err || body}` const cdnJsBundles = util.getCdnJsBundles(assets, setupContext.routeOptions); const bundles = entryPoints.filter(ep => !includedBundles[ep]); + const headSplits = []; const splits = bundles .map(ep => { if (!inlineSubAppJs && !includedBundles[entryName]) { @@ -153,16 +154,24 @@ Response: ${err || body}` cdnJsBundles[ep] && [] .concat(cdnJsBundles[ep]) - .map(jsBundle => { + .reduce((a, jsBundle) => { const ext = Path.extname(jsBundle); if (ext === ".js") { - return ``; + if (context.user.headEntries) { + headSplits.push(``); + } + a.push(``); } else if (ext === ".css") { - return ``; + if (context.user.headEntries) { + headSplits.push(``); + } else { + a.push(``); + } } else { - return ``; + a.push(``); } - }) + return a; + }, []) .join("\n") ); } @@ -179,7 +188,7 @@ Response: ${err || body}` } } - return { bundles, scripts: splits.join("\n") }; + return { bundles, scripts: splits.join("\n"), preLoads: headSplits.join("\n") }; }; const loadSubApp = () => { @@ -210,6 +219,8 @@ Response: ${err || body}` const { request } = context.user; + context.user.numOfSubapps = context.user.numOfSubapps || 0; + if (request.app.webpackDev && subAppLoadTime < request.app.webpackDev.compileTime) { subAppLoadTime = request.app.webpackDev.compileTime; loadSubApp(); @@ -312,6 +323,11 @@ ${stack}`, } outputSpot.close(); + context.user.numOfSubapps--; + if (context.user.numOfSubapps === 0 && context.user.headEntries) { + context.user.headEntries.close(); + context.user.headEntries = undefined; + } }; ssrInfo.done = () => { @@ -329,13 +345,19 @@ ${stack}`, ssrGroups }; - const { bundles, scripts } = await prepareSubAppSplitBundles(context); + context.user.numOfSubapps++; + const { bundles, scripts, preLoads } = await prepareSubAppSplitBundles(context); outputSpot.add(`${comment}`); if (bundles.length > 0) { outputSpot.add(`${scripts} `); } + if (preLoads.length > 0) { + context.user.headEntries.add("\n"); + context.user.headEntries.add(preLoads); + context.user.headEntries.add("\n"); + } if (props.serverSideRendering) { const lib = (ssrInfo.lib = util.getFramework(ref)); diff --git a/packages/subapp-web/package.json b/packages/subapp-web/package.json index 091663317..a738121b5 100644 --- a/packages/subapp-web/package.json +++ b/packages/subapp-web/package.json @@ -48,7 +48,8 @@ "jsdom": "^15.2.1", "mock-require": "^1.3.0", "run-verify": "^1.2.2", - "subapp-pkg-util": "../subapp-pkg-util" + "subapp-pkg-util": "../subapp-pkg-util", + "electrode-react-webapp": "^3.8.9" }, "fyn": { "dependencies": { diff --git a/packages/subapp-web/test/spec/load.spec.js b/packages/subapp-web/test/spec/load.spec.js index e69de29bb..f60ab1db9 100644 --- a/packages/subapp-web/test/spec/load.spec.js +++ b/packages/subapp-web/test/spec/load.spec.js @@ -0,0 +1,83 @@ +"use strict"; + +const { load } = require("../../lib"); +const utils = require("../../lib/util"); +const Path = require("path"); +const reserveSpot = require("../../lib/ReserveSpot"); +const RenderOutput = require("electrode-react-webapp/lib/render-output"); + +describe("load", function () { + const statsPath = Path.join(__dirname, "../data/dev-stats.json"); + const { assets } = utils.loadAssetsFromStats(statsPath); + let context; + let setUpContext; + let props; + + beforeEach(() => { + setUpContext = { + routeOptions: { + cdn: {}, + __internals: { + assets + } + } + }; + context = { + user: { + request: { app: {} }, + assets, + includedBundles: {} + }, + transform: x => x + }; + props = { + props: { + serverSideRendering: false + } + }; + utils.resetCdn(); + }); + + afterEach(() => { + delete process.env.NODE_ENV; + delete process.env.APP_SRC_DIR; + }); + + it("should load bundles for the subapp", done => { + process.env.APP_SRC_DIR = "test/subapps"; + const loadToken = load(setUpContext, { props: { name: "mainbody" } }); + + context.send = results => { + expect(results).to.not.be.empty; + expect(context.user.includedBundles).to.include({ mainbody: true }); + expect(results).to.include(``); + expect(results).to.include( + `` + ); + expect(context.user.headEntries).to.not.be.ok; + done(); + }; + context.output = new RenderOutput(context); + + loadToken.process(context, props); + context.output.close(); + }); + + it("should load preload tags for scripts", done => { + process.env.APP_SRC_DIR = "test/subapps"; + const loadToken = load(setUpContext, { props: { name: "mainbody" } }); + + context.send = results => { + expect(results).to.not.be.empty; + expect(results).to.include(``); + expect(results).to.include(``); + expect(context.user.includedBundles).to.include({ mainbody: true }); + done(); + }; + context.output = new RenderOutput(context); + reserveSpot({ saveId: "headEntries" }, context); + + loadToken.process(context, props); + context.output.close(); + }); +}); diff --git a/packages/subapp-web/test/subapps/subapp1/src/index.js b/packages/subapp-web/test/subapps/subapp1/src/index.js new file mode 100644 index 000000000..aa87052be --- /dev/null +++ b/packages/subapp-web/test/subapps/subapp1/src/index.js @@ -0,0 +1,3 @@ +module.exports = { + foo: () => 123 +}; diff --git a/packages/subapp-web/test/subapps/subapp1/src/server.js b/packages/subapp-web/test/subapps/subapp1/src/server.js new file mode 100644 index 000000000..2c9e83352 --- /dev/null +++ b/packages/subapp-web/test/subapps/subapp1/src/server.js @@ -0,0 +1,3 @@ +module.exports = { + name: "mainbody" +}; diff --git a/packages/subapp-web/test/subapps/subapp1/src/subapp-conf.js b/packages/subapp-web/test/subapps/subapp1/src/subapp-conf.js new file mode 100644 index 000000000..3b724396d --- /dev/null +++ b/packages/subapp-web/test/subapps/subapp1/src/subapp-conf.js @@ -0,0 +1,6 @@ +module.exports = { + name: "mainbody", + subAppDir: "Subapp1/src", + entry: "index.js", + serverEntry: "server.js" +}; diff --git a/packages/subapp-web/test/subapps/subapp1/src/subapp-manifest.js b/packages/subapp-web/test/subapps/subapp1/src/subapp-manifest.js new file mode 100644 index 000000000..926d082e9 --- /dev/null +++ b/packages/subapp-web/test/subapps/subapp1/src/subapp-manifest.js @@ -0,0 +1,6 @@ +module.exports = { + name: "mainbody", + subAppDir: "subapp1/src", + entry: "index.js", + serverEntry: "server.js" +};