Skip to content

Commit

Permalink
feat(react): add nx.server.ready message from Node process to any pot…
Browse files Browse the repository at this point in the history
…ential parent process (#13808)
  • Loading branch information
jaysoo committed Dec 14, 2022
1 parent 8f7feba commit 8df1834
Show file tree
Hide file tree
Showing 19 changed files with 285 additions and 68 deletions.
6 changes: 4 additions & 2 deletions docs/generated/packages/react.json
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,8 @@
},
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
"description": "The port for the dev server of the remote app.",
"default": 4200
},
"ssr": {
"description": "Whether to configure SSR for the host application",
Expand Down Expand Up @@ -1270,7 +1271,8 @@
},
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
"description": "The port for the dev server of the remote app.",
"default": 4200
},
"ssr": {
"description": "Whether to configure SSR for the host application",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { combineAsyncIterables } from './combine-async-iterables';
import { createAsyncIterable } from './create-async-iterable';

function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

describe('combineAsyncIterables', () => {
it('should combine generators', async () => {
async function* a() {
await delay(20);
yield 'a';
}

async function* b() {
await delay(0);
yield 'b';
}

const c = combineAsyncIterables(a(), b());
const results = [];

for await (const x of c) {
results.push(x);
}

expect(results).toEqual(['b', 'a']);
});

it('should support .throw()', async () => {
async function* a() {
yield 1;
yield 2;
}

async function* b() {
yield 3;
yield 4;
}

const c = combineAsyncIterables(a(), b());
const results = [];

try {
for await (const x of c) {
results.push(x);
await c.throw(new Error('oops'));
}
} catch (e) {
expect(e.message).toMatch(/oops/);
expect(results).toEqual([1]);
}
});

it('should support .return()', async () => {
async function* a() {
yield 1;
yield 2;
}

async function* b() {
yield 3;
yield 4;
}

const c = combineAsyncIterables(a(), b());
const results = [];

for await (const x of c) {
results.push(x);
const { value: y } = await c.return(10);
results.push(y);
}

expect(results).toEqual([1, 10]);
});

it('should throw when one generator throws', async () => {
async function* a() {
await delay(20);
yield 'a';
}

async function* b() {
throw new Error('threw in b');
}

const c = combineAsyncIterables(a(), b());

async function* d() {
yield* c;
}

try {
for await (const x of d()) {
}
throw new Error('should not reach here');
} catch (e) {
expect(e.message).toMatch(/threw in b/);
}
});

it('should combine async iterables', async () => {
const a = createAsyncIterable<number>(({ next, done }) => {
next(1);
next(2);
next(3);
done();
});
const b = createAsyncIterable<number>(({ next, done }) => {
next(4);
next(5);
next(6);
done();
});

const c = combineAsyncIterables(a, b);

const results: number[] = [];
for await (const x of c) {
results.push(x);
}

expect(results).toEqual([1, 4, 2, 5, 3, 6]);
});

it('should throw error when an async iterable throws', async () => {
const a = createAsyncIterable<number>(({ next, done }) => {
next(1);
done();
});
const b = createAsyncIterable<number>(({ next, done }) => {
throw new Error('threw in b');
});

const c = combineAsyncIterables(a, b);

try {
for await (const _x of c) {
// nothing
}
} catch (e) {
expect(e.message).toMatch(/threw in b/);
}
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
export async function* combineAsyncIterableIterators<T = any>(
...iterators: { 0: AsyncIterableIterator<T> } & AsyncIterableIterator<T>[]
export async function* combineAsyncIterables<T = any>(
..._iterators: { 0: AsyncIterable<T> } & AsyncIterable<T>[]
): AsyncGenerator<T> {
// Convert iterables into iterators with next, return, throws methods.
// If it's already an iterator, keep it.
const iterators: AsyncIterableIterator<T>[] = _iterators.map((it) => {
if (typeof it['next'] === 'function') {
return it as AsyncIterableIterator<T>;
} else {
return (async function* wrapped() {
for await (const val of it) {
yield val;
}
})();
}
});

let [options] = iterators;

if (typeof options.next === 'function') {
options = Object.create(null);
} else {
Expand Down Expand Up @@ -30,7 +45,7 @@ export async function* combineAsyncIterableIterators<T = any>(
}
} while (asyncIteratorsValues.size > 0);
} finally {
await Promise.allSettled(iterators.map((it) => it.return()));
await Promise.allSettled(iterators.map((it) => it['return']?.()));
}
}

Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion packages/devkit/src/utils/async-iterable/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './create-async-iterable';
export * from './combine-async-iteratable-iterators';
export * from './combine-async-iterables';
export * from './map-async-iteratable';
export * from './tap-async-iteratable';
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { WebDevServerOptions } from '@nrwl/webpack/src/executors/dev-server/sche
import { join } from 'path';
import * as chalk from 'chalk';
import {
combineAsyncIterableIterators,
combineAsyncIterables,
tapAsyncIterable,
} from '@nrwl/devkit/src/utils/async-iterable';

Expand Down Expand Up @@ -50,7 +50,7 @@ export default async function* moduleFederationDevServer(
for (const app of knownRemotes) {
const [appName] = Array.isArray(app) ? app : [app];
const isDev = devServeApps.includes(appName);
iter = combineAsyncIterableIterators(
iter = combineAsyncIterables(
iter,
await runExecutor(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { WebSsrDevServerOptions } from '@nrwl/webpack/src/executors/ssr-dev-serv
import { join } from 'path';
import * as chalk from 'chalk';
import {
combineAsyncIterableIterators,
combineAsyncIterables,
createAsyncIterable,
mapAsyncIterable,
tapAsyncIterable,
Expand Down Expand Up @@ -101,7 +101,7 @@ export default async function* moduleFederationSsrDevServer(
(x) => x
);

iter = combineAsyncIterableIterators(iter, remoteServeIter);
iter = combineAsyncIterables(iter, remoteServeIter);
}

let numAwaiting = knownRemotes.length + 1; // remotes + host
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as path from 'path';
import express from 'express';
import cors from 'cors';

import { handleRequest } from './src/main.server';

const port = process.env['PORT'] || 4200;
const app = express();

const browserDist = path.join(process.cwd(), '<%= browserBuildOutputPath %>');
const indexPath = path.join(browserDist, 'index.html');

app.use(cors());

app.get(
'*.*',
express.static(browserDist, {
maxAge: '1y',
})
);

app.use('*', handleRequest(indexPath));

const server = app.listen(port, () => {
console.log(`Express server listening on http://localhost:${port}`);
});

server.on('error', console.error);
1 change: 1 addition & 0 deletions packages/react/src/generators/host/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export async function hostGenerator(host: Tree, schema: Schema) {
if (options.ssr) {
const setupSsrTask = await setupSsrGenerator(host, {
project: options.projectName,
serverPort: options.devServerPort,
});
tasks.push(setupSsrTask);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export async function setupSsrForHost(
}),
appName,
tmpl: '',
browserBuildOutputPath: project.targets.build.options.outputPath,
}
);

Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/generators/host/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@
},
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
"description": "The port for the dev server of the remote app.",
"default": 4200
},
"ssr": {
"description": "Whether to configure SSR for the host application",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as path from 'path';
import express from 'express';
import cors from 'cors';

import { handleRequest } from './src/main.server';

const port = process.env['PORT'] || 4200;
const app = express();

const browserDist = path.join(process.cwd(), '<%= browserBuildOutputPath %>');
const indexPath = path.join(browserDist, 'index.html');

app.use(cors());

app.get(
'*.*',
express.static(browserDist, {
maxAge: '1y',
})
);

app.use('*', handleRequest(indexPath));

const server = app.listen(port, () => {
console.log(`Express server listening on http://localhost:${port}`);

/**
* DO NOT REMOVE IF USING @nrwl/react:module-federation-dev-ssr executor
* to serve your Host application with this Remote application.
* This message allows Nx to determine when the Remote is ready to be
* consumed by the Host.
*/
process.send?.('nx.server.ready');
});

server.on('error', console.error);
Loading

1 comment on commit 8df1834

@vercel
Copy link

@vercel vercel bot commented on 8df1834 Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-nrwl.vercel.app
nx-five.vercel.app
nx-dev-git-master-nrwl.vercel.app
nx.dev

Please sign in to comment.