@nerest/nerest 0.0.3 → 0.0.5

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.
Files changed (60) hide show
  1. package/README.md +38 -3
  2. package/bin/build.ts +8 -0
  3. package/bin/index.ts +7 -1
  4. package/bin/watch.ts +2 -11
  5. package/build/index.ts +82 -0
  6. package/dist/bin/build.d.ts +1 -0
  7. package/dist/bin/build.js +11 -0
  8. package/dist/bin/index.d.ts +1 -1
  9. package/dist/bin/index.js +7 -1
  10. package/dist/bin/watch.js +2 -8
  11. package/dist/build/index.d.ts +1 -0
  12. package/dist/build/index.js +72 -0
  13. package/dist/server/development.d.ts +1 -0
  14. package/dist/server/{index.js → development.js} +46 -21
  15. package/dist/server/loaders/assets.d.ts +2 -0
  16. package/dist/server/loaders/assets.js +14 -0
  17. package/dist/server/loaders/manifest.d.ts +1 -0
  18. package/dist/server/loaders/manifest.js +17 -0
  19. package/dist/server/{apps.d.ts → parts/apps.d.ts} +1 -1
  20. package/dist/server/{apps.js → parts/apps.js} +9 -16
  21. package/dist/server/parts/k8s-probes.d.ts +2 -0
  22. package/dist/server/parts/k8s-probes.js +31 -0
  23. package/dist/server/parts/render.d.ts +11 -0
  24. package/dist/server/{render.js → parts/render.js} +2 -3
  25. package/package.json +18 -15
  26. package/server/{index.ts → development.ts} +47 -21
  27. package/server/loaders/assets.ts +19 -0
  28. package/server/loaders/manifest.ts +11 -0
  29. package/server/{apps.ts → parts/apps.ts} +10 -17
  30. package/server/parts/k8s-probes.ts +30 -0
  31. package/server/{render.ts → parts/render.ts} +7 -5
  32. package/server/production.ts +115 -0
  33. package/dist/client/entry.d.ts +0 -1
  34. package/dist/client/entry.js +0 -3
  35. package/dist/server/app-parts/assets.d.ts +0 -2
  36. package/dist/server/app-parts/assets.js +0 -15
  37. package/dist/server/app-parts/preview.js +0 -27
  38. package/dist/server/assets.d.ts +0 -1
  39. package/dist/server/assets.js +0 -28
  40. package/dist/server/entry.d.ts +0 -2
  41. package/dist/server/entry.js +0 -17
  42. package/dist/server/index.d.ts +0 -5
  43. package/dist/server/preview.d.ts +0 -1
  44. package/dist/server/render.d.ts +0 -6
  45. package/server/app-parts/assets.ts +0 -17
  46. /package/dist/server/{app-parts → loaders}/examples.d.ts +0 -0
  47. /package/dist/server/{app-parts → loaders}/examples.js +0 -0
  48. /package/dist/server/{app-parts → loaders}/schema.d.ts +0 -0
  49. /package/dist/server/{app-parts → loaders}/schema.js +0 -0
  50. /package/dist/server/{app-parts → parts}/preview.d.ts +0 -0
  51. /package/dist/server/{preview.js → parts/preview.js} +0 -0
  52. /package/dist/server/{swagger.d.ts → parts/swagger.d.ts} +0 -0
  53. /package/dist/server/{swagger.js → parts/swagger.js} +0 -0
  54. /package/dist/server/{validator.d.ts → parts/validator.d.ts} +0 -0
  55. /package/dist/server/{validator.js → parts/validator.js} +0 -0
  56. /package/server/{app-parts → loaders}/examples.ts +0 -0
  57. /package/server/{app-parts → loaders}/schema.ts +0 -0
  58. /package/server/{preview.ts → parts/preview.ts} +0 -0
  59. /package/server/{swagger.ts → parts/swagger.ts} +0 -0
  60. /package/server/{validator.ts → parts/validator.ts} +0 -0
package/README.md CHANGED
@@ -6,21 +6,26 @@ React micro frontend framework
6
6
 
7
7
  ## Installation
8
8
 
9
+ TODO: update package
10
+
9
11
  ```
10
12
  npm i --save @nerest/nerest react react-dom
11
13
  ```
12
14
 
13
15
  ## Points of Interest
14
16
 
15
- - SSR server entry: [server/index.ts](server/index.ts)
17
+ - SSR server entry:
18
+ - Development: [server/development.ts](server/development.ts)
19
+ - Production: [server/production.ts](server/production.ts)
16
20
  - Hydrating client entry: [client/index.ts](client/index.ts)
17
21
  - CLI entry: [bin/index.ts](bin/index.ts)
22
+ - Production build script: [build/index.ts](build/index.ts)
18
23
 
19
24
  ## Conventions
20
25
 
21
26
  The `apps` directory must contain all of the apps provided by the micro frontend. E.g. `/apps/foo/index.tsx` is the entrypoint component of the `foo` app. It becomes available as the `/api/foo` route of the micro frontend server.
22
27
 
23
- See [nerest-harness](https://github.com/nerestjs/harness) for the minimal example of a nerest micro frontend.
28
+ See [nerest-harness](https://gitlab.tcsbank.ru/tj/nerest-harness) for the minimal example of a nerest micro frontend.
24
29
 
25
30
  ### Examples (`/examples/*.json`)
26
31
 
@@ -32,6 +37,36 @@ The app directory should contain a `schema.json` file that describes the schema
32
37
 
33
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`.
34
39
 
40
+ ## Configuration
41
+
42
+ 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.
43
+
44
+ ### Environment Variables
45
+
46
+ #### Buildtime
47
+
48
+ - `NEREST_STATIC_PATH` is required for production build and must contain the URL of where the client static assets will be deployed. It lets the server-side renderer return their paths in the assets field of the response.
49
+
50
+ #### Runtime
51
+
52
+ ##### Client
53
+
54
+ All environment variables prefixed with `NEREST_` will be bundled with your app during buildtime. You can access them in the code using the special `import.meta.env` object. For example, `import.meta.env.NEREST_SOMEVAR` will be statically replaced during buildtime with the value of this environment variable on the build machine.
55
+
56
+ ##### Server
57
+
58
+ Additional environment variables which can be setup for a runtime:
59
+
60
+ - `ENABLE_K8S_PROBES`: activates additional routes `/livenessProbe` and `/readinessProbe` to check application status
61
+
62
+ ### JSON Configuration
63
+
64
+ TODO
65
+
66
+ ### Runtime Hooks
67
+
68
+ TODO
69
+
35
70
  ## Development
36
71
 
37
72
  Run the build script to build the framework.
@@ -41,4 +76,4 @@ npm install
41
76
  npm run build
42
77
  ```
43
78
 
44
- Use [nerest-harness](https://github.com/nerestjs/harness) with `npm link` to test changes locally.
79
+ Use [nerest-harness](https://gitlab.tcsbank.ru/tj/nerest-harness) to test changes locally.
package/bin/build.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { buildMicroFrontend } from '../build';
2
+
3
+ // Produce the production build of the Nerest micro frontend
4
+ export async function build() {
5
+ console.log('Nerest is preparing production build...');
6
+ await buildMicroFrontend();
7
+ console.log('Nerest build is finished');
8
+ }
package/bin/index.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  // All executions of `nerest <command>` get routed through here
3
+ import 'dotenv/config';
4
+
5
+ import { build } from './build';
3
6
  import { watch } from './watch';
4
7
 
8
+ // TODO: add CLI help and manual, maybe use a CLI framework like oclif
5
9
  async function cliEntry(args: string[]) {
6
- if (args[0] === 'watch') {
10
+ if (args[0] === 'build') {
11
+ await build();
12
+ } else if (args[0] === 'watch') {
7
13
  await watch();
8
14
  }
9
15
  }
package/bin/watch.ts CHANGED
@@ -1,18 +1,9 @@
1
- import { createServer } from '../server';
1
+ import { runDevelopmentServer } from '../server/development';
2
2
 
3
3
  // Start dev server in watch mode, that restarts on file change
4
4
  // and rebuilds the client static files
5
5
  export async function watch() {
6
6
  // TODO: will be replaced with nerest logger
7
7
  console.log('Starting Nerest watch...');
8
-
9
- const { app } = await createServer();
10
-
11
- // TODO: remove hardcoded port
12
- await app.listen({
13
- host: '0.0.0.0',
14
- port: 3000,
15
- });
16
-
17
- console.log('Nerest is listening on 0.0.0.0:3000');
8
+ await runDevelopmentServer();
18
9
  }
package/build/index.ts ADDED
@@ -0,0 +1,82 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+
4
+ import vite from 'vite';
5
+ import type { InlineConfig } from 'vite';
6
+
7
+ import { loadApps } from '../server/parts/apps';
8
+
9
+ export async function buildMicroFrontend() {
10
+ const root = process.cwd();
11
+ const staticPath = process.env.NEREST_STATIC_PATH;
12
+
13
+ // TODO: The path where the client files are deployed is built-in during
14
+ // the initial build, but the client scripts aren't using it, so maybe it should
15
+ // be a runtime env variable for the server instead?
16
+ if (!staticPath) {
17
+ throw new Error(
18
+ 'NEREST_STATIC_PATH environment variable is not set but is required for the production build'
19
+ );
20
+ }
21
+
22
+ // Build client
23
+ // TODO: extract shared parts between build/index.ts and server/index.ts
24
+ // into a shared config
25
+ const clientConfig: InlineConfig = {
26
+ root,
27
+ appType: 'custom',
28
+ envPrefix: 'NEREST_',
29
+ build: {
30
+ // Manifest is needed to report used assets in SSR handles
31
+ manifest: true,
32
+ modulePreload: false,
33
+ rollupOptions: {
34
+ input: '/node_modules/@nerest/nerest/client/index.ts',
35
+ output: {
36
+ dir: 'build',
37
+ entryFileNames: `client/assets/[name].js`,
38
+ chunkFileNames: `client/assets/[name].js`,
39
+ assetFileNames: `client/assets/[name].[ext]`,
40
+ },
41
+ },
42
+ },
43
+ };
44
+
45
+ console.log('Producing production client build...');
46
+ await vite.build(clientConfig);
47
+
48
+ console.log('Producing Nerest manifest file...');
49
+ await buildAppsManifest(root, staticPath);
50
+
51
+ // Build server using the client manifest
52
+ const serverConfig: InlineConfig = {
53
+ root,
54
+ appType: 'custom',
55
+ envPrefix: 'NEREST_',
56
+ build: {
57
+ emptyOutDir: false,
58
+ modulePreload: false,
59
+ // This is an important setting for producing a server build
60
+ ssr: true,
61
+ rollupOptions: {
62
+ input: '/node_modules/@nerest/nerest/server/production.ts',
63
+ output: {
64
+ dir: 'build',
65
+ entryFileNames: `server.mjs`,
66
+ },
67
+ },
68
+ },
69
+ };
70
+
71
+ console.log('Producing production server build...');
72
+ await vite.build(serverConfig);
73
+ }
74
+
75
+ async function buildAppsManifest(root: string, staticPath: string) {
76
+ const apps = await loadApps(root, staticPath);
77
+ await fs.writeFile(
78
+ path.join(root, 'build/nerest-manifest.json'),
79
+ JSON.stringify(apps),
80
+ { encoding: 'utf-8' }
81
+ );
82
+ }
@@ -0,0 +1 @@
1
+ export declare function build(): Promise<void>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.build = void 0;
4
+ const build_1 = require("../build");
5
+ // Produce the production build of the Nerest micro frontend
6
+ async function build() {
7
+ console.log('Nerest is preparing production build...');
8
+ await (0, build_1.buildMicroFrontend)();
9
+ console.log('Nerest build is finished');
10
+ }
11
+ exports.build = build;
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import 'dotenv/config';
package/dist/bin/index.js CHANGED
@@ -2,9 +2,15 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  // All executions of `nerest <command>` get routed through here
5
+ require("dotenv/config");
6
+ const build_1 = require("./build");
5
7
  const watch_1 = require("./watch");
8
+ // TODO: add CLI help and manual, maybe use a CLI framework like oclif
6
9
  async function cliEntry(args) {
7
- if (args[0] === 'watch') {
10
+ if (args[0] === 'build') {
11
+ await (0, build_1.build)();
12
+ }
13
+ else if (args[0] === 'watch') {
8
14
  await (0, watch_1.watch)();
9
15
  }
10
16
  }
package/dist/bin/watch.js CHANGED
@@ -1,18 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.watch = void 0;
4
- const server_1 = require("../server");
4
+ const development_1 = require("../server/development");
5
5
  // Start dev server in watch mode, that restarts on file change
6
6
  // and rebuilds the client static files
7
7
  async function watch() {
8
8
  // TODO: will be replaced with nerest logger
9
9
  console.log('Starting Nerest watch...');
10
- const { app } = await (0, server_1.createServer)();
11
- // TODO: remove hardcoded port
12
- await app.listen({
13
- host: '0.0.0.0',
14
- port: 3000,
15
- });
16
- console.log('Nerest is listening on 0.0.0.0:3000');
10
+ await (0, development_1.runDevelopmentServer)();
17
11
  }
18
12
  exports.watch = watch;
@@ -0,0 +1 @@
1
+ export declare function buildMicroFrontend(): Promise<void>;
@@ -0,0 +1,72 @@
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.buildMicroFrontend = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const vite_1 = __importDefault(require("vite"));
10
+ const apps_1 = require("../server/parts/apps");
11
+ async function buildMicroFrontend() {
12
+ const root = process.cwd();
13
+ const staticPath = process.env.NEREST_STATIC_PATH;
14
+ // TODO: The path where the client files are deployed is built-in during
15
+ // the initial build, but the client scripts aren't using it, so maybe it should
16
+ // be a runtime env variable for the server instead?
17
+ if (!staticPath) {
18
+ throw new Error('NEREST_STATIC_PATH environment variable is not set but is required for the production build');
19
+ }
20
+ // Build client
21
+ // TODO: extract shared parts between build/index.ts and server/index.ts
22
+ // into a shared config
23
+ const clientConfig = {
24
+ root,
25
+ appType: 'custom',
26
+ envPrefix: 'NEREST_',
27
+ build: {
28
+ // Manifest is needed to report used assets in SSR handles
29
+ manifest: true,
30
+ modulePreload: false,
31
+ rollupOptions: {
32
+ input: '/node_modules/@nerest/nerest/client/index.ts',
33
+ output: {
34
+ dir: 'build',
35
+ entryFileNames: `client/assets/[name].js`,
36
+ chunkFileNames: `client/assets/[name].js`,
37
+ assetFileNames: `client/assets/[name].[ext]`,
38
+ },
39
+ },
40
+ },
41
+ };
42
+ console.log('Producing production client build...');
43
+ await vite_1.default.build(clientConfig);
44
+ console.log('Producing Nerest manifest file...');
45
+ await buildAppsManifest(root, staticPath);
46
+ // Build server using the client manifest
47
+ const serverConfig = {
48
+ root,
49
+ appType: 'custom',
50
+ envPrefix: 'NEREST_',
51
+ build: {
52
+ emptyOutDir: false,
53
+ modulePreload: false,
54
+ // This is an important setting for producing a server build
55
+ ssr: true,
56
+ rollupOptions: {
57
+ input: '/node_modules/@nerest/nerest/server/production.ts',
58
+ output: {
59
+ dir: 'build',
60
+ entryFileNames: `server.mjs`,
61
+ },
62
+ },
63
+ },
64
+ };
65
+ console.log('Producing production server build...');
66
+ await vite_1.default.build(serverConfig);
67
+ }
68
+ exports.buildMicroFrontend = buildMicroFrontend;
69
+ async function buildAppsManifest(root, staticPath) {
70
+ const apps = await (0, apps_1.loadApps)(root, staticPath);
71
+ await promises_1.default.writeFile(path_1.default.join(root, 'build/nerest-manifest.json'), JSON.stringify(apps), { encoding: 'utf-8' });
72
+ }
@@ -0,0 +1 @@
1
+ export declare function runDevelopmentServer(): Promise<void>;
@@ -3,20 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createServer = void 0;
7
- // This is the nerest server entrypoint
6
+ exports.runDevelopmentServer = void 0;
7
+ // This is the nerest development server entrypoint
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const vite_1 = __importDefault(require("vite"));
10
10
  const fastify_1 = __importDefault(require("fastify"));
11
11
  const static_1 = __importDefault(require("@fastify/static"));
12
- const apps_1 = require("./apps");
13
- const render_1 = require("./render");
14
- const preview_1 = require("./preview");
15
- const validator_1 = require("./validator");
16
- const swagger_1 = require("./swagger");
17
- // TODO: this turned out to be a dev server, production server
18
- // will most likely be implemented separately
19
- async function createServer() {
12
+ const fastify_graceful_shutdown_1 = __importDefault(require("fastify-graceful-shutdown"));
13
+ const apps_1 = require("./parts/apps");
14
+ const render_1 = require("./parts/render");
15
+ const preview_1 = require("./parts/preview");
16
+ const validator_1 = require("./parts/validator");
17
+ const swagger_1 = require("./parts/swagger");
18
+ const k8s_probes_1 = require("./parts/k8s-probes");
19
+ async function runDevelopmentServer() {
20
20
  const root = process.cwd();
21
21
  // TODO: move build config into a separate file
22
22
  // TODO: look at @vitejs/plugin-react (everything seems fine without it though)
@@ -24,6 +24,7 @@ async function createServer() {
24
24
  const config = {
25
25
  root,
26
26
  appType: 'custom',
27
+ envPrefix: 'NEREST_',
27
28
  server: { middlewareMode: true },
28
29
  build: {
29
30
  // Manifest is needed to report used assets in SSR handles
@@ -34,18 +35,25 @@ async function createServer() {
34
35
  rollupOptions: {
35
36
  input: '/node_modules/@nerest/nerest/client/index.ts',
36
37
  output: {
37
- entryFileNames: `assets/[name].js`,
38
- chunkFileNames: `assets/[name].js`,
39
- assetFileNames: `assets/[name].[ext]`,
38
+ dir: 'build',
39
+ entryFileNames: `client/assets/[name].js`,
40
+ chunkFileNames: `client/assets/[name].js`,
41
+ assetFileNames: `client/assets/[name].[ext]`,
40
42
  },
41
43
  },
42
44
  },
45
+ // TODO: this doesn't seem to work without the index.html file entry and
46
+ // produces warnings in dev mode. look into this maybe
47
+ optimizeDeps: {
48
+ disabled: true,
49
+ },
43
50
  };
44
51
  // Build the clientside assets and watch for changes
45
52
  // TODO: this should probably be moved from here
46
53
  await startClientBuildWatcher(config);
47
54
  // Load app entries following the `apps/{name}/index.tsx` convention
48
- const apps = await (0, apps_1.loadApps)(root);
55
+ // TODO: remove hardcoded port
56
+ const apps = await (0, apps_1.loadApps)(root, 'http://0.0.0.0:3000/');
49
57
  // Start vite server that will be rendering SSR components
50
58
  const viteSsr = await vite_1.default.createServer(config);
51
59
  const app = (0, fastify_1.default)();
@@ -57,8 +65,7 @@ async function createServer() {
57
65
  for (const appEntry of Object.values(apps)) {
58
66
  const { name, entry, examples, schema } = appEntry;
59
67
  const routeOptions = {};
60
- // TODO: report error if schema is missing, unless this app is client-only.
61
- // TODO: disallow apps without schemas in production build
68
+ // TODO: report error if schema is missing, unless this app is client-only
62
69
  if (schema) {
63
70
  routeOptions.schema = {
64
71
  // Use description as Swagger summary, since summary is visible
@@ -79,7 +86,11 @@ async function createServer() {
79
86
  const ssrComponent = await viteSsr.ssrLoadModule(entry, {
80
87
  fixStacktrace: true,
81
88
  });
82
- return (0, render_1.renderApp)(appEntry, ssrComponent.default, request.body);
89
+ return (0, render_1.renderApp)({
90
+ name,
91
+ assets: appEntry.assets,
92
+ component: ssrComponent.default,
93
+ }, request.body);
83
94
  });
84
95
  for (const [exampleName, example] of Object.entries(examples)) {
85
96
  // Validate example against schema when specified
@@ -100,15 +111,24 @@ async function createServer() {
100
111
  const ssrComponent = await viteSsr.ssrLoadModule(entry, {
101
112
  fixStacktrace: true,
102
113
  });
103
- const { html, assets } = (0, render_1.renderApp)(appEntry, ssrComponent.default, example);
114
+ const { html, assets } = (0, render_1.renderApp)({
115
+ name,
116
+ assets: appEntry.assets,
117
+ component: ssrComponent.default,
118
+ }, example);
104
119
  reply.type('text/html');
105
120
  return (0, preview_1.renderPreviewPage)(html, assets);
106
121
  });
107
122
  }
108
123
  }
124
+ // Add graceful shutdown handler to prevent requests errors
125
+ await app.register(fastify_graceful_shutdown_1.default);
126
+ if (process.env.ENABLE_K8S_PROBES) {
127
+ await (0, k8s_probes_1.setupK8SProbes)(app);
128
+ }
109
129
  // TODO: only do this locally, load from CDN in production
110
130
  await app.register(static_1.default, {
111
- root: path_1.default.join(root, 'dist'),
131
+ root: path_1.default.join(root, 'build'),
112
132
  // TODO: maybe use @fastify/cors instead
113
133
  setHeaders(res) {
114
134
  res.setHeader('Access-Control-Allow-Origin', '*');
@@ -116,9 +136,14 @@ async function createServer() {
116
136
  res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, content-type, Authorization');
117
137
  },
118
138
  });
119
- return { app };
139
+ // TODO: remove hardcoded port
140
+ await app.listen({
141
+ host: '0.0.0.0',
142
+ port: 3000,
143
+ });
144
+ console.log('Nerest is listening on 0.0.0.0:3000');
120
145
  }
121
- exports.createServer = createServer;
146
+ exports.runDevelopmentServer = runDevelopmentServer;
122
147
  // TODO: this should probably be moved from here
123
148
  async function startClientBuildWatcher(config) {
124
149
  const watcher = (await vite_1.default.build(config));
@@ -0,0 +1,2 @@
1
+ import type { Manifest } from 'vite';
2
+ export declare function loadAppAssets(appName: string, manifest: Manifest, staticPath: string): string[];
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadAppAssets = void 0;
4
+ // Extracts the list of assets for a given app from the manifest file
5
+ function loadAppAssets(appName, manifest, staticPath) {
6
+ // TODO: handling errors and potentially missing entries
7
+ // All apps share the same JS entry that dynamically imports the chunks of the apps
8
+ // that are used on the page based on their name
9
+ const clientEntryJs = manifest['node_modules/@nerest/nerest/client/index.ts'].file;
10
+ // Each app has its own CSS bundles, if it imports any CSS
11
+ const appCss = manifest[`apps/${appName}/index.tsx`].css ?? [];
12
+ return [clientEntryJs, ...appCss].map((x) => staticPath + x);
13
+ }
14
+ exports.loadAppAssets = loadAppAssets;
@@ -0,0 +1 @@
1
+ export declare function loadAppManifest(root: string): Promise<any>;
@@ -0,0 +1,17 @@
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.loadAppManifest = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ // Manifest is used to provide assets list for every app
10
+ // for use with SSR
11
+ async function loadAppManifest(root) {
12
+ // TODO: error handling
13
+ const manifestPath = path_1.default.join(root, 'build', 'manifest.json');
14
+ const manifestData = await promises_1.default.readFile(manifestPath, { encoding: 'utf8' });
15
+ return JSON.parse(manifestData);
16
+ }
17
+ exports.loadAppManifest = loadAppManifest;
@@ -6,6 +6,6 @@ export type AppEntry = {
6
6
  examples: Record<string, unknown>;
7
7
  schema: Record<string, unknown> | null;
8
8
  };
9
- export declare function loadApps(root: string): Promise<{
9
+ export declare function loadApps(root: string, deployedStaticPath: string): Promise<{
10
10
  [k: string]: AppEntry;
11
11
  }>;
@@ -6,26 +6,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.loadApps = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
- const assets_1 = require("./app-parts/assets");
10
- const examples_1 = require("./app-parts/examples");
11
- const schema_1 = require("./app-parts/schema");
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");
12
13
  // Build the record of the available apps by convention
13
14
  // apps -> /apps/{name}/index.tsx
14
15
  // examples -> /apps/{name}/examples/{example}.json
15
- async function loadApps(root) {
16
+ async function loadApps(root, deployedStaticPath) {
16
17
  const appsRoot = path_1.default.join(root, 'apps');
17
- const manifest = await loadManifest(root);
18
+ const manifest = await (0, manifest_1.loadAppManifest)(root);
18
19
  const appsDirs = (await promises_1.default.readdir(appsRoot, { withFileTypes: true }))
19
20
  .filter((d) => d.isDirectory())
20
21
  .map((d) => d.name);
21
22
  const apps = [];
22
23
  for (const appDir of appsDirs) {
23
- apps.push(await loadApp(appsRoot, appDir, manifest));
24
+ apps.push(await loadApp(appsRoot, appDir, manifest, deployedStaticPath));
24
25
  }
25
26
  return Object.fromEntries(apps);
26
27
  }
27
28
  exports.loadApps = loadApps;
28
- async function loadApp(appsRoot, name, manifest) {
29
+ async function loadApp(appsRoot, name, manifest, deployedStaticPath) {
29
30
  // TODO: report problems with loading entries, assets and/or examples
30
31
  const appRoot = path_1.default.join(appsRoot, name);
31
32
  return [
@@ -34,17 +35,9 @@ async function loadApp(appsRoot, name, manifest) {
34
35
  name,
35
36
  root: appRoot,
36
37
  entry: path_1.default.join(appRoot, 'index.tsx'),
37
- assets: (0, assets_1.loadAppAssets)(manifest, name),
38
+ assets: (0, assets_1.loadAppAssets)(name, manifest, deployedStaticPath),
38
39
  examples: await (0, examples_1.loadAppExamples)(appRoot),
39
40
  schema: await (0, schema_1.loadAppSchema)(appRoot),
40
41
  },
41
42
  ];
42
43
  }
43
- // Manifest is used to provide assets list for every app
44
- // for use with SSR
45
- async function loadManifest(root) {
46
- // TODO: error handling
47
- const manifestPath = path_1.default.join(root, 'dist', 'manifest.json');
48
- const manifestData = await promises_1.default.readFile(manifestPath, { encoding: 'utf8' });
49
- return JSON.parse(manifestData);
50
- }
@@ -0,0 +1,2 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ export declare function setupK8SProbes(app: FastifyInstance): Promise<void>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupK8SProbes = void 0;
4
+ // Setup routes for k8s probes to check if application is live
5
+ async function setupK8SProbes(app) {
6
+ // Handler for graceful shutdowns
7
+ // K8s can initiate shutdown at any moment: on pods restart or on deploy.
8
+ // So, if we receive shutdown request, we:
9
+ // - starts to send 503 response code on readiness probes to stop receiving requests
10
+ // - finishes all current requests
11
+ // - shuts down the server
12
+ let isShutdownInProgress = false;
13
+ app.gracefulShutdown((code, next) => {
14
+ // TODO: replace with nerest logger, when it'll be ready
15
+ console.log('Graceful shutdown in process...');
16
+ isShutdownInProgress = true;
17
+ next();
18
+ });
19
+ app.get('/livenessProbe', { logLevel: 'silent' }, (req, res) => {
20
+ res.status(200).send();
21
+ });
22
+ app.get('/readinessProbe', { logLevel: 'silent' }, (req, res) => {
23
+ if (isShutdownInProgress) {
24
+ res.status(503).send({ status: 'Shutdown in progress' });
25
+ }
26
+ else {
27
+ res.status(200).send();
28
+ }
29
+ });
30
+ }
31
+ exports.setupK8SProbes = setupK8SProbes;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ type RenderProps = {
3
+ name: string;
4
+ assets: string[];
5
+ component: React.ComponentType;
6
+ };
7
+ export declare function renderApp({ name, assets, component }: RenderProps, props?: Record<string, unknown>): {
8
+ html: string;
9
+ assets: string[];
10
+ };
11
+ export {};
@@ -7,9 +7,8 @@ exports.renderApp = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const server_1 = require("react-dom/server");
9
9
  const nanoid_1 = require("nanoid");
10
- function renderApp(appEntry, appComponent, props = {}) {
11
- const { name, assets } = appEntry;
12
- const html = renderSsrComponent(name, appComponent, props);
10
+ function renderApp({ name, assets, component }, props = {}) {
11
+ const html = renderSsrComponent(name, component, props);
13
12
  return { html, assets };
14
13
  }
15
14
  exports.renderApp = renderApp;