@nerest/nerest 0.0.9 → 0.1.0

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 CHANGED
@@ -39,10 +39,14 @@ OpenAPI specification is compiled automatically based on the provided schemas an
39
39
 
40
40
  ### Props Hook (`/props.ts`)
41
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:
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 the body of the request and a logger as parameters. 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
43
 
44
44
  ```typescript
45
- export default function (props: Props) {
45
+ import type { FastifyBaseLogger } from 'fastify';
46
+
47
+ export default function (props: Props, logger: FastifyBaseLogger) {
48
+ logger.info('Hello from props.ts!');
49
+
46
50
  return {
47
51
  ...props,
48
52
  greeting: `${props.greeting} (modified in props.ts)`,
@@ -99,7 +103,7 @@ Excludes modules from the client build and maps them to globally available const
99
103
 
100
104
  Here `react` and `react-dom` imports will be replaced with accessing the respective `window` constants.
101
105
 
102
- ### Runtime Hooks
106
+ ### Runtime Hook
103
107
 
104
108
  If the module `nerest-runtime.ts` exists in the root of the micro frontend and exports a default function, this function will be executed when the server starts, and the fastify app instance will be passed to it as its only argument. Example of `nerest-runtime.ts`:
105
109
 
@@ -113,6 +117,22 @@ export default function (app: FastifyInstance) {
113
117
 
114
118
  This runtime hook can be used to adjust fastify settings, register additional plugins or add custom routes.
115
119
 
120
+ #### Logger Configuration
121
+
122
+ Nerest uses the default server-side [fastify logger](https://fastify.dev/docs/latest/Reference/Logging/#logging), enabled by default. To configure or disable it, export a `logger` function from the `nerest-runtime.ts` module. It will be called on server start, and the return value will be passed to fastify as logger configuration.
123
+
124
+ ```typescript
125
+ import type { FastifyServerOptions } from 'fastify';
126
+
127
+ export function logger(): FastifyServerOptions['logger'] {
128
+ return { prettyPrint: true };
129
+ }
130
+ ```
131
+
132
+ To disable the logger, return `false` from the function.
133
+
134
+ You can also supply your own logger instance. Instead of passing configuration options, pass the instance. The logger you supply must conform to the [Pino](https://github.com/pinojs/pino) interface; that is, it must have the following methods: `info`, `error`, `debug`, `fatal`, `warn`, `trace`, `silent`, `child` and a string property `level`.
135
+
116
136
  ## Development
117
137
 
118
138
  Run the build script to build the framework.
@@ -12,6 +12,7 @@ import { setupSwagger } from './parts/swagger.js';
12
12
  import { setupK8SProbes } from './parts/k8s-probes.js';
13
13
  import { runRuntimeHook } from './hooks/runtime.js';
14
14
  import { runPropsHook } from './hooks/props.js';
15
+ import { runLoggerHook } from './hooks/logger.js';
15
16
  // eslint-disable-next-line max-statements
16
17
  export async function runDevelopmentServer() {
17
18
  const root = process.cwd();
@@ -53,7 +54,12 @@ export async function runDevelopmentServer() {
53
54
  const apps = await loadApps(root, 'http://0.0.0.0:3000/');
54
55
  // Start vite server that will be rendering SSR components
55
56
  const viteSsr = await createServer(config);
56
- const app = fastify();
57
+ // Start fastify server that will be processing HTTP requests
58
+ const app = fastify({
59
+ // Receive logger configuration from `nerest-runtime.logger()` or
60
+ // use the default fastify one (`true`)
61
+ logger: (await runLoggerHook(() => viteSsr.ssrLoadModule('/nerest-runtime.ts'))) ?? true,
62
+ });
57
63
  // Setup schema validation. We have to use our own ajv instance that
58
64
  // we can use both to validate request bodies and examples against
59
65
  // app schemas
@@ -83,7 +89,7 @@ export async function runDevelopmentServer() {
83
89
  const ssrComponent = await viteSsr.ssrLoadModule(entry, {
84
90
  fixStacktrace: true,
85
91
  });
86
- const props = await runPropsHook(request.body, () => viteSsr.ssrLoadModule(propsHookEntry));
92
+ const props = await runPropsHook(request.body, request.log, () => viteSsr.ssrLoadModule(propsHookEntry));
87
93
  return renderApp({
88
94
  name,
89
95
  assets: appEntry.assets,
@@ -105,11 +111,11 @@ export async function runDevelopmentServer() {
105
111
  // description so it's easier to navigate to
106
112
  description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
107
113
  },
108
- }, async (_, reply) => {
114
+ }, async (request, reply) => {
109
115
  const ssrComponent = await viteSsr.ssrLoadModule(entry, {
110
116
  fixStacktrace: true,
111
117
  });
112
- const props = await runPropsHook(example, () => viteSsr.ssrLoadModule(propsHookEntry));
118
+ const props = await runPropsHook(example, request.log, () => viteSsr.ssrLoadModule(propsHookEntry));
113
119
  const { html, assets } = renderApp({
114
120
  name,
115
121
  assets: appEntry.assets,
@@ -142,7 +148,6 @@ export async function runDevelopmentServer() {
142
148
  host: '0.0.0.0',
143
149
  port: 3000,
144
150
  });
145
- console.log('Nerest is listening on 0.0.0.0:3000');
146
151
  }
147
152
  // TODO: this should probably be moved from here
148
153
  async function startClientBuildWatcher(config) {
@@ -0,0 +1,2 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ export declare function runLoggerHook(loader: () => Promise<unknown>): Promise<boolean | import("fastify").FastifyBaseLogger | (import("fastify").FastifyLoggerOptions<import("fastify").RawServerDefault, import("fastify").FastifyRequest<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown, import("fastify").FastifyBaseLogger, import("fastify/types/type-provider.js").ResolveFastifyRequestType<import("fastify").FastifyTypeProviderDefault, import("fastify").FastifySchema, import("fastify").RouteGenericInterface>>, import("fastify").FastifyReply<import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").RouteGenericInterface, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>> & import("pino").default.LoggerOptions) | null | undefined>;
@@ -0,0 +1,21 @@
1
+ // Load the runtime hook module and run it if it exists, to receive
2
+ // logger configuration. If the `logger` function does not exist
3
+ // in the module, ignore it and use default configuration.
4
+ export async function runLoggerHook(loader) {
5
+ let module;
6
+ try {
7
+ module = (await loader());
8
+ }
9
+ catch { }
10
+ if (typeof module?.logger === 'function') {
11
+ // If module exists and exports a logger function, execute it
12
+ try {
13
+ return module.logger();
14
+ }
15
+ catch (e) {
16
+ console.error('Failed to load logger configuration', e);
17
+ process.exit(1);
18
+ }
19
+ }
20
+ return null;
21
+ }
@@ -1 +1,2 @@
1
- export declare function runPropsHook(props: unknown, loader: () => Promise<unknown>): Promise<Record<string, unknown>>;
1
+ import type { FastifyBaseLogger } from 'fastify';
2
+ export declare function runPropsHook(props: unknown, logger: FastifyBaseLogger, loader: () => Promise<unknown>): Promise<Record<string, unknown>>;
@@ -1,12 +1,14 @@
1
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) {
2
+ // props object and logger. This hook can be used to modify props before
3
+ // they are passed to the root app component.
4
+ export async function runPropsHook(props, logger, loader) {
5
5
  let module;
6
6
  try {
7
7
  module = (await loader());
8
8
  }
9
9
  catch { }
10
10
  // If module exists and exports a default function, run it to modify props
11
- return (typeof module?.default === 'function' ? module.default(props) : props);
11
+ return (typeof module?.default === 'function'
12
+ ? module.default(props, logger)
13
+ : props);
12
14
  }
@@ -10,6 +10,7 @@ import { renderPreviewPage } from './parts/preview.js';
10
10
  import { setupK8SProbes } from './parts/k8s-probes.js';
11
11
  import { runRuntimeHook } from './hooks/runtime.js';
12
12
  import { runPropsHook } from './hooks/props.js';
13
+ import { runLoggerHook } from './hooks/logger.js';
13
14
  // TODO: refactor to merge the similar parts between production and development server?
14
15
  async function runProductionServer() {
15
16
  const root = process.cwd();
@@ -24,7 +25,13 @@ async function runProductionServer() {
24
25
  const propsHooks = import.meta.glob('/apps/*/props.ts', {
25
26
  eager: true,
26
27
  });
27
- const app = fastify();
28
+ const runtimeHook = import.meta.glob('/nerest-runtime.ts', { eager: true });
29
+ const app = fastify({
30
+ // Receive logger configuration from `nerest-runtime.logger()` or
31
+ // use the default fastify one (`true`)
32
+ logger: (await runLoggerHook(async () => runtimeHook['/nerest-runtime.ts'])) ??
33
+ true,
34
+ });
28
35
  // Setup schema validation. We have to use our own ajv instance that
29
36
  // we can use both to validate request bodies and examples against
30
37
  // app schemas
@@ -48,7 +55,7 @@ async function runProductionServer() {
48
55
  }
49
56
  // POST /api/{name} -> render app with request.body as props
50
57
  app.post(`/api/${name}`, routeOptions, async (request) => {
51
- const props = await runPropsHook(request.body, propsHook);
58
+ const props = await runPropsHook(request.body, request.log, propsHook);
52
59
  return renderApp({ name, assets, component }, props);
53
60
  });
54
61
  for (const [exampleName, example] of Object.entries(examples)) {
@@ -61,8 +68,8 @@ async function runProductionServer() {
61
68
  // description so it's easier to navigate to
62
69
  description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
63
70
  },
64
- }, async (_, reply) => {
65
- const props = await runPropsHook(example, propsHook);
71
+ }, async (request, reply) => {
72
+ const props = await runPropsHook(example, request.log, propsHook);
66
73
  const { html, assets: outAssets } = renderApp({
67
74
  name,
68
75
  assets,
@@ -79,15 +86,11 @@ async function runProductionServer() {
79
86
  await setupK8SProbes(app);
80
87
  }
81
88
  // Execute runtime hook in nerest-runtime.ts if it exists
82
- await runRuntimeHook(app, async () => {
83
- const glob = import.meta.glob('/nerest-runtime.ts', { eager: true });
84
- return glob['/nerest-runtime.ts'];
85
- });
89
+ await runRuntimeHook(app, async () => runtimeHook['/nerest-runtime.ts']);
86
90
  // TODO: remove hardcoded port
87
91
  await app.listen({
88
92
  host: '0.0.0.0',
89
93
  port: 3000,
90
94
  });
91
- console.log('Nerest is listening on 0.0.0.0:3000');
92
95
  }
93
96
  runProductionServer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerest/nerest",
3
- "version": "0.0.9",
3
+ "version": "0.1.0",
4
4
  "description": "React micro frontend framework",
5
5
  "homepage": "https://github.com/nerestjs/nerest#readme",
6
6
  "repository": {
@@ -19,6 +19,7 @@ import { setupSwagger } from './parts/swagger.js';
19
19
  import { setupK8SProbes } from './parts/k8s-probes.js';
20
20
  import { runRuntimeHook } from './hooks/runtime.js';
21
21
  import { runPropsHook } from './hooks/props.js';
22
+ import { runLoggerHook } from './hooks/logger.js';
22
23
 
23
24
  // eslint-disable-next-line max-statements
24
25
  export async function runDevelopmentServer() {
@@ -65,7 +66,16 @@ export async function runDevelopmentServer() {
65
66
 
66
67
  // Start vite server that will be rendering SSR components
67
68
  const viteSsr = await createServer(config);
68
- const app = fastify();
69
+
70
+ // Start fastify server that will be processing HTTP requests
71
+ const app = fastify({
72
+ // Receive logger configuration from `nerest-runtime.logger()` or
73
+ // use the default fastify one (`true`)
74
+ logger:
75
+ (await runLoggerHook(() =>
76
+ viteSsr.ssrLoadModule('/nerest-runtime.ts')
77
+ )) ?? true,
78
+ });
69
79
 
70
80
  // Setup schema validation. We have to use our own ajv instance that
71
81
  // we can use both to validate request bodies and examples against
@@ -101,7 +111,7 @@ export async function runDevelopmentServer() {
101
111
  const ssrComponent = await viteSsr.ssrLoadModule(entry, {
102
112
  fixStacktrace: true,
103
113
  });
104
- const props = await runPropsHook(request.body, () =>
114
+ const props = await runPropsHook(request.body, request.log, () =>
105
115
  viteSsr.ssrLoadModule(propsHookEntry)
106
116
  );
107
117
  return renderApp(
@@ -135,11 +145,11 @@ export async function runDevelopmentServer() {
135
145
  description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
136
146
  },
137
147
  },
138
- async (_, reply) => {
148
+ async (request, reply) => {
139
149
  const ssrComponent = await viteSsr.ssrLoadModule(entry, {
140
150
  fixStacktrace: true,
141
151
  });
142
- const props = await runPropsHook(example, () =>
152
+ const props = await runPropsHook(example, request.log, () =>
143
153
  viteSsr.ssrLoadModule(propsHookEntry)
144
154
  );
145
155
  const { html, assets } = renderApp(
@@ -188,8 +198,6 @@ export async function runDevelopmentServer() {
188
198
  host: '0.0.0.0',
189
199
  port: 3000,
190
200
  });
191
-
192
- console.log('Nerest is listening on 0.0.0.0:3000');
193
201
  }
194
202
 
195
203
  // TODO: this should probably be moved from here
@@ -0,0 +1,28 @@
1
+ import type { FastifyServerOptions } from 'fastify';
2
+
3
+ type LoggerHookModule = {
4
+ logger?: () => FastifyServerOptions['logger'];
5
+ };
6
+
7
+ // Load the runtime hook module and run it if it exists, to receive
8
+ // logger configuration. If the `logger` function does not exist
9
+ // in the module, ignore it and use default configuration.
10
+ export async function runLoggerHook(loader: () => Promise<unknown>) {
11
+ let module: LoggerHookModule | undefined;
12
+
13
+ try {
14
+ module = (await loader()) as LoggerHookModule;
15
+ } catch {}
16
+
17
+ if (typeof module?.logger === 'function') {
18
+ // If module exists and exports a logger function, execute it
19
+ try {
20
+ return module.logger();
21
+ } catch (e) {
22
+ console.error('Failed to load logger configuration', e);
23
+ process.exit(1);
24
+ }
25
+ }
26
+
27
+ return null;
28
+ }
@@ -1,12 +1,15 @@
1
+ import type { FastifyBaseLogger } from 'fastify';
2
+
1
3
  type PropsHookModule = {
2
- default: (props: unknown) => unknown;
4
+ default: (props: unknown, logger: FastifyBaseLogger) => unknown;
3
5
  };
4
6
 
5
7
  // Load the props hook module and run it if it exists, passing down app's
6
- // props object. This hook can be used to modify props before they are passed
7
- // to the root app component.
8
+ // props object and logger. This hook can be used to modify props before
9
+ // they are passed to the root app component.
8
10
  export async function runPropsHook(
9
11
  props: unknown,
12
+ logger: FastifyBaseLogger,
10
13
  loader: () => Promise<unknown>
11
14
  ) {
12
15
  let module: PropsHookModule | undefined;
@@ -17,6 +20,8 @@ export async function runPropsHook(
17
20
 
18
21
  // If module exists and exports a default function, run it to modify props
19
22
  return (
20
- typeof module?.default === 'function' ? module.default(props) : props
23
+ typeof module?.default === 'function'
24
+ ? module.default(props, logger)
25
+ : props
21
26
  ) as Record<string, unknown>;
22
27
  }
@@ -14,6 +14,7 @@ import { renderPreviewPage } from './parts/preview.js';
14
14
  import { setupK8SProbes } from './parts/k8s-probes.js';
15
15
  import { runRuntimeHook } from './hooks/runtime.js';
16
16
  import { runPropsHook } from './hooks/props.js';
17
+ import { runLoggerHook } from './hooks/logger.js';
17
18
 
18
19
  // TODO: refactor to merge the similar parts between production and development server?
19
20
  async function runProductionServer() {
@@ -35,7 +36,15 @@ async function runProductionServer() {
35
36
  eager: true,
36
37
  });
37
38
 
38
- const app = fastify();
39
+ const runtimeHook = import.meta.glob('/nerest-runtime.ts', { eager: true });
40
+
41
+ const app = fastify({
42
+ // Receive logger configuration from `nerest-runtime.logger()` or
43
+ // use the default fastify one (`true`)
44
+ logger:
45
+ (await runLoggerHook(async () => runtimeHook['/nerest-runtime.ts'])) ??
46
+ true,
47
+ });
39
48
 
40
49
  // Setup schema validation. We have to use our own ajv instance that
41
50
  // we can use both to validate request bodies and examples against
@@ -65,7 +74,7 @@ async function runProductionServer() {
65
74
 
66
75
  // POST /api/{name} -> render app with request.body as props
67
76
  app.post(`/api/${name}`, routeOptions, async (request) => {
68
- const props = await runPropsHook(request.body, propsHook);
77
+ const props = await runPropsHook(request.body, request.log, propsHook);
69
78
  return renderApp({ name, assets, component }, props);
70
79
  });
71
80
 
@@ -82,8 +91,8 @@ async function runProductionServer() {
82
91
  description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
83
92
  },
84
93
  },
85
- async (_, reply) => {
86
- const props = await runPropsHook(example, propsHook);
94
+ async (request, reply) => {
95
+ const props = await runPropsHook(example, request.log, propsHook);
87
96
  const { html, assets: outAssets } = renderApp(
88
97
  {
89
98
  name,
@@ -109,18 +118,13 @@ async function runProductionServer() {
109
118
  }
110
119
 
111
120
  // Execute runtime hook in nerest-runtime.ts if it exists
112
- await runRuntimeHook(app, async () => {
113
- const glob = import.meta.glob('/nerest-runtime.ts', { eager: true });
114
- return glob['/nerest-runtime.ts'];
115
- });
121
+ await runRuntimeHook(app, async () => runtimeHook['/nerest-runtime.ts']);
116
122
 
117
123
  // TODO: remove hardcoded port
118
124
  await app.listen({
119
125
  host: '0.0.0.0',
120
126
  port: 3000,
121
127
  });
122
-
123
- console.log('Nerest is listening on 0.0.0.0:3000');
124
128
  }
125
129
 
126
130
  runProductionServer();