@modern-js/prod-server 1.0.1 → 1.0.3

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 (34) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/js/modern/constants.js +4 -0
  3. package/dist/js/modern/libs/render/modern/index.js +3 -1
  4. package/dist/js/modern/server/index.js +47 -57
  5. package/dist/js/modern/server/modern-server-split.js +42 -33
  6. package/dist/js/modern/server/modern-server.js +55 -48
  7. package/dist/js/modern/type.js +1 -1
  8. package/dist/js/node/constants.js +7 -2
  9. package/dist/js/node/libs/render/modern/index.js +3 -1
  10. package/dist/js/node/server/index.js +45 -57
  11. package/dist/js/node/server/modern-server-split.js +40 -37
  12. package/dist/js/node/server/modern-server.js +55 -48
  13. package/dist/js/node/type.js +1 -3
  14. package/dist/types/constants.d.ts +5 -1
  15. package/dist/types/server/index.d.ts +5 -9
  16. package/dist/types/server/modern-server-split.d.ts +2 -26
  17. package/dist/types/server/modern-server.d.ts +16 -14
  18. package/dist/types/type.d.ts +16 -2
  19. package/package.json +4 -4
  20. package/src/constants.ts +5 -0
  21. package/src/libs/render/index.ts +1 -0
  22. package/src/libs/render/modern/index.ts +1 -1
  23. package/src/server/index.ts +51 -71
  24. package/src/server/modern-server-split.ts +42 -46
  25. package/src/server/modern-server.ts +61 -53
  26. package/src/type.ts +41 -1
  27. package/tests/fixtures/pure/test-dist/bundles/main.js +5 -0
  28. package/tests/fixtures/pure/test-dist/html/main/index.html +36 -0
  29. package/tests/fixtures/pure/test-dist/route.json +31 -0
  30. package/tests/fixtures/ssr/bundle-error.js +3 -0
  31. package/tests/fixtures/ssr/tpl.html +11 -0
  32. package/tests/render.test.ts +106 -2
  33. package/tests/server.test.ts +192 -5
  34. package/tests/spr.test.ts +38 -0
package/src/type.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from 'buffer';
2
+ import { IncomingMessage, Server, ServerResponse } from 'http';
2
3
  import { serverManager } from '@modern-js/server-core';
3
4
  import type { NormalizedConfig } from '@modern-js/core';
4
5
  import type { Metrics, Logger, NextFunction } from '@modern-js/types/server';
@@ -36,6 +37,7 @@ export type ModernServerOptions = {
36
37
  ssr?: string;
37
38
  api?: string;
38
39
  };
40
+ runMode?: string;
39
41
  [propName: string]: any;
40
42
  };
41
43
 
@@ -56,6 +58,44 @@ export type Then<T> = T extends PromiseLike<infer U> ? U : T;
56
58
 
57
59
  export type ServerHookRunner = Then<ReturnType<typeof serverManager.init>>;
58
60
 
59
- export type ReadyOptions = { routes?: ModernRouteInterface[] };
61
+ export type BuildOptions = { routes?: ModernRouteInterface[] };
60
62
 
61
63
  export type { Metrics, Logger, NextFunction };
64
+
65
+ export type HookNames =
66
+ | 'beforeMatch'
67
+ | 'afterMatch'
68
+ | 'beforeRender'
69
+ | 'afterRender';
70
+
71
+ export interface ModernServerInterface {
72
+ pwd: string;
73
+
74
+ distDir: string;
75
+
76
+ onInit: (runner: ServerHookRunner) => Promise<void>;
77
+
78
+ onClose: () => Promise<void>;
79
+
80
+ onRepack: (options: BuildOptions) => void;
81
+
82
+ onListening: (app: Server) => void;
83
+
84
+ getRequestHandler: () => (
85
+ req: IncomingMessage,
86
+ res: ServerResponse,
87
+ next?: () => void,
88
+ ) => void;
89
+
90
+ createHTTPServer: (
91
+ handler: (
92
+ req: IncomingMessage,
93
+ res: ServerResponse,
94
+ next?: () => void,
95
+ ) => void,
96
+ ) => Promise<Server>;
97
+ }
98
+
99
+ export type ServerConstructor = (
100
+ options: ModernServerOptions,
101
+ ) => ModernServerInterface;
@@ -0,0 +1,5 @@
1
+ const { SERVER_RENDER_FUNCTION_NAME } = require('@modern-js/utils');
2
+
3
+ module.exports = {
4
+ [SERVER_RENDER_FUNCTION_NAME]: () => '<div>Modern.js</div>',
5
+ };
@@ -0,0 +1,36 @@
1
+
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, viewport-fit=cover, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
8
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
9
+ <meta name="renderer" content="webkit">
10
+ <meta name="layoutmode" content="standard">
11
+ <meta name="imagemode" content="force">
12
+ <meta name="wap-font-scale" content="no">
13
+ <meta name="format-detection" content="telephone=no">
14
+ <title></title>
15
+
16
+
17
+
18
+ <script>
19
+ window.__assetPrefix__ = '';
20
+ </script>
21
+
22
+ <script defer src="/static/js/runtime-main.js"></script><script defer src="/static/js/vendors-node_modules_pnpm_babel_runtime_7_16_7_node_modules_babel_runtime_regenerator_index_j-7f9278.js"></script><script defer src="/static/js/main.js"></script></head>
23
+
24
+ <body>
25
+ <!--<?- chunksMap.css ?>-->
26
+ <noscript>
27
+ We're sorry but react app doesn't work properly without JavaScript enabled. Please enable it to continue.
28
+ </noscript>
29
+ <div id="root"><!--<?- html ?>--></div>
30
+
31
+ <!--<?- chunksMap.js ?>-->
32
+ <!--<?- SSRDataScript ?>-->
33
+
34
+ </body>
35
+
36
+ </html>
@@ -0,0 +1,31 @@
1
+ {
2
+ "routes": [
3
+ {
4
+ "urlPath": "/",
5
+ "entryName": "main",
6
+ "entryPath": "html/main/index.html",
7
+ "isSPA": true,
8
+ "isSSR": true,
9
+ "enableModernMode": false,
10
+ "bundle": "bundles/main.js"
11
+ },
12
+ {
13
+ "urlPath": "/500",
14
+ "entryName": "500",
15
+ "entryPath": "html/main/index.html",
16
+ "isSPA": true,
17
+ "isSSR": true,
18
+ "enableModernMode": false,
19
+ "bundle": "bundles/main.js"
20
+ },
21
+ {
22
+ "urlPath": "/404",
23
+ "entryName": "500",
24
+ "entryPath": "html/main/index.html",
25
+ "isSPA": true,
26
+ "isSSR": true,
27
+ "enableModernMode": false,
28
+ "bundle": "bundles/main.js"
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ error: () => '<div>Modern.js</div>',
3
+ };
@@ -0,0 +1,11 @@
1
+ <html lang="en">
2
+ <head>
3
+ <meta charset="UTF-8">
4
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Document</title>
7
+ </head>
8
+ <body>
9
+ <div>csr</div>
10
+ </body>
11
+ </html>
@@ -1,11 +1,13 @@
1
1
  import path from 'path';
2
- import { render } from '../src/libs/render/ssr';
2
+ import { createRenderHandler } from '../src/libs/render';
3
+ import { render as renderSSR } from '../src/libs/render/ssr';
3
4
  import { handleDirectory } from '../src/libs/render/static';
4
5
  import { LruReader } from '../src/libs/render/reader';
6
+ import { supportModern } from '../src/libs/render/modern';
5
7
 
6
8
  describe('test render function', () => {
7
9
  test('should return content correctly ', async () => {
8
- const renderResult = await render(
10
+ const renderResult = await renderSSR(
9
11
  {
10
12
  params: {},
11
13
  pathname: '/foo',
@@ -99,4 +101,106 @@ describe('test render function', () => {
99
101
  expect(res2).not.toBeNull();
100
102
  expect(res2?.content.toString()).toMatch('modern');
101
103
  });
104
+
105
+ test('should entry render fn work correctly', async () => {
106
+ const render = createRenderHandler({
107
+ distDir: path.join(__dirname, 'fixtures', 'ssr'),
108
+ staticGenerate: false,
109
+ });
110
+ const renderResult = await render({
111
+ ctx: {
112
+ params: {},
113
+ pathname: '/foo',
114
+ host: 'localhost:8080',
115
+ query: {},
116
+ url: 'localhost:8080/foo',
117
+ cookieMap: {},
118
+ headers: {},
119
+ resHasHandled: () => false,
120
+ } as any,
121
+ route: {
122
+ urlPath: '/foo',
123
+ bundle: 'bundle.js',
124
+ entryPath: 'tpl.html',
125
+ entryName: 'foo',
126
+ isSSR: true,
127
+ isSPA: true,
128
+ } as any,
129
+ runner: {
130
+ extendSSRContext: () => {
131
+ // empty
132
+ },
133
+ } as any,
134
+ });
135
+ expect(renderResult!.content).toMatch('Modern.js');
136
+ expect(renderResult!.contentType).toMatch('text/html; charset=utf-8');
137
+ });
138
+
139
+ test('should entry render fn fallback work correctly', async () => {
140
+ const render = createRenderHandler({
141
+ distDir: path.join(__dirname, 'fixtures', 'ssr'),
142
+ staticGenerate: false,
143
+ });
144
+ const renderResult = await render({
145
+ ctx: {
146
+ params: {},
147
+ pathname: '/foo',
148
+ host: 'localhost:8080',
149
+ query: {},
150
+ url: 'localhost:8080/foo',
151
+ cookieMap: {},
152
+ headers: {},
153
+ res: {
154
+ setHeader: () => false,
155
+ },
156
+ error: () => false,
157
+ resHasHandled: () => false,
158
+ } as any,
159
+ route: {
160
+ urlPath: '/foo',
161
+ bundle: 'bundle-error.js',
162
+ entryPath: 'tpl.html',
163
+ entryName: 'foo',
164
+ isSSR: true,
165
+ isSPA: true,
166
+ } as any,
167
+ runner: {
168
+ extendSSRContext: () => {
169
+ // empty
170
+ },
171
+ } as any,
172
+ });
173
+ expect(renderResult!.content.toString()).toMatch('csr');
174
+ expect(renderResult!.contentType).toMatch('text/html; charset=utf-8');
175
+ });
176
+ });
177
+
178
+ describe('test modern render', () => {
179
+ test('should return true if has target query', () => {
180
+ const res = supportModern({ query: { modern_es6: true } } as any);
181
+ expect(res).toBeTruthy();
182
+ });
183
+
184
+ test('should return false if no ua', () => {
185
+ const res = supportModern({ headers: {} } as any);
186
+ expect(res).toBeFalsy();
187
+ });
188
+
189
+ test('should return false if ua is not string', () => {
190
+ const res = supportModern({ headers: { 'user-agent': true } } as any);
191
+ expect(res).toBeFalsy();
192
+ });
193
+
194
+ test('should return false if ua no browser version', () => {
195
+ const res = supportModern({ headers: { 'user-agent': 'mock' } } as any);
196
+ expect(res).toBeFalsy();
197
+ });
198
+
199
+ test('should return false if ua no match name', () => {
200
+ const ua =
201
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36';
202
+
203
+ const res = supportModern({ headers: { 'user-agent': ua } } as any);
204
+ expect(res).toBeTruthy();
205
+ });
102
206
  });
@@ -1,9 +1,14 @@
1
1
  import path from 'path';
2
+ import { EventEmitter, Readable } from 'stream';
2
3
  import { defaultsConfig, NormalizedConfig } from '@modern-js/core';
3
4
  import { ModernServerContext, NextFunction } from '@modern-js/types';
4
- import createServer, { Server } from '../src';
5
+ import httpMocks from 'node-mocks-http';
6
+ import portfinder from 'portfinder';
7
+ import createServer, { RUN_MODE, Server } from '../src';
5
8
  import { ModernServer } from '../src/server/modern-server';
9
+ import { createContext } from '../src/libs/context';
6
10
 
11
+ const appDirectory = path.join(__dirname, './fixtures/pure');
7
12
  describe('test server', () => {
8
13
  test('should throw error when ', resolve => {
9
14
  try {
@@ -19,14 +24,17 @@ describe('test server', () => {
19
24
  test('shoule get modern server instance', async () => {
20
25
  const server = await createServer({
21
26
  config: defaultsConfig as NormalizedConfig,
22
- pwd: path.join(__dirname, './fixtures/pure'),
27
+ pwd: appDirectory,
23
28
  });
29
+ const port = await portfinder.getPortPromise();
24
30
  expect(server instanceof Server).toBe(true);
31
+
32
+ server.listen(port, () => {
33
+ server.close();
34
+ });
25
35
  });
26
36
 
27
37
  describe('shoule get production modern server instance', () => {
28
- const appDirectory = path.join(__dirname, './fixtures/pure');
29
-
30
38
  test('should init server correctly', async () => {
31
39
  const server = await createServer({
32
40
  config: defaultsConfig as NormalizedConfig,
@@ -89,13 +97,192 @@ describe('test server', () => {
89
97
 
90
98
  test('should get request handler correctly', async () => {
91
99
  const server = await createServer({
92
- config: defaultsConfig as NormalizedConfig,
100
+ config: {
101
+ ...(defaultsConfig as NormalizedConfig),
102
+ output: {
103
+ path: 'test-dist',
104
+ },
105
+ },
93
106
  pwd: appDirectory,
94
107
  });
95
108
 
96
109
  const modernServer: ModernServer = (server as any).server;
97
110
  const handler = modernServer.getRequestHandler();
98
111
  expect(typeof handler === 'function').toBeTruthy();
112
+
113
+ const req = httpMocks.createRequest({
114
+ url: '/',
115
+ headers: {
116
+ host: 'modernjs.com',
117
+ },
118
+ eventEmitter: Readable,
119
+ method: 'GET',
120
+ });
121
+ const res = httpMocks.createResponse({ eventEmitter: EventEmitter });
122
+ handler(req, res, () => {
123
+ // empty
124
+ });
125
+ const html = await new Promise((resolve, _reject) => {
126
+ res.on('finish', () => {
127
+ resolve(res._getData());
128
+ });
129
+ });
130
+
131
+ expect(html).toMatch('<div>Modern.js</div>');
132
+ });
133
+
134
+ test('should error handler correctly with custom entry', async () => {
135
+ const server = await createServer({
136
+ config: {
137
+ ...(defaultsConfig as NormalizedConfig),
138
+ output: {
139
+ path: 'test-dist',
140
+ },
141
+ },
142
+ pwd: appDirectory,
143
+ });
144
+
145
+ const modernServer: ModernServer = (server as any).server;
146
+ const req = httpMocks.createRequest({
147
+ url: '/',
148
+ headers: {
149
+ host: 'modernjs.com',
150
+ },
151
+ eventEmitter: Readable,
152
+ method: 'GET',
153
+ });
154
+ const res = httpMocks.createResponse({ eventEmitter: EventEmitter });
155
+ const ctx = createContext(req, res);
156
+ ctx.error = () => {
157
+ // empty
158
+ };
159
+
160
+ setTimeout(() => {
161
+ (modernServer as any).onError(ctx, new Error('test error'));
162
+ }, 100);
163
+ const html = await new Promise((resolve, _reject) => {
164
+ res.on('finish', () => {
165
+ resolve(res._getData());
166
+ });
167
+ });
168
+ expect(html).toMatch('<div>Modern.js</div>');
169
+ });
170
+
171
+ test('should error handler correctly with fallback doc', async () => {
172
+ const server = await createServer({
173
+ config: {
174
+ ...(defaultsConfig as NormalizedConfig),
175
+ output: {
176
+ path: 'test-dist',
177
+ },
178
+ },
179
+ pwd: appDirectory,
180
+ });
181
+
182
+ const modernServer: ModernServer = (server as any).server;
183
+ const req = httpMocks.createRequest({
184
+ url: '/',
185
+ headers: {
186
+ host: 'modernjs.com',
187
+ },
188
+ eventEmitter: Readable,
189
+ method: 'GET',
190
+ });
191
+ const res = httpMocks.createResponse({ eventEmitter: EventEmitter });
192
+ const ctx = createContext(req, res);
193
+ ctx.error = () => {
194
+ // empty
195
+ };
196
+
197
+ setTimeout(() => {
198
+ (modernServer as any).renderErrorPage(ctx, 404);
199
+ }, 100);
200
+ const html = await new Promise((resolve, _reject) => {
201
+ res.on('finish', () => {
202
+ resolve(res._getData());
203
+ });
204
+ });
205
+ expect(html).toMatch('This page could not be found.');
206
+ });
207
+ });
208
+
209
+ describe('should split server work correctly', () => {
210
+ test('should init api server correctly', async () => {
211
+ const server = await createServer({
212
+ config: defaultsConfig as NormalizedConfig,
213
+ pwd: appDirectory,
214
+ apiOnly: true,
215
+ runMode: RUN_MODE.FULL,
216
+ });
217
+ const modernServer = (server as any).server;
218
+ modernServer.emitRouteHook('reset', {});
219
+ expect(modernServer.prepareWebHandler()).toBeNull();
220
+ await server.close();
221
+ });
222
+
223
+ test('should init web server correctly', async () => {
224
+ const server = await createServer({
225
+ config: defaultsConfig as NormalizedConfig,
226
+ pwd: appDirectory,
227
+ ssrOnly: true,
228
+ runMode: RUN_MODE.FULL,
229
+ });
230
+ const modernServer = (server as any).server;
231
+ modernServer.emitRouteHook('reset', {});
232
+ expect(modernServer.prepareAPIHandler()).toBeNull();
233
+ await server.close();
234
+ });
235
+
236
+ test('should init ssr server correctly', async () => {
237
+ const server = await createServer({
238
+ config: defaultsConfig as NormalizedConfig,
239
+ pwd: appDirectory,
240
+ webOnly: true,
241
+ });
242
+ const modernServer = (server as any).server;
243
+ const req = httpMocks.createRequest({
244
+ url: '/',
245
+ eventEmitter: Readable,
246
+ method: 'GET',
247
+ });
248
+ const res = httpMocks.createResponse({ eventEmitter: EventEmitter });
249
+ const ctx = createContext(req, res);
250
+ ctx.resHasHandled = () => true;
251
+ ctx.error = () => {
252
+ // empty
253
+ };
254
+ expect(await modernServer.warmupSSRBundle()).toBeNull();
255
+ expect(await modernServer.handleAPI(ctx, {})).toBeUndefined();
256
+ expect(await modernServer.handleWeb(ctx, {})).toBeNull();
257
+ });
258
+
259
+ test('should init web server with proxy correctly', async () => {
260
+ const server = await createServer({
261
+ config: defaultsConfig as NormalizedConfig,
262
+ pwd: appDirectory,
263
+ webOnly: true,
264
+ proxyTarget: {
265
+ api: '/',
266
+ ssr: '/',
267
+ },
268
+ });
269
+ const modernServer = (server as any).server;
270
+ const req = httpMocks.createRequest({
271
+ url: '/',
272
+ eventEmitter: Readable,
273
+ method: 'GET',
274
+ });
275
+ const res = httpMocks.createResponse({ eventEmitter: EventEmitter });
276
+ const ctx = createContext(req, res);
277
+ ctx.resHasHandled = () => true;
278
+ ctx.error = () => {
279
+ // empty
280
+ };
281
+
282
+ expect(await modernServer.warmupSSRBundle()).toBeNull();
283
+ expect(await modernServer.handleAPI(ctx, {})).toBeNull();
284
+ expect(await modernServer.handleWeb(ctx, { isSSR: true })).toBeNull();
285
+ await server.close();
99
286
  });
100
287
  });
101
288
  });
@@ -0,0 +1,38 @@
1
+ import renderCreator from '../src/libs/render/cache';
2
+
3
+ const createCacheConfig = (config: any = {}) => ({
4
+ excludes: null,
5
+ includes: null,
6
+ interval: 10,
7
+ staleLimit: false,
8
+ level: 0,
9
+ fallback: false,
10
+ matches: null,
11
+ ...config,
12
+ });
13
+
14
+ describe('test serverless pre-render', () => {
15
+ it('should return render function after invoke', async () => {
16
+ const renderfn = async () => 'hello modern';
17
+ const context = {
18
+ entry: '',
19
+ pathname: '',
20
+ query: {},
21
+ headers: {},
22
+ res: {
23
+ setHeader: () => false,
24
+ },
25
+ };
26
+ const doRender = renderCreator(renderfn, context as any);
27
+
28
+ const res = await doRender({
29
+ cacheConfig: createCacheConfig(),
30
+ } as any);
31
+ expect(res).toBe(await renderfn());
32
+
33
+ const res1 = await doRender({
34
+ cacheConfig: createCacheConfig(),
35
+ } as any);
36
+ expect(res1).toBe(await renderfn());
37
+ });
38
+ });