From aebd9afc4e86a77876a07ba35673af0ea23d5dc1 Mon Sep 17 00:00:00 2001 From: Joel Chen Date: Tue, 28 May 2019 16:34:18 -0700 Subject: [PATCH] subapp - handle load JS bundles from CDN mapped URLs (#1258) --- packages/subapp-web/lib/init.js | 33 ++++++++------ packages/subapp-web/lib/load.js | 15 ++++-- packages/subapp-web/lib/util.js | 81 ++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 20 deletions(-) diff --git a/packages/subapp-web/lib/init.js b/packages/subapp-web/lib/init.js index e4d63ea20..3413c28bc 100644 --- a/packages/subapp-web/lib/init.js +++ b/packages/subapp-web/lib/init.js @@ -17,30 +17,35 @@ module.exports = function setup(setupContext) { const routeData = setupContext.routeOptions.__internals; - const vendorAssets = util.getVendorBundles(routeData.assets); - const bundleBase = util.getBundleBase(setupContext.routeOptions); - const bundleNames = vendorAssets.map(a => a.chunkNames[0]); + const cdnJsBundles = util.getCdnJsBundles( + routeData.assets.byChunkName, + setupContext.routeOptions + ); + let vendorBundleLoadJs = ""; let commonScript = ""; + + const vendorAssets = util.getVendorBundles(routeData.assets); + const bundleNames = vendorAssets.map(a => a.chunkNames[0]); + if (vendorAssets.length > 0) { vendorBundleLoadJs = `markBundlesLoaded(${JSON.stringify(bundleNames)});`; commonScript = vendorAssets - .map(a => ``) + .map(a => { + // map all chunk names that has a URL mapped and load them with script tags + return a.chunkNames + .map(x => cdnJsBundles[x] && ``) + .filter(x => x) + .join(""); + }) .join("\n"); } - const jsBundleByChunkName = Object.entries(routeData.assets.byChunkName).reduce( - (a, [name, bundle]) => { - a[name] = Array.isArray(bundle) ? bundle.find(x => x.endsWith(".js")) : bundle; - return a; - }, - {} - ); - const bundleAssets = { - byChunkName: jsBundleByChunkName, - basePath: bundleBase + byChunkName: cdnJsBundles, + basePath: "" }; + const webSubAppJs = `\n diff --git a/packages/subapp-web/lib/load.js b/packages/subapp-web/lib/load.js index 9e98712fa..9e114dc61 100644 --- a/packages/subapp-web/lib/load.js +++ b/packages/subapp-web/lib/load.js @@ -35,7 +35,6 @@ module.exports = function setup(setupContext, token) { const name = props.name; const routeData = setupContext.routeOptions.__internals; const bundleAsset = util.getSubAppBundle(name, routeData.assets); - const bundleJs = bundleAsset.name; const async = props.async ? " async" : ""; const defer = props.defer ? " defer" : ""; const bundleBase = util.getBundleBase(setupContext.routeOptions); @@ -43,7 +42,7 @@ module.exports = function setup(setupContext, token) { const retrieveDevServerBundle = async () => { return new Promise((resolve, reject) => { - request(`${bundleBase}${bundleJs}`, (err, resp, body) => { + request(`${bundleBase}${bundleAsset.name}`, (err, resp, body) => { if (err) { reject(err); } else { @@ -56,13 +55,21 @@ module.exports = function setup(setupContext, token) { let bundleScript; const webpackDev = process.env.WEBPACK_DEV === "true"; + const cdnJsBundles = util.getCdnJsBundles( + routeData.assets.byChunkName, + setupContext.routeOptions + ); + if (props.inlineScript === "always" || (props.inlineScript === true && !webpackDev)) { if (!webpackDev) { - const src = Fs.readFileSync(Path.resolve("dist/js", bundleJs)).toString(); + const src = Fs.readFileSync(Path.resolve("dist/js", bundleAsset.name)).toString(); bundleScript = ``; } } else { - bundleScript = ``; + bundleScript = bundleAsset.chunkNames + .map(x => cdnJsBundles[x] && ``) + .filter(x => x) + .join(""); } let SubApp; diff --git a/packages/subapp-web/lib/util.js b/packages/subapp-web/lib/util.js index 6f6da4ad7..830d407b5 100644 --- a/packages/subapp-web/lib/util.js +++ b/packages/subapp-web/lib/util.js @@ -1,10 +1,14 @@ "use strict"; -/* eslint-disable global-require */ +/* eslint-disable global-require, max-statements */ const assert = require("assert"); +const Path = require("path"); -module.exports = { +let CDN_ASSETS; +let CDN_JS_BUNDLES; + +const utils = { getVendorBundles: assets => { const chunkNames = Object.keys(assets.byChunkName) .filter(x => x.startsWith("vendors")) @@ -34,5 +38,78 @@ module.exports = { } else { return routeData.devBundleBase; } + }, + + /* + From an object of bundles: + + { + bundleName: "bundle-file.js" + } + + map with a CDN assets object: + + { + "/dist/js/bundle-file.js": "/cdn.com/hash-12345.js" + } + + to: + + { + bundleName: "/cdn.com/hash-12345.js" + } + + If a bundleName doesn't have a CDN map, then its URL is created by prepending basePath: + + { + bundleName: "${basePath}bundle-file.js" + } + */ + + mapCdnAssets(bundlesByName, basePath = "", assetsFile = "config/assets.json") { + if (!CDN_ASSETS) { + const assetsFp = Path.resolve(assetsFile); + try { + CDN_ASSETS = require(assetsFp); + } catch (err) { + CDN_ASSETS = {}; + } + } + + const cdnBundles = {}; + const bundleNames = Object.keys(bundlesByName); + + for (const name of bundleNames) { + const bundleFile = bundlesByName[name]; + for (const mapName in CDN_ASSETS) { + if (mapName.endsWith(bundleFile)) { + cdnBundles[name] = CDN_ASSETS[mapName]; + break; + } + } + + if (!cdnBundles[name]) { + cdnBundles[name] = basePath.concat(bundlesByName[name]); + } + } + + return cdnBundles; + }, + + getCdnJsBundles(byChunkNameAssets, routeOptions) { + if (CDN_JS_BUNDLES) { + return CDN_JS_BUNDLES; + } + + const jsBundleByChunkName = Object.entries(byChunkNameAssets).reduce((a, [name, bundle]) => { + a[name] = Array.isArray(bundle) ? bundle.find(x => x.endsWith(".js")) : bundle; + return a; + }, {}); + + const bundleBase = utils.getBundleBase(routeOptions); + + return (CDN_JS_BUNDLES = utils.mapCdnAssets(jsBundleByChunkName, bundleBase)); } }; + +module.exports = utils;