@modern-js/prod-server 1.0.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 (173) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +30 -0
  4. package/dist/js/modern/constants.js +26 -0
  5. package/dist/js/modern/index.js +14 -0
  6. package/dist/js/modern/libs/context/context.js +180 -0
  7. package/dist/js/modern/libs/context/index.js +3 -0
  8. package/dist/js/modern/libs/hook-api/route.js +39 -0
  9. package/dist/js/modern/libs/hook-api/template.js +61 -0
  10. package/dist/js/modern/libs/metrics.js +12 -0
  11. package/dist/js/modern/libs/proxy.js +33 -0
  12. package/dist/js/modern/libs/render/cache/__tests__/cache.fun.test.js +70 -0
  13. package/dist/js/modern/libs/render/cache/__tests__/cache.test.js +233 -0
  14. package/dist/js/modern/libs/render/cache/__tests__/cacheable.js +53 -0
  15. package/dist/js/modern/libs/render/cache/__tests__/error-configuration.js +35 -0
  16. package/dist/js/modern/libs/render/cache/__tests__/matched-cache.js +121 -0
  17. package/dist/js/modern/libs/render/cache/index.js +74 -0
  18. package/dist/js/modern/libs/render/cache/page-caches/index.js +9 -0
  19. package/dist/js/modern/libs/render/cache/page-caches/lru.js +35 -0
  20. package/dist/js/modern/libs/render/cache/spr.js +280 -0
  21. package/dist/js/modern/libs/render/cache/type.js +1 -0
  22. package/dist/js/modern/libs/render/cache/util.js +79 -0
  23. package/dist/js/modern/libs/render/index.js +65 -0
  24. package/dist/js/modern/libs/render/modern/browser-list.js +7 -0
  25. package/dist/js/modern/libs/render/modern/index.js +42 -0
  26. package/dist/js/modern/libs/render/reader.js +112 -0
  27. package/dist/js/modern/libs/render/ssr.js +58 -0
  28. package/dist/js/modern/libs/render/static.js +46 -0
  29. package/dist/js/modern/libs/render/type.js +7 -0
  30. package/dist/js/modern/libs/route/index.js +68 -0
  31. package/dist/js/modern/libs/route/matcher.js +94 -0
  32. package/dist/js/modern/libs/route/route.js +24 -0
  33. package/dist/js/modern/libs/serve-file.js +28 -0
  34. package/dist/js/modern/server/index.js +120 -0
  35. package/dist/js/modern/server/modern-server-split.js +81 -0
  36. package/dist/js/modern/server/modern-server.js +576 -0
  37. package/dist/js/modern/type.js +1 -0
  38. package/dist/js/modern/utils.js +112 -0
  39. package/dist/js/node/constants.js +36 -0
  40. package/dist/js/node/index.js +74 -0
  41. package/dist/js/node/libs/context/context.js +194 -0
  42. package/dist/js/node/libs/context/index.js +18 -0
  43. package/dist/js/node/libs/hook-api/route.js +48 -0
  44. package/dist/js/node/libs/hook-api/template.js +69 -0
  45. package/dist/js/node/libs/metrics.js +18 -0
  46. package/dist/js/node/libs/proxy.js +44 -0
  47. package/dist/js/node/libs/render/cache/__tests__/cache.fun.test.js +77 -0
  48. package/dist/js/node/libs/render/cache/__tests__/cache.test.js +238 -0
  49. package/dist/js/node/libs/render/cache/__tests__/cacheable.js +60 -0
  50. package/dist/js/node/libs/render/cache/__tests__/error-configuration.js +42 -0
  51. package/dist/js/node/libs/render/cache/__tests__/matched-cache.js +128 -0
  52. package/dist/js/node/libs/render/cache/index.js +86 -0
  53. package/dist/js/node/libs/render/cache/page-caches/index.js +17 -0
  54. package/dist/js/node/libs/render/cache/page-caches/lru.js +47 -0
  55. package/dist/js/node/libs/render/cache/spr.js +298 -0
  56. package/dist/js/node/libs/render/cache/type.js +5 -0
  57. package/dist/js/node/libs/render/cache/util.js +105 -0
  58. package/dist/js/node/libs/render/index.js +91 -0
  59. package/dist/js/node/libs/render/modern/browser-list.js +14 -0
  60. package/dist/js/node/libs/render/modern/index.js +58 -0
  61. package/dist/js/node/libs/render/reader.js +139 -0
  62. package/dist/js/node/libs/render/ssr.js +76 -0
  63. package/dist/js/node/libs/render/static.js +62 -0
  64. package/dist/js/node/libs/render/type.js +14 -0
  65. package/dist/js/node/libs/route/index.js +83 -0
  66. package/dist/js/node/libs/route/matcher.js +108 -0
  67. package/dist/js/node/libs/route/route.js +33 -0
  68. package/dist/js/node/libs/serve-file.js +41 -0
  69. package/dist/js/node/server/index.js +142 -0
  70. package/dist/js/node/server/modern-server-split.js +97 -0
  71. package/dist/js/node/server/modern-server.js +614 -0
  72. package/dist/js/node/type.js +5 -0
  73. package/dist/js/node/utils.js +143 -0
  74. package/dist/js/styles/tsconfig.json +12 -0
  75. package/dist/types/constants.d.ts +20 -0
  76. package/dist/types/index.d.ts +11 -0
  77. package/dist/types/libs/context/context.d.ts +61 -0
  78. package/dist/types/libs/context/index.d.ts +4 -0
  79. package/dist/types/libs/hook-api/route.d.ts +14 -0
  80. package/dist/types/libs/hook-api/template.d.ts +14 -0
  81. package/dist/types/libs/metrics.d.ts +3 -0
  82. package/dist/types/libs/proxy.d.ts +4 -0
  83. package/dist/types/libs/render/cache/__tests__/cache.fun.test.d.ts +1 -0
  84. package/dist/types/libs/render/cache/__tests__/cache.test.d.ts +1 -0
  85. package/dist/types/libs/render/cache/__tests__/cacheable.d.ts +62 -0
  86. package/dist/types/libs/render/cache/__tests__/error-configuration.d.ts +28 -0
  87. package/dist/types/libs/render/cache/__tests__/matched-cache.d.ts +124 -0
  88. package/dist/types/libs/render/cache/index.d.ts +6 -0
  89. package/dist/types/libs/render/cache/page-caches/index.d.ts +2 -0
  90. package/dist/types/libs/render/cache/page-caches/lru.d.ts +15 -0
  91. package/dist/types/libs/render/cache/spr.d.ts +24 -0
  92. package/dist/types/libs/render/cache/type.d.ts +48 -0
  93. package/dist/types/libs/render/cache/util.d.ts +17 -0
  94. package/dist/types/libs/render/index.d.ts +18 -0
  95. package/dist/types/libs/render/modern/browser-list.d.ts +1 -0
  96. package/dist/types/libs/render/modern/index.d.ts +3 -0
  97. package/dist/types/libs/render/reader.d.ts +18 -0
  98. package/dist/types/libs/render/ssr.d.ts +10 -0
  99. package/dist/types/libs/render/static.d.ts +3 -0
  100. package/dist/types/libs/render/type.d.ts +33 -0
  101. package/dist/types/libs/route/index.d.ts +15 -0
  102. package/dist/types/libs/route/matcher.d.ts +15 -0
  103. package/dist/types/libs/route/route.d.ts +14 -0
  104. package/dist/types/libs/serve-file.d.ts +8 -0
  105. package/dist/types/server/index.d.ts +20 -0
  106. package/dist/types/server/modern-server-split.d.ts +26 -0
  107. package/dist/types/server/modern-server.d.ts +72 -0
  108. package/dist/types/type.d.ts +56 -0
  109. package/dist/types/utils.d.ts +19 -0
  110. package/jest.config.js +9 -0
  111. package/modern.config.js +2 -0
  112. package/package.json +82 -0
  113. package/src/constants.ts +26 -0
  114. package/src/index.ts +18 -0
  115. package/src/libs/context/context.ts +183 -0
  116. package/src/libs/context/index.ts +7 -0
  117. package/src/libs/hook-api/route.ts +42 -0
  118. package/src/libs/hook-api/template.ts +53 -0
  119. package/src/libs/metrics.ts +15 -0
  120. package/src/libs/proxy.ts +42 -0
  121. package/src/libs/render/cache/__tests__/cache.fun.test.ts +94 -0
  122. package/src/libs/render/cache/__tests__/cache.test.ts +240 -0
  123. package/src/libs/render/cache/__tests__/cacheable.ts +44 -0
  124. package/src/libs/render/cache/__tests__/error-configuration.ts +34 -0
  125. package/src/libs/render/cache/__tests__/matched-cache.ts +88 -0
  126. package/src/libs/render/cache/index.ts +75 -0
  127. package/src/libs/render/cache/page-caches/index.ts +11 -0
  128. package/src/libs/render/cache/page-caches/lru.ts +38 -0
  129. package/src/libs/render/cache/spr.ts +301 -0
  130. package/src/libs/render/cache/type.ts +59 -0
  131. package/src/libs/render/cache/util.ts +97 -0
  132. package/src/libs/render/index.ts +79 -0
  133. package/src/libs/render/modern/browser-list.ts +7 -0
  134. package/src/libs/render/modern/index.ts +41 -0
  135. package/src/libs/render/modern/module.d.ts +4 -0
  136. package/src/libs/render/reader.ts +119 -0
  137. package/src/libs/render/ssr.ts +67 -0
  138. package/src/libs/render/static.ts +52 -0
  139. package/src/libs/render/type.ts +38 -0
  140. package/src/libs/route/index.ts +76 -0
  141. package/src/libs/route/matcher.ts +108 -0
  142. package/src/libs/route/route.ts +34 -0
  143. package/src/libs/serve-file.ts +34 -0
  144. package/src/server/index.ts +147 -0
  145. package/src/server/modern-server-split.ts +97 -0
  146. package/src/server/modern-server.ts +613 -0
  147. package/src/tsconfig.json +12 -0
  148. package/src/type.ts +61 -0
  149. package/src/utils.ts +122 -0
  150. package/tests/.eslintrc.js +6 -0
  151. package/tests/context.test.ts +52 -0
  152. package/tests/fixtures/hosting-files/static/index.js +1 -0
  153. package/tests/fixtures/pure/modern.config.js +5 -0
  154. package/tests/fixtures/pure/package.json +21 -0
  155. package/tests/fixtures/pure/src/App.css +119 -0
  156. package/tests/fixtures/pure/src/App.tsx +43 -0
  157. package/tests/fixtures/pure/tsconfig.json +12 -0
  158. package/tests/fixtures/reader/index.ts +3 -0
  159. package/tests/fixtures/route-spec/dynamic.json +13 -0
  160. package/tests/fixtures/route-spec/index.json +29 -0
  161. package/tests/fixtures/ssr/bundle.js +5 -0
  162. package/tests/fixtures/static-dir/bar.html +11 -0
  163. package/tests/fixtures/static-dir/baz/index.html +11 -0
  164. package/tests/fixtures/static-dir/foo/index.html +11 -0
  165. package/tests/helper.ts +8 -0
  166. package/tests/hook.test.ts +44 -0
  167. package/tests/middleware.test.ts +179 -0
  168. package/tests/render.test.ts +102 -0
  169. package/tests/route.test.ts +77 -0
  170. package/tests/server.test.ts +101 -0
  171. package/tests/tsconfig.json +12 -0
  172. package/tests/utils.test.ts +106 -0
  173. package/tsconfig.json +11 -0
@@ -0,0 +1,4 @@
1
+ declare module '@babel/compat-data/native-modules' {
2
+ const nativeModules: { 'es6.module': Record<string, string> };
3
+ export default nativeModules;
4
+ }
@@ -0,0 +1,119 @@
1
+ import { Buffer } from 'buffer';
2
+ import { fs } from '@modern-js/utils';
3
+ import LRU from 'lru-cache';
4
+
5
+ const Byte = 1;
6
+ const KB = 1024 * Byte;
7
+ const MB = 1024 * KB;
8
+
9
+ type FileCache = {
10
+ content: Buffer;
11
+ mtime: Date;
12
+ };
13
+
14
+ const getContentLength = (cache: FileCache) => cache.content.length;
15
+
16
+ const createCacheItem = async (filepath: string, mtime: Date) => {
17
+ const content = await fs.readFile(filepath);
18
+
19
+ return {
20
+ content,
21
+ mtime,
22
+ };
23
+ };
24
+ export class LruReader {
25
+ private readonly cache: LRU<string, FileCache>;
26
+
27
+ // private timer?: NodeJS.Timeout;
28
+
29
+ constructor() {
30
+ this.cache = new LRU({
31
+ max: 256 * MB,
32
+ length: getContentLength,
33
+ maxAge: 5 * 60 * 5000, // 60s
34
+ });
35
+ }
36
+
37
+ public init() {
38
+ // this.timeTask();
39
+ }
40
+
41
+ public close() {
42
+ // if (this.timer) {
43
+ // clearInterval(this.timer);
44
+ // }
45
+ }
46
+
47
+ public async read(filepath: string) {
48
+ if (this.cache.has(filepath)) {
49
+ const { content } = this.cache.get(filepath)!;
50
+
51
+ return { content };
52
+ }
53
+
54
+ if (!fs.existsSync(filepath)) {
55
+ return null;
56
+ }
57
+
58
+ const stat = fs.statSync(filepath);
59
+ if (stat.isDirectory()) {
60
+ return null;
61
+ }
62
+
63
+ // if file more than 20 MB
64
+ if (stat.size > 20 * MB) {
65
+ return null;
66
+ }
67
+
68
+ const item = await createCacheItem(filepath, stat.mtime);
69
+ this.cache.set(filepath, item);
70
+ return item;
71
+ }
72
+
73
+ public update() {
74
+ const { cache } = this;
75
+ const files = cache.keys();
76
+
77
+ for (const filepath of files) {
78
+ if (!fs.existsSync(filepath)) {
79
+ cache.del(filepath);
80
+ }
81
+
82
+ try {
83
+ const item = cache.get(filepath)!;
84
+ const stat = fs.statSync(filepath);
85
+ const { mtime } = stat;
86
+ // file is modify
87
+ if (item.mtime < mtime) {
88
+ cache.del(filepath);
89
+ }
90
+ } catch (e) {
91
+ // for safe
92
+ cache.del(filepath);
93
+ }
94
+ }
95
+ }
96
+
97
+ // private timeTask() {
98
+ // this.timer = setInterval(() => this.update, 5 * 60 * 1000).unref();
99
+ // }
100
+ }
101
+
102
+ const reader = new LruReader();
103
+
104
+ export const readFile = async (filepath: string) => {
105
+ const file = await reader.read(filepath);
106
+ return file?.content;
107
+ };
108
+
109
+ export const updateFile = () => {
110
+ reader.update();
111
+ };
112
+
113
+ export const init = () => {
114
+ reader.init();
115
+ };
116
+
117
+ export const close = () => {
118
+ reader.close();
119
+ };
@@ -0,0 +1,67 @@
1
+ import path from 'path';
2
+ import { SERVER_RENDER_FUNCTION_NAME } from '@modern-js/utils';
3
+ import mime from 'mime-types';
4
+ import cookie from 'cookie';
5
+ import { ModernServerContext } from '../context';
6
+ import { RenderResult, ServerHookRunner } from '../../type';
7
+ import cache from './cache';
8
+ import { SSRServerContext } from './type';
9
+
10
+ export const render = async (
11
+ ctx: ModernServerContext,
12
+ renderOptions: {
13
+ distDir: string;
14
+ bundle: string;
15
+ urlPath: string;
16
+ template: string;
17
+ entryName: string;
18
+ staticGenerate: boolean;
19
+ },
20
+ runner: ServerHookRunner,
21
+ ): Promise<RenderResult> => {
22
+ const { urlPath, bundle, distDir, template, entryName, staticGenerate } =
23
+ renderOptions;
24
+ const bundleJS = path.join(distDir, bundle);
25
+
26
+ const context: SSRServerContext = {
27
+ request: {
28
+ baseUrl: urlPath,
29
+ params: ctx.params,
30
+ pathname: ctx.path,
31
+ host: ctx.host,
32
+ query: ctx.query as Record<string, string>,
33
+ url: ctx.href,
34
+ cookieMap: cookie.parse(ctx.headers.cookie || ''),
35
+ headers: ctx.headers,
36
+ },
37
+ redirection: {},
38
+ template,
39
+ entryName,
40
+ distDir,
41
+ staticGenerate,
42
+ logger: ctx.logger,
43
+ metrics: ctx.metrics,
44
+ };
45
+
46
+ runner.extendSSRContext(context);
47
+
48
+ const serverRender = require(bundleJS)[SERVER_RENDER_FUNCTION_NAME];
49
+
50
+ const html = await cache(serverRender, ctx)(context);
51
+
52
+ const { url, status = 302 } = context.redirection;
53
+
54
+ if (url) {
55
+ return {
56
+ content: url,
57
+ contentType: '',
58
+ statusCode: status,
59
+ redirect: true,
60
+ };
61
+ }
62
+
63
+ return {
64
+ content: html,
65
+ contentType: mime.contentType('html') as string,
66
+ };
67
+ };
@@ -0,0 +1,52 @@
1
+ import path from 'path';
2
+ import mime from 'mime-types';
3
+ import { RenderResult } from '../../type';
4
+ import { ModernServerContext } from '../context';
5
+ import { readFile } from './reader';
6
+
7
+ export async function handleDirectory(
8
+ ctx: ModernServerContext,
9
+ entryPath: string,
10
+ urlPath: string,
11
+ ): Promise<RenderResult | null> {
12
+ const { path: pathname } = ctx;
13
+ const filepath = path.join(entryPath, trimLeft(pathname, urlPath));
14
+
15
+ // If can match accurately, always return the one that matches accurately
16
+ let content = await readFile(filepath);
17
+ let contentType = mime.contentType(path.extname(filepath) || '');
18
+
19
+ // automatic addressing
20
+ if (!content) {
21
+ if (pathname.endsWith('/')) {
22
+ content = await readFile(`${filepath}index.html`);
23
+ } else if (!pathname.includes('.')) {
24
+ content = await readFile(`${filepath}.html`);
25
+ if (!content) {
26
+ content = await readFile(`${filepath}/index.html`);
27
+ }
28
+ }
29
+
30
+ // set content-type as html
31
+ if (content) {
32
+ contentType = mime.contentType('html');
33
+ }
34
+ }
35
+
36
+ if (!content) {
37
+ return null;
38
+ }
39
+
40
+ return {
41
+ content,
42
+ contentType: contentType || '',
43
+ };
44
+ }
45
+
46
+ const trimLeft = (str: string, prefix: string): string => {
47
+ if (str.startsWith(prefix)) {
48
+ return str.substring(prefix.length);
49
+ }
50
+
51
+ return str;
52
+ };
@@ -0,0 +1,38 @@
1
+ import { BaseSSRServerContext } from '@modern-js/types/server';
2
+
3
+ type MetaKeyMap = {
4
+ header?: string[];
5
+ query?: string[];
6
+ };
7
+ type MetaKeyMatch = {
8
+ header?: MatchMap;
9
+ query?: MatchMap;
10
+ };
11
+ type MatchMap = Record<string, Record<string, string>>;
12
+
13
+ export type CacheConfig = {
14
+ interval: number;
15
+ staleLimit: number | boolean;
16
+ level: number;
17
+ includes?: MetaKeyMap | null;
18
+ excludes?: MetaKeyMap | null;
19
+ matches?: MetaKeyMatch | null;
20
+ };
21
+
22
+ export enum RenderLevel {
23
+ CLIENT_RENDER,
24
+ SERVER_PREFETCH,
25
+ SERVER_RENDER,
26
+ }
27
+
28
+ export type SSRServerContext = BaseSSRServerContext & {
29
+ cacheConfig?: CacheConfig;
30
+ staticGenerate?: boolean;
31
+ };
32
+
33
+ export type ModernSSRReactComponent = React.ComponentType<any> & {
34
+ init: (context: SSRServerContext) => Promise<void>;
35
+ prefetch: (context: SSRServerContext) => Promise<Record<string, any>>;
36
+ };
37
+
38
+ export type RenderFunction = (context: SSRServerContext) => Promise<string>;
@@ -0,0 +1,76 @@
1
+ import { RouteMatcher } from './matcher';
2
+ import { ModernRoute, ModernRouteInterface } from './route';
3
+
4
+ export class RouteMatchManager {
5
+ public matchers: RouteMatcher[];
6
+
7
+ private specs: ModernRouteInterface[] = [];
8
+
9
+ constructor() {
10
+ this.matchers = [];
11
+ }
12
+
13
+ // get all routes matches pathname
14
+ private filter(pathname: string) {
15
+ return this.matchers.reduce((matches, matcher) => {
16
+ if (matcher.matchUrlPath(pathname)) {
17
+ matches.push(matcher);
18
+ }
19
+ return matches;
20
+ }, [] as RouteMatcher[]);
21
+ }
22
+
23
+ // get best match from a set of matches
24
+ private best(pathname: string, matches: RouteMatcher[]) {
25
+ let best: RouteMatcher | undefined;
26
+ let matchedLen = 0;
27
+
28
+ for (const match of matches) {
29
+ const len = match.matchLength(pathname);
30
+
31
+ if (len === null) {
32
+ continue;
33
+ }
34
+
35
+ if (len > matchedLen) {
36
+ best = match;
37
+ matchedLen = len;
38
+ }
39
+ }
40
+
41
+ return best;
42
+ }
43
+
44
+ // reset routes matcher
45
+ public reset(specs: ModernRouteInterface[]) {
46
+ this.specs = specs;
47
+ const matchers = specs.reduce((ms, spec) => {
48
+ ms.push(new RouteMatcher(spec));
49
+ return ms;
50
+ }, [] as RouteMatcher[]);
51
+
52
+ this.matchers = matchers;
53
+ }
54
+
55
+ // get best match from all matcher in manager
56
+ public match(pathname: string) {
57
+ const matches = this.filter(pathname);
58
+ const best = this.best(pathname, matches);
59
+ return best;
60
+ }
61
+
62
+ public matchEntry(entryname: string) {
63
+ return this.matchers.find(matcher => matcher.matchEntry(entryname));
64
+ }
65
+
66
+ public getBundles() {
67
+ const bundles = this.specs
68
+ .filter(route => route.isSSR)
69
+ .map(route => route.bundle);
70
+
71
+ return bundles;
72
+ }
73
+ }
74
+
75
+ export type { ModernRouteInterface, ModernRoute };
76
+ export { RouteMatcher };
@@ -0,0 +1,108 @@
1
+ import { removeTailSlash } from '@modern-js/utils';
2
+ import {
3
+ MatchFunction,
4
+ MatchResult,
5
+ match,
6
+ pathToRegexp,
7
+ } from 'path-to-regexp';
8
+ import { toPath } from '../../utils';
9
+ import { ModernRoute, ModernRouteInterface } from './route';
10
+
11
+ // eslint-disable-next-line no-useless-escape
12
+ const regCharsDetector = /[^a-zA-Z\-_0-9\/\.]/;
13
+ export class RouteMatcher {
14
+ public spec: ModernRouteInterface;
15
+
16
+ public urlPath: string = '';
17
+
18
+ public urlMatcher?: MatchFunction;
19
+
20
+ public urlReg?: RegExp;
21
+
22
+ constructor(spec: ModernRouteInterface) {
23
+ this.spec = spec;
24
+ this.setupUrlPath();
25
+ }
26
+
27
+ // generate modern route object
28
+ public generate(url: string) {
29
+ const route = new ModernRoute(this.spec);
30
+
31
+ if (this.urlPath) {
32
+ const params = this.parseURLParams(url);
33
+ route.urlPath = toPath(route.urlPath, params);
34
+ route.params = params;
35
+ }
36
+
37
+ return route;
38
+ }
39
+
40
+ public parseURLParams(pathname: string) {
41
+ if (!this.urlMatcher) {
42
+ return {};
43
+ } else {
44
+ const matchResult = this.urlMatcher(pathname);
45
+
46
+ return (matchResult as MatchResult<Record<string, string>>).params;
47
+ }
48
+ }
49
+
50
+ // get match url length
51
+ public matchLength(pathname: string): number | null {
52
+ if (!this.urlReg) {
53
+ return this.urlPath.length;
54
+ } else {
55
+ const result = this.urlReg.exec(pathname);
56
+ return result?.[0]?.length || null;
57
+ }
58
+ }
59
+
60
+ // if match url path
61
+ public matchUrlPath(requestUrl: string): boolean {
62
+ const urlWithoutSlash =
63
+ requestUrl.endsWith('/') && requestUrl !== '/'
64
+ ? requestUrl.slice(0, -1)
65
+ : requestUrl;
66
+
67
+ if (this.urlMatcher) {
68
+ return Boolean(this.urlMatcher(urlWithoutSlash));
69
+ } else {
70
+ if (urlWithoutSlash.startsWith(this.urlPath)) {
71
+ // avoid /abcd match /a
72
+ if (
73
+ this.urlPath !== '/' &&
74
+ urlWithoutSlash.length > this.urlPath.length &&
75
+ !urlWithoutSlash.startsWith(`${this.urlPath}/`)
76
+ ) {
77
+ return false;
78
+ }
79
+
80
+ return true;
81
+ }
82
+
83
+ return false;
84
+ }
85
+ }
86
+
87
+ public matchEntry(entryName: string): boolean {
88
+ return this.spec.entryName === entryName;
89
+ }
90
+
91
+ // compiler urlPath to regexp if necessary
92
+ private setupUrlPath() {
93
+ const { urlPath } = this.spec;
94
+ this.urlPath = urlPath === '/' ? urlPath : removeTailSlash(urlPath);
95
+
96
+ const useReg = regCharsDetector.test(urlPath);
97
+ if (useReg) {
98
+ this.urlMatcher = match(urlPath, {
99
+ end: false,
100
+ decode: decodeURIComponent,
101
+ });
102
+
103
+ this.urlReg = pathToRegexp(urlPath, [], {
104
+ end: false,
105
+ });
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,34 @@
1
+ import { ServerRoute as ModernRouteInterface } from '@modern-js/types';
2
+
3
+ export type { ModernRouteInterface };
4
+
5
+ export class ModernRoute implements ModernRouteInterface {
6
+ public entryName: string;
7
+
8
+ public urlPath: string;
9
+
10
+ public entryPath: string;
11
+
12
+ public bundle: string;
13
+
14
+ public isApi: boolean;
15
+
16
+ public isSSR: boolean;
17
+
18
+ public isSPA: boolean;
19
+
20
+ public enableModernMode?: boolean;
21
+
22
+ public params: Record<string, any> = {};
23
+
24
+ constructor(routeSpec: ModernRouteInterface) {
25
+ this.entryName = routeSpec.entryName || '';
26
+ this.urlPath = routeSpec.urlPath;
27
+ this.entryPath = routeSpec.entryPath || '';
28
+ this.isSSR = routeSpec.isSSR || false;
29
+ this.isSPA = routeSpec.isSPA || false;
30
+ this.isApi = routeSpec.isApi || false;
31
+ this.bundle = routeSpec.bundle || '';
32
+ this.enableModernMode = routeSpec.enableModernMode ?? false;
33
+ }
34
+ }
@@ -0,0 +1,34 @@
1
+ // Todo 看看是不是能 fork 一份,即使命中也返回
2
+ import serve from 'serve-static';
3
+ import { isString, isRegExp } from '@modern-js/utils';
4
+ import { NextFunction } from '../type';
5
+ import { ModernServerContext } from './context';
6
+
7
+ type Rule = {
8
+ path: string | RegExp;
9
+ target: string;
10
+ };
11
+
12
+ export const createStaticFileHandler =
13
+ (rules: Rule[]) =>
14
+ // eslint-disable-next-line consistent-return
15
+ async (context: ModernServerContext, next: NextFunction) => {
16
+ const { url: requestUrl, req, res } = context;
17
+
18
+ const hitRule = rules.find(item => {
19
+ if (isString(item.path) && requestUrl.startsWith(item.path)) {
20
+ return true;
21
+ } else if (isRegExp(item.path) && item.path.test(requestUrl)) {
22
+ return true;
23
+ }
24
+ return false;
25
+ });
26
+
27
+ if (hitRule) {
28
+ serve(hitRule.target)(req, res, () => {
29
+ next();
30
+ });
31
+ } else {
32
+ return next();
33
+ }
34
+ };
@@ -0,0 +1,147 @@
1
+ import { IncomingMessage, ServerResponse, Server as httpServer } from 'http';
2
+ import path from 'path';
3
+ import {
4
+ serverManager,
5
+ AppContext,
6
+ ConfigContext,
7
+ } from '@modern-js/server-core';
8
+ import { compatRequire, logger as defaultLogger } from '@modern-js/utils';
9
+ import {
10
+ initAppContext,
11
+ initAppDir,
12
+ loadUserConfig,
13
+ UserConfig,
14
+ } from '@modern-js/core';
15
+ import { ModernServerOptions, ServerHookRunner, ReadyOptions } from '../type';
16
+ import { metrics as defaultMetrics } from '../libs/metrics';
17
+ import { ModernServer } from './modern-server';
18
+ import {
19
+ ModernAPIServer,
20
+ ModernSSRServer,
21
+ ModernWebServer,
22
+ } from './modern-server-split';
23
+
24
+ const createProdServer = (options: ModernServerOptions): ModernServer => {
25
+ if (options.apiOnly) {
26
+ return new ModernAPIServer(options);
27
+ } else if (options.ssrOnly) {
28
+ return new ModernSSRServer(options);
29
+ } else if (options.webOnly) {
30
+ return new ModernWebServer(options);
31
+ } else {
32
+ return new ModernServer(options);
33
+ }
34
+ };
35
+
36
+ export class Server {
37
+ public options: ModernServerOptions;
38
+
39
+ protected serverImpl: (options: ModernServerOptions) => ModernServer =
40
+ createProdServer;
41
+
42
+ private server!: ModernServer;
43
+
44
+ private app!: httpServer;
45
+
46
+ private runner!: ServerHookRunner;
47
+
48
+ constructor(options: ModernServerOptions) {
49
+ this.options = options;
50
+ }
51
+
52
+ public getRequestHandler() {
53
+ return (req: IncomingMessage, res: ServerResponse, next?: () => void) => {
54
+ const requestHandler = this.server.getRequestHandler();
55
+ return requestHandler(req, res, next);
56
+ };
57
+ }
58
+
59
+ public ready(readyOptions: ReadyOptions = {}) {
60
+ this.server.ready(readyOptions);
61
+ }
62
+
63
+ public async init() {
64
+ const { options } = this;
65
+
66
+ options.logger = options.logger || defaultLogger;
67
+ options.metrics = options.metrics || defaultMetrics;
68
+
69
+ // initialize server
70
+ this.server = this.serverImpl(options);
71
+
72
+ // create http-server
73
+ this.app = await this.server.createHTTPServer(this.getRequestHandler());
74
+
75
+ this.runner = await this.createHookRunner();
76
+
77
+ // runner can only be used after server init
78
+ await this.server.init(this.runner);
79
+
80
+ return this;
81
+ }
82
+
83
+ public listen(port = 8080, listener: any) {
84
+ this.app.listen(process.env.PORT || port, () => {
85
+ if (listener) {
86
+ listener();
87
+ }
88
+
89
+ this.listener(this.app);
90
+ });
91
+ }
92
+
93
+ public listener(app: httpServer) {
94
+ this.server.onListening(app);
95
+ }
96
+
97
+ public async close() {
98
+ await this.server.close();
99
+ await new Promise<void>(resolve =>
100
+ this.app.close(() => {
101
+ resolve();
102
+ }),
103
+ );
104
+ }
105
+
106
+ private async createHookRunner() {
107
+ const { options } = this;
108
+
109
+ serverManager.clear();
110
+
111
+ options.plugins?.forEach(p => {
112
+ serverManager.usePlugin(compatRequire(p.pluginPath));
113
+ });
114
+
115
+ const appContext = await this.initAppContext();
116
+ serverManager.run(() => {
117
+ ConfigContext.set(this.options.config as UserConfig);
118
+ AppContext.set({
119
+ ...appContext,
120
+ distDirectory: path.join(
121
+ options.pwd,
122
+ options.config.output?.path || 'dist',
123
+ ),
124
+ });
125
+ });
126
+
127
+ return serverManager.init({});
128
+ }
129
+
130
+ private async initAppContext() {
131
+ const appDirectory = await initAppDir();
132
+
133
+ const loaded = await loadUserConfig(appDirectory);
134
+
135
+ const plugins = this.options.plugins?.map(p => ({
136
+ server: p,
137
+ cli: undefined,
138
+ }));
139
+
140
+ const appContext = initAppContext(
141
+ appDirectory,
142
+ plugins || [],
143
+ loaded.filePath,
144
+ );
145
+ return appContext;
146
+ }
147
+ }