@anansi/core 0.7.4 → 0.8.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/server.js +27 -23
  3. package/dist/server.js.map +1 -1
  4. package/lib/floodSpouts.d.ts +6 -0
  5. package/lib/floodSpouts.d.ts.map +1 -0
  6. package/lib/index.d.ts +5 -0
  7. package/lib/index.d.ts.map +1 -0
  8. package/lib/index.server.d.ts +6 -0
  9. package/lib/index.server.d.ts.map +1 -0
  10. package/lib/laySpouts.d.ts +8 -0
  11. package/lib/laySpouts.d.ts.map +1 -0
  12. package/lib/scripts/index.d.ts +3 -0
  13. package/lib/scripts/index.d.ts.map +1 -0
  14. package/lib/scripts/index.js +15 -0
  15. package/lib/scripts/serve.d.ts +4 -0
  16. package/lib/scripts/serve.d.ts.map +1 -0
  17. package/lib/scripts/serve.js +162 -0
  18. package/lib/scripts/startDevserver.d.ts +4 -0
  19. package/lib/scripts/startDevserver.d.ts.map +1 -0
  20. package/lib/scripts/startDevserver.js +175 -164
  21. package/lib/scripts/types.d.ts +7 -0
  22. package/lib/scripts/types.d.ts.map +1 -0
  23. package/lib/spouts/DocumentComponent.d.ts +23 -0
  24. package/lib/spouts/DocumentComponent.d.ts.map +1 -0
  25. package/lib/spouts/DocumentComponent.js +10 -6
  26. package/lib/spouts/document.d.ts +13 -0
  27. package/lib/spouts/document.d.ts.map +1 -0
  28. package/lib/spouts/document.server.d.ts +18 -0
  29. package/lib/spouts/document.server.d.ts.map +1 -0
  30. package/lib/spouts/document.server.js +1 -1
  31. package/lib/spouts/prefetch.server.d.ts +8 -0
  32. package/lib/spouts/prefetch.server.d.ts.map +1 -0
  33. package/lib/spouts/restHooks.d.ts +8 -0
  34. package/lib/spouts/restHooks.d.ts.map +1 -0
  35. package/lib/spouts/restHooks.server.d.ts +9 -0
  36. package/lib/spouts/restHooks.server.d.ts.map +1 -0
  37. package/lib/spouts/router.d.ts +12 -0
  38. package/lib/spouts/router.d.ts.map +1 -0
  39. package/lib/spouts/router.server.d.ts +12 -0
  40. package/lib/spouts/router.server.d.ts.map +1 -0
  41. package/lib/spouts/types.d.ts +16 -0
  42. package/lib/spouts/types.d.ts.map +1 -0
  43. package/package.json +8 -3
  44. package/src/scripts/index.ts +2 -0
  45. package/src/scripts/serve.ts +168 -0
  46. package/src/scripts/startDevserver.ts +218 -202
  47. package/src/spouts/DocumentComponent.tsx +8 -2
  48. package/src/spouts/document.server.tsx +2 -0
@@ -18,222 +18,238 @@ import logging from 'webpack/lib/logging/runtime';
18
18
  import 'cross-fetch/polyfill';
19
19
  import { BoundRender } from './types';
20
20
 
21
- // eslint-disable-next-line @typescript-eslint/no-var-requires
22
- const webpackConfig = require(require.resolve(
23
- // TODO: use normal resolution algorithm to find webpack file
24
- path.join(process.cwd(), 'webpack.config'),
25
- ));
26
-
27
- const entrypoint = process.argv[2];
28
- //process.env.WEBPACK_PUBLIC_HOST = `http://localhost:${PORT}`; this breaks compatibility with stackblitz
29
- process.env.WEBPACK_PUBLIC_PATH = '/assets/';
30
-
31
- if (!entrypoint) {
32
- console.log(`Usage: start-anansi <entrypoint-file>`);
33
- process.exit(-1);
34
- }
35
-
36
- const log = logging.getLogger('anansi-devserver');
37
-
38
- // Set up in memory filesystem
39
- const volume = new Volume();
40
- const fs = createFsFromVolume(volume);
41
- ufs.use(diskFs).use(fs as any);
42
-
43
- patchRequire(ufs);
44
- const readFile = promisify(ufs.readFile);
45
- let server: Server | undefined;
46
-
47
- // Generate a temporary file so we can hot reload from the root of the application
48
- function hotEntry(entryPath: string) {
49
- // eslint-disable-next-line
50
- // @ts-ignore for some reason it's not picking up that other options are optional
51
- const generatedEntrypoint = tmp.fileSync({ postfix: '.js' });
52
- diskFs.writeSync(
53
- generatedEntrypoint.fd,
54
- `
55
- import entry from "${path.resolve(process.cwd(), entryPath)}";
56
-
57
- if (module.hot) {
58
- module.hot.accept();
59
- }
60
-
61
- export default entry;
62
- `,
63
- );
64
- return generatedEntrypoint;
65
- }
21
+ // run directly from node
22
+ if (require.main === module) {
23
+ const entrypoint = process.argv[2];
24
+ //process.env.WEBPACK_PUBLIC_HOST = `http://localhost:${PORT}`; this breaks compatibility with stackblitz
25
+ process.env.WEBPACK_PUBLIC_PATH = '/assets/';
26
+
27
+ if (!entrypoint) {
28
+ console.log(`Usage: start-anansi <entrypoint-file>`);
29
+ process.exit(-1);
30
+ }
66
31
 
67
- const webpackConfigs = [
68
- webpackConfig(
69
- {
70
- entrypath: hotEntry(entrypoint).name,
71
- name: 'client',
72
- },
73
- { mode: 'development' },
74
- ),
75
- webpackConfig(
76
- {
77
- entrypath: entrypoint.replace('.tsx', '.server.tsx'),
78
- name: 'server',
79
- BROWSERSLIST_ENV: 'current node',
80
- },
81
- { mode: 'development', target: 'node' },
82
- ),
83
- ] as const;
84
- // only have one output for server so we can avoid cached modules
85
- webpackConfigs[1].plugins.push(
86
- new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
87
- );
88
- // initialize the webpack compiler
89
- const compiler: MultiCompiler = webpack(webpackConfigs);
90
-
91
- sourceMapSupport.install({ hookRequire: true });
92
-
93
- function getServerBundle(serverStats: webpack.Stats) {
94
- const serverJson = serverStats.toJson({ assets: true });
95
- return path.join(serverJson.outputPath ?? '', 'server.js');
96
- }
97
- function handleErrors<
98
- F extends (
99
- req: Request | IncomingMessage,
100
- res: Response | ServerResponse,
101
- ) => Promise<void>,
102
- >(fn: F) {
103
- return async function (
104
- req: Request | IncomingMessage,
105
- res: Response | ServerResponse,
106
- next: NextFunction,
107
- ) {
108
- try {
109
- return await fn(req, res);
110
- } catch (x) {
111
- next(x);
112
- }
113
- };
32
+ startDevServer(entrypoint);
114
33
  }
115
34
 
116
- const initRender:
117
- | { args: Parameters<BoundRender>; resolve: () => void }[]
118
- | undefined = [];
119
- let render: BoundRender = (...args) =>
120
- new Promise(resolve => {
121
- initRender.push({ args, resolve });
122
- });
123
-
124
- function importRender(stats: webpack.Stats[]) {
125
- const [clientStats, serverStats] = stats;
126
- if (
127
- clientStats?.compilation?.errors?.length ||
128
- serverStats?.compilation?.errors?.length
129
- ) {
130
- log.error('Errors for client build: ' + clientStats.compilation.errors);
131
- log.error('Errors for server build: ' + serverStats.compilation.errors);
132
- // TODO: handle more gracefully
133
- process.exit(-1);
134
- } else {
135
- log.info('Launching SSR');
35
+ export default function startDevServer(
36
+ entrypoint: string,
37
+ env: Record<string, unknown> = {},
38
+ ) {
39
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
40
+ const webpackConfig = require(require.resolve(
41
+ // TODO: use normal resolution algorithm to find webpack file
42
+ path.join(process.cwd(), 'webpack.config'),
43
+ ));
44
+
45
+ const log = logging.getLogger('anansi-devserver');
46
+
47
+ // Set up in memory filesystem
48
+ const volume = new Volume();
49
+ const fs = createFsFromVolume(volume);
50
+ ufs.use(diskFs).use(fs as any);
51
+
52
+ patchRequire(ufs);
53
+ const readFile = promisify(ufs.readFile);
54
+ let server: Server | undefined;
55
+
56
+ // Generate a temporary file so we can hot reload from the root of the application
57
+ function hotEntry(entryPath: string) {
58
+ // eslint-disable-next-line
59
+ // @ts-ignore for some reason it's not picking up that other options are optional
60
+ const generatedEntrypoint = tmp.fileSync({ postfix: '.js' });
61
+ diskFs.writeSync(
62
+ generatedEntrypoint.fd,
63
+ `
64
+ import entry from "${path.resolve(process.cwd(), entryPath)}";
65
+
66
+ if (module.hot) {
67
+ module.hot.accept();
136
68
  }
137
69
 
138
- // ASSETS
139
- const clientManifest = clientStats.toJson();
140
-
141
- // SERVER SIDE ENTRYPOINT
142
- if (Array.isArray(initRender)) {
143
- // eslint-disable-next-line @typescript-eslint/no-var-requires
144
- render = (require(getServerBundle(serverStats)) as any).default.bind(
145
- undefined,
146
- clientManifest,
147
- );
148
- initRender.forEach(init => render(...init.args).then(init.resolve));
149
- } else {
150
- render = (importFresh(getServerBundle(serverStats)) as any).default.bind(
151
- undefined,
152
- clientManifest,
70
+ export default entry;
71
+ `,
153
72
  );
73
+ return generatedEntrypoint;
154
74
  }
155
- }
156
75
 
157
- const devServer = new WebpackDevServer(
158
- // write to memory filesystem so we can import
159
- {
160
- ...webpackConfigs[0].devServer,
161
- /*client: {
162
- ...webpackConfigs[0].devServer?.client,
163
- webSocketURL: {
164
- ...webpackConfigs[0].devServer?.client.webSocketURL,
165
- port: 8080,
76
+ const webpackConfigs = [
77
+ webpackConfig(
78
+ {
79
+ ...env,
80
+ entrypath: hotEntry(entrypoint).name,
81
+ name: 'client',
166
82
  },
167
- },*/
168
- devMiddleware: {
169
- ...webpackConfigs[0]?.devServer?.devMiddleware,
170
- outputFileSystem: {
171
- ...fs,
172
- join: path.join as any,
173
- } as any as typeof fs,
174
- },
175
- setupMiddlewares: (middlewares, devServer) => {
176
- if (!devServer) {
177
- throw new Error('webpack-dev-server is not defined');
83
+ { mode: 'development' },
84
+ ),
85
+ webpackConfig(
86
+ {
87
+ ...env,
88
+ entrypath: entrypoint.replace('.tsx', '.server.tsx'),
89
+ name: 'server',
90
+ BROWSERSLIST_ENV: 'current node',
91
+ },
92
+ { mode: 'development', target: 'node' },
93
+ ),
94
+ ] as const;
95
+ // only have one output for server so we can avoid cached modules
96
+ webpackConfigs[1].plugins.push(
97
+ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
98
+ );
99
+ // initialize the webpack compiler
100
+ const compiler: MultiCompiler = webpack(webpackConfigs);
101
+
102
+ sourceMapSupport.install({ hookRequire: true });
103
+
104
+ function getServerBundle(serverStats: webpack.Stats) {
105
+ const serverJson = serverStats.toJson({ assets: true });
106
+ return path.join(serverJson.outputPath ?? '', 'server.js');
107
+ }
108
+ function handleErrors<
109
+ F extends (
110
+ req: Request | IncomingMessage,
111
+ res: Response | ServerResponse,
112
+ ) => Promise<void>,
113
+ >(fn: F) {
114
+ return async function (
115
+ req: Request | IncomingMessage,
116
+ res: Response | ServerResponse,
117
+ next: NextFunction,
118
+ ) {
119
+ try {
120
+ return await fn(req, res);
121
+ } catch (x) {
122
+ next(x);
178
123
  }
124
+ };
125
+ }
179
126
 
180
- // serve SSR for non-WEBPACK_PUBLIC_PATH
181
- devServer.app?.get(
182
- new RegExp(`^(?!${process.env.WEBPACK_PUBLIC_PATH})`),
183
- handleErrors(async function (req: any, res: any) {
184
- if (req.url.endsWith('favicon.ico')) {
185
- res.statusCode = 404;
186
- res.setHeader('Content-type', 'text/html');
187
- res.send('not found');
188
- return;
189
- }
190
- res.socket.on('error', (error: unknown) => {
191
- console.error('Fatal', error);
192
- });
127
+ const initRender:
128
+ | { args: Parameters<BoundRender>; resolve: () => void }[]
129
+ | undefined = [];
130
+ let render: BoundRender = (...args) =>
131
+ new Promise(resolve => {
132
+ initRender.push({ args, resolve });
133
+ });
134
+
135
+ function importRender(stats: webpack.Stats[]) {
136
+ const [clientStats, serverStats] = stats;
137
+ if (
138
+ clientStats?.compilation?.errors?.length ||
139
+ serverStats?.compilation?.errors?.length
140
+ ) {
141
+ log.error('Errors for client build: ' + clientStats.compilation.errors);
142
+ log.error('Errors for server build: ' + serverStats.compilation.errors);
143
+ // TODO: handle more gracefully
144
+ process.exit(-1);
145
+ } else {
146
+ log.info('Launching SSR');
147
+ }
193
148
 
194
- await render(req, res);
195
- }),
196
- );
149
+ // ASSETS
150
+ const clientManifest = clientStats.toJson();
197
151
 
198
- return middlewares;
199
- },
200
- },
201
- compiler,
202
- );
203
- const runServer = async () => {
204
- await devServer.start();
205
- devServer.compiler.hooks.done.tap(
206
- 'Anansi Server',
207
- (multiStats: webpack.MultiStats | webpack.Stats) => {
208
- if (!multiStats) {
209
- log.error('stats not send');
210
- process.exit(-1);
211
- }
152
+ // SERVER SIDE ENTRYPOINT
153
+ if (Array.isArray(initRender)) {
154
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
155
+ render = (require(getServerBundle(serverStats)) as any).default.bind(
156
+ undefined,
157
+ clientManifest,
158
+ );
159
+ initRender.forEach(init => render(...init.args).then(init.resolve));
160
+ } else {
161
+ render = (importFresh(getServerBundle(serverStats)) as any).default.bind(
162
+ undefined,
163
+ clientManifest,
164
+ );
165
+ }
166
+ }
212
167
 
213
- if (!Object.hasOwn(multiStats, 'stats')) return;
214
- if ((multiStats as webpack.MultiStats).stats.length > 1) {
215
- try {
216
- importRender((multiStats as webpack.MultiStats).stats);
217
- } catch (e: any) {
218
- log.error('Failed to load serve entrypoint');
219
- throw e;
168
+ const devServer = new WebpackDevServer(
169
+ // write to memory filesystem so we can import
170
+ {
171
+ ...webpackConfigs[0].devServer,
172
+ /*client: {
173
+ ...webpackConfigs[0].devServer?.client,
174
+ webSocketURL: {
175
+ ...webpackConfigs[0].devServer?.client.webSocketURL,
176
+ port: 8080,
177
+ },
178
+ },*/
179
+ devMiddleware: {
180
+ ...webpackConfigs[0]?.devServer?.devMiddleware,
181
+ outputFileSystem: {
182
+ ...fs,
183
+ join: path.join as any,
184
+ } as any as typeof fs,
185
+ },
186
+ setupMiddlewares: (middlewares, devServer) => {
187
+ if (!devServer) {
188
+ throw new Error('webpack-dev-server is not defined');
220
189
  }
221
- } else {
222
- log.error('Only compiler one stat');
223
- }
190
+
191
+ const otherRoutes = [
192
+ process.env.WEBPACK_PUBLIC_PATH,
193
+ ...Object.keys(webpackConfigs[0].devServer?.proxy ?? {}),
194
+ ];
195
+ // serve SSR for non-WEBPACK_PUBLIC_PATH
196
+ devServer.app?.get(
197
+ new RegExp(`^(?!${otherRoutes.join('|')})`),
198
+ handleErrors(async function (req: any, res: any) {
199
+ if (req.url.endsWith('favicon.ico')) {
200
+ res.statusCode = 404;
201
+ res.setHeader('Content-type', 'text/html');
202
+ res.send('not found');
203
+ return;
204
+ }
205
+ res.socket.on('error', (error: unknown) => {
206
+ console.error('Fatal', error);
207
+ });
208
+
209
+ await render(req, res);
210
+ }),
211
+ );
212
+
213
+ return middlewares;
214
+ },
224
215
  },
216
+ compiler,
225
217
  );
226
- };
227
- const stopServer = async () => {
228
- log.info('Stopping server...');
229
- await devServer.stop();
230
- log.info('Server closed');
231
- };
232
-
233
- process.on('SIGINT', () => {
234
- log.warn('Received SIGINT, devserver shutting down');
235
- stopServer();
236
- process.exit(-1);
237
- });
238
-
239
- runServer();
218
+ const runServer = async () => {
219
+ await devServer.start();
220
+ devServer.compiler.hooks.done.tap(
221
+ 'Anansi Server',
222
+ (multiStats: webpack.MultiStats | webpack.Stats) => {
223
+ if (!multiStats) {
224
+ log.error('stats not send');
225
+ process.exit(-1);
226
+ }
227
+
228
+ if (!Object.hasOwn(multiStats, 'stats')) return;
229
+ if ((multiStats as webpack.MultiStats).stats.length > 1) {
230
+ try {
231
+ importRender((multiStats as webpack.MultiStats).stats);
232
+ } catch (e: any) {
233
+ log.error('Failed to load serve entrypoint');
234
+ throw e;
235
+ }
236
+ } else {
237
+ log.error('Only compiler one stat');
238
+ }
239
+ },
240
+ );
241
+ };
242
+ const stopServer = async () => {
243
+ log.info('Stopping server...');
244
+ await devServer.stop();
245
+ log.info('Server closed');
246
+ };
247
+
248
+ process.on('SIGINT', () => {
249
+ log.warn('Received SIGINT, devserver shutting down');
250
+ stopServer();
251
+ process.exit(-1);
252
+ });
253
+
254
+ runServer();
255
+ }
@@ -4,6 +4,7 @@ type Props = {
4
4
  head: React.ReactNode;
5
5
  title: string;
6
6
  rootId: string;
7
+ charSet: string;
7
8
  };
8
9
 
9
10
  export default function Document({
@@ -12,10 +13,12 @@ export default function Document({
12
13
  children,
13
14
  title,
14
15
  rootId,
16
+ charSet,
15
17
  }: Props) {
16
18
  return (
17
19
  <html>
18
20
  <head>
21
+ <meta charSet={charSet} />
19
22
  {head}
20
23
  {assets.map((asset, i) => (
21
24
  <link key={i} rel="preload" {...asset} />
@@ -42,10 +45,13 @@ export default function Document({
42
45
  Document.defaultProps = {
43
46
  head: (
44
47
  <>
45
- <meta charSet="utf-8" />
46
48
  <meta name="viewport" content="width=device-width, initial-scale=1" />
47
- <link rel="shortcut icon" href="/assets/favicon.ico" />
49
+ <link
50
+ rel="shortcut icon"
51
+ href={`${process.env.WEBPACK_PUBLIC_PATH ?? '/'}favicon.ico`}
52
+ />
48
53
  </>
49
54
  ),
55
+ charSet: 'utf-8',
50
56
  rootId: 'anansi-root',
51
57
  };
@@ -8,12 +8,14 @@ import Document from './DocumentComponent';
8
8
  type NeededProps = {
9
9
  matchedRoutes: Route<any>[];
10
10
  title?: string;
11
+ charSet?: string;
11
12
  } & ResolveProps;
12
13
 
13
14
  export default function DocumentSpout(options: {
14
15
  head?: React.ReactNode;
15
16
  title: string;
16
17
  rootId: string;
18
+ charSet: string;
17
19
  }) {
18
20
  return function <T extends NeededProps>(
19
21
  next: (props: ServerProps) => Promise<T>,