diff --git a/packages/subapp-server/lib/setup-hapi-routes.js b/packages/subapp-server/lib/setup-hapi-routes.js index 3916eb3af..299743c93 100644 --- a/packages/subapp-server/lib/setup-hapi-routes.js +++ b/packages/subapp-server/lib/setup-hapi-routes.js @@ -106,17 +106,25 @@ function setupRouteRender({ subAppsByPath, srcDir, routeOptions }) { // load subapps for the route if (routeOptions.subApps) { routeOptions.__internals.subApps = [].concat(routeOptions.subApps).map(x => { - let options = {}; + let options; if (Array.isArray(x)) { options = x[1]; x = x[0]; } // absolute: use as path + // module: resolve module path // else: assume dir under srcDir - // TBD: handle it being a module + if (!x.startsWith(".") && !x.startsWith("/")) { + const subAppPath = optionalRequire.resolve(x); + if (subAppPath) { + const { manifest, subAppOptions } = require(x); + x = manifest ? Path.dirname(subAppPath) : x; + options = options || subAppOptions; + } + } return { subapp: subAppsByPath[Path.isAbsolute(x) ? x : Path.resolve(srcDir, x)], - options + options: options || {} }; }); } diff --git a/packages/subapp-util/lib/index.js b/packages/subapp-util/lib/index.js index 098c19581..dc0b6574e 100644 --- a/packages/subapp-util/lib/index.js +++ b/packages/subapp-util/lib/index.js @@ -5,6 +5,7 @@ const Url = require("url"); const Path = require("path"); const assert = require("assert"); +const _ = require("lodash"); const optionalRequire = require("optional-require")(require); const scanDir = require("filter-scan-dir"); const appMode = optionalRequire(Path.resolve(".prod/.app-mode.json"), { default: {} }); @@ -29,6 +30,18 @@ function removeExt(f) { return ix > 0 ? f.substring(0, ix) : f; } +const validateModuleManifest = manifest => { + const entries = _.pick(manifest, ["entry", "serverEntry", "reducers"]); + Object.keys(entries).forEach(x => { + if (!Path.isAbsolute(entries[x])) { + throw new Error( + `Could not resolve subapp ${x} "${entries[x]}".\ + Please provide absolute path to the ${x} in subapp manifest` + ); + } + }); +}; + // // scan a subapp's directory for additional things: // - server side entry in server.js, server-{name}.js, server-{verbatimName}.js @@ -122,11 +135,12 @@ function scanSubAppsFromDir(srcDir, maxLevel = Infinity) { const fullSrcDir = Path.resolve(srcDir); const subApps = { [MAP_BY_PATH_SYM]: {} }; - const { maniFiles = [], files = [] } = scanDir.sync({ + const { maniFiles = [], files = [], modFiles = [] } = scanDir.sync({ dir: srcDir, grouping: true, maxLevel, filter: (f, p, ex) => { + if (ex.noExt === "subapps-modules") return "modFiles"; if (ex.noExt === "subapp-manifest") return "maniFiles"; return f.startsWith("subapp"); }, @@ -162,7 +176,25 @@ function scanSubAppsFromDir(srcDir, maxLevel = Infinity) { return null; }); - const errors = [].concat(errors1, errors2).filter(x => x); + // process subapps modules + let errors3 = []; + if (modFiles.length > 0) { + const manifests = es6Require(Path.join(fullSrcDir, modFiles[0])); + errors3 = Object.keys(manifests).map(modName => { + try { + const manifest = manifests[modName]; + validateModuleManifest(manifest); + const modFullDir = Path.dirname(require.resolve(modName)); + const subapp = Object.assign({ subAppDir: modName, module: true }, manifest); + subApps[manifest.name] = subApps[MAP_BY_PATH_SYM][modFullDir] = subapp; + } catch (error) { + return error; + } + return null; + }); + } + + const errors = [].concat(errors1, errors2, errors3).filter(x => x); if (errors.length > 0) { console.error("Loading SubApps failed"); errors.forEach(err => { @@ -242,7 +274,7 @@ function loadSubAppByName(name) { const container = getSubAppContainer(); const subAppDir = manifest.subAppDir; // load subapp's entry - xrequire(Path.resolve(appSrcDir(), subAppDir, manifest.entry)); + xrequire(manifest.module ? manifest.entry : Path.resolve(appSrcDir(), subAppDir, manifest.entry)); // if subapp did not register itself then register it if (!container[name]) { @@ -258,13 +290,17 @@ function loadSubAppServerByName(name) { const { subAppDir, serverEntry } = manifest; if (serverEntry) { - return es6Require(Path.resolve(appSrcDir(), subAppDir, serverEntry)); + return es6Require( + manifest.module ? serverEntry : Path.resolve(appSrcDir(), subAppDir, serverEntry) + ); } else if (serverEntry === false) { return {}; } // generate a server from subapp's main file - const subapp = es6Require(Path.resolve(appSrcDir(), subAppDir, manifest.entry)); + const subapp = es6Require( + manifest.module ? manifest.entry : Path.resolve(appSrcDir(), subAppDir, manifest.entry) + ); return { StartComponent: subapp.Component @@ -275,11 +311,15 @@ function refreshSubAppByName(name) { const manifest = subAppManifest()[name]; const { subAppDir } = manifest; - const entryFullPath = xrequire.resolve(Path.resolve(appSrcDir(), subAppDir, manifest.entry)); + const entryFullPath = xrequire.resolve( + manifest.module ? manifest.entry : Path.resolve(appSrcDir(), subAppDir, manifest.entry) + ); if (!xrequire.cache[entryFullPath] && manifest.serverEntry) { // also reload server side module const serverEntryFullPath = xrequire.resolve( - Path.resolve(appSrcDir(), subAppDir, manifest.serverEntry) + manifest.module + ? manifest.serverEntry + : Path.resolve(appSrcDir(), subAppDir, manifest.serverEntry) ); console.log("reloading server side subapp", name, serverEntryFullPath); delete xrequire.cache[serverEntryFullPath]; diff --git a/packages/xarc-webpack/lib/partials/entry.js b/packages/xarc-webpack/lib/partials/entry.js index b3eb2760c..84d7d784b 100644 --- a/packages/xarc-webpack/lib/partials/entry.js +++ b/packages/xarc-webpack/lib/partials/entry.js @@ -52,21 +52,24 @@ function makeEntryPartial() { } function genSubAppHmrEntry(hmrDir, isDev, manifest) { - let subAppReq = `${manifest.subAppDir}/${manifest.entry}`; + let subAppReq = manifest.module ? manifest.entry : `${manifest.subAppDir}/${manifest.entry}`; + // subapp has built-in code to handle HMR accept // or not running in webpack dev mode // => do not generate HMR accept code if (manifest.hmrSelfAccept || !isDev) { - return `./${subAppReq}`; + return manifest.module ? subAppReq : `./${subAppReq}`; } const hmrEntry = `hmr-${manifest.subAppDir.replace(/[\/\\]/g, "-")}.js`; - subAppReq = `../${subAppReq}`; + subAppReq = manifest.module ? subAppReq : `../${subAppReq}`; let reducerHmrCode = ""; if (manifest.reducers) { - const subAppReducers = `../${manifest.subAppDir}/reducers`; + const subAppReducers = manifest.module + ? manifest.reducers + : `../${manifest.subAppDir}/reducers`; reducerHmrCode = ` import { getReduxCreateStore } from "subapp-redux"; import reducers from "${subAppReducers}"; diff --git a/samples/demo-tree-shaking/package.json b/samples/demo-tree-shaking/package.json index 3d65dc2ce..2b9b1ccc3 100644 --- a/samples/demo-tree-shaking/package.json +++ b/samples/demo-tree-shaking/package.json @@ -55,6 +55,7 @@ "lodash": "^4.17.10", "milligram": "^1.3.0", "react-intl": "^3.11.0", + "intl-format-cache": "4.2.46", "react-notify-toast": "^0.4.1", "react-router-config": "^5.1.1" }, diff --git a/samples/react-jest-app/package.json b/samples/react-jest-app/package.json index 70fd71e4b..a85adc8f4 100644 --- a/samples/react-jest-app/package.json +++ b/samples/react-jest-app/package.json @@ -62,7 +62,7 @@ "@xarc/app-dev": "^5.3.4", "electrode-archetype-opt-critical-css": "^1.0.3", "electrode-archetype-opt-eslint": "^1.0.3", - "electrode-archetype-opt-jest": "^25.0.0", + "electrode-archetype-opt-jest": "^26.0.0", "electrode-archetype-opt-less": "^1.0.2", "electrode-archetype-opt-mocha": "^1.0.3", "electrode-archetype-opt-phantomjs": "^1.0.2", @@ -80,8 +80,7 @@ "electrode-auto-ssr": "../../packages/electrode-auto-ssr" }, "devDependencies": { - "@xarc/app-dev": "../../packages/xarc-app-dev", - "electrode-archetype-opt-jest": "../../packages/electrode-archetype-opt-jest" + "@xarc/app-dev": "../../packages/xarc-app-dev" } }, "optionalDependencies": {}