@webqit/webflo 0.10.3 → 0.11.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/README.md +1509 -3
- package/bundle.html.json +1665 -0
- package/package.json +3 -3
- package/src/Context.js +8 -4
- package/src/config-pi/deployment/Env.js +2 -2
- package/src/config-pi/deployment/Layout.js +2 -2
- package/src/config-pi/deployment/Origins.js +2 -2
- package/src/config-pi/deployment/Virtualization.js +2 -2
- package/src/config-pi/runtime/Client.js +39 -11
- package/src/config-pi/runtime/Server.js +26 -10
- package/src/config-pi/runtime/client/Worker.js +32 -14
- package/src/config-pi/runtime/server/Headers.js +2 -2
- package/src/config-pi/runtime/server/Redirects.js +2 -2
- package/src/config-pi/static/Manifest.js +2 -2
- package/src/config-pi/static/Ssg.js +2 -2
- package/src/runtime-pi/Router.js +1 -1
- package/src/runtime-pi/client/Runtime.js +116 -62
- package/src/runtime-pi/client/RuntimeClient.js +28 -43
- package/src/runtime-pi/client/Workport.js +163 -0
- package/src/runtime-pi/client/generate.js +285 -77
- package/src/runtime-pi/client/{generate.oohtml.js → oohtml/full.js} +0 -0
- package/src/runtime-pi/client/oohtml/namespacing.js +7 -0
- package/src/runtime-pi/client/oohtml/scripting.js +8 -0
- package/src/runtime-pi/client/oohtml/templating.js +8 -0
- package/src/runtime-pi/client/worker/Worker.js +58 -24
- package/src/runtime-pi/client/worker/Workport.js +80 -0
- package/src/runtime-pi/server/Router.js +2 -2
- package/src/runtime-pi/server/Runtime.js +30 -11
- package/src/runtime-pi/server/RuntimeClient.js +24 -14
- package/src/runtime-pi/util.js +2 -2
- package/src/webflo.js +7 -9
- package/test/site/package.json +9 -0
- package/test/site/public/bundle.html +6 -0
- package/test/site/public/bundle.html.json +4 -0
- package/test/site/public/bundle.js +2 -0
- package/test/site/public/bundle.js.gz +0 -0
- package/test/site/public/bundle.webflo.js +15 -0
- package/test/site/public/bundle.webflo.js.gz +0 -0
- package/test/site/public/index.html +30 -0
- package/test/site/public/index1.html +35 -0
- package/test/site/public/page-2/bundle.html +5 -0
- package/test/site/public/page-2/bundle.html.json +1 -0
- package/test/site/public/page-2/bundle.js +2 -0
- package/test/site/public/page-2/bundle.js.gz +0 -0
- package/test/site/public/page-2/index.html +46 -0
- package/test/site/public/page-2/logo-130x130.png +0 -0
- package/test/site/public/page-2/main.html +3 -0
- package/test/site/public/page-3/logo-130x130.png +0 -0
- package/test/site/public/page-4/subpage/bundle.html +0 -0
- package/test/site/public/page-4/subpage/bundle.html.json +1 -0
- package/test/site/public/page-4/subpage/bundle.js +2 -0
- package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
- package/test/site/public/page-4/subpage/index.html +31 -0
- package/test/site/public/sparoots.json +5 -0
- package/test/site/public/worker.js +3 -0
- package/test/site/public/worker.js.gz +0 -0
- package/test/site/server/index.js +16 -0
- package/src/Cli.js +0 -131
- package/src/Configurator.js +0 -97
- package/src/runtime-pi/client/WorkerComm.js +0 -102
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
import Fs from 'fs';
|
|
6
6
|
import Url from 'url';
|
|
7
7
|
import Path from 'path';
|
|
8
|
-
import
|
|
8
|
+
import Jsdom from 'jsdom';
|
|
9
|
+
import EsBuild from 'esbuild';
|
|
10
|
+
import { _afterLast, _beforeLast } from '@webqit/util/str/index.js';
|
|
9
11
|
import { _isObject, _isArray } from '@webqit/util/js/index.js';
|
|
10
|
-
import
|
|
12
|
+
import { jsFile } from '@webqit/backpack/src/dotfile/index.js';
|
|
11
13
|
import { gzipSync, brotliCompressSync } from 'zlib';
|
|
12
|
-
import
|
|
14
|
+
import { urlPattern } from '../util.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* @generate
|
|
@@ -20,6 +22,9 @@ export async function generate() {
|
|
|
20
22
|
if (!cx.config.runtime?.Client) {
|
|
21
23
|
throw new Error(`The Client configurator "config.runtime.Client" is required in context.`);
|
|
22
24
|
}
|
|
25
|
+
if (!cx.config.deployment?.Layout) {
|
|
26
|
+
throw new Error(`The Client configurator "config.deployment.Layout" is required in context.`);
|
|
27
|
+
}
|
|
23
28
|
const clientConfig = await (new cx.config.runtime.Client(cx)).read();
|
|
24
29
|
if (clientConfig.support_service_worker && !cx.config.runtime.client?.Worker) {
|
|
25
30
|
throw new Error(`The Service Worker configurator "config.runtime.client.Worker" is required in context.`);
|
|
@@ -30,88 +35,235 @@ export async function generate() {
|
|
|
30
35
|
throw new Error(`The Layout configurator "config.deployment.Layout" is required in context.`);
|
|
31
36
|
}
|
|
32
37
|
const layoutConfig = await (new cx.config.deployment.Layout(cx)).read();
|
|
38
|
+
// -----------
|
|
33
39
|
const dirPublic = Path.resolve(cx.CWD || '', layoutConfig.PUBLIC_DIR);
|
|
40
|
+
const dirClient = Path.resolve(cx.CWD || '', layoutConfig.CLIENT_DIR);
|
|
41
|
+
const dirWorker = Path.resolve(cx.CWD || '', layoutConfig.WORKER_DIR);
|
|
34
42
|
const dirSelf = Path.dirname(Url.fileURLToPath(import.meta.url)).replace(/\\/g, '/');
|
|
35
43
|
// -----------
|
|
44
|
+
// Scan Subdocuments
|
|
45
|
+
const scanSubroots = (sparoot, rootFileName) => {
|
|
46
|
+
let dir = Path.join(dirPublic, sparoot), passes = 0;
|
|
47
|
+
return [ Fs.readdirSync(dir).reduce((sparoots, f) => {
|
|
48
|
+
let resource = Path.join(dir, f);
|
|
49
|
+
if (Fs.statSync(resource).isDirectory()) {
|
|
50
|
+
let subsparoot = Path.join(sparoot, f);
|
|
51
|
+
if (Fs.existsSync(Path.join(resource, rootFileName))) {
|
|
52
|
+
return sparoots.concat(subsparoot);
|
|
53
|
+
}
|
|
54
|
+
passes ++;
|
|
55
|
+
return sparoots.concat(scanSubroots(subsparoot, rootFileName)[ 0 ]);
|
|
56
|
+
}
|
|
57
|
+
return sparoots;
|
|
58
|
+
}, []), passes ];
|
|
59
|
+
};
|
|
60
|
+
// -----------
|
|
36
61
|
// Generate client build
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
const generateClient = async function(sparoot, spaGraphCallback = null) {
|
|
63
|
+
let [ subsparoots, targets ] = (sparoot && scanSubroots(sparoot, 'index.html')) || [ [], false ];
|
|
64
|
+
if (!sparoot) sparoot = '/';
|
|
65
|
+
let spaRouting = { root: sparoot, subroots: subsparoots, targets };
|
|
66
|
+
let codeSplitting = !!(sparoot !== '/' || subsparoots.length);
|
|
67
|
+
let outfileMain = Path.join(sparoot, clientConfig.bundle_filename),
|
|
68
|
+
outfileWebflo = _beforeLast(clientConfig.bundle_filename, '.js') + '.webflo.js';
|
|
69
|
+
let gen = { imports: {}, code: [], };
|
|
70
|
+
// ------------------
|
|
71
|
+
const initWebflo = gen => {
|
|
72
|
+
if (clientConfig.oohtml_support === 'namespacing') {
|
|
73
|
+
gen.imports[`${dirSelf}/oohtml/namespacing.js`] = null;
|
|
74
|
+
} else if (clientConfig.oohtml_support === 'scripting') {
|
|
75
|
+
gen.imports[`${dirSelf}/oohtml/scripting.js`] = null;
|
|
76
|
+
} else if (clientConfig.oohtml_support === 'templating') {
|
|
77
|
+
gen.imports[`${dirSelf}/oohtml/templating.js`] = null;
|
|
78
|
+
} else if (clientConfig.oohtml_support !== 'none') {
|
|
79
|
+
gen.imports[`${dirSelf}/oohtml/full.js`] = null;
|
|
80
|
+
}
|
|
81
|
+
gen.imports[`${dirSelf}/index.js`] = `* as Webflo`;
|
|
82
|
+
gen.code.push(``);
|
|
83
|
+
gen.code.push(`if (!globalThis.WebQit) {`);
|
|
84
|
+
gen.code.push(` globalThis.WebQit = {}`);
|
|
85
|
+
gen.code.push(`}`);
|
|
86
|
+
gen.code.push(`WebQit.Webflo = Webflo`);
|
|
87
|
+
return gen;
|
|
88
|
+
};
|
|
89
|
+
// ------------------
|
|
90
|
+
if (!codeSplitting) {
|
|
91
|
+
initWebflo(gen);
|
|
92
|
+
} else if (sparoot === '/') {
|
|
93
|
+
if (cx.logger) {
|
|
94
|
+
cx.logger.log(cx.logger.style.keyword(`-----------------`));
|
|
95
|
+
cx.logger.log(`Base Build`);
|
|
96
|
+
cx.logger.log(cx.logger.style.keyword(`-----------------`));
|
|
97
|
+
}
|
|
98
|
+
let gen1 = initWebflo({ imports: {}, code: [], });
|
|
99
|
+
await bundle.call(cx, gen1, Path.join(dirPublic, outfileWebflo), true/* asModule */);
|
|
100
|
+
}
|
|
101
|
+
// ------------------
|
|
102
|
+
if (cx.logger) {
|
|
103
|
+
cx.logger.log(cx.logger.style.keyword(`-----------------`));
|
|
104
|
+
cx.logger.log(`Client Build ` + cx.logger.style.comment(`(sparoot:${sparoot}; is-split:${codeSplitting})`));
|
|
105
|
+
cx.logger.log(cx.logger.style.keyword(`-----------------`));
|
|
106
|
+
}
|
|
107
|
+
gen.code.push(`const { start } = WebQit.Webflo`);
|
|
108
|
+
// ------------------
|
|
109
|
+
// Bundle
|
|
110
|
+
declareStart.call(cx, gen, dirClient, dirPublic, clientConfig, spaRouting);
|
|
111
|
+
await bundle.call(cx, gen, Path.join(dirPublic, outfileMain), true/* asModule */);
|
|
112
|
+
// ------------------
|
|
113
|
+
// Embed/unembed
|
|
114
|
+
let targetDocumentFile = Path.join(dirPublic, sparoot, 'index.html'),
|
|
115
|
+
outfileWebfloPublic = Path.join(clientConfig.public_base_url, outfileWebflo),
|
|
116
|
+
outfileMainPublic = Path.join(clientConfig.public_base_url, outfileMain),
|
|
117
|
+
embedList = [],
|
|
118
|
+
unembedList = [];
|
|
119
|
+
if (cx.flags['auto-embed']) {
|
|
120
|
+
if (codeSplitting) {
|
|
121
|
+
embedList.push(outfileWebfloPublic);
|
|
122
|
+
} else {
|
|
123
|
+
unembedList.push(outfileWebfloPublic);
|
|
124
|
+
}
|
|
125
|
+
embedList.push(outfileMainPublic);
|
|
126
|
+
} else {
|
|
127
|
+
unembedList.push(outfileWebfloPublic, outfileMainPublic);
|
|
128
|
+
}
|
|
129
|
+
handleEmbeds(targetDocumentFile, embedList, unembedList);
|
|
130
|
+
// ------------------
|
|
131
|
+
// Recurse
|
|
132
|
+
spaGraphCallback && spaGraphCallback(sparoot, subsparoots);
|
|
133
|
+
if (cx.flags.recursive) {
|
|
134
|
+
while (subsparoots.length) {
|
|
135
|
+
await generateClient(subsparoots.shift(), spaGraphCallback);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
43
139
|
// -----------
|
|
44
140
|
// Generate worker build
|
|
45
|
-
|
|
46
|
-
let
|
|
47
|
-
|
|
48
|
-
|
|
141
|
+
const generateWorker = async function(workerroot, workerGraphCallbak = null) {
|
|
142
|
+
let [ subworkerroots, targets ] = workerroot && scanSubroots(workerroot, 'workerroot') || [ [], false ];
|
|
143
|
+
if (!workerroot) workerroot = '/';
|
|
144
|
+
let workerRouting = { root: workerroot, subroots: subworkerroots, targets };
|
|
145
|
+
let gen = { imports: {}, code: [], };
|
|
146
|
+
if (cx.logger) {
|
|
147
|
+
cx.logger.log(cx.logger.style.comment(`-----------------`));
|
|
148
|
+
cx.logger.log(`Worker Build - workerroot:${workerroot}`);
|
|
149
|
+
cx.logger.log(cx.logger.style.comment(`-----------------`));
|
|
150
|
+
}
|
|
151
|
+
// ------------------
|
|
152
|
+
// >> Modules import
|
|
153
|
+
gen.imports[`${dirSelf}/worker/index.js`] = `{ start }`;
|
|
154
|
+
gen.code.push(``);
|
|
155
|
+
// ------------------
|
|
156
|
+
// Bundle
|
|
157
|
+
if (workerConfig.cache_only_urls.length) {
|
|
158
|
+
// Separate URLs from patterns
|
|
159
|
+
let [ urls, patterns ] = workerConfig.cache_only_urls.reduce(([ urls, patterns ], url) => {
|
|
160
|
+
let patternInstance = urlPattern(url, 'http://localhost'),
|
|
161
|
+
isPattern = patternInstance.isPattern();
|
|
162
|
+
if (isPattern && (patternInstance.pattern.pattern.hostname !== 'localhost' || patternInstance.pattern.pattern.port)) {
|
|
163
|
+
throw new Error(`Pattern URLs must have no origin part. Recieved "${url}".`);
|
|
164
|
+
}
|
|
165
|
+
return isPattern ? [ urls, patterns.concat(patternInstance) ] : [ urls.concat(url), patterns ];
|
|
166
|
+
}, [ [], [] ]);
|
|
167
|
+
// Resolve patterns
|
|
168
|
+
if (patterns.length) {
|
|
169
|
+
// List all files
|
|
170
|
+
let scan = dir => Fs.readdirSync(dir).reduce((result, f) => {
|
|
171
|
+
let resource = Path.join(dir, f);
|
|
172
|
+
return result.concat(Fs.statSync(resource).isDirectory() ? scan(resource) : '/' + Path.relative(dirPublic, resource));
|
|
173
|
+
}, []);
|
|
174
|
+
let files = scan(dirPublic);
|
|
175
|
+
// Resolve patterns from files
|
|
176
|
+
workerConfig.cache_only_urls = patterns.reduce((all, pattern) => {
|
|
177
|
+
let matchedFiles = files.filter(file => pattern.test(file, 'http://localhost'));
|
|
178
|
+
if (matchedFiles.length) return all.concat(matchedFiles);
|
|
179
|
+
throw new Error(`The pattern "${pattern.pattern.pattern.pathname}" didn't match any files.`);
|
|
180
|
+
}, urls);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
declareStart.call(cx, gen, dirWorker, dirPublic, workerConfig, workerRouting);
|
|
184
|
+
await bundle.call(cx, gen, Path.join(dirPublic, workerroot, clientConfig.worker_filename));
|
|
185
|
+
// ------------------
|
|
186
|
+
// Recurse
|
|
187
|
+
workerGraphCallbak && workerGraphCallbak(workerroot, subworkerroots);
|
|
188
|
+
if (cx.flags.recursive) {
|
|
189
|
+
while (subworkerroots.length) {
|
|
190
|
+
await generateWorker(subworkerroots.shift());
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
// -----------
|
|
195
|
+
// Generate now...
|
|
196
|
+
let sparootsFile = Path.join(dirPublic, 'sparoots.json');
|
|
197
|
+
if (clientConfig.spa_routing !== false) {
|
|
198
|
+
const sparoots = [];
|
|
199
|
+
await generateClient('/', root => sparoots.push(root));
|
|
200
|
+
Fs.writeFileSync(sparootsFile, JSON.stringify(sparoots, null, 4));
|
|
201
|
+
} else {
|
|
202
|
+
await generateClient();
|
|
203
|
+
Fs.existsSync(sparootsFile) && Fs.unlinkSync(sparootsFile);
|
|
204
|
+
}
|
|
205
|
+
if (clientConfig.service_worker_support) {
|
|
206
|
+
await generateWorker('/');
|
|
49
207
|
}
|
|
50
208
|
}
|
|
51
209
|
|
|
52
210
|
/**
|
|
53
211
|
* Compile routes.
|
|
54
212
|
*
|
|
55
|
-
* @param
|
|
56
|
-
* @param string
|
|
57
|
-
* @param
|
|
58
|
-
* @param
|
|
213
|
+
* @param object gen
|
|
214
|
+
* @param string routesDir
|
|
215
|
+
* @param string targetPublic
|
|
216
|
+
* @param object paramsObj
|
|
217
|
+
* @param object routing
|
|
59
218
|
*
|
|
60
219
|
* @return Object
|
|
61
220
|
*/
|
|
62
|
-
function
|
|
221
|
+
function declareStart(gen, routesDir, targetDir, paramsObj, routing) {
|
|
63
222
|
const cx = this || {};
|
|
64
|
-
if (cx.logger) {
|
|
65
|
-
cx.logger.log(cx.logger.style.comment(`-----------------`));
|
|
66
|
-
cx.logger.log(desc);
|
|
67
|
-
cx.logger.log(cx.logger.style.comment(`-----------------`));
|
|
68
|
-
cx.logger.log('');
|
|
69
|
-
}
|
|
70
|
-
// ------------------
|
|
71
|
-
const gen = { imports: {}, code: [], };
|
|
72
|
-
// ------------------
|
|
73
|
-
// >> Modules import
|
|
74
|
-
gen.imports[`${modulesDir}/index.js`] = `{ start }`;
|
|
75
|
-
gen.code.push(``);
|
|
76
223
|
// ------------------
|
|
77
224
|
// >> Routes mapping
|
|
78
225
|
gen.code.push(`// >> Routes`);
|
|
79
|
-
declareRoutesObj.call(cx, gen, routesDir, 'layout',
|
|
226
|
+
declareRoutesObj.call(cx, gen, routesDir, targetDir, 'layout', routing);
|
|
80
227
|
gen.code.push(``);
|
|
81
228
|
// ------------------
|
|
82
229
|
// >> Params
|
|
83
230
|
gen.code.push(`// >> Params`);
|
|
84
|
-
declareParamsObj.call(cx, gen, paramsObj, 'params');
|
|
231
|
+
declareParamsObj.call(cx, gen, { ...paramsObj, routing }, 'params');
|
|
85
232
|
gen.code.push(``);
|
|
86
233
|
// ------------------
|
|
87
234
|
// >> Startup
|
|
88
235
|
gen.code.push(`// >> Startup`);
|
|
89
236
|
gen.code.push(`start.call({ layout, params })`);
|
|
90
|
-
return gen;
|
|
91
237
|
}
|
|
92
238
|
|
|
93
239
|
/**
|
|
94
240
|
* Compile routes.
|
|
95
241
|
*
|
|
96
|
-
* @param object
|
|
97
|
-
* @param string
|
|
98
|
-
* @param string
|
|
242
|
+
* @param object gen
|
|
243
|
+
* @param string routesDir
|
|
244
|
+
* @param string targetDir
|
|
245
|
+
* @param string varName
|
|
246
|
+
* @param object routing
|
|
99
247
|
*
|
|
100
248
|
* @return void
|
|
101
249
|
*/
|
|
102
|
-
function declareRoutesObj(gen, routesDir, varName) {
|
|
250
|
+
function declareRoutesObj(gen, routesDir, targetDir, varName, routing) {
|
|
103
251
|
const cx = this || {};
|
|
104
|
-
|
|
252
|
+
let _routesDir = Path.join(routesDir, routing.root),
|
|
253
|
+
_targetDir = Path.join(targetDir, routing.root);
|
|
254
|
+
cx.logger && cx.logger.log(cx.logger.style.keyword(`> `) + `Declaring routes...`);
|
|
105
255
|
// ----------------
|
|
106
256
|
// Directory walker
|
|
107
257
|
const walk = (dir, callback) => {
|
|
108
258
|
Fs.readdirSync(dir).forEach(f => {
|
|
109
259
|
let resource = Path.join(dir, f);
|
|
260
|
+
let namespace = _beforeLast('/' + Path.relative(routesDir, resource), '/index.js') || '/';
|
|
110
261
|
if (Fs.statSync(resource).isDirectory()) {
|
|
262
|
+
if (routing.subroots.includes(namespace)) return;
|
|
111
263
|
walk(resource, callback);
|
|
112
264
|
} else {
|
|
113
|
-
let
|
|
114
|
-
callback(resource,
|
|
265
|
+
let relativePath = Path.relative(_targetDir, resource);
|
|
266
|
+
callback(resource, namespace, relativePath);
|
|
115
267
|
}
|
|
116
268
|
});
|
|
117
269
|
};
|
|
@@ -119,29 +271,25 @@ function declareRoutesObj(gen, routesDir, varName) {
|
|
|
119
271
|
// >> Routes mapping
|
|
120
272
|
gen.code.push(`const ${varName} = {};`);
|
|
121
273
|
let indexCount = 0;
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
walk(routesDir, (file, ext) => {
|
|
274
|
+
if (Fs.existsSync(_routesDir)) {
|
|
275
|
+
walk(_routesDir, (file, namespace, relativePath) => {
|
|
125
276
|
//relativePath = relativePath.replace(/\\/g, '/');
|
|
126
277
|
if (file.replace(/\\/g, '/').endsWith('/index.js')) {
|
|
127
|
-
let relativePath = Path.relative(routesDir, file).replace(/\\/g, '/');
|
|
128
278
|
// Import code
|
|
129
279
|
let routeName = 'index' + (++ indexCount);
|
|
130
280
|
// IMPORTANT: we;re taking a step back here so that the parent-child relationship for
|
|
131
281
|
// the directories be involved
|
|
132
|
-
gen.imports[
|
|
282
|
+
gen.imports[relativePath] = '* as ' + routeName;
|
|
133
283
|
// Definition code
|
|
134
|
-
|
|
135
|
-
gen.code.push(`${varName}['${routePath || '/'}'] = ${routeName};`);
|
|
284
|
+
gen.code.push(`${varName}['${namespace}'] = ${routeName};`);
|
|
136
285
|
// Show
|
|
137
|
-
cx.logger && cx.logger.log(
|
|
286
|
+
cx.logger && cx.logger.log(cx.logger.style.comment(` [${namespace}]: `) + cx.logger.style.url(relativePath) + cx.logger.style.comment(` (${Fs.statSync(file).size / 1024} KB)`));
|
|
138
287
|
}
|
|
139
288
|
});
|
|
140
289
|
}
|
|
141
290
|
if (!indexCount) {
|
|
142
|
-
cx.logger && cx.logger.log(
|
|
291
|
+
cx.logger && cx.logger.log(cx.logger.style.comment(` (none)`));
|
|
143
292
|
}
|
|
144
|
-
cx.logger && cx.logger.log(``);
|
|
145
293
|
}
|
|
146
294
|
|
|
147
295
|
/**
|
|
@@ -149,7 +297,7 @@ function declareRoutesObj(gen, routesDir, varName) {
|
|
|
149
297
|
*
|
|
150
298
|
* @param object gen
|
|
151
299
|
* @param object paramsObj
|
|
152
|
-
* @param string
|
|
300
|
+
* @param string varName
|
|
153
301
|
*
|
|
154
302
|
* @return void
|
|
155
303
|
*/
|
|
@@ -189,7 +337,9 @@ function declareParamsObj(gen, paramsObj, varName = null, indentation = 0) {
|
|
|
189
337
|
*/
|
|
190
338
|
async function bundle(gen, outfile, asModule = false) {
|
|
191
339
|
const cx = this || {};
|
|
192
|
-
const compression = cx.flags.
|
|
340
|
+
const compression = !cx.flags.compression ? false : (
|
|
341
|
+
cx.flags.compression === true ? ['gz'] : cx.flags.compression.split(',').map(s => s.trim())
|
|
342
|
+
);
|
|
193
343
|
const moduleFile = `${_beforeLast(outfile, '.')}.esm.js`;
|
|
194
344
|
|
|
195
345
|
// ------------------
|
|
@@ -197,11 +347,10 @@ async function bundle(gen, outfile, asModule = false) {
|
|
|
197
347
|
if (cx.logger) {
|
|
198
348
|
let waiting = cx.logger.waiting(cx.logger.f`Writing the ES module file: ${moduleFile}`);
|
|
199
349
|
waiting.start();
|
|
200
|
-
|
|
350
|
+
jsFile.write(gen, moduleFile, 'ES Module file');
|
|
201
351
|
waiting.stop();
|
|
202
|
-
cx.logger.info(cx.logger.f`The module file: ${moduleFile}`);
|
|
203
352
|
} else {
|
|
204
|
-
|
|
353
|
+
jsFile.write(gen, moduleFile, 'ES Module file');
|
|
205
354
|
}
|
|
206
355
|
|
|
207
356
|
// ----------------
|
|
@@ -227,34 +376,93 @@ async function bundle(gen, outfile, asModule = false) {
|
|
|
227
376
|
let waiting;
|
|
228
377
|
if (cx.logger) {
|
|
229
378
|
waiting = cx.logger.waiting(`Bundling...`);
|
|
230
|
-
cx.logger.log('');
|
|
231
|
-
cx.logger.log('> Bundling...');
|
|
232
|
-
cx.logger.info(cx.logger.f`FROM: ${bundlingConfig.entryPoints[0]}`);
|
|
233
|
-
cx.logger.info(cx.logger.f`TO: ${bundlingConfig.outfile}`);
|
|
379
|
+
cx.logger.log(cx.logger.style.keyword(`> `) + 'Bundling...');
|
|
234
380
|
waiting.start();
|
|
235
381
|
}
|
|
236
|
-
//
|
|
382
|
+
// Main
|
|
237
383
|
await EsBuild.build(bundlingConfig);
|
|
238
|
-
if (waiting) waiting.stop();
|
|
239
|
-
// Remove moduleFile build
|
|
240
|
-
Fs.unlinkSync(bundlingConfig.entryPoints[0]);
|
|
241
|
-
|
|
242
|
-
// ----------------
|
|
243
384
|
// Compress...
|
|
385
|
+
let compressedFiles = [], removals = [];
|
|
244
386
|
if (compression) {
|
|
245
|
-
if (cx.logger) {
|
|
246
|
-
waiting = cx.logger.waiting(`Compressing...`);
|
|
247
|
-
waiting.start();
|
|
248
|
-
}
|
|
249
387
|
const contents = Fs.readFileSync(bundlingConfig.outfile);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
388
|
+
if (compression.includes('gz')) {
|
|
389
|
+
const gzip = gzipSync(contents, {});
|
|
390
|
+
Fs.writeFileSync(bundlingConfig.outfile + '.gz', gzip);
|
|
391
|
+
compressedFiles.push(bundlingConfig.outfile + '.gz');
|
|
392
|
+
} else {
|
|
393
|
+
removals.push(bundlingConfig.outfile + '.gz');
|
|
394
|
+
}
|
|
395
|
+
if (compression.includes('br')) {
|
|
396
|
+
const brotli = brotliCompressSync(contents, {});
|
|
397
|
+
Fs.writeFileSync(bundlingConfig.outfile + '.br', brotli);
|
|
398
|
+
compressedFiles.push(bundlingConfig.outfile + '.br');
|
|
399
|
+
} else {
|
|
400
|
+
removals.push(bundlingConfig.outfile + '.br');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Remove moduleFile build
|
|
404
|
+
Fs.unlinkSync(bundlingConfig.entryPoints[0]);
|
|
405
|
+
removals.forEach(file => Fs.existsSync(file) && Fs.unlinkSync(file));
|
|
406
|
+
if (waiting) waiting.stop();
|
|
407
|
+
// ----------------
|
|
408
|
+
// Stats
|
|
409
|
+
if (cx.logger) {
|
|
410
|
+
[bundlingConfig.outfile].concat(compressedFiles).forEach(file => {
|
|
411
|
+
let ext = '.' + _afterLast(file, '.');
|
|
412
|
+
cx.logger.info(cx.logger.style.comment(` [${ext}]: `) + cx.logger.style.url(file) + cx.logger.style.comment(` (${Fs.statSync(file).size / 1024} KB)`));
|
|
413
|
+
});
|
|
414
|
+
cx.logger.log('');
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Handles auto-embeds
|
|
420
|
+
*
|
|
421
|
+
* @param String targetDocumentFile
|
|
422
|
+
* @param Array embedList
|
|
423
|
+
* @param Array unembedList
|
|
424
|
+
*
|
|
425
|
+
* @return Void
|
|
426
|
+
*/
|
|
427
|
+
function handleEmbeds(targetDocumentFile, embedList, unembedList) {
|
|
428
|
+
let targetDocument, successLevel = 0;
|
|
429
|
+
if (Fs.existsSync(targetDocumentFile) && (targetDocument = Fs.readFileSync(targetDocumentFile).toString()) && targetDocument.trim().startsWith('<!DOCTYPE html')) {
|
|
430
|
+
successLevel = 1;
|
|
431
|
+
let dom = new Jsdom.JSDOM(targetDocument), by = 'webflo', touched;
|
|
432
|
+
let embed = (src, before) => {
|
|
433
|
+
let embedded = dom.window.document.querySelector(`script[src="${src}"]`);
|
|
434
|
+
if (!embedded) {
|
|
435
|
+
embedded = dom.window.document.createElement('script');
|
|
436
|
+
embedded.setAttribute('type', 'module');
|
|
437
|
+
embedded.setAttribute('src', src);
|
|
438
|
+
embedded.setAttribute('by', by);
|
|
439
|
+
if (before) {
|
|
440
|
+
before.before(embedded, `\n\t\t`);
|
|
441
|
+
} else {
|
|
442
|
+
dom.window.document.head.appendChild(embedded);
|
|
443
|
+
}
|
|
444
|
+
touched = true;
|
|
445
|
+
}
|
|
446
|
+
return embedded;
|
|
447
|
+
};
|
|
448
|
+
let unembed = src => {
|
|
449
|
+
src = Path.join('/', src);
|
|
450
|
+
let embedded = dom.window.document.querySelector(`script[src="${src}"][by="${by}"]`);
|
|
451
|
+
if (embedded) {
|
|
452
|
+
embedded.remove();
|
|
453
|
+
touched = true;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
embedList.reverse().reduce((prev, src) => {
|
|
457
|
+
return embed(src, prev);
|
|
458
|
+
}, dom.window.document.querySelector(`script[src]`) || dom.window.document.querySelector(`script`));
|
|
459
|
+
unembedList.forEach(src => {
|
|
460
|
+
unembed(src);
|
|
461
|
+
});
|
|
462
|
+
if (touched) {
|
|
463
|
+
Fs.writeFileSync(targetDocumentFile, dom.serialize());
|
|
464
|
+
successLevel = 2;
|
|
258
465
|
}
|
|
259
466
|
}
|
|
467
|
+
return successLevel;
|
|
260
468
|
}
|
|
File without changes
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { _any } from '@webqit/util/arr/index.js';
|
|
6
6
|
import { HttpEvent, Request, Response, Observer } from '../Runtime.js';
|
|
7
7
|
import { urlPattern } from '../../util.js';
|
|
8
|
+
import Workport from './Workport.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* ---------------------------
|
|
@@ -43,7 +44,7 @@ export default class Worker {
|
|
|
43
44
|
// Add files to cache
|
|
44
45
|
evt.waitUntil( self.caches.open(this.cx.params.cache_name).then(cache => {
|
|
45
46
|
if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Pre-caching resources.'); }
|
|
46
|
-
const cache_only_urls = (this.cx.params.cache_only_urls || []).map(c => c.trim()).filter(c => c && !
|
|
47
|
+
const cache_only_urls = (this.cx.params.cache_only_urls || []).map(c => c.trim()).filter(c => c && !urlPattern(c, self.origin).isPattern());
|
|
47
48
|
return cache.addAll(cache_only_urls);
|
|
48
49
|
}) );
|
|
49
50
|
}
|
|
@@ -71,11 +72,13 @@ export default class Worker {
|
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
// -------------
|
|
74
|
-
// ONFETCH
|
|
75
|
+
// ONFETCH
|
|
75
76
|
self.addEventListener('fetch', event => {
|
|
76
77
|
// URL schemes that might arrive here but not supported; e.g.: chrome-extension://
|
|
77
78
|
if (!event.request.url.startsWith('http')) return;
|
|
78
79
|
event.respondWith((async (req, evt) => {
|
|
80
|
+
let requestingClient = await self.clients.get(event.clientId);
|
|
81
|
+
this.workport.setCurrentClient(requestingClient);
|
|
79
82
|
const requestInit = [
|
|
80
83
|
'method', 'headers', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity',
|
|
81
84
|
].reduce((init, prop) => ({ [prop]: req[prop], ...init }), {});
|
|
@@ -93,6 +96,33 @@ export default class Worker {
|
|
|
93
96
|
})(event.request, event));
|
|
94
97
|
});
|
|
95
98
|
|
|
99
|
+
// -------------
|
|
100
|
+
// Workport
|
|
101
|
+
let workport = new Workport();
|
|
102
|
+
Observer.set(this, 'workport', workport);
|
|
103
|
+
workport.messaging.listen(async evt => {
|
|
104
|
+
let responsePort = evt.ports[0];
|
|
105
|
+
let client = this.clients.get('*');
|
|
106
|
+
let response = client.alert && await client.alert(evt);
|
|
107
|
+
if (responsePort) {
|
|
108
|
+
if (response instanceof Promise) {
|
|
109
|
+
response.then(data => {
|
|
110
|
+
responsePort.postMessage(data);
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
responsePort.postMessage(response);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
workport.notifications.listen(async evt => {
|
|
118
|
+
let client = this.clients.get('*');
|
|
119
|
+
client.alert && await client.alert(evt);
|
|
120
|
+
});
|
|
121
|
+
workport.push.listen(async evt => {
|
|
122
|
+
let client = this.clients.get('*');
|
|
123
|
+
client.alert && await client.alert(evt);
|
|
124
|
+
});
|
|
125
|
+
|
|
96
126
|
// ---------------
|
|
97
127
|
Observer.set(this, 'location', {});
|
|
98
128
|
Observer.set(this, 'network', {});
|
|
@@ -163,22 +193,26 @@ export default class Worker {
|
|
|
163
193
|
}
|
|
164
194
|
const matchUrl = (patterns, url) => _any((patterns || []).map(p => p.trim()).filter(p => p), p => urlPattern(p, self.origin).test(url));
|
|
165
195
|
const execFetch = () => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
196
|
+
// network_first_urls
|
|
197
|
+
if (!this.cx.params.default_fetching_strategy || this.cx.params.default_fetching_strategy === 'network-first' || matchUrl(this.cx.params.network_first_urls, request.url)) {
|
|
198
|
+
Observer.set(this.network, 'strategy', 'network-first');
|
|
199
|
+
return this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
|
|
200
|
+
}
|
|
201
|
+
// cache_first_urls
|
|
202
|
+
if (this.cx.params.default_fetching_strategy === 'cache-first' || matchUrl(this.cx.params.cache_first_urls, request.url)) {
|
|
203
|
+
Observer.set(this.network, 'strategy', 'cache-first');
|
|
204
|
+
return this.cacheFetch(request, { networkFallback: true, cacheRefresh: true });
|
|
169
205
|
}
|
|
170
206
|
// network_only_urls
|
|
171
|
-
if (matchUrl(this.cx.params.network_only_urls, request.url)) {
|
|
207
|
+
if (this.cx.params.default_fetching_strategy === 'network-only' || matchUrl(this.cx.params.network_only_urls, request.url)) {
|
|
172
208
|
Observer.set(this.network, 'strategy', 'network-only');
|
|
173
209
|
return this.networkFetch(request, { cacheFallback: false, cacheRefresh: false });
|
|
174
210
|
}
|
|
175
|
-
//
|
|
176
|
-
if (matchUrl(this.cx.params.
|
|
177
|
-
Observer.set(this.network, 'strategy', 'cache-
|
|
178
|
-
return this.cacheFetch(request, { networkFallback:
|
|
211
|
+
// cache_only_urls
|
|
212
|
+
if (this.cx.params.default_fetching_strategy === 'cache-only' || matchUrl(this.cx.params.cache_only_urls, request.url)) {
|
|
213
|
+
Observer.set(this.network, 'strategy', 'cache-only');
|
|
214
|
+
return this.cacheFetch(request, { networkFallback: false, cacheRefresh: false });
|
|
179
215
|
}
|
|
180
|
-
Observer.set(this.network, 'strategy', 'network-first');
|
|
181
|
-
return this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
|
|
182
216
|
};
|
|
183
217
|
let response = execFetch(request);
|
|
184
218
|
// This catch() is NOT intended to handle failure of the fetch
|
|
@@ -187,18 +221,6 @@ export default class Worker {
|
|
|
187
221
|
return response.then(_response => Response.compat(_response));
|
|
188
222
|
}
|
|
189
223
|
|
|
190
|
-
// Caching strategy: cache_first
|
|
191
|
-
cacheFetch(request, params = {}) {
|
|
192
|
-
return this.getRequestCache(request).then(cache => cache.match(request).then(response => {
|
|
193
|
-
// Nothing cache, use network
|
|
194
|
-
if (!response && params.networkFallback) return this.networkFetch(request, { ...params, cacheFallback: false });
|
|
195
|
-
// Note: fetch, but for refreshing purposes only... not the returned response
|
|
196
|
-
if (response && params.cacheRefresh) this.networkFetch(request, { ...params, justRefreshing: true });
|
|
197
|
-
Observer.set(this.network, 'cache', true);
|
|
198
|
-
return response;
|
|
199
|
-
}));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
224
|
// Caching strategy: network_first
|
|
203
225
|
networkFetch(request, params = {}) {
|
|
204
226
|
if (!params.cacheFallback) {
|
|
@@ -215,6 +237,18 @@ export default class Worker {
|
|
|
215
237
|
}));
|
|
216
238
|
}
|
|
217
239
|
|
|
240
|
+
// Caching strategy: cache_first
|
|
241
|
+
cacheFetch(request, params = {}) {
|
|
242
|
+
return this.getRequestCache(request).then(cache => cache.match(request).then(response => {
|
|
243
|
+
// Nothing cache, use network
|
|
244
|
+
if (!response && params.networkFallback) return this.networkFetch(request, { ...params, cacheFallback: false });
|
|
245
|
+
// Note: fetch, but for refreshing purposes only... not the returned response
|
|
246
|
+
if (response && params.cacheRefresh) this.networkFetch(request, { ...params, justRefreshing: true });
|
|
247
|
+
Observer.set(this.network, 'cache', true);
|
|
248
|
+
return response;
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
|
|
218
252
|
// Caches response
|
|
219
253
|
refreshCache(request, response) {
|
|
220
254
|
// Check if we received a valid response
|