@nerest/nerest 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/bin/build.ts +1 -1
- package/bin/index.ts +2 -2
- package/bin/watch.ts +1 -1
- package/build/index.ts +6 -6
- package/client/index.ts +5 -1
- package/dist/bin/build.js +3 -7
- package/dist/bin/index.js +5 -7
- package/dist/bin/watch.js +3 -7
- package/dist/build/excludes/index.js +1 -5
- package/dist/build/index.js +17 -24
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +32 -0
- package/dist/server/development.js +35 -39
- package/dist/server/hooks/props.d.ts +1 -0
- package/dist/server/hooks/props.js +12 -0
- package/dist/server/hooks/runtime.d.ts +2 -0
- package/dist/server/hooks/runtime.js +28 -0
- package/dist/server/loaders/assets.js +1 -5
- package/dist/server/loaders/examples.js +10 -17
- package/dist/server/loaders/manifest.js +5 -12
- package/dist/server/loaders/schema.js +7 -14
- package/dist/server/parts/apps.d.ts +1 -0
- package/dist/server/parts/apps.js +16 -22
- package/dist/server/parts/k8s-probes.js +1 -5
- package/dist/server/parts/preview.js +1 -5
- package/dist/server/parts/props-hook.d.ts +1 -0
- package/dist/server/parts/props-hook.js +8 -0
- package/dist/server/parts/render.js +6 -13
- package/dist/server/parts/runtime-hook.d.ts +1 -1
- package/dist/server/parts/runtime-hook.js +1 -5
- package/dist/server/parts/swagger.js +8 -15
- package/dist/server/parts/validator.d.ts +1 -1
- package/dist/server/parts/validator.js +9 -12
- package/dist/server/production.d.ts +1 -0
- package/dist/server/production.js +93 -0
- package/package.json +4 -3
- package/server/development.ts +20 -13
- package/server/hooks/props.ts +22 -0
- package/server/{parts/runtime-hook.ts → hooks/runtime.ts} +1 -1
- package/server/parts/apps.ts +6 -4
- package/server/parts/validator.ts +5 -2
- package/server/production.ts +19 -15
package/README.md
CHANGED
|
@@ -37,6 +37,21 @@ The app directory should contain a `schema.json` file that describes the schema
|
|
|
37
37
|
|
|
38
38
|
OpenAPI specification is compiled automatically based on the provided schemas and becomes available at `/api/json`. It can also be explored through Swagger UI that becomes available at `/api`.
|
|
39
39
|
|
|
40
|
+
### Props Hook (`/props.ts`)
|
|
41
|
+
|
|
42
|
+
The app directory may contain a `props.ts` module that exports a default function. If it exists, this function will be executed on the server for every incoming request, receiving a single object as an argument -- the body of the request. You can use this hook to modify and return a new object, which will be passed down to the `index.tsx` entrypoint component instead. For example:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export default function (props: Props) {
|
|
46
|
+
return {
|
|
47
|
+
...props,
|
|
48
|
+
greeting: `${props.greeting} (modified in props.ts)`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The exported function may be async, in which case nerest will wait for the Promise to resolve, then pass the result object to the entrypoint component.
|
|
54
|
+
|
|
40
55
|
## Configuration
|
|
41
56
|
|
|
42
57
|
Different aspects of Nerest apps can be configured via environment variables, JSON configuration and runtime hooks written in TypeScript. Examples of all kinds of configuration can be viewed in the [nerest-harness](https://gitlab.tcsbank.ru/tj/nerest-harness) repository.
|
package/bin/build.ts
CHANGED
package/bin/index.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// All executions of `nerest <command>` get routed through here
|
|
3
3
|
import 'dotenv/config';
|
|
4
4
|
|
|
5
|
-
import { build } from './build';
|
|
6
|
-
import { watch } from './watch';
|
|
5
|
+
import { build } from './build.js';
|
|
6
|
+
import { watch } from './watch.js';
|
|
7
7
|
|
|
8
8
|
// TODO: add CLI help and manual, maybe use a CLI framework like oclif
|
|
9
9
|
async function cliEntry(args: string[]) {
|
package/bin/watch.ts
CHANGED
package/build/index.ts
CHANGED
|
@@ -2,13 +2,13 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { build } from 'vite';
|
|
6
6
|
import type { InlineConfig } from 'vite';
|
|
7
7
|
import { viteExternalsPlugin } from 'vite-plugin-externals';
|
|
8
8
|
|
|
9
|
-
import { loadApps } from '../server/parts/apps';
|
|
10
|
-
import type { BuildConfiguration } from '../schemas/nerest-build.schema';
|
|
11
|
-
import { excludes } from './excludes';
|
|
9
|
+
import { loadApps } from '../server/parts/apps.js';
|
|
10
|
+
import type { BuildConfiguration } from '../schemas/nerest-build.schema.js';
|
|
11
|
+
import { excludes } from './excludes/index.js';
|
|
12
12
|
|
|
13
13
|
export async function buildMicroFrontend() {
|
|
14
14
|
const root = process.cwd();
|
|
@@ -57,7 +57,7 @@ export async function buildMicroFrontend() {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
console.log('Producing production client build...');
|
|
60
|
-
await
|
|
60
|
+
await build(clientConfig);
|
|
61
61
|
|
|
62
62
|
console.log('Producing Nerest manifest file...');
|
|
63
63
|
await buildAppsManifest(root, staticPath);
|
|
@@ -83,7 +83,7 @@ export async function buildMicroFrontend() {
|
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
console.log('Producing production server build...');
|
|
86
|
-
await
|
|
86
|
+
await build(serverConfig);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
async function buildAppsManifest(root: string, staticPath: string) {
|
package/client/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Shared client entrypoint that bootstraps all of the apps supplied
|
|
2
2
|
// by current microfrontend
|
|
3
3
|
import React from 'react';
|
|
4
|
+
import type { ComponentType } from 'react';
|
|
4
5
|
import ReactDOM from 'react-dom/client';
|
|
5
6
|
|
|
6
7
|
// Since this is a shared entrypoint, it dynamically imports all of the
|
|
@@ -28,7 +29,10 @@ async function runHydration() {
|
|
|
28
29
|
// TODO: more robust error handling and error logging
|
|
29
30
|
const props = JSON.parse(propsContainer?.textContent ?? '{}');
|
|
30
31
|
|
|
31
|
-
const reactElement = React.createElement(
|
|
32
|
+
const reactElement = React.createElement(
|
|
33
|
+
(await appModuleLoader()) as ComponentType,
|
|
34
|
+
props
|
|
35
|
+
);
|
|
32
36
|
ReactDOM.hydrateRoot(container, reactElement);
|
|
33
37
|
}
|
|
34
38
|
}
|
package/dist/bin/build.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.build = void 0;
|
|
4
|
-
const build_1 = require("../build");
|
|
1
|
+
import { buildMicroFrontend } from '../build/index.js';
|
|
5
2
|
// Produce the production build of the Nerest micro frontend
|
|
6
|
-
async function build() {
|
|
3
|
+
export async function build() {
|
|
7
4
|
console.log('Nerest is preparing production build...');
|
|
8
|
-
await
|
|
5
|
+
await buildMicroFrontend();
|
|
9
6
|
console.log('Nerest build is finished');
|
|
10
7
|
}
|
|
11
|
-
exports.build = build;
|
package/dist/bin/index.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
2
|
// All executions of `nerest <command>` get routed through here
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import 'dotenv/config';
|
|
4
|
+
import { build } from './build.js';
|
|
5
|
+
import { watch } from './watch.js';
|
|
8
6
|
// TODO: add CLI help and manual, maybe use a CLI framework like oclif
|
|
9
7
|
async function cliEntry(args) {
|
|
10
8
|
if (args[0] === 'build') {
|
|
11
|
-
await
|
|
9
|
+
await build();
|
|
12
10
|
}
|
|
13
11
|
else if (args[0] === 'watch') {
|
|
14
|
-
await
|
|
12
|
+
await watch();
|
|
15
13
|
}
|
|
16
14
|
}
|
|
17
15
|
// [<path to node>, <path to nerest binary>, ...args]
|
package/dist/bin/watch.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.watch = void 0;
|
|
4
|
-
const development_1 = require("../server/development");
|
|
1
|
+
import { runDevelopmentServer } from '../server/development.js';
|
|
5
2
|
// Start dev server in watch mode, that restarts on file change
|
|
6
3
|
// and rebuilds the client static files
|
|
7
|
-
async function watch() {
|
|
4
|
+
export async function watch() {
|
|
8
5
|
// TODO: will be replaced with nerest logger
|
|
9
6
|
console.log('Starting Nerest watch...');
|
|
10
|
-
await
|
|
7
|
+
await runDevelopmentServer();
|
|
11
8
|
}
|
|
12
|
-
exports.watch = watch;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.excludes = void 0;
|
|
4
1
|
/**
|
|
5
2
|
* Maps list of imports into a list of rollup aliases that resolve
|
|
6
3
|
* into an empty module.
|
|
7
4
|
*/
|
|
8
|
-
function excludes(list) {
|
|
5
|
+
export function excludes(list) {
|
|
9
6
|
return list?.map((exclude) => ({
|
|
10
7
|
// Excluding '@some/package' should exclude both '@some/package' and
|
|
11
8
|
// '@some/package/...` imports
|
|
@@ -13,7 +10,6 @@ function excludes(list) {
|
|
|
13
10
|
replacement: '@nerest/nerest/build/excludes/empty-module',
|
|
14
11
|
}));
|
|
15
12
|
}
|
|
16
|
-
exports.excludes = excludes;
|
|
17
13
|
/**
|
|
18
14
|
* Escapes string to use inside of a regular expression.
|
|
19
15
|
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
package/dist/build/index.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const fs_1 = require("fs");
|
|
10
|
-
const vite_1 = __importDefault(require("vite"));
|
|
11
|
-
const vite_plugin_externals_1 = require("vite-plugin-externals");
|
|
12
|
-
const apps_1 = require("../server/parts/apps");
|
|
13
|
-
const excludes_1 = require("./excludes");
|
|
14
|
-
async function buildMicroFrontend() {
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { build } from 'vite';
|
|
5
|
+
import { viteExternalsPlugin } from 'vite-plugin-externals';
|
|
6
|
+
import { loadApps } from '../server/parts/apps.js';
|
|
7
|
+
import { excludes } from './excludes/index.js';
|
|
8
|
+
export async function buildMicroFrontend() {
|
|
15
9
|
const root = process.cwd();
|
|
16
10
|
const staticPath = process.env.NEREST_STATIC_PATH;
|
|
17
11
|
// TODO: The path where the client files are deployed is built-in during
|
|
@@ -44,15 +38,15 @@ async function buildMicroFrontend() {
|
|
|
44
38
|
},
|
|
45
39
|
resolve: {
|
|
46
40
|
// excludes - map buildConfig.excludes packages to an empty module
|
|
47
|
-
alias:
|
|
41
|
+
alias: excludes(buildConfig?.excludes),
|
|
48
42
|
},
|
|
49
43
|
plugins: [
|
|
50
44
|
// externals - map buildConfig.externals packages to a global variable on window
|
|
51
|
-
|
|
45
|
+
viteExternalsPlugin(buildConfig?.externals, { useWindow: false }),
|
|
52
46
|
],
|
|
53
47
|
};
|
|
54
48
|
console.log('Producing production client build...');
|
|
55
|
-
await
|
|
49
|
+
await build(clientConfig);
|
|
56
50
|
console.log('Producing Nerest manifest file...');
|
|
57
51
|
await buildAppsManifest(root, staticPath);
|
|
58
52
|
// Build server using the client manifest
|
|
@@ -75,18 +69,17 @@ async function buildMicroFrontend() {
|
|
|
75
69
|
},
|
|
76
70
|
};
|
|
77
71
|
console.log('Producing production server build...');
|
|
78
|
-
await
|
|
72
|
+
await build(serverConfig);
|
|
79
73
|
}
|
|
80
|
-
exports.buildMicroFrontend = buildMicroFrontend;
|
|
81
74
|
async function buildAppsManifest(root, staticPath) {
|
|
82
|
-
const apps = await
|
|
83
|
-
await
|
|
75
|
+
const apps = await loadApps(root, staticPath);
|
|
76
|
+
await fs.writeFile(path.join(root, 'build/nerest-manifest.json'), JSON.stringify(apps), { encoding: 'utf-8' });
|
|
84
77
|
}
|
|
85
78
|
// TODO: error handling
|
|
86
79
|
async function readBuildConfig(root) {
|
|
87
|
-
const configPath =
|
|
88
|
-
if (
|
|
89
|
-
const content = await
|
|
80
|
+
const configPath = path.join(root, 'nerest-build.json');
|
|
81
|
+
if (existsSync(configPath)) {
|
|
82
|
+
const content = await fs.readFile(configPath, { encoding: 'utf-8' });
|
|
90
83
|
return JSON.parse(content);
|
|
91
84
|
}
|
|
92
85
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Shared client entrypoint that bootstraps all of the apps supplied
|
|
2
|
+
// by current microfrontend
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import ReactDOM from 'react-dom/client';
|
|
5
|
+
// Since this is a shared entrypoint, it dynamically imports all of the
|
|
6
|
+
// available apps. They will be built as separate chunks and only loaded
|
|
7
|
+
// if needed.
|
|
8
|
+
const modules = import.meta.glob('/apps/*/index.tsx', { import: 'default' });
|
|
9
|
+
async function runHydration() {
|
|
10
|
+
for (const container of document.querySelectorAll('div[data-app-name]')) {
|
|
11
|
+
const appName = container.getAttribute('data-app-name');
|
|
12
|
+
const appModuleLoader = modules[`/apps/${appName}/index.tsx`];
|
|
13
|
+
if (!appModuleLoader || container.hasAttribute('data-app-hydrated')) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
// Mark container as hydrated, in case there are multiple instances
|
|
17
|
+
// of the same microfrontend script on the page
|
|
18
|
+
container.setAttribute('data-app-hydrated', 'true');
|
|
19
|
+
const appId = container.getAttribute('data-app-id');
|
|
20
|
+
const propsContainer = document.querySelector(`script[data-app-id="${appId}"]`);
|
|
21
|
+
// TODO: more robust error handling and error logging
|
|
22
|
+
const props = JSON.parse(propsContainer?.textContent ?? '{}');
|
|
23
|
+
const reactElement = React.createElement((await appModuleLoader()), props);
|
|
24
|
+
ReactDOM.hydrateRoot(container, reactElement);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (document.readyState !== 'complete') {
|
|
28
|
+
document.addEventListener('DOMContentLoaded', runHydration);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
runHydration();
|
|
32
|
+
}
|
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.runDevelopmentServer = void 0;
|
|
7
1
|
// This is the nerest development server entrypoint
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { build, createServer } from 'vite';
|
|
4
|
+
import fastify from 'fastify';
|
|
5
|
+
import fastifyStatic from '@fastify/static';
|
|
6
|
+
import fastifyGracefulShutdown from 'fastify-graceful-shutdown';
|
|
7
|
+
import { loadApps } from './parts/apps.js';
|
|
8
|
+
import { renderApp } from './parts/render.js';
|
|
9
|
+
import { renderPreviewPage } from './parts/preview.js';
|
|
10
|
+
import { validator } from './parts/validator.js';
|
|
11
|
+
import { setupSwagger } from './parts/swagger.js';
|
|
12
|
+
import { setupK8SProbes } from './parts/k8s-probes.js';
|
|
13
|
+
import { runRuntimeHook } from './hooks/runtime.js';
|
|
14
|
+
import { runPropsHook } from './hooks/props.js';
|
|
20
15
|
// eslint-disable-next-line max-statements
|
|
21
|
-
async function runDevelopmentServer() {
|
|
16
|
+
export async function runDevelopmentServer() {
|
|
22
17
|
const root = process.cwd();
|
|
23
18
|
// TODO: move build config into a separate file
|
|
24
19
|
// TODO: look at @vitejs/plugin-react (everything seems fine without it though)
|
|
@@ -55,17 +50,17 @@ async function runDevelopmentServer() {
|
|
|
55
50
|
await startClientBuildWatcher(config);
|
|
56
51
|
// Load app entries following the `apps/{name}/index.tsx` convention
|
|
57
52
|
// TODO: remove hardcoded port
|
|
58
|
-
const apps = await
|
|
53
|
+
const apps = await loadApps(root, 'http://0.0.0.0:3000/');
|
|
59
54
|
// Start vite server that will be rendering SSR components
|
|
60
|
-
const viteSsr = await
|
|
61
|
-
const app = (
|
|
55
|
+
const viteSsr = await createServer(config);
|
|
56
|
+
const app = fastify();
|
|
62
57
|
// Setup schema validation. We have to use our own ajv instance that
|
|
63
58
|
// we can use both to validate request bodies and examples against
|
|
64
59
|
// app schemas
|
|
65
|
-
app.setValidatorCompiler(({ schema }) =>
|
|
66
|
-
await
|
|
60
|
+
app.setValidatorCompiler(({ schema }) => validator.compile(schema));
|
|
61
|
+
await setupSwagger(app);
|
|
67
62
|
for (const appEntry of Object.values(apps)) {
|
|
68
|
-
const { name, entry, examples, schema } = appEntry;
|
|
63
|
+
const { name, entry, propsHookEntry, examples, schema } = appEntry;
|
|
69
64
|
const routeOptions = {};
|
|
70
65
|
// TODO: report error if schema is missing, unless this app is client-only
|
|
71
66
|
if (schema) {
|
|
@@ -88,17 +83,18 @@ async function runDevelopmentServer() {
|
|
|
88
83
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
89
84
|
fixStacktrace: true,
|
|
90
85
|
});
|
|
91
|
-
|
|
86
|
+
const props = await runPropsHook(request.body, () => viteSsr.ssrLoadModule(propsHookEntry));
|
|
87
|
+
return renderApp({
|
|
92
88
|
name,
|
|
93
89
|
assets: appEntry.assets,
|
|
94
90
|
component: ssrComponent.default,
|
|
95
|
-
},
|
|
91
|
+
}, props);
|
|
96
92
|
});
|
|
97
93
|
for (const [exampleName, example] of Object.entries(examples)) {
|
|
98
94
|
// Validate example against schema when specified
|
|
99
|
-
if (schema && !
|
|
95
|
+
if (schema && !validator.validate(schema, example)) {
|
|
100
96
|
// TODO: use logger and display errors more prominently
|
|
101
|
-
console.error(`Example "${exampleName}" of app "${name}" does not satisfy schema: ${
|
|
97
|
+
console.error(`Example "${exampleName}" of app "${name}" does not satisfy schema: ${validator.errorsText()}`);
|
|
102
98
|
}
|
|
103
99
|
// GET /api/{name}/examples/{example} -> render a preview page
|
|
104
100
|
// with a predefined example body
|
|
@@ -113,24 +109,25 @@ async function runDevelopmentServer() {
|
|
|
113
109
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
114
110
|
fixStacktrace: true,
|
|
115
111
|
});
|
|
116
|
-
const
|
|
112
|
+
const props = await runPropsHook(example, () => viteSsr.ssrLoadModule(propsHookEntry));
|
|
113
|
+
const { html, assets } = renderApp({
|
|
117
114
|
name,
|
|
118
115
|
assets: appEntry.assets,
|
|
119
116
|
component: ssrComponent.default,
|
|
120
|
-
},
|
|
117
|
+
}, props);
|
|
121
118
|
reply.type('text/html');
|
|
122
|
-
return
|
|
119
|
+
return renderPreviewPage(html, assets);
|
|
123
120
|
});
|
|
124
121
|
}
|
|
125
122
|
}
|
|
126
123
|
// Add graceful shutdown handler to prevent requests errors
|
|
127
|
-
await app.register(
|
|
124
|
+
await app.register(fastifyGracefulShutdown);
|
|
128
125
|
if (process.env.ENABLE_K8S_PROBES) {
|
|
129
|
-
await
|
|
126
|
+
await setupK8SProbes(app);
|
|
130
127
|
}
|
|
131
128
|
// TODO: only do this locally, load from CDN in production
|
|
132
|
-
await app.register(
|
|
133
|
-
root:
|
|
129
|
+
await app.register(fastifyStatic, {
|
|
130
|
+
root: path.join(root, 'build'),
|
|
134
131
|
// TODO: maybe use @fastify/cors instead
|
|
135
132
|
setHeaders(res) {
|
|
136
133
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
@@ -139,7 +136,7 @@ async function runDevelopmentServer() {
|
|
|
139
136
|
},
|
|
140
137
|
});
|
|
141
138
|
// Execute runtime hook in nerest-runtime.ts if it exists
|
|
142
|
-
await
|
|
139
|
+
await runRuntimeHook(app, () => viteSsr.ssrLoadModule('/nerest-runtime.ts'));
|
|
143
140
|
// TODO: remove hardcoded port
|
|
144
141
|
await app.listen({
|
|
145
142
|
host: '0.0.0.0',
|
|
@@ -147,10 +144,9 @@ async function runDevelopmentServer() {
|
|
|
147
144
|
});
|
|
148
145
|
console.log('Nerest is listening on 0.0.0.0:3000');
|
|
149
146
|
}
|
|
150
|
-
exports.runDevelopmentServer = runDevelopmentServer;
|
|
151
147
|
// TODO: this should probably be moved from here
|
|
152
148
|
async function startClientBuildWatcher(config) {
|
|
153
|
-
const watcher = (await
|
|
149
|
+
const watcher = (await build(config));
|
|
154
150
|
return new Promise((resolve) => {
|
|
155
151
|
// We need to have a built manifest.json to provide assets
|
|
156
152
|
// links in SSR. We will wait for rollup to report when it
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runPropsHook(props: unknown, loader: () => Promise<unknown>): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Load the props hook module and run it if it exists, passing down app's
|
|
2
|
+
// props object. This hook can be used to modify props before they are passed
|
|
3
|
+
// to the root app component.
|
|
4
|
+
export async function runPropsHook(props, loader) {
|
|
5
|
+
let module;
|
|
6
|
+
try {
|
|
7
|
+
module = (await loader());
|
|
8
|
+
}
|
|
9
|
+
catch { }
|
|
10
|
+
// If module exists and exports a default function, run it to modify props
|
|
11
|
+
return (typeof module?.default === 'function' ? module.default(props) : props);
|
|
12
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Load the runtime hook module and run it if it exists, passing down our
|
|
2
|
+
// fastify instance. This hook can be used to modify fastify settings, add
|
|
3
|
+
// plugins or routes on an individual app level.
|
|
4
|
+
export async function runRuntimeHook(app, loader) {
|
|
5
|
+
let module;
|
|
6
|
+
try {
|
|
7
|
+
module = (await loader());
|
|
8
|
+
}
|
|
9
|
+
catch { }
|
|
10
|
+
if (typeof module?.default === 'function') {
|
|
11
|
+
// If module exists and exports a default function, execute it and
|
|
12
|
+
// pass down the fastify instance
|
|
13
|
+
try {
|
|
14
|
+
await module.default(app);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
console.error('Failed to execute runtime hook', e);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else if (module) {
|
|
22
|
+
console.error("Runtime hook found, but doesn't export default function!");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.log('Runtime hook not found, skipping...');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadAppAssets = void 0;
|
|
4
1
|
// Extracts the list of assets for a given app from the manifest file
|
|
5
|
-
function loadAppAssets(appName, manifest, staticPath) {
|
|
2
|
+
export function loadAppAssets(appName, manifest, staticPath) {
|
|
6
3
|
// TODO: handling errors and potentially missing entries
|
|
7
4
|
// All apps share the same JS entry that dynamically imports the chunks of the apps
|
|
8
5
|
// that are used on the page based on their name
|
|
@@ -11,4 +8,3 @@ function loadAppAssets(appName, manifest, staticPath) {
|
|
|
11
8
|
const appCss = manifest[`apps/${appName}/index.tsx`].css ?? [];
|
|
12
9
|
return [clientEntryJs, ...appCss].map((x) => staticPath + x);
|
|
13
10
|
}
|
|
14
|
-
exports.loadAppAssets = loadAppAssets;
|
|
@@ -1,31 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.loadAppExamples = void 0;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const fs_1 = require("fs");
|
|
9
|
-
const promises_1 = __importDefault(require("fs/promises"));
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import fs from 'fs/promises';
|
|
10
4
|
// Loads and parses the example json files for providing
|
|
11
5
|
// `/examples/` routes of the dev server
|
|
12
|
-
async function loadAppExamples(appRoot) {
|
|
13
|
-
const examplesRoot =
|
|
6
|
+
export async function loadAppExamples(appRoot) {
|
|
7
|
+
const examplesRoot = path.join(appRoot, 'examples');
|
|
14
8
|
// Examples are optional and may not exist
|
|
15
|
-
if (!
|
|
9
|
+
if (!existsSync(examplesRoot)) {
|
|
16
10
|
return {};
|
|
17
11
|
}
|
|
18
|
-
const exampleFiles = (await
|
|
12
|
+
const exampleFiles = (await fs.readdir(examplesRoot, { withFileTypes: true }))
|
|
19
13
|
.filter((d) => d.isFile() && d.name.endsWith('.json'))
|
|
20
14
|
.map((d) => d.name);
|
|
21
15
|
const examples = {};
|
|
22
16
|
// TODO: error handling and reporting
|
|
23
17
|
for (const filename of exampleFiles) {
|
|
24
|
-
const file =
|
|
25
|
-
const content = await
|
|
18
|
+
const file = path.join(examplesRoot, filename);
|
|
19
|
+
const content = await fs.readFile(file, { encoding: 'utf8' });
|
|
26
20
|
const json = JSON.parse(content);
|
|
27
|
-
examples[
|
|
21
|
+
examples[path.basename(filename, '.json')] = json;
|
|
28
22
|
}
|
|
29
23
|
return examples;
|
|
30
24
|
}
|
|
31
|
-
exports.loadAppExamples = loadAppExamples;
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.loadAppManifest = void 0;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const promises_1 = __importDefault(require("fs/promises"));
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
9
3
|
// Manifest is used to provide assets list for every app
|
|
10
4
|
// for use with SSR
|
|
11
|
-
async function loadAppManifest(root) {
|
|
5
|
+
export async function loadAppManifest(root) {
|
|
12
6
|
// TODO: error handling
|
|
13
|
-
const manifestPath =
|
|
14
|
-
const manifestData = await
|
|
7
|
+
const manifestPath = path.join(root, 'build', 'manifest.json');
|
|
8
|
+
const manifestData = await fs.readFile(manifestPath, { encoding: 'utf8' });
|
|
15
9
|
return JSON.parse(manifestData);
|
|
16
10
|
}
|
|
17
|
-
exports.loadAppManifest = loadAppManifest;
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.loadAppSchema = void 0;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const fs_1 = require("fs");
|
|
9
|
-
const promises_1 = __importDefault(require("fs/promises"));
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import fs from 'fs/promises';
|
|
10
4
|
// Loads and parses the schema file for a specific app
|
|
11
|
-
async function loadAppSchema(appRoot) {
|
|
12
|
-
const schemaPath =
|
|
5
|
+
export async function loadAppSchema(appRoot) {
|
|
6
|
+
const schemaPath = path.join(appRoot, 'schema.json');
|
|
13
7
|
let schema = null;
|
|
14
8
|
// TODO: error handling and reporting
|
|
15
|
-
if (
|
|
16
|
-
const file = await
|
|
9
|
+
if (existsSync(schemaPath)) {
|
|
10
|
+
const file = await fs.readFile(schemaPath, { encoding: 'utf-8' });
|
|
17
11
|
schema = JSON.parse(file);
|
|
18
12
|
}
|
|
19
13
|
return schema;
|
|
20
14
|
}
|
|
21
|
-
exports.loadAppSchema = loadAppSchema;
|