@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.
- package/CHANGELOG.md +12 -0
- package/dist/server.js +27 -23
- package/dist/server.js.map +1 -1
- package/lib/floodSpouts.d.ts +6 -0
- package/lib/floodSpouts.d.ts.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.server.d.ts +6 -0
- package/lib/index.server.d.ts.map +1 -0
- package/lib/laySpouts.d.ts +8 -0
- package/lib/laySpouts.d.ts.map +1 -0
- package/lib/scripts/index.d.ts +3 -0
- package/lib/scripts/index.d.ts.map +1 -0
- package/lib/scripts/index.js +15 -0
- package/lib/scripts/serve.d.ts +4 -0
- package/lib/scripts/serve.d.ts.map +1 -0
- package/lib/scripts/serve.js +162 -0
- package/lib/scripts/startDevserver.d.ts +4 -0
- package/lib/scripts/startDevserver.d.ts.map +1 -0
- package/lib/scripts/startDevserver.js +175 -164
- package/lib/scripts/types.d.ts +7 -0
- package/lib/scripts/types.d.ts.map +1 -0
- package/lib/spouts/DocumentComponent.d.ts +23 -0
- package/lib/spouts/DocumentComponent.d.ts.map +1 -0
- package/lib/spouts/DocumentComponent.js +10 -6
- package/lib/spouts/document.d.ts +13 -0
- package/lib/spouts/document.d.ts.map +1 -0
- package/lib/spouts/document.server.d.ts +18 -0
- package/lib/spouts/document.server.d.ts.map +1 -0
- package/lib/spouts/document.server.js +1 -1
- package/lib/spouts/prefetch.server.d.ts +8 -0
- package/lib/spouts/prefetch.server.d.ts.map +1 -0
- package/lib/spouts/restHooks.d.ts +8 -0
- package/lib/spouts/restHooks.d.ts.map +1 -0
- package/lib/spouts/restHooks.server.d.ts +9 -0
- package/lib/spouts/restHooks.server.d.ts.map +1 -0
- package/lib/spouts/router.d.ts +12 -0
- package/lib/spouts/router.d.ts.map +1 -0
- package/lib/spouts/router.server.d.ts +12 -0
- package/lib/spouts/router.server.d.ts.map +1 -0
- package/lib/spouts/types.d.ts +16 -0
- package/lib/spouts/types.d.ts.map +1 -0
- package/package.json +8 -3
- package/src/scripts/index.ts +2 -0
- package/src/scripts/serve.ts +168 -0
- package/src/scripts/startDevserver.ts +218 -202
- package/src/spouts/DocumentComponent.tsx +8 -2
- 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
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
process.
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
...
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
149
|
+
// ASSETS
|
|
150
|
+
const clientManifest = clientStats.toJson();
|
|
197
151
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
process.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
|
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>,
|