@nerest/nerest 0.1.0 → 1.5.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/LICENSE.md +178 -0
- package/README.md +129 -40
- package/bin/index.ts +3 -0
- package/bin/typegen.ts +35 -0
- package/bin/watch.ts +3 -2
- package/build/configs/development.ts +63 -0
- package/build/configs/production.ts +59 -0
- package/build/configs/shared.ts +51 -0
- package/build/configs/vite-logger.development.ts +30 -0
- package/build/index.ts +53 -76
- package/client/index.ts +14 -2
- package/dist/bin/index.js +4 -0
- package/dist/bin/typegen.d.ts +1 -0
- package/dist/bin/typegen.js +31 -0
- package/dist/bin/watch.js +1 -2
- package/dist/build/configs/development.d.ts +4 -0
- package/dist/build/configs/development.js +53 -0
- package/dist/build/configs/production.d.ts +4 -0
- package/dist/build/configs/production.js +50 -0
- package/dist/build/configs/shared.d.ts +10 -0
- package/dist/build/configs/shared.js +24 -0
- package/dist/build/configs/vite-logger.development.d.ts +2 -0
- package/dist/build/configs/vite-logger.development.js +25 -0
- package/dist/build/index.js +39 -69
- package/dist/client/index.js +9 -2
- package/dist/server/development.d.ts +1 -1
- package/dist/server/development.js +51 -134
- package/dist/server/hooks/logger.d.ts +1 -2
- package/dist/server/hooks/logger.js +3 -0
- package/dist/server/hooks/props.d.ts +2 -2
- package/dist/server/hooks/props.js +2 -2
- package/dist/server/hooks/runtime.js +3 -3
- package/dist/server/{parts → loaders}/apps.d.ts +2 -1
- package/dist/server/loaders/apps.js +36 -0
- package/dist/server/loaders/assets.js +25 -2
- package/dist/server/loaders/build.d.ts +2 -0
- package/dist/server/loaders/build.js +11 -0
- package/dist/server/loaders/examples.js +7 -13
- package/dist/server/loaders/manifest.d.ts +8 -1
- package/dist/server/loaders/manifest.js +12 -4
- package/dist/server/loaders/preview.d.ts +4 -0
- package/dist/server/loaders/preview.js +11 -0
- package/dist/server/loaders/project.d.ts +16 -0
- package/dist/server/loaders/project.js +11 -0
- package/dist/server/loaders/schema.d.ts +2 -1
- package/dist/server/loaders/schema.js +12 -8
- package/dist/server/parts/k8s-probes.js +10 -6
- package/dist/server/parts/preview.d.ts +2 -1
- package/dist/server/parts/preview.js +5 -4
- package/dist/server/parts/render.d.ts +5 -3
- package/dist/server/parts/render.js +8 -8
- package/dist/server/parts/swagger.d.ts +2 -1
- package/dist/server/parts/swagger.js +8 -19
- package/dist/server/parts/validator.d.ts +3 -1
- package/dist/server/parts/validator.js +13 -0
- package/dist/server/production.js +17 -83
- package/dist/server/shared.d.ts +14 -0
- package/dist/server/shared.js +94 -0
- package/dist/server/utils.d.ts +1 -0
- package/dist/server/utils.js +5 -0
- package/package.json +46 -43
- package/schemas/nerest-build.schema.d.ts +19 -1
- package/schemas/nerest-build.schema.json +21 -1
- package/server/development.ts +67 -172
- package/server/hooks/logger.ts +3 -0
- package/server/hooks/props.ts +5 -5
- package/server/hooks/runtime.ts +3 -3
- package/server/loaders/apps.ts +58 -0
- package/server/loaders/assets.ts +30 -2
- package/server/loaders/build.ts +14 -0
- package/server/loaders/examples.ts +7 -15
- package/server/loaders/manifest.ts +23 -4
- package/server/loaders/preview.ts +17 -0
- package/server/loaders/project.ts +33 -0
- package/server/loaders/schema.ts +12 -10
- package/server/parts/k8s-probes.ts +26 -13
- package/server/parts/preview.ts +11 -4
- package/server/parts/render.ts +13 -9
- package/server/parts/swagger.ts +10 -29
- package/server/parts/validator.ts +14 -0
- package/server/production.ts +22 -110
- package/server/shared.ts +150 -0
- package/server/utils.ts +6 -0
- package/dist/server/parts/apps.js +0 -37
- package/dist/server/parts/props-hook.d.ts +0 -1
- package/dist/server/parts/props-hook.js +0 -8
- package/dist/server/parts/runtime-hook.d.ts +0 -2
- package/dist/server/parts/runtime-hook.js +0 -28
- package/server/parts/apps.ts +0 -59
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// https://vite.dev/config/shared-options.html#customlogger
|
|
2
|
+
import { createLogger } from 'vite';
|
|
3
|
+
|
|
4
|
+
const logger = createLogger();
|
|
5
|
+
|
|
6
|
+
const loggerError = logger.error;
|
|
7
|
+
logger.error = (msg, options) => {
|
|
8
|
+
if (typeof msg === 'string' && !isIgnoredError(msg)) {
|
|
9
|
+
loggerError(msg, options);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default logger;
|
|
14
|
+
|
|
15
|
+
// These are errors expected in development that we don't need to log.
|
|
16
|
+
// If the error message includes all markers from a set, it is suppressed
|
|
17
|
+
const ignoredErrors = [
|
|
18
|
+
// Hook files are optional, but in development vite logs an error even though
|
|
19
|
+
// we suppress the exception. Silence these logs manually.
|
|
20
|
+
['cannot find entry point module', 'props.ts'],
|
|
21
|
+
['cannot find entry point module', 'runtime.ts'],
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function isIgnoredError(msg: string) {
|
|
25
|
+
for (const markers of ignoredErrors) {
|
|
26
|
+
if (markers.every((m) => msg.includes(m))) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
package/build/index.ts
CHANGED
|
@@ -1,105 +1,82 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
3
|
|
|
5
4
|
import { build } from 'vite';
|
|
6
|
-
import type { InlineConfig } from 'vite';
|
|
7
|
-
import { viteExternalsPlugin } from 'vite-plugin-externals';
|
|
8
5
|
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
6
|
+
import { loadBuildConfig } from '../server/loaders/build.js';
|
|
7
|
+
import { loadApps } from '../server/loaders/apps.js';
|
|
8
|
+
import type { Project } from '../server/loaders/project.js';
|
|
9
|
+
import { loadProject } from '../server/loaders/project.js';
|
|
10
|
+
import {
|
|
11
|
+
viteConfigProductionClient,
|
|
12
|
+
viteConfigProductionServer,
|
|
13
|
+
} from './configs/production.js';
|
|
12
14
|
|
|
13
15
|
export async function buildMicroFrontend() {
|
|
14
16
|
const root = process.cwd();
|
|
15
|
-
const staticPath =
|
|
17
|
+
const staticPath = prepareStaticPath();
|
|
16
18
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
// be a runtime env variable for the server instead?
|
|
20
|
-
if (!staticPath) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'NEREST_STATIC_PATH environment variable is not set but is required for the production build'
|
|
23
|
-
);
|
|
24
|
-
}
|
|
19
|
+
// Read build customizations from nerest/build.json
|
|
20
|
+
const buildConfig = await loadBuildConfig(root);
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
// Read project meta info from package.json
|
|
23
|
+
const project = await loadProject(root);
|
|
27
24
|
|
|
28
25
|
// Build client
|
|
29
|
-
|
|
30
|
-
// into a shared config
|
|
31
|
-
const clientConfig: InlineConfig = {
|
|
26
|
+
const clientViteConfig = await viteConfigProductionClient({
|
|
32
27
|
root,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
manifest: true,
|
|
38
|
-
modulePreload: false,
|
|
39
|
-
rollupOptions: {
|
|
40
|
-
input: '/node_modules/@nerest/nerest/client/index.ts',
|
|
41
|
-
output: {
|
|
42
|
-
dir: 'build',
|
|
43
|
-
entryFileNames: `client/assets/[name].js`,
|
|
44
|
-
chunkFileNames: `client/assets/[name].js`,
|
|
45
|
-
assetFileNames: `client/assets/[name].[ext]`,
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
resolve: {
|
|
50
|
-
// excludes - map buildConfig.excludes packages to an empty module
|
|
51
|
-
alias: excludes(buildConfig?.excludes),
|
|
52
|
-
},
|
|
53
|
-
plugins: [
|
|
54
|
-
// externals - map buildConfig.externals packages to a global variable on window
|
|
55
|
-
viteExternalsPlugin(buildConfig?.externals, { useWindow: false }),
|
|
56
|
-
],
|
|
57
|
-
};
|
|
58
|
-
|
|
28
|
+
base: staticPath,
|
|
29
|
+
buildConfig,
|
|
30
|
+
project,
|
|
31
|
+
});
|
|
59
32
|
console.log('Producing production client build...');
|
|
60
|
-
await build(
|
|
33
|
+
await build(clientViteConfig);
|
|
61
34
|
|
|
35
|
+
// Create nerest-manifest.json that production server reads on startup
|
|
62
36
|
console.log('Producing Nerest manifest file...');
|
|
63
|
-
await
|
|
37
|
+
await createNerestManifest(root, staticPath, project);
|
|
64
38
|
|
|
65
|
-
// Build server
|
|
66
|
-
const
|
|
39
|
+
// Build server
|
|
40
|
+
const serverViteConfig = await viteConfigProductionServer({
|
|
67
41
|
root,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
modulePreload: false,
|
|
73
|
-
// This is an important setting for producing a server build
|
|
74
|
-
ssr: true,
|
|
75
|
-
rollupOptions: {
|
|
76
|
-
input: '/node_modules/@nerest/nerest/server/production.ts',
|
|
77
|
-
output: {
|
|
78
|
-
dir: 'build',
|
|
79
|
-
entryFileNames: `server.mjs`,
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
|
|
42
|
+
base: staticPath,
|
|
43
|
+
buildConfig,
|
|
44
|
+
project,
|
|
45
|
+
});
|
|
85
46
|
console.log('Producing production server build...');
|
|
86
|
-
await build(
|
|
47
|
+
await build(serverViteConfig);
|
|
87
48
|
}
|
|
88
49
|
|
|
89
|
-
async function
|
|
50
|
+
async function createNerestManifest(
|
|
51
|
+
root: string,
|
|
52
|
+
staticPath: string,
|
|
53
|
+
project: Project
|
|
54
|
+
) {
|
|
90
55
|
const apps = await loadApps(root, staticPath);
|
|
91
56
|
await fs.writeFile(
|
|
92
57
|
path.join(root, 'build/nerest-manifest.json'),
|
|
93
|
-
JSON.stringify(apps),
|
|
94
|
-
|
|
58
|
+
JSON.stringify({ project, apps }),
|
|
59
|
+
'utf-8'
|
|
95
60
|
);
|
|
96
61
|
}
|
|
97
62
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
63
|
+
function prepareStaticPath() {
|
|
64
|
+
let staticPath = process.env.STATIC_PATH;
|
|
65
|
+
|
|
66
|
+
// The path where the client files are deployed is embedded
|
|
67
|
+
// during the initial build.
|
|
68
|
+
// TODO: handle error if STATIC_PATH isn't a valid base URL
|
|
69
|
+
if (!staticPath) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
'STATIC_PATH environment variable is not set but is required for the production build'
|
|
72
|
+
);
|
|
104
73
|
}
|
|
74
|
+
|
|
75
|
+
// Static path is a directory URI. To be treated as such by node:url,
|
|
76
|
+
// it has to end with a trailing slash
|
|
77
|
+
if (!staticPath.endsWith('/')) {
|
|
78
|
+
staticPath += '/';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return staticPath;
|
|
105
82
|
}
|
package/client/index.ts
CHANGED
|
@@ -10,8 +10,10 @@ import ReactDOM from 'react-dom/client';
|
|
|
10
10
|
const modules = import.meta.glob('/apps/*/index.tsx', { import: 'default' });
|
|
11
11
|
|
|
12
12
|
async function runHydration() {
|
|
13
|
-
for (const container of document.querySelectorAll(
|
|
14
|
-
|
|
13
|
+
for (const container of document.querySelectorAll(
|
|
14
|
+
`div[data-project-name="${import.meta.env.NEREST_PROJECT_NAME}"]`
|
|
15
|
+
)) {
|
|
16
|
+
const appName = container.getAttribute(`data-app-name`);
|
|
15
17
|
const appModuleLoader = modules[`/apps/${appName}/index.tsx`];
|
|
16
18
|
|
|
17
19
|
if (!appModuleLoader || container.hasAttribute('data-app-hydrated')) {
|
|
@@ -37,8 +39,18 @@ async function runHydration() {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
// Apps can only be hydrated after DOM is ready, because we need to query
|
|
43
|
+
// apps' containers and their corresponding scripts.
|
|
40
44
|
if (document.readyState !== 'complete') {
|
|
41
45
|
document.addEventListener('DOMContentLoaded', runHydration);
|
|
42
46
|
} else {
|
|
43
47
|
runHydration();
|
|
44
48
|
}
|
|
49
|
+
|
|
50
|
+
// Entries might be self-initializing (e.g. client-only apps) or have other
|
|
51
|
+
// side effects. In that case we have to load them eagerly, so that their
|
|
52
|
+
// initialization code can run, even if there is nothing to hydrate.
|
|
53
|
+
const clientSideEffects: string[] | undefined = JSON.parse(
|
|
54
|
+
import.meta.env.NEREST_CLIENT_SIDE_EFFECTS
|
|
55
|
+
);
|
|
56
|
+
clientSideEffects?.forEach((name) => modules[`/apps/${name}/index.tsx`]?.());
|
package/dist/bin/index.js
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
// All executions of `nerest <command>` get routed through here
|
|
3
3
|
import 'dotenv/config';
|
|
4
4
|
import { build } from './build.js';
|
|
5
|
+
import { typegen } from './typegen.js';
|
|
5
6
|
import { watch } from './watch.js';
|
|
6
7
|
// TODO: add CLI help and manual, maybe use a CLI framework like oclif
|
|
7
8
|
async function cliEntry(args) {
|
|
8
9
|
if (args[0] === 'build') {
|
|
9
10
|
await build();
|
|
10
11
|
}
|
|
12
|
+
else if (args[0] === 'typegen') {
|
|
13
|
+
await typegen(args.slice(1));
|
|
14
|
+
}
|
|
11
15
|
else if (args[0] === 'watch') {
|
|
12
16
|
await watch();
|
|
13
17
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function typegen(globs: string[]): Promise<void>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import fg from 'fast-glob';
|
|
4
|
+
import { compileFromFile } from 'json-schema-to-typescript';
|
|
5
|
+
// Comment at the start of every type definition file, alerting
|
|
6
|
+
// developers to not modify the file by hand
|
|
7
|
+
const bannerComment = `
|
|
8
|
+
/**
|
|
9
|
+
* This file was automatically generated by Nerest.
|
|
10
|
+
* DO NOT MODIFY IT BY HAND. Instead, modify the source schema.json file,
|
|
11
|
+
* and run "nerest typegen" to regenerate this file.
|
|
12
|
+
*/`.trim();
|
|
13
|
+
// Generates a TypeScript type definition for each JSON Schema in the given globs
|
|
14
|
+
export async function typegen(globs) {
|
|
15
|
+
// If no globs are provided, default to the schema.json file in each app directory
|
|
16
|
+
if (globs.length === 0) {
|
|
17
|
+
globs = ['apps/*/schema.json'];
|
|
18
|
+
}
|
|
19
|
+
const paths = await fg.glob(globs, { onlyFiles: true });
|
|
20
|
+
console.log(`Found ${paths.length} schemas, generating types...`);
|
|
21
|
+
for (const schemaPath of paths) {
|
|
22
|
+
// Turn each schema.json file into schema.d.ts
|
|
23
|
+
const typePath = schemaPath.replace('.json', '.d.ts');
|
|
24
|
+
const type = await compileFromFile(schemaPath, {
|
|
25
|
+
cwd: path.resolve(path.dirname(schemaPath)),
|
|
26
|
+
bannerComment,
|
|
27
|
+
});
|
|
28
|
+
await fs.writeFile(typePath, type, 'utf-8');
|
|
29
|
+
console.log(`${schemaPath} -> ${typePath}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/bin/watch.js
CHANGED
|
@@ -2,7 +2,6 @@ import { runDevelopmentServer } from '../server/development.js';
|
|
|
2
2
|
// Start dev server in watch mode, that restarts on file change
|
|
3
3
|
// and rebuilds the client static files
|
|
4
4
|
export async function watch() {
|
|
5
|
-
// TODO: will be replaced with nerest logger
|
|
6
5
|
console.log('Starting Nerest watch...');
|
|
7
|
-
await runDevelopmentServer();
|
|
6
|
+
await runDevelopmentServer(process.env.PORT ? Number(process.env.PORT) : 3000);
|
|
8
7
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { InlineConfig } from 'vite';
|
|
2
|
+
import type { BuildArgs } from './shared.js';
|
|
3
|
+
export declare function viteConfigDevelopmentClient(args: BuildArgs): Promise<InlineConfig>;
|
|
4
|
+
export declare function viteConfigDevelopmentServer(args: BuildArgs): Promise<InlineConfig>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { viteConfigShared } from './shared.js';
|
|
2
|
+
import logger from './vite-logger.development.js';
|
|
3
|
+
export async function viteConfigDevelopmentClient(args) {
|
|
4
|
+
return {
|
|
5
|
+
...(await viteConfigShared(args)),
|
|
6
|
+
build: {
|
|
7
|
+
// Manifest is needed to report used assets in SSR handles
|
|
8
|
+
manifest: true,
|
|
9
|
+
modulePreload: false,
|
|
10
|
+
watch: {},
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
input: '/node_modules/@nerest/nerest/client/index.ts',
|
|
13
|
+
output: {
|
|
14
|
+
dir: 'build/client/assets',
|
|
15
|
+
entryFileNames: `[name].js`,
|
|
16
|
+
chunkFileNames: `[name].js`,
|
|
17
|
+
assetFileNames: `[name].[ext]`,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
customLogger: logger,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export async function viteConfigDevelopmentServer(args) {
|
|
25
|
+
return {
|
|
26
|
+
...(await viteConfigShared(args)),
|
|
27
|
+
server: {
|
|
28
|
+
// Middleware lets vite compile on the fly, providing
|
|
29
|
+
// hot reload of certain modules
|
|
30
|
+
middlewareMode: true,
|
|
31
|
+
// Origin to serve imported assets from (like images)
|
|
32
|
+
origin: new URL(args.base).origin,
|
|
33
|
+
// Run HMR WebSocket server on random port to prevent conflicts
|
|
34
|
+
// between multiple projects running simultaneously
|
|
35
|
+
hmr: { port: randomPort() },
|
|
36
|
+
// Allow requests from all hosts in development
|
|
37
|
+
allowedHosts: true,
|
|
38
|
+
},
|
|
39
|
+
// optimizeDeps is only necessary with index.html entrypoint,
|
|
40
|
+
// which we don't have
|
|
41
|
+
optimizeDeps: {
|
|
42
|
+
noDiscovery: true,
|
|
43
|
+
include: [],
|
|
44
|
+
},
|
|
45
|
+
customLogger: logger,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Returns a random high-number port to prevent conflicts
|
|
49
|
+
function randomPort() {
|
|
50
|
+
// 49152-65535 is the ephemeral port range
|
|
51
|
+
// https://datatracker.ietf.org/doc/html/rfc6335#section-6
|
|
52
|
+
return 49152 + Math.floor(Math.random() * (65535 - 49152));
|
|
53
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { InlineConfig } from 'vite';
|
|
2
|
+
import type { BuildArgs } from './shared.js';
|
|
3
|
+
export declare function viteConfigProductionClient(args: BuildArgs): Promise<InlineConfig>;
|
|
4
|
+
export declare function viteConfigProductionServer(args: BuildArgs): Promise<InlineConfig>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { viteExternalsPlugin } from 'vite-plugin-externals';
|
|
2
|
+
import { viteConfigShared } from './shared.js';
|
|
3
|
+
import { excludes } from '../excludes/index.js';
|
|
4
|
+
export async function viteConfigProductionClient(args) {
|
|
5
|
+
return {
|
|
6
|
+
...(await viteConfigShared(args)),
|
|
7
|
+
build: {
|
|
8
|
+
// Manifest is needed to report used assets in SSR handles
|
|
9
|
+
manifest: true,
|
|
10
|
+
modulePreload: false,
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
input: '/node_modules/@nerest/nerest/client/index.ts',
|
|
13
|
+
output: {
|
|
14
|
+
dir: 'build/client/assets',
|
|
15
|
+
entryFileNames: `[name].js`,
|
|
16
|
+
chunkFileNames: `[name].js`,
|
|
17
|
+
assetFileNames: `[name].[ext]`,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
resolve: {
|
|
22
|
+
// excludes - map buildConfig.excludes packages to an empty module
|
|
23
|
+
alias: excludes(args.buildConfig?.excludes),
|
|
24
|
+
},
|
|
25
|
+
plugins: [
|
|
26
|
+
// externals - map buildConfig.externals packages to a global variable on window
|
|
27
|
+
viteExternalsPlugin(args.buildConfig?.externals, { useWindow: false }),
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export async function viteConfigProductionServer(args) {
|
|
32
|
+
return {
|
|
33
|
+
...(await viteConfigShared(args)),
|
|
34
|
+
build: {
|
|
35
|
+
emptyOutDir: false,
|
|
36
|
+
modulePreload: false,
|
|
37
|
+
// This is an important setting for producing a server build
|
|
38
|
+
ssr: true,
|
|
39
|
+
rollupOptions: {
|
|
40
|
+
input: '/node_modules/@nerest/nerest/server/production.ts',
|
|
41
|
+
output: {
|
|
42
|
+
dir: 'build',
|
|
43
|
+
entryFileNames: `server.mjs`,
|
|
44
|
+
chunkFileNames: `[name].mjs`,
|
|
45
|
+
assetFileNames: `[name].[ext]`,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { InlineConfig } from 'vite';
|
|
2
|
+
import type { BuildConfiguration } from '../../schemas/nerest-build.schema.js';
|
|
3
|
+
import type { Project } from '../../server/loaders/project.js';
|
|
4
|
+
export type BuildArgs = {
|
|
5
|
+
root: string;
|
|
6
|
+
base: string;
|
|
7
|
+
buildConfig: BuildConfiguration | undefined;
|
|
8
|
+
project: Project;
|
|
9
|
+
};
|
|
10
|
+
export declare function viteConfigShared({ root, base, buildConfig, project, }: BuildArgs): Promise<InlineConfig>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export async function viteConfigShared({ root, base, buildConfig, project, }) {
|
|
2
|
+
// This will be available to client scripts with import.meta.env
|
|
3
|
+
process.env.NEREST_PROJECT_NAME = project.name;
|
|
4
|
+
process.env.NEREST_CLIENT_SIDE_EFFECTS = JSON.stringify(buildConfig?.clientSideEffects ?? []);
|
|
5
|
+
return {
|
|
6
|
+
root,
|
|
7
|
+
base,
|
|
8
|
+
appType: 'custom',
|
|
9
|
+
envPrefix: 'NEREST_',
|
|
10
|
+
css: {
|
|
11
|
+
postcss: {
|
|
12
|
+
// postcss plugins - import modules mentioned in buildConfig.postcss.plugins
|
|
13
|
+
plugins: await loadPostcssPlugins(buildConfig?.postcss?.plugins),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async function loadPostcssPlugins(plugins) {
|
|
19
|
+
if (!plugins) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const imports = Object.entries(plugins).map(async ([name, options]) => (await import(name)).default(options));
|
|
23
|
+
return Promise.all(imports);
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// https://vite.dev/config/shared-options.html#customlogger
|
|
2
|
+
import { createLogger } from 'vite';
|
|
3
|
+
const logger = createLogger();
|
|
4
|
+
const loggerError = logger.error;
|
|
5
|
+
logger.error = (msg, options) => {
|
|
6
|
+
if (typeof msg === 'string' && !isIgnoredError(msg)) {
|
|
7
|
+
loggerError(msg, options);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
export default logger;
|
|
11
|
+
// These are errors expected in development that we don't need to log.
|
|
12
|
+
// If the error message includes all markers from a set, it is suppressed
|
|
13
|
+
const ignoredErrors = [
|
|
14
|
+
// Hook files are optional, but in development vite logs an error even though
|
|
15
|
+
// we suppress the exception. Silence these logs manually.
|
|
16
|
+
['cannot find entry point module', 'props.ts'],
|
|
17
|
+
['cannot find entry point module', 'runtime.ts'],
|
|
18
|
+
];
|
|
19
|
+
function isIgnoredError(msg) {
|
|
20
|
+
for (const markers of ignoredErrors) {
|
|
21
|
+
if (markers.every((m) => msg.includes(m))) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/build/index.js
CHANGED
|
@@ -1,85 +1,55 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
3
|
import { build } from 'vite';
|
|
5
|
-
import {
|
|
6
|
-
import { loadApps } from '../server/
|
|
7
|
-
import {
|
|
4
|
+
import { loadBuildConfig } from '../server/loaders/build.js';
|
|
5
|
+
import { loadApps } from '../server/loaders/apps.js';
|
|
6
|
+
import { loadProject } from '../server/loaders/project.js';
|
|
7
|
+
import { viteConfigProductionClient, viteConfigProductionServer, } from './configs/production.js';
|
|
8
8
|
export async function buildMicroFrontend() {
|
|
9
9
|
const root = process.cwd();
|
|
10
|
-
const staticPath =
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
throw new Error('NEREST_STATIC_PATH environment variable is not set but is required for the production build');
|
|
16
|
-
}
|
|
17
|
-
const buildConfig = await readBuildConfig(root);
|
|
10
|
+
const staticPath = prepareStaticPath();
|
|
11
|
+
// Read build customizations from nerest/build.json
|
|
12
|
+
const buildConfig = await loadBuildConfig(root);
|
|
13
|
+
// Read project meta info from package.json
|
|
14
|
+
const project = await loadProject(root);
|
|
18
15
|
// Build client
|
|
19
|
-
|
|
20
|
-
// into a shared config
|
|
21
|
-
const clientConfig = {
|
|
16
|
+
const clientViteConfig = await viteConfigProductionClient({
|
|
22
17
|
root,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
manifest: true,
|
|
28
|
-
modulePreload: false,
|
|
29
|
-
rollupOptions: {
|
|
30
|
-
input: '/node_modules/@nerest/nerest/client/index.ts',
|
|
31
|
-
output: {
|
|
32
|
-
dir: 'build',
|
|
33
|
-
entryFileNames: `client/assets/[name].js`,
|
|
34
|
-
chunkFileNames: `client/assets/[name].js`,
|
|
35
|
-
assetFileNames: `client/assets/[name].[ext]`,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
resolve: {
|
|
40
|
-
// excludes - map buildConfig.excludes packages to an empty module
|
|
41
|
-
alias: excludes(buildConfig?.excludes),
|
|
42
|
-
},
|
|
43
|
-
plugins: [
|
|
44
|
-
// externals - map buildConfig.externals packages to a global variable on window
|
|
45
|
-
viteExternalsPlugin(buildConfig?.externals, { useWindow: false }),
|
|
46
|
-
],
|
|
47
|
-
};
|
|
18
|
+
base: staticPath,
|
|
19
|
+
buildConfig,
|
|
20
|
+
project,
|
|
21
|
+
});
|
|
48
22
|
console.log('Producing production client build...');
|
|
49
|
-
await build(
|
|
23
|
+
await build(clientViteConfig);
|
|
24
|
+
// Create nerest-manifest.json that production server reads on startup
|
|
50
25
|
console.log('Producing Nerest manifest file...');
|
|
51
|
-
await
|
|
52
|
-
// Build server
|
|
53
|
-
const
|
|
26
|
+
await createNerestManifest(root, staticPath, project);
|
|
27
|
+
// Build server
|
|
28
|
+
const serverViteConfig = await viteConfigProductionServer({
|
|
54
29
|
root,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
modulePreload: false,
|
|
60
|
-
// This is an important setting for producing a server build
|
|
61
|
-
ssr: true,
|
|
62
|
-
rollupOptions: {
|
|
63
|
-
input: '/node_modules/@nerest/nerest/server/production.ts',
|
|
64
|
-
output: {
|
|
65
|
-
dir: 'build',
|
|
66
|
-
entryFileNames: `server.mjs`,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
};
|
|
30
|
+
base: staticPath,
|
|
31
|
+
buildConfig,
|
|
32
|
+
project,
|
|
33
|
+
});
|
|
71
34
|
console.log('Producing production server build...');
|
|
72
|
-
await build(
|
|
35
|
+
await build(serverViteConfig);
|
|
73
36
|
}
|
|
74
|
-
async function
|
|
37
|
+
async function createNerestManifest(root, staticPath, project) {
|
|
75
38
|
const apps = await loadApps(root, staticPath);
|
|
76
|
-
await fs.writeFile(path.join(root, 'build/nerest-manifest.json'), JSON.stringify(apps),
|
|
39
|
+
await fs.writeFile(path.join(root, 'build/nerest-manifest.json'), JSON.stringify({ project, apps }), 'utf-8');
|
|
77
40
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
41
|
+
function prepareStaticPath() {
|
|
42
|
+
let staticPath = process.env.STATIC_PATH;
|
|
43
|
+
// The path where the client files are deployed is embedded
|
|
44
|
+
// during the initial build.
|
|
45
|
+
// TODO: handle error if STATIC_PATH isn't a valid base URL
|
|
46
|
+
if (!staticPath) {
|
|
47
|
+
throw new Error('STATIC_PATH environment variable is not set but is required for the production build');
|
|
48
|
+
}
|
|
49
|
+
// Static path is a directory URI. To be treated as such by node:url,
|
|
50
|
+
// it has to end with a trailing slash
|
|
51
|
+
if (!staticPath.endsWith('/')) {
|
|
52
|
+
staticPath += '/';
|
|
84
53
|
}
|
|
54
|
+
return staticPath;
|
|
85
55
|
}
|
package/dist/client/index.js
CHANGED
|
@@ -7,8 +7,8 @@ import ReactDOM from 'react-dom/client';
|
|
|
7
7
|
// if needed.
|
|
8
8
|
const modules = import.meta.glob('/apps/*/index.tsx', { import: 'default' });
|
|
9
9
|
async function runHydration() {
|
|
10
|
-
for (const container of document.querySelectorAll(
|
|
11
|
-
const appName = container.getAttribute(
|
|
10
|
+
for (const container of document.querySelectorAll(`div[data-project-name="${import.meta.env.NEREST_PROJECT_NAME}"]`)) {
|
|
11
|
+
const appName = container.getAttribute(`data-app-name`);
|
|
12
12
|
const appModuleLoader = modules[`/apps/${appName}/index.tsx`];
|
|
13
13
|
if (!appModuleLoader || container.hasAttribute('data-app-hydrated')) {
|
|
14
14
|
continue;
|
|
@@ -24,9 +24,16 @@ async function runHydration() {
|
|
|
24
24
|
ReactDOM.hydrateRoot(container, reactElement);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
// Apps can only be hydrated after DOM is ready, because we need to query
|
|
28
|
+
// apps' containers and their corresponding scripts.
|
|
27
29
|
if (document.readyState !== 'complete') {
|
|
28
30
|
document.addEventListener('DOMContentLoaded', runHydration);
|
|
29
31
|
}
|
|
30
32
|
else {
|
|
31
33
|
runHydration();
|
|
32
34
|
}
|
|
35
|
+
// Entries might be self-initializing (e.g. client-only apps) or have other
|
|
36
|
+
// side effects. In that case we have to load them eagerly, so that their
|
|
37
|
+
// initialization code can run, even if there is nothing to hydrate.
|
|
38
|
+
const clientSideEffects = JSON.parse(import.meta.env.NEREST_CLIENT_SIDE_EFFECTS);
|
|
39
|
+
clientSideEffects?.forEach((name) => modules[`/apps/${name}/index.tsx`]?.());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function runDevelopmentServer(): Promise<void>;
|
|
1
|
+
export declare function runDevelopmentServer(port: number): Promise<void>;
|