Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support mock CDN in dev-proxy for mock prod mode #1627

Merged
merged 2 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions packages/xarc-app-dev/lib/dev-admin/cdn-mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use strict";

/* eslint-disable no-console, no-magic-numbers, prefer-template */

/*
* search all files under dist and generate a config/assets.json file for mocking CDN
*/

const Path = require("path");
const filterScanDir = require("filter-scan-dir");
const Url = require("url");
const Fs = require("fs");
const chokidar = require("chokidar");
const mime = require("mime");
const LOADED_ASSETS = {};

const cdnMock = {
generateMockAssets(baseUrl) {
const watcher = chokidar.watch("dist");
let timer;
const updateCdnMock = path => {
LOADED_ASSETS[path] = undefined;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
timer = undefined;
console.log("Refreshing mock CDN mapping - please restart app");
cdnMock._generateMockAssets(baseUrl);
}, 250).unref();
};
watcher.on("change", updateCdnMock);
cdnMock._generateMockAssets(baseUrl);
},

_generateMockAssets(baseUrl) {
const files = filterScanDir.sync({ dir: "dist" });

const url = Url.parse(baseUrl);
const noProtocolBase = Path.posix.join(`/`, url.host, url.path);
const timestamp = Math.floor(Date.now() / 1000);

const mockAssets = files.reduce((acc, file) => {
acc[Path.basename(file)] = "/" + Path.posix.join(noProtocolBase, `${timestamp}`, file);
return acc;
}, {});

Fs.writeFileSync("config/assets.json", `${JSON.stringify(mockAssets, null, 2)}\n`);
},

respondAsset(req, res) {
try {
const filePath = req.url.replace(/\/__mock-cdn\/[0-9]+/, "dist");
const fp = Path.resolve(filePath);
let asset = LOADED_ASSETS[filePath];
if (!asset) {
asset = LOADED_ASSETS[filePath] = Fs.readFileSync(fp);
}
const ext = Path.extname(filePath);
const mimeType = mime.getType(ext);
res.writeHead(200, {
"Content-Type": mimeType,
"Content-Length": Buffer.byteLength(asset)
});
res.write(asset);
res.end();
} catch (err) {
res.statusCode = 404;
res.write("Not Found");
res.end();
}
}
};

module.exports = cdnMock;
29 changes: 25 additions & 4 deletions packages/xarc-app-dev/lib/dev-admin/redbird-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const redbird = require("@jchip/redbird");
const ck = require("chalker");
const optionalRequire = require("optional-require")(require);
const { settings, searchSSLCerts, controlPaths } = require("../../config/dev-proxy");
const cdnMock = require("./cdn-mock");

const { formUrl } = require("../utils");

Expand Down Expand Up @@ -115,7 +116,8 @@ const registerElectrodeDevRules = ({
port,
appPort,
webpackDevPort,
restart
restart,
enableCdnMock
}) => {
const { dev: devPath, admin: adminPath, hmr: hmrPath, appLog, reporter } = controlPaths;
const appForwards = [
Expand Down Expand Up @@ -183,6 +185,22 @@ const registerElectrodeDevRules = ({
return false;
}
});

// mock-cdn

if (enableCdnMock) {
const mockCdnSrc = formUrl({ protocol, host, port, path: `/__mock-cdn` });
cdnMock.generateMockAssets(mockCdnSrc);
proxy.register({
ssl,
src: mockCdnSrc,
target: `http://localhost:29999/__mock-cdn`,
onRequest(req, res) {
cdnMock.respondAsset(req, res);
return false;
}
});
}
};

const startProxy = inOptions => {
Expand Down Expand Up @@ -255,8 +273,9 @@ const startProxy = inOptions => {
res.end();
});

const enableCdnMock = process.argv.includes("--mock-cdn");
// register with primary protocol/host/port
registerElectrodeDevRules({ ...options, ssl, proxy, restart });
registerElectrodeDevRules({ ...options, ssl, proxy, restart, enableCdnMock });

// if primary protocol is https, then register regular http rules at httpPort
if (ssl) {
Expand All @@ -277,9 +296,11 @@ const startProxy = inOptions => {
}

const proxyUrl = formUrl({ protocol, host, port: options.port });
const mockCdnMsg = enableCdnMock
? `\nMock CDN is enabled (mapping saved to config/assets.json)\n`
: "\n";
console.log(
ck`Electrode dev proxy server running:

ck`Electrode dev proxy server running:${mockCdnMsg}
${buildProxyTree(options, ["appPort", "webpackDevPort"])}
View status at <green>${proxyUrl}${controlPaths.status}</>`
);
Expand Down
15 changes: 8 additions & 7 deletions packages/xarc-app-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"babel-plugin-transform-react-remove-prop-types": "^0.4.20",
"boxen": "^4.2.0",
"chalker": "^1.2.0",
"chokidar": "^2.0.4",
"chokidar": "^3.3.1",
"core-js": "^3",
"electrode-hapi-compat": "^1.2.0",
"electrode-node-resolver": "^2.0.0",
Expand All @@ -66,12 +66,13 @@
"isomorphic-loader": "^3.0.0",
"lodash": "^4.13.1",
"log-update": "^4.0.0",
"mime": "^1.0.0",
"mime": "^2.4.4",
"mkdirp": "^0.5.1",
"nix-clap": "^1.3.7",
"nyc": "^14.1.1",
"optional-require": "^1.0.0",
"prompts": "^2.2.1",
"ps-get": "^1.0.1",
"regenerator-runtime": "^0.13.2",
"request": "^2.88.0",
"require-at": "^1.0.2",
Expand All @@ -84,8 +85,8 @@
"webpack-dev-middleware": "^3.4.0",
"webpack-hot-middleware": "^2.22.2",
"winston": "^2.3.1",
"xaa": "^1.4.0",
"xclap": "^0.2.48",
"xaa": "^1.5.0",
"xclap": "^0.2.50",
"xenv-config": "^1.3.1",
"xsh": "^0.4.4"
},
Expand Down Expand Up @@ -119,10 +120,10 @@
},
"fyn": {
"dependencies": {
"electrode-node-resolver": "../electrode-node-resolver",
"subapp-util": "../subapp-util",
"@jchip/redbird": "../../../redbird",
"@xarc/webpack": "../xarc-webpack"
"@xarc/webpack": "../xarc-webpack",
"electrode-node-resolver": "../electrode-node-resolver",
"subapp-util": "../subapp-util"
},
"devDependencies": {
"@xarc/app": "../xarc-app",
Expand Down
87 changes: 78 additions & 9 deletions packages/xarc-app/arch-clap.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ require.resolve(`${archetype.devArchetypeName}/package.json`);

const devRequire = archetype.devRequire;
const ck = devRequire("chalker");
const xaa = devRequire("xaa");
const { psChildren } = devRequire("ps-get");

const detectCssModule = devRequire("@xarc/webpack/lib/util/detect-css-module");

Expand All @@ -24,6 +26,10 @@ const { getWebpackStartConfig, setWebpackProfile } = devRequire(
"@xarc/webpack/lib/util/custom-check"
);

const chokidar = devRequire("chokidar");

const { spawn } = require("child_process");

const optFlow = devOptRequire("electrode-archetype-opt-flow");

const scanDir = devRequire("filter-scan-dir");
Expand Down Expand Up @@ -56,6 +62,46 @@ const logger = require("./lib/logger");

const jestTestDirectories = ["_test_", "_tests_", "__test__", "__tests__"];

const watchExec = (files, cmd) => {
let timer;
let child;
let defer = xaa.makeDefer();
const doExec = () => {
if (timer) {
clearTimeout(timer);
}

timer = setTimeout(async () => {
timer = undefined;
const run = msg => {
child = true;
console.log(`${msg} '${cmd}'`);
const ch = spawn(cmd, { shell: true, stdio: "inherit" });
ch.on("close", () => {
if (child === "restart") {
run("Restarting");
} else {
defer.resolve();
}
});
child = ch;
};
if (!child) {
run("Running");
} else if (child.kill && child.pid) {
const ch = child;
child = "restart";
(await psChildren(ch.pid)).reverse().forEach(c => process.kill(c.pid));
ch.kill();
}
}, 500);
};
const watcher = chokidar.watch([].concat(files));
watcher.on("change", doExec);
doExec();
return defer.promise;
};

// By default, the dev proxy server will be hosted from PORT (3000)
// and the app from APP_SERVER_PORT (3100).
// If the APP_SERVER_PORT is set to the empty string however,
Expand All @@ -68,10 +114,6 @@ function quote(str) {
return str.startsWith(`"`) ? str : `"${str}"`;
}

function webpackConfig(file) {
return Path.join(config.webpack, file);
}

function karmaConfig(file) {
return Path.join(config.karma, file);
}
Expand Down Expand Up @@ -728,6 +770,33 @@ Individual .babelrc files were generated for you in src/client and src/server

debug: ["build-dev-static", "server-debug"],
devbrk: ["dev --inspect-brk"],

"mock-cloud": {
desc: `Run app locally like it's deployed to cloud with CDN mock and HTTPS proxy.
You must run clap build first and set env vars like HOST, PORT, NODE_ENV=production yourself.
options: [all options will be passed to node when starting your app server]`,
task(context) {
const mockTask = xclap.concurrent([
"dev-proxy --mock-cdn",
xclap.serial(
() => xaa.delay(500),
() => watchExec("config/assets.json", `node ${context.args.join(" ")} lib/server`)
)
]);

if (!Fs.existsSync("dist")) {
console.log("dist does not exist, running build task first.");
return xclap.serial(
"build",
() => console.log("build completed, starting mock prod mode with proxy"),
mockTask
);
}

return xclap.serial(() => console.log("dist exist, skipping build task"), mockTask);
}
},

dev: {
desc: `Start your app with watch in development mode with dev-admin.
options: node.js --inspect can be used to debug the dev-admin`,
Expand Down Expand Up @@ -849,12 +918,12 @@ Individual .babelrc files were generated for you in src/client and src/server
},

"dev-proxy": {
desc:
"Start Electrode dev reverse proxy by itself - useful for running it with sudo (options: --debug)",
task() {
const debug = this.argv.includes("--debug") ? "--inspect-brk " : "";
desc: `Start Electrode dev reverse proxy by itself - useful for running it with sudo.
options: --debug --mock-cdn`,
task(context) {
const debug = context.argOpts.debug ? "--inspect-brk " : "";
const proxySpawn = require.resolve("@xarc/app-dev/lib/dev-admin/redbird-spawn");
return `~(tty)$node ${debug}${proxySpawn}`;
return `~(tty)$node ${debug}${proxySpawn} ${context.args.join(" ")}`;
}
},

Expand Down