@nerest/nerest 0.0.7 → 0.0.8
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/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 +29 -36
- 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.js +15 -22
- package/dist/server/parts/k8s-probes.js +1 -5
- package/dist/server/parts/preview.js +1 -5
- 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 +85 -0
- package/package.json +4 -3
- package/server/development.ts +10 -10
- package/server/parts/apps.ts +4 -4
- package/server/parts/runtime-hook.ts +1 -1
- package/server/parts/validator.ts +5 -2
- package/server/production.ts +7 -7
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,18 @@
|
|
|
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 './parts/runtime-hook.js';
|
|
20
14
|
// eslint-disable-next-line max-statements
|
|
21
|
-
async function runDevelopmentServer() {
|
|
15
|
+
export async function runDevelopmentServer() {
|
|
22
16
|
const root = process.cwd();
|
|
23
17
|
// TODO: move build config into a separate file
|
|
24
18
|
// TODO: look at @vitejs/plugin-react (everything seems fine without it though)
|
|
@@ -55,15 +49,15 @@ async function runDevelopmentServer() {
|
|
|
55
49
|
await startClientBuildWatcher(config);
|
|
56
50
|
// Load app entries following the `apps/{name}/index.tsx` convention
|
|
57
51
|
// TODO: remove hardcoded port
|
|
58
|
-
const apps = await
|
|
52
|
+
const apps = await loadApps(root, 'http://0.0.0.0:3000/');
|
|
59
53
|
// Start vite server that will be rendering SSR components
|
|
60
|
-
const viteSsr = await
|
|
61
|
-
const app = (
|
|
54
|
+
const viteSsr = await createServer(config);
|
|
55
|
+
const app = fastify();
|
|
62
56
|
// Setup schema validation. We have to use our own ajv instance that
|
|
63
57
|
// we can use both to validate request bodies and examples against
|
|
64
58
|
// app schemas
|
|
65
|
-
app.setValidatorCompiler(({ schema }) =>
|
|
66
|
-
await
|
|
59
|
+
app.setValidatorCompiler(({ schema }) => validator.compile(schema));
|
|
60
|
+
await setupSwagger(app);
|
|
67
61
|
for (const appEntry of Object.values(apps)) {
|
|
68
62
|
const { name, entry, examples, schema } = appEntry;
|
|
69
63
|
const routeOptions = {};
|
|
@@ -88,7 +82,7 @@ async function runDevelopmentServer() {
|
|
|
88
82
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
89
83
|
fixStacktrace: true,
|
|
90
84
|
});
|
|
91
|
-
return
|
|
85
|
+
return renderApp({
|
|
92
86
|
name,
|
|
93
87
|
assets: appEntry.assets,
|
|
94
88
|
component: ssrComponent.default,
|
|
@@ -96,9 +90,9 @@ async function runDevelopmentServer() {
|
|
|
96
90
|
});
|
|
97
91
|
for (const [exampleName, example] of Object.entries(examples)) {
|
|
98
92
|
// Validate example against schema when specified
|
|
99
|
-
if (schema && !
|
|
93
|
+
if (schema && !validator.validate(schema, example)) {
|
|
100
94
|
// TODO: use logger and display errors more prominently
|
|
101
|
-
console.error(`Example "${exampleName}" of app "${name}" does not satisfy schema: ${
|
|
95
|
+
console.error(`Example "${exampleName}" of app "${name}" does not satisfy schema: ${validator.errorsText()}`);
|
|
102
96
|
}
|
|
103
97
|
// GET /api/{name}/examples/{example} -> render a preview page
|
|
104
98
|
// with a predefined example body
|
|
@@ -113,24 +107,24 @@ async function runDevelopmentServer() {
|
|
|
113
107
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
114
108
|
fixStacktrace: true,
|
|
115
109
|
});
|
|
116
|
-
const { html, assets } =
|
|
110
|
+
const { html, assets } = renderApp({
|
|
117
111
|
name,
|
|
118
112
|
assets: appEntry.assets,
|
|
119
113
|
component: ssrComponent.default,
|
|
120
114
|
}, example);
|
|
121
115
|
reply.type('text/html');
|
|
122
|
-
return
|
|
116
|
+
return renderPreviewPage(html, assets);
|
|
123
117
|
});
|
|
124
118
|
}
|
|
125
119
|
}
|
|
126
120
|
// Add graceful shutdown handler to prevent requests errors
|
|
127
|
-
await app.register(
|
|
121
|
+
await app.register(fastifyGracefulShutdown);
|
|
128
122
|
if (process.env.ENABLE_K8S_PROBES) {
|
|
129
|
-
await
|
|
123
|
+
await setupK8SProbes(app);
|
|
130
124
|
}
|
|
131
125
|
// TODO: only do this locally, load from CDN in production
|
|
132
|
-
await app.register(
|
|
133
|
-
root:
|
|
126
|
+
await app.register(fastifyStatic, {
|
|
127
|
+
root: path.join(root, 'build'),
|
|
134
128
|
// TODO: maybe use @fastify/cors instead
|
|
135
129
|
setHeaders(res) {
|
|
136
130
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
@@ -139,7 +133,7 @@ async function runDevelopmentServer() {
|
|
|
139
133
|
},
|
|
140
134
|
});
|
|
141
135
|
// Execute runtime hook in nerest-runtime.ts if it exists
|
|
142
|
-
await
|
|
136
|
+
await runRuntimeHook(app, () => viteSsr.ssrLoadModule('/nerest-runtime.ts'));
|
|
143
137
|
// TODO: remove hardcoded port
|
|
144
138
|
await app.listen({
|
|
145
139
|
host: '0.0.0.0',
|
|
@@ -147,10 +141,9 @@ async function runDevelopmentServer() {
|
|
|
147
141
|
});
|
|
148
142
|
console.log('Nerest is listening on 0.0.0.0:3000');
|
|
149
143
|
}
|
|
150
|
-
exports.runDevelopmentServer = runDevelopmentServer;
|
|
151
144
|
// TODO: this should probably be moved from here
|
|
152
145
|
async function startClientBuildWatcher(config) {
|
|
153
|
-
const watcher = (await
|
|
146
|
+
const watcher = (await build(config));
|
|
154
147
|
return new Promise((resolve) => {
|
|
155
148
|
// We need to have a built manifest.json to provide assets
|
|
156
149
|
// links in SSR. We will wait for rollup to report when it
|
|
@@ -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;
|
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
-
const assets_1 = require("../loaders/assets");
|
|
10
|
-
const examples_1 = require("../loaders/examples");
|
|
11
|
-
const schema_1 = require("../loaders/schema");
|
|
12
|
-
const manifest_1 = require("../loaders/manifest");
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { loadAppAssets } from '../loaders/assets.js';
|
|
4
|
+
import { loadAppExamples } from '../loaders/examples.js';
|
|
5
|
+
import { loadAppSchema } from '../loaders/schema.js';
|
|
6
|
+
import { loadAppManifest } from '../loaders/manifest.js';
|
|
13
7
|
// Build the record of the available apps by convention
|
|
14
8
|
// apps -> /apps/{name}/index.tsx
|
|
15
9
|
// examples -> /apps/{name}/examples/{example}.json
|
|
16
|
-
async function loadApps(root, deployedStaticPath) {
|
|
17
|
-
const appsRoot =
|
|
18
|
-
const manifest = await
|
|
19
|
-
const appsDirs = (await
|
|
10
|
+
export async function loadApps(root, deployedStaticPath) {
|
|
11
|
+
const appsRoot = path.join(root, 'apps');
|
|
12
|
+
const manifest = await loadAppManifest(root);
|
|
13
|
+
const appsDirs = (await fs.readdir(appsRoot, { withFileTypes: true }))
|
|
20
14
|
.filter((d) => d.isDirectory())
|
|
21
15
|
.map((d) => d.name);
|
|
22
16
|
const apps = [];
|
|
@@ -25,19 +19,18 @@ async function loadApps(root, deployedStaticPath) {
|
|
|
25
19
|
}
|
|
26
20
|
return Object.fromEntries(apps);
|
|
27
21
|
}
|
|
28
|
-
exports.loadApps = loadApps;
|
|
29
22
|
async function loadApp(appsRoot, name, manifest, deployedStaticPath) {
|
|
30
23
|
// TODO: report problems with loading entries, assets and/or examples
|
|
31
|
-
const appRoot =
|
|
24
|
+
const appRoot = path.join(appsRoot, name);
|
|
32
25
|
return [
|
|
33
26
|
name,
|
|
34
27
|
{
|
|
35
28
|
name,
|
|
36
29
|
root: appRoot,
|
|
37
|
-
entry:
|
|
38
|
-
assets:
|
|
39
|
-
examples: await
|
|
40
|
-
schema: await
|
|
30
|
+
entry: path.join(appRoot, 'index.tsx'),
|
|
31
|
+
assets: loadAppAssets(name, manifest, deployedStaticPath),
|
|
32
|
+
examples: await loadAppExamples(appRoot),
|
|
33
|
+
schema: await loadAppSchema(appRoot),
|
|
41
34
|
},
|
|
42
35
|
];
|
|
43
36
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setupK8SProbes = void 0;
|
|
4
1
|
// Setup routes for k8s probes to check if application is live
|
|
5
|
-
async function setupK8SProbes(app) {
|
|
2
|
+
export async function setupK8SProbes(app) {
|
|
6
3
|
// Handler for graceful shutdowns
|
|
7
4
|
// K8s can initiate shutdown at any moment: on pods restart or on deploy.
|
|
8
5
|
// So, if we receive shutdown request, we:
|
|
@@ -28,4 +25,3 @@ async function setupK8SProbes(app) {
|
|
|
28
25
|
}
|
|
29
26
|
});
|
|
30
27
|
}
|
|
31
|
-
exports.setupK8SProbes = setupK8SProbes;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.renderPreviewPage = void 0;
|
|
4
1
|
// Renders the preview page available by convention at /api/{name}/examples/{example}
|
|
5
|
-
function renderPreviewPage(html, assets) {
|
|
2
|
+
export function renderPreviewPage(html, assets) {
|
|
6
3
|
const { scripts, styles } = mapAssets(assets);
|
|
7
4
|
return `
|
|
8
5
|
<html>
|
|
@@ -16,7 +13,6 @@ function renderPreviewPage(html, assets) {
|
|
|
16
13
|
</html>
|
|
17
14
|
`;
|
|
18
15
|
}
|
|
19
|
-
exports.renderPreviewPage = renderPreviewPage;
|
|
20
16
|
function mapAssets(assets) {
|
|
21
17
|
// TODO: script type="module" is not supported by older browsers
|
|
22
18
|
// but vite doesn't provide `nomodule` fallback by default
|
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.renderApp = void 0;
|
|
7
|
-
const react_1 = __importDefault(require("react"));
|
|
8
|
-
const server_1 = require("react-dom/server");
|
|
9
|
-
const nanoid_1 = require("nanoid");
|
|
10
|
-
function renderApp({ name, assets, component }, props = {}) {
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderToString } from 'react-dom/server';
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
export function renderApp({ name, assets, component }, props = {}) {
|
|
11
5
|
const html = renderSsrComponent(name, component, props);
|
|
12
6
|
return { html, assets };
|
|
13
7
|
}
|
|
14
|
-
exports.renderApp = renderApp;
|
|
15
8
|
function renderSsrComponent(appName, appComponent, props) {
|
|
16
|
-
const html =
|
|
9
|
+
const html = renderToString(React.createElement(appComponent, props));
|
|
17
10
|
// There may be multiple instances of the same app on the page,
|
|
18
11
|
// so we will use a randomized id to avoid collisions
|
|
19
|
-
const appId =
|
|
12
|
+
const appId = nanoid();
|
|
20
13
|
// data-app-name and data-app-id are used by client entrypoint to hydrate
|
|
21
14
|
// apps using correct serialized props
|
|
22
15
|
const container = `<div data-app-name="${appName}" data-app-id="${appId}">${html}</div>`;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { FastifyInstance } from 'fastify';
|
|
2
|
-
export declare function runRuntimeHook(app: FastifyInstance, loader: () => Promise<
|
|
2
|
+
export declare function runRuntimeHook(app: FastifyInstance, loader: () => Promise<unknown>): Promise<void>;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runRuntimeHook = void 0;
|
|
4
1
|
// Load the runtime hook module and run it if it exists, passing down our
|
|
5
2
|
// fastify instance. This hook can be used to modify fastify settings, add
|
|
6
3
|
// plugins or routes on an individual app level.
|
|
7
|
-
async function runRuntimeHook(app, loader) {
|
|
4
|
+
export async function runRuntimeHook(app, loader) {
|
|
8
5
|
let module;
|
|
9
6
|
try {
|
|
10
7
|
module = (await loader());
|
|
@@ -29,4 +26,3 @@ async function runRuntimeHook(app, loader) {
|
|
|
29
26
|
console.log('Runtime hook not found, skipping...');
|
|
30
27
|
}
|
|
31
28
|
}
|
|
32
|
-
exports.runRuntimeHook = runRuntimeHook;
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.setupSwagger = void 0;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const swagger_1 = __importDefault(require("@fastify/swagger"));
|
|
10
|
-
const swagger_ui_1 = __importDefault(require("@fastify/swagger-ui"));
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import fastifySwagger from '@fastify/swagger';
|
|
4
|
+
import fastifySwaggerUi from '@fastify/swagger-ui';
|
|
11
5
|
// Setup automatic OpenAPI specification compilation and enable
|
|
12
6
|
// Swagger UI at the `/api` route
|
|
13
|
-
async function setupSwagger(app) {
|
|
7
|
+
export async function setupSwagger(app) {
|
|
14
8
|
let appInfo = {};
|
|
15
9
|
try {
|
|
16
|
-
const packageJson =
|
|
10
|
+
const packageJson = fs.readFileSync(path.join(process.cwd(), 'package.json'), { encoding: 'utf-8' });
|
|
17
11
|
appInfo = JSON.parse(packageJson);
|
|
18
12
|
}
|
|
19
13
|
catch (e) {
|
|
@@ -24,7 +18,7 @@ async function setupSwagger(app) {
|
|
|
24
18
|
(typeof appInfo.repository === 'string'
|
|
25
19
|
? appInfo.repository
|
|
26
20
|
: appInfo.repository?.url);
|
|
27
|
-
await app.register(
|
|
21
|
+
await app.register(fastifySwagger, {
|
|
28
22
|
openapi: {
|
|
29
23
|
info: {
|
|
30
24
|
title: appInfo.name ?? 'Nerest micro frontend',
|
|
@@ -43,8 +37,7 @@ async function setupSwagger(app) {
|
|
|
43
37
|
},
|
|
44
38
|
},
|
|
45
39
|
});
|
|
46
|
-
await app.register(
|
|
40
|
+
await app.register(fastifySwaggerUi, {
|
|
47
41
|
routePrefix: '/api',
|
|
48
42
|
});
|
|
49
43
|
}
|
|
50
|
-
exports.setupSwagger = setupSwagger;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import Ajv from 'ajv';
|
|
2
|
-
export declare const validator: Ajv;
|
|
2
|
+
export declare const validator: Ajv.default;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const fast_uri_1 = __importDefault(require("fast-uri"));
|
|
9
|
-
const ajv_formats_1 = __importDefault(require("ajv-formats"));
|
|
10
|
-
exports.validator = new ajv_1.default({
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import fastUri from 'fast-uri';
|
|
3
|
+
import addFormats from 'ajv-formats';
|
|
4
|
+
// Ajv default export is broken, so we have to specify `.default`
|
|
5
|
+
// manually: https://github.com/ajv-validator/ajv/issues/2132
|
|
6
|
+
// eslint-disable-next-line new-cap
|
|
7
|
+
export const validator = new Ajv.default({
|
|
11
8
|
coerceTypes: 'array',
|
|
12
9
|
useDefaults: true,
|
|
13
10
|
removeAdditional: true,
|
|
14
|
-
uriResolver:
|
|
11
|
+
uriResolver: fastUri,
|
|
15
12
|
addUsedSchema: false,
|
|
16
13
|
// Explicitly set allErrors to `false`.
|
|
17
14
|
// When set to `true`, a DoS attack is possible.
|
|
@@ -20,4 +17,4 @@ exports.validator = new ajv_1.default({
|
|
|
20
17
|
// Support additional type formats in JSON schema like `date`,
|
|
21
18
|
// `email`, `url`, etc. Used by default in fastify
|
|
22
19
|
// https://www.npmjs.com/package/ajv-formats
|
|
23
|
-
|
|
20
|
+
addFormats.default(validator);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// This is the nerest production server entrypoint
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import fastify from 'fastify';
|
|
5
|
+
import fastifyGracefulShutdown from 'fastify-graceful-shutdown';
|
|
6
|
+
import { renderApp } from './parts/render.js';
|
|
7
|
+
import { setupSwagger } from './parts/swagger.js';
|
|
8
|
+
import { validator } from './parts/validator.js';
|
|
9
|
+
import { renderPreviewPage } from './parts/preview.js';
|
|
10
|
+
import { setupK8SProbes } from './parts/k8s-probes.js';
|
|
11
|
+
import { runRuntimeHook } from './parts/runtime-hook.js';
|
|
12
|
+
// TODO: refactor to merge the similar parts between production and development server?
|
|
13
|
+
async function runProductionServer() {
|
|
14
|
+
const root = process.cwd();
|
|
15
|
+
// TODO: error handling for file reading
|
|
16
|
+
const apps = JSON.parse(await fs.readFile(path.join(root, 'build/nerest-manifest.json'), {
|
|
17
|
+
encoding: 'utf-8',
|
|
18
|
+
}));
|
|
19
|
+
// TODO: fix client-side vite types
|
|
20
|
+
const components = import.meta.glob('/apps/*/index.tsx', {
|
|
21
|
+
import: 'default',
|
|
22
|
+
eager: true,
|
|
23
|
+
});
|
|
24
|
+
const app = fastify();
|
|
25
|
+
// Setup schema validation. We have to use our own ajv instance that
|
|
26
|
+
// we can use both to validate request bodies and examples against
|
|
27
|
+
// app schemas
|
|
28
|
+
app.setValidatorCompiler(({ schema }) => validator.compile(schema));
|
|
29
|
+
await setupSwagger(app);
|
|
30
|
+
for (const appEntry of Object.values(apps)) {
|
|
31
|
+
const { name, examples, schema, assets } = appEntry;
|
|
32
|
+
const component = components[`/apps/${name}/index.tsx`];
|
|
33
|
+
const routeOptions = {};
|
|
34
|
+
// TODO: report error if schema is missing, unless this app is client-only
|
|
35
|
+
// TODO: disallow apps without schemas in production build
|
|
36
|
+
if (schema) {
|
|
37
|
+
routeOptions.schema = {
|
|
38
|
+
// Use description as Swagger summary, since summary is visible
|
|
39
|
+
// even when the route is collapsed in the UI
|
|
40
|
+
summary: schema.description,
|
|
41
|
+
// TODO: do we need to mix in examples like in the development server?
|
|
42
|
+
body: schema,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// POST /api/{name} -> render app with request.body as props
|
|
46
|
+
app.post(`/api/${name}`, routeOptions, (request) => renderApp({ name, assets, component }, request.body));
|
|
47
|
+
for (const [exampleName, example] of Object.entries(examples)) {
|
|
48
|
+
// GET /api/{name}/examples/{example} -> render a preview page
|
|
49
|
+
// with a predefined example body
|
|
50
|
+
const exampleRoute = `/api/${name}/examples/${exampleName}`;
|
|
51
|
+
app.get(exampleRoute, {
|
|
52
|
+
schema: {
|
|
53
|
+
// Add a clickable link to the example route in route's Swagger
|
|
54
|
+
// description so it's easier to navigate to
|
|
55
|
+
description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
|
|
56
|
+
},
|
|
57
|
+
}, async (_, reply) => {
|
|
58
|
+
const { html, assets: outAssets } = renderApp({
|
|
59
|
+
name,
|
|
60
|
+
assets,
|
|
61
|
+
component,
|
|
62
|
+
}, example);
|
|
63
|
+
reply.type('text/html');
|
|
64
|
+
return renderPreviewPage(html, outAssets);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Add graceful shutdown handler to prevent requests errors
|
|
69
|
+
await app.register(fastifyGracefulShutdown);
|
|
70
|
+
if (process.env.ENABLE_K8S_PROBES) {
|
|
71
|
+
await setupK8SProbes(app);
|
|
72
|
+
}
|
|
73
|
+
// Execute runtime hook in nerest-runtime.ts if it exists
|
|
74
|
+
await runRuntimeHook(app, async () => {
|
|
75
|
+
const glob = import.meta.glob('/nerest-runtime.ts', { eager: true });
|
|
76
|
+
return glob['/nerest-runtime.ts'];
|
|
77
|
+
});
|
|
78
|
+
// TODO: remove hardcoded port
|
|
79
|
+
await app.listen({
|
|
80
|
+
host: '0.0.0.0',
|
|
81
|
+
port: 3000,
|
|
82
|
+
});
|
|
83
|
+
console.log('Nerest is listening on 0.0.0.0:3000');
|
|
84
|
+
}
|
|
85
|
+
runProductionServer();
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerest/nerest",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "React micro frontend framework",
|
|
5
5
|
"homepage": "https://github.com/nerestjs/nerest#readme",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/nerestjs/nerest.git"
|
|
9
9
|
},
|
|
10
|
+
"type": "module",
|
|
10
11
|
"bin": {
|
|
11
12
|
"nerest": "dist/bin/index.js"
|
|
12
13
|
},
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
"fast-uri": "^2.3.0",
|
|
62
63
|
"fastify": "^4.24.3",
|
|
63
64
|
"fastify-graceful-shutdown": "^3.5.1",
|
|
64
|
-
"nanoid": "^
|
|
65
|
+
"nanoid": "^5.0.2",
|
|
65
66
|
"vite": "^4.5.0",
|
|
66
67
|
"vite-plugin-externals": "^0.6.2"
|
|
67
68
|
},
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
"@tinkoff/eslint-config": "^1.54.4",
|
|
70
71
|
"@tinkoff/eslint-config-react": "^1.54.4",
|
|
71
72
|
"@tinkoff/prettier-config": "^1.52.1",
|
|
72
|
-
"@types/react": "^18.2.
|
|
73
|
+
"@types/react": "^18.2.34",
|
|
73
74
|
"@types/react-dom": "^18.2.14",
|
|
74
75
|
"jest": "^29.7.0",
|
|
75
76
|
"json-schema-to-typescript": "^13.1.1",
|
package/server/development.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import type { ServerResponse } from 'http';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { build, createServer } from 'vite';
|
|
6
6
|
import type { InlineConfig } from 'vite';
|
|
7
7
|
import type { RollupWatcher, RollupWatcherEvent } from 'rollup';
|
|
8
8
|
|
|
@@ -11,13 +11,13 @@ import fastify from 'fastify';
|
|
|
11
11
|
import fastifyStatic from '@fastify/static';
|
|
12
12
|
import fastifyGracefulShutdown from 'fastify-graceful-shutdown';
|
|
13
13
|
|
|
14
|
-
import { loadApps } from './parts/apps';
|
|
15
|
-
import { renderApp } from './parts/render';
|
|
16
|
-
import { renderPreviewPage } from './parts/preview';
|
|
17
|
-
import { validator } from './parts/validator';
|
|
18
|
-
import { setupSwagger } from './parts/swagger';
|
|
19
|
-
import { setupK8SProbes } from './parts/k8s-probes';
|
|
20
|
-
import { runRuntimeHook } from './parts/runtime-hook';
|
|
14
|
+
import { loadApps } from './parts/apps.js';
|
|
15
|
+
import { renderApp } from './parts/render.js';
|
|
16
|
+
import { renderPreviewPage } from './parts/preview.js';
|
|
17
|
+
import { validator } from './parts/validator.js';
|
|
18
|
+
import { setupSwagger } from './parts/swagger.js';
|
|
19
|
+
import { setupK8SProbes } from './parts/k8s-probes.js';
|
|
20
|
+
import { runRuntimeHook } from './parts/runtime-hook.js';
|
|
21
21
|
|
|
22
22
|
// eslint-disable-next-line max-statements
|
|
23
23
|
export async function runDevelopmentServer() {
|
|
@@ -63,7 +63,7 @@ export async function runDevelopmentServer() {
|
|
|
63
63
|
const apps = await loadApps(root, 'http://0.0.0.0:3000/');
|
|
64
64
|
|
|
65
65
|
// Start vite server that will be rendering SSR components
|
|
66
|
-
const viteSsr = await
|
|
66
|
+
const viteSsr = await createServer(config);
|
|
67
67
|
const app = fastify();
|
|
68
68
|
|
|
69
69
|
// Setup schema validation. We have to use our own ajv instance that
|
|
@@ -187,7 +187,7 @@ export async function runDevelopmentServer() {
|
|
|
187
187
|
|
|
188
188
|
// TODO: this should probably be moved from here
|
|
189
189
|
async function startClientBuildWatcher(config: InlineConfig) {
|
|
190
|
-
const watcher = (await
|
|
190
|
+
const watcher = (await build(config)) as RollupWatcher;
|
|
191
191
|
return new Promise<void>((resolve) => {
|
|
192
192
|
// We need to have a built manifest.json to provide assets
|
|
193
193
|
// links in SSR. We will wait for rollup to report when it
|
package/server/parts/apps.ts
CHANGED
|
@@ -2,10 +2,10 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import type { Manifest } from 'vite';
|
|
4
4
|
|
|
5
|
-
import { loadAppAssets } from '../loaders/assets';
|
|
6
|
-
import { loadAppExamples } from '../loaders/examples';
|
|
7
|
-
import { loadAppSchema } from '../loaders/schema';
|
|
8
|
-
import { loadAppManifest } from '../loaders/manifest';
|
|
5
|
+
import { loadAppAssets } from '../loaders/assets.js';
|
|
6
|
+
import { loadAppExamples } from '../loaders/examples.js';
|
|
7
|
+
import { loadAppSchema } from '../loaders/schema.js';
|
|
8
|
+
import { loadAppManifest } from '../loaders/manifest.js';
|
|
9
9
|
|
|
10
10
|
export type AppEntry = {
|
|
11
11
|
name: string;
|
|
@@ -9,7 +9,7 @@ type RuntimeHookModule = {
|
|
|
9
9
|
// plugins or routes on an individual app level.
|
|
10
10
|
export async function runRuntimeHook(
|
|
11
11
|
app: FastifyInstance,
|
|
12
|
-
loader: () => Promise<
|
|
12
|
+
loader: () => Promise<unknown>
|
|
13
13
|
) {
|
|
14
14
|
let module: RuntimeHookModule | undefined;
|
|
15
15
|
|
|
@@ -2,7 +2,10 @@ import Ajv from 'ajv';
|
|
|
2
2
|
import fastUri from 'fast-uri';
|
|
3
3
|
import addFormats from 'ajv-formats';
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
// Ajv default export is broken, so we have to specify `.default`
|
|
6
|
+
// manually: https://github.com/ajv-validator/ajv/issues/2132
|
|
7
|
+
// eslint-disable-next-line new-cap
|
|
8
|
+
export const validator = new Ajv.default({
|
|
6
9
|
coerceTypes: 'array',
|
|
7
10
|
useDefaults: true,
|
|
8
11
|
removeAdditional: true,
|
|
@@ -16,4 +19,4 @@ export const validator = new Ajv({
|
|
|
16
19
|
// Support additional type formats in JSON schema like `date`,
|
|
17
20
|
// `email`, `url`, etc. Used by default in fastify
|
|
18
21
|
// https://www.npmjs.com/package/ajv-formats
|
|
19
|
-
addFormats(validator);
|
|
22
|
+
addFormats.default(validator);
|
package/server/production.ts
CHANGED
|
@@ -6,13 +6,13 @@ import fastify from 'fastify';
|
|
|
6
6
|
import fastifyGracefulShutdown from 'fastify-graceful-shutdown';
|
|
7
7
|
import type { RouteShorthandOptions } from 'fastify';
|
|
8
8
|
|
|
9
|
-
import type { AppEntry } from './parts/apps';
|
|
10
|
-
import { renderApp } from './parts/render';
|
|
11
|
-
import { setupSwagger } from './parts/swagger';
|
|
12
|
-
import { validator } from './parts/validator';
|
|
13
|
-
import { renderPreviewPage } from './parts/preview';
|
|
14
|
-
import { setupK8SProbes } from './parts/k8s-probes';
|
|
15
|
-
import { runRuntimeHook } from './parts/runtime-hook';
|
|
9
|
+
import type { AppEntry } from './parts/apps.js';
|
|
10
|
+
import { renderApp } from './parts/render.js';
|
|
11
|
+
import { setupSwagger } from './parts/swagger.js';
|
|
12
|
+
import { validator } from './parts/validator.js';
|
|
13
|
+
import { renderPreviewPage } from './parts/preview.js';
|
|
14
|
+
import { setupK8SProbes } from './parts/k8s-probes.js';
|
|
15
|
+
import { runRuntimeHook } from './parts/runtime-hook.js';
|
|
16
16
|
|
|
17
17
|
// TODO: refactor to merge the similar parts between production and development server?
|
|
18
18
|
async function runProductionServer() {
|