@nerest/nerest 0.1.0 → 1.5.1

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 (89) hide show
  1. package/LICENSE.md +178 -0
  2. package/README.md +130 -39
  3. package/bin/index.ts +3 -0
  4. package/bin/typegen.ts +35 -0
  5. package/bin/watch.ts +3 -2
  6. package/build/configs/development.ts +63 -0
  7. package/build/configs/production.ts +59 -0
  8. package/build/configs/shared.ts +51 -0
  9. package/build/configs/vite-logger.development.ts +30 -0
  10. package/build/index.ts +53 -76
  11. package/client/index.ts +14 -2
  12. package/dist/bin/index.js +4 -0
  13. package/dist/bin/typegen.d.ts +1 -0
  14. package/dist/bin/typegen.js +31 -0
  15. package/dist/bin/watch.js +1 -2
  16. package/dist/build/configs/development.d.ts +4 -0
  17. package/dist/build/configs/development.js +53 -0
  18. package/dist/build/configs/production.d.ts +4 -0
  19. package/dist/build/configs/production.js +50 -0
  20. package/dist/build/configs/shared.d.ts +10 -0
  21. package/dist/build/configs/shared.js +24 -0
  22. package/dist/build/configs/vite-logger.development.d.ts +2 -0
  23. package/dist/build/configs/vite-logger.development.js +25 -0
  24. package/dist/build/index.js +39 -69
  25. package/dist/client/index.js +9 -2
  26. package/dist/server/development.d.ts +1 -1
  27. package/dist/server/development.js +51 -134
  28. package/dist/server/hooks/logger.d.ts +1 -2
  29. package/dist/server/hooks/logger.js +3 -0
  30. package/dist/server/hooks/props.d.ts +2 -2
  31. package/dist/server/hooks/props.js +2 -2
  32. package/dist/server/hooks/runtime.js +3 -3
  33. package/dist/server/{parts → loaders}/apps.d.ts +2 -1
  34. package/dist/server/loaders/apps.js +36 -0
  35. package/dist/server/loaders/assets.js +25 -2
  36. package/dist/server/loaders/build.d.ts +2 -0
  37. package/dist/server/loaders/build.js +11 -0
  38. package/dist/server/loaders/examples.js +7 -13
  39. package/dist/server/loaders/manifest.d.ts +8 -1
  40. package/dist/server/loaders/manifest.js +12 -4
  41. package/dist/server/loaders/preview.d.ts +4 -0
  42. package/dist/server/loaders/preview.js +11 -0
  43. package/dist/server/loaders/project.d.ts +16 -0
  44. package/dist/server/loaders/project.js +11 -0
  45. package/dist/server/loaders/schema.d.ts +2 -1
  46. package/dist/server/loaders/schema.js +12 -8
  47. package/dist/server/parts/k8s-probes.js +10 -6
  48. package/dist/server/parts/preview.d.ts +2 -1
  49. package/dist/server/parts/preview.js +5 -4
  50. package/dist/server/parts/render.d.ts +5 -3
  51. package/dist/server/parts/render.js +8 -8
  52. package/dist/server/parts/swagger.d.ts +2 -1
  53. package/dist/server/parts/swagger.js +8 -19
  54. package/dist/server/parts/validator.d.ts +3 -1
  55. package/dist/server/parts/validator.js +13 -0
  56. package/dist/server/production.js +17 -83
  57. package/dist/server/shared.d.ts +14 -0
  58. package/dist/server/shared.js +94 -0
  59. package/dist/server/utils.d.ts +1 -0
  60. package/dist/server/utils.js +5 -0
  61. package/package.json +45 -43
  62. package/schemas/nerest-build.schema.d.ts +19 -1
  63. package/schemas/nerest-build.schema.json +21 -1
  64. package/server/development.ts +67 -172
  65. package/server/hooks/logger.ts +3 -0
  66. package/server/hooks/props.ts +5 -5
  67. package/server/hooks/runtime.ts +3 -3
  68. package/server/loaders/apps.ts +58 -0
  69. package/server/loaders/assets.ts +30 -2
  70. package/server/loaders/build.ts +14 -0
  71. package/server/loaders/examples.ts +7 -15
  72. package/server/loaders/manifest.ts +23 -4
  73. package/server/loaders/preview.ts +17 -0
  74. package/server/loaders/project.ts +33 -0
  75. package/server/loaders/schema.ts +12 -10
  76. package/server/parts/k8s-probes.ts +26 -13
  77. package/server/parts/preview.ts +11 -4
  78. package/server/parts/render.ts +13 -9
  79. package/server/parts/swagger.ts +10 -29
  80. package/server/parts/validator.ts +14 -0
  81. package/server/production.ts +22 -110
  82. package/server/shared.ts +150 -0
  83. package/server/utils.ts +6 -0
  84. package/dist/server/parts/apps.js +0 -37
  85. package/dist/server/parts/props-hook.d.ts +0 -1
  86. package/dist/server/parts/props-hook.js +0 -8
  87. package/dist/server/parts/runtime-hook.d.ts +0 -2
  88. package/dist/server/parts/runtime-hook.js +0 -28
  89. package/server/parts/apps.ts +0 -59
@@ -1,18 +1,18 @@
1
- import React from 'react';
1
+ import { createElement } from 'react';
2
2
  import { renderToString } from 'react-dom/server';
3
- import { nanoid } from 'nanoid';
4
- export function renderApp({ name, assets, component }, props = {}) {
5
- const html = renderSsrComponent(name, component, props);
3
+ import { randomId } from '../utils.js';
4
+ export function renderApp({ name, assets, component, project }, props = {}) {
5
+ const html = renderSsrComponent(name, component, project, props);
6
6
  return { html, assets };
7
7
  }
8
- function renderSsrComponent(appName, appComponent, props) {
9
- const html = renderToString(React.createElement(appComponent, props));
8
+ function renderSsrComponent(appName, appComponent, project, props) {
9
+ const html = renderToString(createElement(appComponent, props));
10
10
  // There may be multiple instances of the same app on the page,
11
11
  // so we will use a randomized id to avoid collisions
12
- const appId = nanoid();
12
+ const appId = randomId();
13
13
  // data-app-name and data-app-id are used by client entrypoint to hydrate
14
14
  // apps using correct serialized props
15
- const container = `<div data-app-name="${appName}" data-app-id="${appId}">${html}</div>`;
15
+ const container = `<div data-project-name="${project.name}" data-app-name="${appName}" data-app-id="${appId}">${html}</div>`;
16
16
  const script = `<script type="application/json" data-app-id="${appId}">${JSON.stringify(props)}</script>`;
17
17
  return container + script;
18
18
  }
@@ -1,2 +1,3 @@
1
1
  import type { FastifyInstance } from 'fastify';
2
- export declare function setupSwagger(app: FastifyInstance): Promise<void>;
2
+ import type { Project } from '../loaders/project.js';
3
+ export declare function setupSwagger(app: FastifyInstance, project: Project): Promise<void>;
@@ -1,29 +1,18 @@
1
- import path from 'path';
2
- import fs from 'fs';
3
1
  import fastifySwagger from '@fastify/swagger';
4
2
  import fastifySwaggerUi from '@fastify/swagger-ui';
5
3
  // Setup automatic OpenAPI specification compilation and enable
6
4
  // Swagger UI at the `/api` route
7
- export async function setupSwagger(app) {
8
- let appInfo = {};
9
- try {
10
- const packageJson = fs.readFileSync(path.join(process.cwd(), 'package.json'), { encoding: 'utf-8' });
11
- appInfo = JSON.parse(packageJson);
12
- }
13
- catch (e) {
14
- // We only use package.json info to setup Swagger info and links,
15
- // if we are unable to load them -- that's fine
16
- }
17
- const homepage = appInfo.homepage ||
18
- (typeof appInfo.repository === 'string'
19
- ? appInfo.repository
20
- : appInfo.repository?.url);
5
+ export async function setupSwagger(app, project) {
6
+ const homepage = project.homepage ||
7
+ (typeof project.repository === 'string'
8
+ ? project.repository
9
+ : project.repository?.url);
21
10
  await app.register(fastifySwagger, {
22
11
  openapi: {
23
12
  info: {
24
- title: appInfo.name ?? 'Nerest micro frontend',
25
- description: appInfo.description,
26
- version: appInfo.version ?? '',
13
+ title: project.name || 'Nerest micro frontend',
14
+ description: project.description,
15
+ version: project.version ?? '',
27
16
  contact: homepage
28
17
  ? {
29
18
  name: 'Homepage',
@@ -1,2 +1,4 @@
1
1
  import Ajv from 'ajv';
2
- export declare const validator: Ajv.default;
2
+ import type { FastifyInstance } from 'fastify';
3
+ export declare const validator: Ajv.Ajv;
4
+ export declare function setupValidator(app: FastifyInstance): void;
@@ -18,3 +18,16 @@ export const validator = new Ajv.default({
18
18
  // `email`, `url`, etc. Used by default in fastify
19
19
  // https://www.npmjs.com/package/ajv-formats
20
20
  addFormats.default(validator);
21
+ // Setup schema validation. We have to use our own ajv instance that
22
+ // we can use both to validate request bodies and examples against
23
+ // app schemas
24
+ export function setupValidator(app) {
25
+ if (process.env.DISABLE_SCHEMA_VALIDATION) {
26
+ // If schema validation is disabled, return data as is without any checks
27
+ app.setValidatorCompiler(() => (data) => ({ value: data }));
28
+ }
29
+ else {
30
+ // If schema validation is enabled, validate and coerce data via ajv
31
+ app.setValidatorCompiler(({ schema }) => validator.compile(schema));
32
+ }
33
+ }
@@ -1,23 +1,11 @@
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 './hooks/runtime.js';
12
- import { runPropsHook } from './hooks/props.js';
13
- import { runLoggerHook } from './hooks/logger.js';
14
- // TODO: refactor to merge the similar parts between production and development server?
15
- async function runProductionServer() {
1
+ import { createServer } from './shared.js';
2
+ import { loadNerestManifest } from './loaders/manifest.js';
3
+ // Important: this file is the server entrypoint that will be built by vite
4
+ // in `build/index.ts`. All of the import.meta.glob's will be resolved at build time
5
+ async function runProductionServer(port) {
16
6
  const root = process.cwd();
17
- // TODO: error handling for file reading
18
- const apps = JSON.parse(await fs.readFile(path.join(root, 'build/nerest-manifest.json'), {
19
- encoding: 'utf-8',
20
- }));
7
+ // Load project information from the manifest generated during production build
8
+ const { project, apps } = await loadNerestManifest(root);
21
9
  const components = import.meta.glob('/apps/*/index.tsx', {
22
10
  import: 'default',
23
11
  eager: true,
@@ -25,72 +13,18 @@ async function runProductionServer() {
25
13
  const propsHooks = import.meta.glob('/apps/*/props.ts', {
26
14
  eager: true,
27
15
  });
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,
16
+ const runtimeHook = import.meta.glob('/nerest/runtime.ts', { eager: true });
17
+ const app = await createServer({
18
+ root,
19
+ project,
20
+ apps,
21
+ loadComponent: async (entry) => components[`/apps/${entry}/index.tsx`],
22
+ loadPropsHook: async (entry) => propsHooks[`/apps/${entry}/props.ts`],
23
+ loadRuntimeHook: async () => runtimeHook['/nerest/runtime.ts'],
34
24
  });
35
- // Setup schema validation. We have to use our own ajv instance that
36
- // we can use both to validate request bodies and examples against
37
- // app schemas
38
- app.setValidatorCompiler(({ schema }) => validator.compile(schema));
39
- await setupSwagger(app);
40
- for (const appEntry of Object.values(apps)) {
41
- const { name, examples, schema, assets } = appEntry;
42
- const component = components[`/apps/${name}/index.tsx`];
43
- const propsHook = async () => propsHooks[`/apps/${name}/props.ts`];
44
- const routeOptions = {};
45
- // TODO: report error if schema is missing, unless this app is client-only
46
- // TODO: disallow apps without schemas in production build
47
- if (schema) {
48
- routeOptions.schema = {
49
- // Use description as Swagger summary, since summary is visible
50
- // even when the route is collapsed in the UI
51
- summary: schema.description,
52
- // TODO: do we need to mix in examples like in the development server?
53
- body: schema,
54
- };
55
- }
56
- // POST /api/{name} -> render app with request.body as props
57
- app.post(`/api/${name}`, routeOptions, async (request) => {
58
- const props = await runPropsHook(request.body, request.log, propsHook);
59
- return renderApp({ name, assets, component }, props);
60
- });
61
- for (const [exampleName, example] of Object.entries(examples)) {
62
- // GET /api/{name}/examples/{example} -> render a preview page
63
- // with a predefined example body
64
- const exampleRoute = `/api/${name}/examples/${exampleName}`;
65
- app.get(exampleRoute, {
66
- schema: {
67
- // Add a clickable link to the example route in route's Swagger
68
- // description so it's easier to navigate to
69
- description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
70
- },
71
- }, async (request, reply) => {
72
- const props = await runPropsHook(example, request.log, propsHook);
73
- const { html, assets: outAssets } = renderApp({
74
- name,
75
- assets,
76
- component,
77
- }, props);
78
- reply.type('text/html');
79
- return renderPreviewPage(html, outAssets);
80
- });
81
- }
82
- }
83
- // Add graceful shutdown handler to prevent requests errors
84
- await app.register(fastifyGracefulShutdown);
85
- if (process.env.ENABLE_K8S_PROBES) {
86
- await setupK8SProbes(app);
87
- }
88
- // Execute runtime hook in nerest-runtime.ts if it exists
89
- await runRuntimeHook(app, async () => runtimeHook['/nerest-runtime.ts']);
90
- // TODO: remove hardcoded port
91
25
  await app.listen({
92
26
  host: '0.0.0.0',
93
- port: 3000,
27
+ port,
94
28
  });
95
29
  }
96
- runProductionServer();
30
+ runProductionServer(process.env.PORT ? Number(process.env.PORT) : 3000);
@@ -0,0 +1,14 @@
1
+ import fastify from 'fastify';
2
+ import type { ComponentType } from 'react';
3
+ import type { Project } from './loaders/project.js';
4
+ import type { AppEntry } from './loaders/apps.js';
5
+ type ServerOptions = {
6
+ root: string;
7
+ project: Project;
8
+ apps: Record<string, AppEntry>;
9
+ loadComponent: (entry: string) => Promise<ComponentType>;
10
+ loadPropsHook: (entry: string) => Promise<unknown>;
11
+ loadRuntimeHook: () => Promise<unknown>;
12
+ };
13
+ export declare function createServer(options: ServerOptions): Promise<fastify.FastifyInstance<fastify.RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, fastify.FastifyBaseLogger, fastify.FastifyTypeProviderDefault>>;
14
+ export {};
@@ -0,0 +1,94 @@
1
+ import fastify from 'fastify';
2
+ import fastifyGracefulShutdown from 'fastify-graceful-shutdown';
3
+ import { renderApp } from './parts/render.js';
4
+ import { setupSwagger } from './parts/swagger.js';
5
+ import { setupValidator, validator } from './parts/validator.js';
6
+ import { renderPreviewPage } from './parts/preview.js';
7
+ import { setupK8SProbes } from './parts/k8s-probes.js';
8
+ import { runRuntimeHook } from './hooks/runtime.js';
9
+ import { runPropsHook } from './hooks/props.js';
10
+ import { runLoggerHook } from './hooks/logger.js';
11
+ import { loadPreviewParts } from './loaders/preview.js';
12
+ import { randomId } from './utils.js';
13
+ export async function createServer(options) {
14
+ const { project, root, loadRuntimeHook } = options;
15
+ const app = fastify({
16
+ logger: (await runLoggerHook(loadRuntimeHook)) ?? true,
17
+ ignoreTrailingSlash: true,
18
+ useSemicolonDelimiter: false,
19
+ // JSON parsing can take a long time and blocks the event loop,
20
+ // so we need to limit the size of the body. 10MB is a good compromise
21
+ // baseline that was chosen by experimenting with real world usage
22
+ bodyLimit: 10 * 1024 * 1024,
23
+ genReqId(req) {
24
+ return String(req.headers['x-request-id'] || randomId());
25
+ },
26
+ });
27
+ // Setup payload validation and Swagger based on apps' JSON Schema
28
+ setupValidator(app);
29
+ await setupSwagger(app, project);
30
+ // Load preview parts from `nerest/preview-{part}.html` files
31
+ const previewParts = await loadPreviewParts(root);
32
+ await setupRoutes(app, { ...options, previewParts });
33
+ // Add graceful shutdown handler to prevent requests errors
34
+ await app.register(fastifyGracefulShutdown);
35
+ if (process.env.ENABLE_K8S_PROBES) {
36
+ await setupK8SProbes(app);
37
+ }
38
+ // Execute runtime hook in nerest/runtime.ts if it exists
39
+ await runRuntimeHook(app, loadRuntimeHook);
40
+ return app;
41
+ }
42
+ async function setupRoutes(app, options) {
43
+ const { project, apps, previewParts, loadComponent, loadPropsHook } = options;
44
+ for (const appEntry of Object.values(apps)) {
45
+ const { name, examples, schema, assets } = appEntry;
46
+ const routeOptions = {};
47
+ // TODO: report error if schema is missing, making it mandatory
48
+ if (schema) {
49
+ routeOptions.schema = {
50
+ summary: schema.description,
51
+ // Tags are used to group routes in Swagger UI
52
+ tags: [name],
53
+ body: {
54
+ ...schema,
55
+ // Examples are also displayed in Swagger UI
56
+ examples: Object.values(examples),
57
+ },
58
+ };
59
+ }
60
+ // POST /api/{name} -> render app with request.body as props
61
+ app.post(`/api/${name}`, routeOptions, async (request) => {
62
+ const component = await loadComponent(name);
63
+ const props = await runPropsHook(app, () => loadPropsHook(name), request.body);
64
+ return renderApp({ name, assets, component, project }, props);
65
+ });
66
+ for (const [exampleName, example] of Object.entries(examples)) {
67
+ // Validate examples against schema
68
+ if (schema && !validator.validate(schema, example)) {
69
+ app.log.error(`Example "${exampleName}" of app "${name}" does not satisfy schema: ${validator.errorsText()}`);
70
+ }
71
+ // GET /api/{name}/examples/{example} -> render a preview page
72
+ const exampleRoute = `/api/${name}/examples/${exampleName}`;
73
+ app.get(exampleRoute, {
74
+ schema: {
75
+ // Add clickable link to go to the example in Swagger UI
76
+ description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
77
+ // Place examples under the same tag as the app
78
+ tags: [name],
79
+ },
80
+ }, async (request, reply) => {
81
+ const component = await loadComponent(name);
82
+ const props = await runPropsHook(app, () => loadPropsHook(name), example);
83
+ const { html, assets: outAssets } = renderApp({
84
+ name,
85
+ assets,
86
+ component,
87
+ project,
88
+ }, props);
89
+ reply.type('text/html');
90
+ return renderPreviewPage(html, outAssets, previewParts);
91
+ });
92
+ }
93
+ }
94
+ }
@@ -0,0 +1 @@
1
+ export declare function randomId(): string;
@@ -0,0 +1,5 @@
1
+ import crypto from 'crypto';
2
+ // Generate a random string ID 20 characters long
3
+ export function randomId() {
4
+ return crypto.randomBytes(10).toString('hex');
5
+ }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@nerest/nerest",
3
- "version": "0.1.0",
3
+ "version": "1.5.1",
4
4
  "description": "React micro frontend framework",
5
- "homepage": "https://github.com/nerestjs/nerest#readme",
5
+ "homepage": "https://github.com/nerestjs/nerest",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/nerestjs/nerest.git"
9
9
  },
10
+ "license": "Apache-2.0",
10
11
  "type": "module",
11
12
  "bin": {
12
13
  "nerest": "dist/bin/index.js"
@@ -18,16 +19,22 @@
18
19
  "/client",
19
20
  "/schemas",
20
21
  "/server",
21
- "/README.md"
22
+ "/README.md",
23
+ "/LICENSE.md"
22
24
  ],
23
25
  "scripts": {
24
26
  "build": "tsc -p tsconfig.json -d",
25
- "lint": "eslint --ext .ts server bin",
27
+ "lint": "eslint --ext .ts .",
26
28
  "prepare": "simple-git-hooks",
29
+ "test": "npm run test:module && npm run test:integration:dev && npm run test:integration:prod",
30
+ "test:integration:dev": "node tests/integration/run.development.js",
31
+ "test:integration:prod": "node tests/integration/run.production.js",
32
+ "test:module": "vitest run tests/module/**",
27
33
  "typegen": "json2ts -i 'schemas/**/*.json' -o schemas --bannerComment ''"
28
34
  },
29
35
  "simple-git-hooks": {
30
- "pre-commit": "npx lint-staged"
36
+ "pre-commit": "npx lint-staged",
37
+ "commit-msg": "npx commitlint --edit ${1}"
31
38
  },
32
39
  "lint-staged": {
33
40
  "*.ts": [
@@ -41,51 +48,46 @@
41
48
  "sort-package-json"
42
49
  ]
43
50
  },
44
- "prettier": "@tinkoff/prettier-config",
45
- "eslintConfig": {
46
- "parserOptions": {
47
- "project": true
48
- },
49
- "extends": [
50
- "@tinkoff/eslint-config/lib",
51
- "@tinkoff/eslint-config/jest",
52
- "@tinkoff/eslint-config-react"
53
- ]
54
- },
55
51
  "dependencies": {
56
- "@fastify/static": "^6.12.0",
57
- "@fastify/swagger": "^8.12.0",
58
- "@fastify/swagger-ui": "^1.10.1",
59
- "ajv": "^8.12.0",
60
- "ajv-formats": "^2.1.1",
61
- "dotenv": "^16.3.1",
62
- "fast-uri": "^2.3.0",
63
- "fastify": "^4.24.3",
64
- "fastify-graceful-shutdown": "^3.5.1",
65
- "nanoid": "^5.0.2",
66
- "vite": "^4.5.0",
52
+ "@apidevtools/json-schema-ref-parser": "^11.9.3",
53
+ "@fastify/middie": "^9.0.3",
54
+ "@fastify/static": "^8.1.1",
55
+ "@fastify/swagger": "^9.4.2",
56
+ "@fastify/swagger-ui": "^5.2.2",
57
+ "ajv": "^8.17.1",
58
+ "ajv-formats": "^3.0.1",
59
+ "dotenv": "^16.4.7",
60
+ "fast-glob": "^3.3.3",
61
+ "fast-uri": "^3.0.6",
62
+ "fastify": "^5.2.1",
63
+ "fastify-graceful-shutdown": "^4.0.1",
64
+ "json-schema-to-typescript": "^15.0.4",
65
+ "vite": "^6.2.2",
67
66
  "vite-plugin-externals": "^0.6.2"
68
67
  },
69
68
  "devDependencies": {
70
- "@tinkoff/eslint-config": "^1.54.4",
71
- "@tinkoff/eslint-config-react": "^1.54.4",
72
- "@tinkoff/prettier-config": "^1.52.1",
73
- "@types/react": "^18.2.34",
74
- "@types/react-dom": "^18.2.14",
75
- "jest": "^29.7.0",
76
- "json-schema-to-typescript": "^13.1.1",
77
- "lint-staged": "^15.0.2",
78
- "react": "^18.2.0",
79
- "react-dom": "^18.2.0",
80
- "simple-git-hooks": "^2.9.0",
81
- "sort-package-json": "^2.6.0",
82
- "typescript": "^5.2.2"
69
+ "@commitlint/cli": "^19.8.0",
70
+ "@commitlint/config-conventional": "^19.8.0",
71
+ "@playwright/test": "^1.51.1",
72
+ "@tinkoff/eslint-config": "^5.0.1",
73
+ "@tinkoff/eslint-config-react": "^5.0.1",
74
+ "@tinkoff/prettier-config": "^5.0.0",
75
+ "@types/react": "^19.0.11",
76
+ "@types/react-dom": "^19.0.4",
77
+ "execa": "^9.5.2",
78
+ "lint-staged": "^15.5.0",
79
+ "react": "^19.0.0",
80
+ "react-dom": "^19.0.0",
81
+ "simple-git-hooks": "^2.11.1",
82
+ "sort-package-json": "^3.0.0",
83
+ "typescript": "^5.8.2",
84
+ "vitest": "^3.0.9"
83
85
  },
84
86
  "peerDependencies": {
85
- "react": "^18.0.0",
86
- "react-dom": "^18.0.0"
87
+ "react": "^18.0.0 || ^19.0.0",
88
+ "react-dom": "^18.0.0 || ^19.0.0"
87
89
  },
88
90
  "engines": {
89
- "node": ">=16.0.0"
91
+ "node": ">=v20.17.0"
90
92
  }
91
93
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Build configuration placed as nerest-build.schema.json in the root of the application.
2
+ * Build configuration placed as nerest/build.json in the root of the application.
3
3
  */
4
4
  export interface BuildConfiguration {
5
5
  /**
@@ -12,5 +12,23 @@ export interface BuildConfiguration {
12
12
  externals?: {
13
13
  [k: string]: string;
14
14
  };
15
+ /**
16
+ * Additional PostCSS configuration. Nerest uses the standard vite PostCSS configuration by default.
17
+ */
18
+ postcss?: {
19
+ /**
20
+ * List packages and options of PostCSS plugins to use. They will be added to the default plugin list.
21
+ */
22
+ plugins?: {
23
+ [k: string]: {
24
+ [k: string]: unknown;
25
+ };
26
+ };
27
+ [k: string]: unknown;
28
+ };
29
+ /**
30
+ * List of names of apps that have client side-effects, for example have their own self-initialization code that runs on import. Their entries will be loaded when hydration starts to run side-effect code.
31
+ */
32
+ clientSideEffects?: string[];
15
33
  [k: string]: unknown;
16
34
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft-07/schema",
3
3
  "title": "Build Configuration",
4
- "description": "Build configuration placed as nerest-build.schema.json in the root of the application.",
4
+ "description": "Build configuration placed as nerest/build.json in the root of the application.",
5
5
  "type": "object",
6
6
  "properties": {
7
7
  "excludes": {
@@ -17,6 +17,26 @@
17
17
  "additionalProperties": {
18
18
  "type": "string"
19
19
  }
20
+ },
21
+ "postcss": {
22
+ "description": "Additional PostCSS configuration. Nerest uses the standard vite PostCSS configuration by default.",
23
+ "type": "object",
24
+ "properties": {
25
+ "plugins": {
26
+ "description": "List packages and options of PostCSS plugins to use. They will be added to the default plugin list.",
27
+ "type": "object",
28
+ "additionalProperties": {
29
+ "type": "object"
30
+ }
31
+ }
32
+ }
33
+ },
34
+ "clientSideEffects": {
35
+ "description": "List of names of apps that have client side-effects, for example have their own self-initialization code that runs on import. Their entries will be loaded when hydration starts to run side-effect code.",
36
+ "type": "array",
37
+ "items": {
38
+ "type": "string"
39
+ }
20
40
  }
21
41
  }
22
42
  }