wirejs-scripts 2.0.4 → 3.0.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/bin.js +381 -44
- package/configs/webpack.config.js +146 -125
- package/package.json +39 -33
- package/.gitattributes +0 -1
package/bin.js
CHANGED
|
@@ -1,51 +1,389 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import process from 'process';
|
|
4
|
+
import http from 'http';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import webpack from 'webpack';
|
|
9
|
+
import webpackConfigure from './configs/webpack.config.js';
|
|
10
|
+
import { rimraf } from 'rimraf';
|
|
11
|
+
|
|
12
|
+
import { JSDOM } from 'jsdom';
|
|
13
|
+
import { useJSDOM } from 'wirejs-dom/v2';
|
|
14
|
+
import { requiresContext, Context, CookieJar } from '../wirejs-resources/lib/types.js';
|
|
11
15
|
|
|
12
16
|
const CWD = process.cwd();
|
|
13
17
|
const webpackConfig = webpackConfigure(process.env, process.argv);
|
|
14
18
|
const [_nodeBinPath, _scriptPath, action] = process.argv;
|
|
15
19
|
const processes = [];
|
|
16
20
|
|
|
21
|
+
const logger = {
|
|
22
|
+
log(...items) {
|
|
23
|
+
console.log('wirejs', ...items);
|
|
24
|
+
},
|
|
25
|
+
error(...items) {
|
|
26
|
+
console.error('wirejs ERROR', ...items);
|
|
27
|
+
},
|
|
28
|
+
info(...items) {
|
|
29
|
+
console.info('wirejs', ...items);
|
|
30
|
+
},
|
|
31
|
+
warn(...items) {
|
|
32
|
+
console.warn('wirejs', ...items);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const oldFetch = globalThis.fetch;
|
|
37
|
+
globalThis.fetch = (url, ...args) => {
|
|
38
|
+
if (typeof url === 'string') {
|
|
39
|
+
try {
|
|
40
|
+
return fetch(new URL(url), ...args);
|
|
41
|
+
} catch {
|
|
42
|
+
return fetch(`http://localhost:3000${url}`, ...args);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
return oldFetch(url, ...args);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @param {http.IncomingMessage} req
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
54
|
+
function createContext(req) {
|
|
55
|
+
const { url, headers } = req;
|
|
56
|
+
const origin = headers.origin || `http://${headers.host}`;
|
|
57
|
+
const location = new URL(`${origin}${url}`);
|
|
58
|
+
const cookies = new CookieJar(headers.cookie);
|
|
59
|
+
return new Context({ cookies, location });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param {object} api
|
|
65
|
+
* @param {{method: string, args: any[]}} call
|
|
66
|
+
* @param {Context} context
|
|
67
|
+
* @returns {Promise<any>}
|
|
68
|
+
*/
|
|
69
|
+
async function callApiMethod(api, call, context) {
|
|
70
|
+
try {
|
|
71
|
+
const [scope, ...rest] = call.method;
|
|
72
|
+
logger.info('api method parsed', { scope, rest });
|
|
73
|
+
if (rest.length === 0) {
|
|
74
|
+
logger.info('api method resolved. invoking...');
|
|
75
|
+
if (requiresContext(api[scope])) {
|
|
76
|
+
return {
|
|
77
|
+
data: await api[scope](context, ...call.args.slice(1))
|
|
78
|
+
};
|
|
79
|
+
} else {
|
|
80
|
+
return {
|
|
81
|
+
data: await api[scope](...call.args)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
logger.info('nested scope found');
|
|
86
|
+
return callApiMethod(api[scope], {
|
|
87
|
+
...call,
|
|
88
|
+
method: rest,
|
|
89
|
+
}, context);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return { error: error.message };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @param {http.IncomingMessage} req
|
|
99
|
+
* @param {http.ServerResponse} res
|
|
100
|
+
* @returns
|
|
101
|
+
*/
|
|
102
|
+
async function handleApiResponse(req, res) {
|
|
103
|
+
const {
|
|
104
|
+
headers, url, method, params, query,
|
|
105
|
+
baseUrl, originalUrl, trailers
|
|
106
|
+
} = req;
|
|
107
|
+
|
|
108
|
+
const context = createContext(req);
|
|
109
|
+
|
|
110
|
+
if (url === '/api') {
|
|
111
|
+
const body = await postData(req);
|
|
112
|
+
const calls = JSON.parse(body);
|
|
113
|
+
logger.info('handling API request', body);
|
|
114
|
+
|
|
115
|
+
const apiPath = path.join(CWD, 'api', 'index.js');
|
|
116
|
+
const api = await import(`${apiPath}?cache-id=${new Date().getTime()}`);
|
|
117
|
+
|
|
118
|
+
const responses = [];
|
|
119
|
+
for (const call of calls) {
|
|
120
|
+
logger.info('handling API call', call);
|
|
121
|
+
responses.push(await callApiMethod(api, call, context));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
logger.info('setting cookies', context.cookies.getSetCookies());
|
|
125
|
+
for (const cookie of context.cookies.getSetCookies()) {
|
|
126
|
+
const cookieOptions = [];
|
|
127
|
+
if (cookie.maxAge) cookieOptions.push(`Max-Age=${cookie.maxAge}`);
|
|
128
|
+
if (cookie.httpOnly) cookieOptions.push('HttpOnly');
|
|
129
|
+
if (cookie.secure) cookieOptions.push('Secure');
|
|
130
|
+
|
|
131
|
+
logger.info('setting cookie', cookie.name, cookie.value, cookieOptions);
|
|
132
|
+
res.appendHeader(
|
|
133
|
+
'Set-Cookie',
|
|
134
|
+
`${cookie.name}=${cookie.value}; ${cookieOptions.join('; ')}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
139
|
+
res.end(JSON.stringify(
|
|
140
|
+
responses
|
|
141
|
+
));
|
|
142
|
+
} else {
|
|
143
|
+
logger.error('Bad endpoint given', { url });
|
|
144
|
+
|
|
145
|
+
res.statusCode = 404;
|
|
146
|
+
res.end("404 - Endpoint not found");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
*
|
|
152
|
+
* @param {http.IncomingMessage} req
|
|
153
|
+
* @returns
|
|
154
|
+
*/
|
|
155
|
+
function fullPathFrom(req) {
|
|
156
|
+
const relativePath = req.url === '/' ? 'index.html' : req.url;
|
|
157
|
+
return path.join(CWD, 'dist', relativePath);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
*
|
|
163
|
+
* @param {http.IncomingMessage} req
|
|
164
|
+
* @param {http.ServerResponse} res
|
|
165
|
+
* @returns
|
|
166
|
+
*/
|
|
167
|
+
async function tryStaticPath(req, res) {
|
|
168
|
+
const fullPath = fullPathFrom(req);
|
|
169
|
+
|
|
170
|
+
logger.info('checking static', fullPath);
|
|
171
|
+
if (!fs.existsSync(fullPath)) return false;
|
|
172
|
+
logger.info('static found');
|
|
173
|
+
|
|
174
|
+
if (fullPath.endsWith(".html")) {
|
|
175
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
176
|
+
} else if (fullPath.endsWith(".js")) {
|
|
177
|
+
res.setHeader('Content-Type', 'text/javascript; charset=utf-8');
|
|
178
|
+
} else {
|
|
179
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
res.end(fs.readFileSync(fullPath));
|
|
184
|
+
} catch {
|
|
185
|
+
res.statusCode = 404;
|
|
186
|
+
res.end("404 - File not found (b)");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Compare two strings by length for sorting in order of increasing length.
|
|
194
|
+
*
|
|
195
|
+
* @param {string} a
|
|
196
|
+
* @param {string} b
|
|
197
|
+
* @returns
|
|
198
|
+
*/
|
|
199
|
+
function byLength(a, b) {
|
|
200
|
+
return a.length - b.length;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @param {string} pattern - string pattern, where `*` matches anything
|
|
205
|
+
* @param {string} text
|
|
206
|
+
* @returns
|
|
207
|
+
*/
|
|
208
|
+
function globMatch(pattern, text) {
|
|
209
|
+
const parts = pattern.split('*');
|
|
210
|
+
const regex = new RegExp(parts.join('.+'));
|
|
211
|
+
return regex.test(text);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
*
|
|
216
|
+
* @param {Context} context
|
|
217
|
+
* @param {string} [forceExt]
|
|
218
|
+
*/
|
|
219
|
+
function routeSSR(context, forceExt) {
|
|
220
|
+
const SSR_ROOT = path.join(CWD, 'dist', 'ssr');
|
|
221
|
+
const asJSPath = forceExt ?
|
|
222
|
+
context.location.pathname.replace(/\.(\w+)$/, `.${forceExt}`)
|
|
223
|
+
: context.location.pathname
|
|
224
|
+
;
|
|
225
|
+
const allHandlers = fs.readdirSync(SSR_ROOT, { recursive: true })
|
|
226
|
+
.filter(p => p.endsWith('.js'))
|
|
227
|
+
.map(p => `/${p}`)
|
|
228
|
+
;
|
|
229
|
+
const matchingHandlers = allHandlers.filter(h => globMatch(h, asJSPath));
|
|
230
|
+
const match = matchingHandlers.sort(byLength).pop();
|
|
231
|
+
|
|
232
|
+
if (match) {
|
|
233
|
+
return path.join(SSR_ROOT, match);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @param {http.IncomingMessage} req
|
|
239
|
+
* @param {http.ServerResponse} res
|
|
240
|
+
* @returns
|
|
241
|
+
*/
|
|
242
|
+
async function trySSRScriptPath(req, res) {
|
|
243
|
+
const context = createContext(req);
|
|
244
|
+
const srcPath = routeSSR(context);
|
|
245
|
+
if (!srcPath) return false;
|
|
246
|
+
|
|
247
|
+
logger.info('SSR handler associated script found', srcPath);
|
|
248
|
+
|
|
249
|
+
res.setHeader('Content-Type', 'text/javascript');
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
res.end(fs.readFileSync(srcPath));
|
|
253
|
+
} catch {
|
|
254
|
+
res.statusCode = 404;
|
|
255
|
+
res.end("404 - File not found (c)");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
*
|
|
263
|
+
* @param {http.IncomingMessage} req
|
|
264
|
+
* @param {http.ServerResponse} res
|
|
265
|
+
* @returns
|
|
266
|
+
*/
|
|
267
|
+
async function trySSRPath(req, res) {
|
|
268
|
+
const context = createContext(req);
|
|
269
|
+
|
|
270
|
+
const asJSPath = context.location.pathname.replace(/\.(\w+)$/, '.js');
|
|
271
|
+
const srcPath = routeSSR(context, 'js');
|
|
272
|
+
if (!srcPath) return false;
|
|
273
|
+
|
|
274
|
+
logger.info('SSR handler found', srcPath);
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
useJSDOM(JSDOM);
|
|
278
|
+
global.self = global.window;
|
|
279
|
+
await import(`${srcPath}?cache-id=${new Date().getTime()}`);
|
|
280
|
+
const module = self.exports;
|
|
281
|
+
console.log({module});
|
|
282
|
+
if (typeof module.generate === 'function') {
|
|
283
|
+
const doc = await module.generate(context);
|
|
284
|
+
const doctype = doc.parentNode.doctype?.name || '';
|
|
285
|
+
|
|
286
|
+
let hydrationsFound = 0;
|
|
287
|
+
while (globalThis.pendingDehydrations?.length > 0) {
|
|
288
|
+
globalThis.pendingDehydrations.shift()(doc);
|
|
289
|
+
hydrationsFound++;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (hydrationsFound) {
|
|
293
|
+
const script = doc.parentNode.createElement('script');
|
|
294
|
+
script.src = asJSPath;
|
|
295
|
+
doc.parentNode.body.appendChild(script);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
res.setHeader('Content-type', 'text/html; charset=utf-8')
|
|
299
|
+
res.end([
|
|
300
|
+
doctype ? `<!doctype ${doctype}>\n` : '',
|
|
301
|
+
doc.outerHTML
|
|
302
|
+
].join(''));
|
|
303
|
+
|
|
304
|
+
return true;
|
|
305
|
+
} else {
|
|
306
|
+
logger.info('SSR module missing generate function');
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
logger.error('ssr error', error);
|
|
311
|
+
res.statusCode = 404;
|
|
312
|
+
res.end("404 - File not found (a)");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
*
|
|
320
|
+
* @param {http.IncomingMessage} req
|
|
321
|
+
* @param {http.ServerResponse} res
|
|
322
|
+
* @param {any} compiler
|
|
323
|
+
* @returns
|
|
324
|
+
*/
|
|
325
|
+
async function handleRequest(req, res, compiler) {
|
|
326
|
+
logger.info('received', JSON.stringify({ url: req.url }, null, 2));
|
|
327
|
+
|
|
328
|
+
if (req.url.startsWith('/api')) {
|
|
329
|
+
return handleApiResponse(req, res, compiler);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// const fs = compiler.outputFileSystem;
|
|
333
|
+
|
|
334
|
+
if (await tryStaticPath(req, res, fs)) return;
|
|
335
|
+
if (await trySSRScriptPath(req, res, fs)) return;
|
|
336
|
+
if (await trySSRPath(req, res, fs)) return;
|
|
337
|
+
|
|
338
|
+
// if we've made it this far, we don't have what you're looking for
|
|
339
|
+
res.statusCode = '404';
|
|
340
|
+
res.end('404 - Not found');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
*
|
|
345
|
+
* @param {http.IncomingMessage} request
|
|
346
|
+
* @returns
|
|
347
|
+
*/
|
|
348
|
+
async function postData(request) {
|
|
349
|
+
return new Promise((resolve, reject) => {
|
|
350
|
+
const buffer = [];
|
|
351
|
+
const timeout = setTimeout(() => {
|
|
352
|
+
reject("Post data not received.");
|
|
353
|
+
}, 5000);
|
|
354
|
+
request.on('data', data => buffer.push(data));
|
|
355
|
+
request.on('end', () => {
|
|
356
|
+
if (!timeout) return;
|
|
357
|
+
clearTimeout(timeout);
|
|
358
|
+
resolve(buffer.join(''));
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
|
|
17
363
|
async function compile(watch = false) {
|
|
18
|
-
const stats = await new Promise((resolve, reject) => {
|
|
364
|
+
const stats = await new Promise(async (resolve, reject) => {
|
|
365
|
+
let compiler;
|
|
19
366
|
if (watch) {
|
|
20
|
-
|
|
367
|
+
webpack({
|
|
21
368
|
...webpackConfig,
|
|
22
|
-
mode: 'development'
|
|
23
|
-
|
|
369
|
+
mode: 'development',
|
|
370
|
+
watch: true
|
|
371
|
+
}, () => {}).run(() => {});
|
|
24
372
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
open: true,
|
|
30
|
-
}, compiler);
|
|
31
|
-
|
|
32
|
-
console.log('Starting server...');
|
|
33
|
-
server.start().then(() => {
|
|
34
|
-
resolve({});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
resolve({});
|
|
373
|
+
logger.log('Starting server...');
|
|
374
|
+
const server = http.createServer(handleRequest);
|
|
375
|
+
server.listen(3000);
|
|
38
376
|
} else {
|
|
39
|
-
|
|
377
|
+
logger.log('instantiating webpack compiler');
|
|
40
378
|
compiler = webpack(webpackConfig);
|
|
41
379
|
compiler.run((err, res) => {
|
|
42
|
-
|
|
380
|
+
logger.log('invoking webpack compiler');
|
|
43
381
|
if (err) {
|
|
44
|
-
|
|
45
|
-
|
|
382
|
+
logger.error('webpack compiler failed');
|
|
383
|
+
logger.error(err);
|
|
46
384
|
reject(err);
|
|
47
385
|
} else {
|
|
48
|
-
|
|
386
|
+
logger.error('webpack compiler succeeded');
|
|
49
387
|
resolve(res);
|
|
50
388
|
}
|
|
51
389
|
});
|
|
@@ -53,7 +391,7 @@ async function compile(watch = false) {
|
|
|
53
391
|
});
|
|
54
392
|
|
|
55
393
|
if (stats?.compilation?.errors?.length > 0) {
|
|
56
|
-
|
|
394
|
+
logger.log('compilation errors', stats.compilation.errors);
|
|
57
395
|
throw new Error('Build failed.');
|
|
58
396
|
}
|
|
59
397
|
|
|
@@ -62,31 +400,30 @@ async function compile(watch = false) {
|
|
|
62
400
|
|
|
63
401
|
const engine = {
|
|
64
402
|
async build({ watch = false } = {}) {
|
|
65
|
-
|
|
403
|
+
logger.log('build starting');
|
|
66
404
|
|
|
67
405
|
rimraf.sync('dist');
|
|
68
|
-
|
|
406
|
+
logger.log('cleared old dist folder');
|
|
69
407
|
|
|
70
408
|
fs.mkdirSync('dist');
|
|
71
|
-
|
|
409
|
+
logger.log('recreated dist folder');
|
|
72
410
|
|
|
73
411
|
try {
|
|
74
|
-
|
|
75
412
|
await compile(watch);
|
|
76
|
-
|
|
413
|
+
logger.log('finished compile');
|
|
77
414
|
} catch (err) {
|
|
78
|
-
|
|
415
|
+
logger.error(err);
|
|
79
416
|
}
|
|
80
|
-
|
|
417
|
+
logger.log('build finished')
|
|
81
418
|
},
|
|
82
419
|
|
|
83
420
|
async start() {
|
|
84
|
-
|
|
421
|
+
logger.log('starting')
|
|
85
422
|
this.build({ watch: true });
|
|
86
423
|
|
|
87
424
|
await new Promise(resolve => {
|
|
88
425
|
function exitGracefully() {
|
|
89
|
-
|
|
426
|
+
logger.log('Exiting gracefully ...');
|
|
90
427
|
processes.forEach(p => p.kill());
|
|
91
428
|
resolve();
|
|
92
429
|
}
|
|
@@ -95,17 +432,17 @@ const engine = {
|
|
|
95
432
|
});
|
|
96
433
|
|
|
97
434
|
// explicit exit forces lingering child processes to die.
|
|
98
|
-
|
|
435
|
+
logger.log('stopping')
|
|
99
436
|
process.exit();
|
|
100
437
|
}
|
|
101
438
|
|
|
102
439
|
};
|
|
103
440
|
|
|
104
441
|
if (typeof engine[action] === 'function') {
|
|
105
|
-
|
|
442
|
+
logger.log(`Running ${action} ... `);
|
|
106
443
|
engine[action]().then(() => {
|
|
107
|
-
|
|
444
|
+
logger.log('All done!');
|
|
108
445
|
});
|
|
109
446
|
} else {
|
|
110
|
-
|
|
111
|
-
}
|
|
447
|
+
logger.error(`Invalid wirejs-scripts action: ${action}`);
|
|
448
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { useJSDOM } from 'wirejs-dom/v2';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import glob from 'glob';
|
|
5
|
+
import process from 'process';
|
|
6
|
+
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
|
7
|
+
import marked from 'marked';
|
|
8
|
+
import { JSDOM } from 'jsdom';
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
const CWD = process.cwd();
|
|
10
12
|
|
|
@@ -25,20 +27,14 @@ const BUILD_ID = (new Date()).getTime();
|
|
|
25
27
|
|
|
26
28
|
fs.writeFileSync('./src/build_id.json', JSON.stringify(BUILD_ID.toString()));
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
// TODO: Refactor these transforms out of here.
|
|
30
|
-
// TODO: Create a separate package to manage all of this for easy reuse on
|
|
31
|
-
// other projects.
|
|
32
|
-
// TODO: consider whether using the front-end framework for SSG would be safe,
|
|
33
|
-
// and intuitive, rather than having two completely separate rendering modes.
|
|
34
|
-
|
|
35
|
-
function distPath({ subpathOut = '', subpathIn = '' } = {}) {
|
|
30
|
+
function distPath({ subpathOut = '', subpathIn = '', extensionOut } = {}) {
|
|
36
31
|
return function ({ context, absoluteFilename }) {
|
|
37
32
|
const prefixIn = path.resolve(context, subpathIn);
|
|
38
33
|
const prefixOut = path.resolve(context, 'dist', subpathOut);
|
|
39
34
|
const relativeName = path.join('./', absoluteFilename.slice(prefixIn.toString().length));
|
|
40
|
-
const fullOutPath =
|
|
41
|
-
.replace(
|
|
35
|
+
const fullOutPath = extensionOut ?
|
|
36
|
+
path.resolve(prefixOut, relativeName).replace(/\.\w+$/, ".html")
|
|
37
|
+
: path.resolve(prefixOut, relativeName);
|
|
42
38
|
console.log(`Mapping ${relativeName} to ${fullOutPath}`);
|
|
43
39
|
return fullOutPath;
|
|
44
40
|
};
|
|
@@ -57,58 +53,15 @@ const CollectLayouts = {
|
|
|
57
53
|
const SSG = {
|
|
58
54
|
transformer: async (content, _path) => {
|
|
59
55
|
let _meta = {};
|
|
60
|
-
function meta(o) {
|
|
61
|
-
_meta = o;
|
|
62
|
-
return '';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const _require = require;
|
|
66
|
-
const WD = path.dirname(_path);
|
|
67
56
|
|
|
68
57
|
let body;
|
|
69
58
|
try {
|
|
70
|
-
|
|
71
|
-
const absolutePath = _require.resolve(requirePath, {paths: [WD]});
|
|
72
|
-
return _require(absolutePath);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (_path.endsWith('.md')) {
|
|
76
|
-
let isInCodeBlock = false;
|
|
77
|
-
const escapedMarkdown = content.toString().split(/\n/)
|
|
78
|
-
.reduce((lines, line) => {
|
|
79
|
-
if (isInCodeBlock) {
|
|
80
|
-
lines[lines.length - 1] += "\n" + line;
|
|
81
|
-
} else {
|
|
82
|
-
lines.push(line);
|
|
83
|
-
}
|
|
84
|
-
if (line.startsWith('```')) {
|
|
85
|
-
isInCodeBlock = !isInCodeBlock;
|
|
86
|
-
}
|
|
87
|
-
return lines;
|
|
88
|
-
}, [])
|
|
89
|
-
.map(l => l.trim()).join('\n')
|
|
90
|
-
.replace(/(``+)/g, m => Array(m.length).fill('\\`').join(''))
|
|
91
|
-
;
|
|
92
|
-
const bodyMarkdown = eval('`' + escapedMarkdown + '`');
|
|
93
|
-
body = marked(bodyMarkdown);
|
|
94
|
-
} else {
|
|
95
|
-
body = eval('`' + content + '`');
|
|
96
|
-
}
|
|
59
|
+
body = _path.endsWith('.md') ? marked(content.toString()) : content.toString();
|
|
97
60
|
} catch (err) {
|
|
98
61
|
console.error(`Could not parse page ${_path}`, err);
|
|
99
62
|
throw err;
|
|
100
|
-
} finally {
|
|
101
|
-
require = _require;
|
|
102
63
|
}
|
|
103
64
|
|
|
104
|
-
const metatags = Object.entries(_meta).map(([tag, content]) => {
|
|
105
|
-
tag = tag.replace(/"/g, '"');
|
|
106
|
-
content = content.replace(/"/g, '"');
|
|
107
|
-
return `<meta name="${tag}" content="${content}" />`;
|
|
108
|
-
}).join('\n');
|
|
109
|
-
|
|
110
|
-
let title = _meta.title;
|
|
111
|
-
|
|
112
65
|
// apply no layout if the document has already provided the
|
|
113
66
|
// overarching html structure.
|
|
114
67
|
if (!_meta.layout && body && (
|
|
@@ -127,7 +80,7 @@ const SSG = {
|
|
|
127
80
|
const layout = layouts[layoutPath];
|
|
128
81
|
|
|
129
82
|
try {
|
|
130
|
-
return
|
|
83
|
+
return layout;
|
|
131
84
|
} catch (err) {
|
|
132
85
|
console.error(`Could not parse layout ${layoutPath}`, err);
|
|
133
86
|
throw err;
|
|
@@ -135,7 +88,47 @@ const SSG = {
|
|
|
135
88
|
}
|
|
136
89
|
};
|
|
137
90
|
|
|
138
|
-
|
|
91
|
+
const Generated = {
|
|
92
|
+
transformer: async (content, contentPath) => {
|
|
93
|
+
useJSDOM(JSDOM);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
if (contentPath.endsWith('.js')) {
|
|
97
|
+
const module = await import(contentPath);
|
|
98
|
+
if (typeof module.generate === 'function') {
|
|
99
|
+
const doc = await module.generate(contentPath);
|
|
100
|
+
const doctype = doc.parentNode.doctype?.name || '';
|
|
101
|
+
|
|
102
|
+
let hydrationsFound = 0;
|
|
103
|
+
while (globalThis.pendingDehydrations?.length > 0) {
|
|
104
|
+
globalThis.pendingDehydrations.shift()(doc);
|
|
105
|
+
hydrationsFound++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (hydrationsFound) {
|
|
109
|
+
const script = doc.parentNode.createElement('script');
|
|
110
|
+
script.src = contentPath.substring((CWD + '/src/ssg').length);
|
|
111
|
+
doc.parentNode.body.appendChild(script);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return [
|
|
115
|
+
doctype ? `<!doctype ${doctype}>\n` : '',
|
|
116
|
+
doc.outerHTML
|
|
117
|
+
].join('');
|
|
118
|
+
} else {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
return content.toString();
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error(`Could not generate page ${contentPath}`, err);
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default (env, argv) => {
|
|
139
132
|
var devtool = 'source-map';
|
|
140
133
|
if (argv.mode == 'development') {
|
|
141
134
|
devtool = 'eval-cheap-source-map';
|
|
@@ -144,10 +137,17 @@ module.exports = (env, argv) => {
|
|
|
144
137
|
const sources = ['./src/index.js']
|
|
145
138
|
.concat(glob.sync('./src/layouts/**/*.js'))
|
|
146
139
|
.concat(glob.sync('./src/routes/**/*.js'))
|
|
140
|
+
.concat(glob.sync('./src/ssg/**/*.js'))
|
|
141
|
+
.concat(glob.sync('./src/ssr/**/*.js'))
|
|
147
142
|
;
|
|
148
143
|
|
|
149
144
|
const entry = sources.reduce((files, path) => {
|
|
150
|
-
if (path.match(/src\/
|
|
145
|
+
if (path.match(/src\/ssg/)) {
|
|
146
|
+
files[path.toString().slice('./src/ssg'.length)] = path;
|
|
147
|
+
} else if (path.match(/src\/ssr/)) {
|
|
148
|
+
// keep SSR bundles in the ssr subfolder
|
|
149
|
+
files[path.toString().slice('./src'.length)] = path;
|
|
150
|
+
} else if (path.match(/src\/routes/)) {
|
|
151
151
|
files[path.toString().slice('./src/routes'.length)] = path;
|
|
152
152
|
} else if (path.match(/src\/layouts/)) {
|
|
153
153
|
files[path.toString().slice('./src/'.length)] = path;
|
|
@@ -156,17 +156,12 @@ module.exports = (env, argv) => {
|
|
|
156
156
|
}, {});
|
|
157
157
|
|
|
158
158
|
return {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
port: 9999,
|
|
165
|
-
watchContentBase: true,
|
|
166
|
-
// liveReload: true,
|
|
167
|
-
// hot: true
|
|
159
|
+
// here's where we might need to select different "import" conditions,
|
|
160
|
+
// which match with `exports: { path: { condition: "" } }` in package.json
|
|
161
|
+
// for different build types for API's, SSR, etc.
|
|
162
|
+
resolve: {
|
|
163
|
+
conditionNames: ['wirejs:client'],
|
|
168
164
|
},
|
|
169
|
-
*/
|
|
170
165
|
watchOptions: {
|
|
171
166
|
ignored: [
|
|
172
167
|
"**/dist/*",
|
|
@@ -178,8 +173,13 @@ module.exports = (env, argv) => {
|
|
|
178
173
|
},
|
|
179
174
|
entry,
|
|
180
175
|
output: {
|
|
181
|
-
filename: "[name]"
|
|
176
|
+
filename: "[name]",
|
|
177
|
+
library: {
|
|
178
|
+
type: 'global',
|
|
179
|
+
name: 'exports'
|
|
180
|
+
}
|
|
182
181
|
},
|
|
182
|
+
target: 'web',
|
|
183
183
|
devtool,
|
|
184
184
|
plugins: [
|
|
185
185
|
|
|
@@ -210,58 +210,79 @@ module.exports = (env, argv) => {
|
|
|
210
210
|
priority: 10,
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
|
-
from: './src/
|
|
214
|
-
to: distPath({ subpathIn: 'src/
|
|
215
|
-
transform:
|
|
216
|
-
noErrorOnMissing: true,
|
|
217
|
-
priority: 3,
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
from: './src/routes/**/*.html',
|
|
221
|
-
to: distPath({ subpathIn: 'src/routes' }),
|
|
222
|
-
transform: SSG,
|
|
223
|
-
noErrorOnMissing: true,
|
|
224
|
-
priority: 3,
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
from: './src/routes/**/*.css',
|
|
228
|
-
to: distPath({ subpathIn: 'src/routes' }),
|
|
229
|
-
noErrorOnMissing: true,
|
|
230
|
-
// trasform: ???
|
|
231
|
-
priority: 3,
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
from: './src/routes/**/*.png',
|
|
235
|
-
to: distPath({ subpathIn: 'src/routes' }),
|
|
236
|
-
noErrorOnMissing: true,
|
|
237
|
-
priority: 3,
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
from: './src/routes/**/*.jpg',
|
|
241
|
-
to: distPath({ subpathIn: 'src/routes' }),
|
|
213
|
+
from: './src/ssg/**/*.js',
|
|
214
|
+
to: distPath({ subpathIn: 'src/ssg', extensionOut: 'html' }),
|
|
215
|
+
transform: Generated,
|
|
242
216
|
noErrorOnMissing: true,
|
|
243
|
-
priority:
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
from: './src/routes/**/*.json',
|
|
247
|
-
to: distPath({ subpathIn: 'src/routes' }),
|
|
248
|
-
noErrorOnMissing: true,
|
|
249
|
-
priority: 3,
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
from: './src/routes/**/*.svg',
|
|
253
|
-
to: distPath({ subpathIn: 'src/routes' }),
|
|
254
|
-
noErrorOnMissing: true,
|
|
255
|
-
priority: 3,
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
from: './src/routes/**/*.mp3',
|
|
259
|
-
to: distPath({ subpathIn: 'src/routes' }),
|
|
260
|
-
noErrorOnMissing: true,
|
|
261
|
-
priority: 3,
|
|
217
|
+
priority: 5
|
|
262
218
|
},
|
|
219
|
+
// {
|
|
220
|
+
// from: './src/routes/**/*.md',
|
|
221
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
222
|
+
// transform: SSG,
|
|
223
|
+
// noErrorOnMissing: true,
|
|
224
|
+
// priority: 3,
|
|
225
|
+
// },
|
|
226
|
+
// {
|
|
227
|
+
// from: './src/routes/**/*.html',
|
|
228
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
229
|
+
// transform: SSG,
|
|
230
|
+
// noErrorOnMissing: true,
|
|
231
|
+
// priority: 3,
|
|
232
|
+
// },
|
|
233
|
+
// {
|
|
234
|
+
// from: './src/routes/**/*.xml',
|
|
235
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
236
|
+
// transform: SSG,
|
|
237
|
+
// noErrorOnMissing: true,
|
|
238
|
+
// priority: 3,
|
|
239
|
+
// },
|
|
240
|
+
// {
|
|
241
|
+
// from: './src/routes/**/*.rss',
|
|
242
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
243
|
+
// transform: SSG,
|
|
244
|
+
// noErrorOnMissing: true,
|
|
245
|
+
// priority: 3,
|
|
246
|
+
// },
|
|
247
|
+
// {
|
|
248
|
+
// from: './src/routes/**/*.css',
|
|
249
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
250
|
+
// noErrorOnMissing: true,
|
|
251
|
+
// // trasform: ???
|
|
252
|
+
// priority: 3,
|
|
253
|
+
// },
|
|
254
|
+
// {
|
|
255
|
+
// from: './src/routes/**/*.png',
|
|
256
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
257
|
+
// noErrorOnMissing: true,
|
|
258
|
+
// priority: 3,
|
|
259
|
+
// },
|
|
260
|
+
// {
|
|
261
|
+
// from: './src/routes/**/*.jpg',
|
|
262
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
263
|
+
// noErrorOnMissing: true,
|
|
264
|
+
// priority: 3,
|
|
265
|
+
// },
|
|
266
|
+
// {
|
|
267
|
+
// from: './src/routes/**/*.json',
|
|
268
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
269
|
+
// noErrorOnMissing: true,
|
|
270
|
+
// priority: 3,
|
|
271
|
+
// },
|
|
272
|
+
// {
|
|
273
|
+
// from: './src/routes/**/*.svg',
|
|
274
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
275
|
+
// noErrorOnMissing: true,
|
|
276
|
+
// priority: 3,
|
|
277
|
+
// },
|
|
278
|
+
// {
|
|
279
|
+
// from: './src/routes/**/*.mp3',
|
|
280
|
+
// to: distPath({ subpathIn: 'src/routes' }),
|
|
281
|
+
// noErrorOnMissing: true,
|
|
282
|
+
// priority: 3,
|
|
283
|
+
// },
|
|
263
284
|
],
|
|
264
|
-
})
|
|
285
|
+
}),
|
|
265
286
|
],
|
|
266
287
|
module: {
|
|
267
288
|
rules: [
|
|
@@ -302,4 +323,4 @@ module.exports = (env, argv) => {
|
|
|
302
323
|
]
|
|
303
324
|
}
|
|
304
325
|
};
|
|
305
|
-
};
|
|
326
|
+
};
|
package/package.json
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "wirejs-scripts",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Basic build and start commands for wirejs apps",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wirejs-scripts": "./bin.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "echo \"Nothing to build\"",
|
|
11
|
+
"clean": "echo \"Nothing to clean\""
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/svidgen/create-wirejs-app.git"
|
|
16
|
+
},
|
|
17
|
+
"author": "Jon Wire",
|
|
18
|
+
"license": "AGPL-3.0-only",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/svidgen/create-wirejs-app/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/svidgen/create-wirejs-app#readme",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"copy-webpack-plugin": "^10.2.4",
|
|
25
|
+
"css-loader": "^5.2.0",
|
|
26
|
+
"file-loader": "^6.2.0",
|
|
27
|
+
"glob": "^7.2.0",
|
|
28
|
+
"highlight.js": "^11.1.0",
|
|
29
|
+
"jsdom": "^25.0.1",
|
|
30
|
+
"marked": "^2.0.1",
|
|
31
|
+
"raw-loader": "^4.0.2",
|
|
32
|
+
"rimraf": "^6.0.1",
|
|
33
|
+
"style-loader": "^2.0.0",
|
|
34
|
+
"webpack": "^5.97.1",
|
|
35
|
+
"webpack-cli": "^6.0.1",
|
|
36
|
+
"webpack-dev-server": "^5.2.0",
|
|
37
|
+
"wirejs-dom": "^1.0.34"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/.gitattributes
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
* text=auto
|