@webqit/webflo 0.10.4 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1490 -52
  2. package/bundle.html.json +1665 -0
  3. package/package.json +2 -2
  4. package/src/Context.js +1 -1
  5. package/src/config-pi/runtime/Client.js +37 -9
  6. package/src/config-pi/runtime/Server.js +24 -8
  7. package/src/config-pi/runtime/client/Worker.js +30 -12
  8. package/src/runtime-pi/Router.js +1 -1
  9. package/src/runtime-pi/client/Runtime.js +116 -62
  10. package/src/runtime-pi/client/RuntimeClient.js +28 -43
  11. package/src/runtime-pi/client/Workport.js +163 -0
  12. package/src/runtime-pi/client/generate.js +282 -74
  13. package/src/runtime-pi/client/{generate.oohtml.js → oohtml/full.js} +0 -0
  14. package/src/runtime-pi/client/oohtml/namespacing.js +7 -0
  15. package/src/runtime-pi/client/oohtml/scripting.js +8 -0
  16. package/src/runtime-pi/client/oohtml/templating.js +8 -0
  17. package/src/runtime-pi/client/worker/Worker.js +58 -24
  18. package/src/runtime-pi/client/worker/Workport.js +80 -0
  19. package/src/runtime-pi/server/Router.js +2 -2
  20. package/src/runtime-pi/server/Runtime.js +30 -11
  21. package/src/runtime-pi/server/RuntimeClient.js +24 -14
  22. package/src/runtime-pi/util.js +2 -2
  23. package/test/site/package.json +9 -0
  24. package/test/site/public/bundle.html +6 -0
  25. package/test/site/public/bundle.html.json +4 -0
  26. package/test/site/public/bundle.js +2 -0
  27. package/test/site/public/bundle.js.gz +0 -0
  28. package/test/site/public/bundle.webflo.js +15 -0
  29. package/test/site/public/bundle.webflo.js.gz +0 -0
  30. package/test/site/public/index.html +30 -0
  31. package/test/site/public/index1.html +35 -0
  32. package/test/site/public/page-2/bundle.html +5 -0
  33. package/test/site/public/page-2/bundle.html.json +1 -0
  34. package/test/site/public/page-2/bundle.js +2 -0
  35. package/test/site/public/page-2/bundle.js.gz +0 -0
  36. package/test/site/public/page-2/index.html +46 -0
  37. package/test/site/public/page-2/logo-130x130.png +0 -0
  38. package/test/site/public/page-2/main.html +3 -0
  39. package/test/site/public/page-3/logo-130x130.png +0 -0
  40. package/test/site/public/page-4/subpage/bundle.html +0 -0
  41. package/test/site/public/page-4/subpage/bundle.html.json +1 -0
  42. package/test/site/public/page-4/subpage/bundle.js +2 -0
  43. package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
  44. package/test/site/public/page-4/subpage/index.html +31 -0
  45. package/test/site/public/sparoots.json +5 -0
  46. package/test/site/public/worker.js +3 -0
  47. package/test/site/public/worker.js.gz +0 -0
  48. package/test/site/server/index.js +16 -0
  49. package/docker/Dockerfile +0 -26
  50. package/docker/README.md +0 -77
  51. package/src/runtime-pi/client/WorkerComm.js +0 -102
@@ -0,0 +1,163 @@
1
+
2
+
3
+ /**
4
+ * @imports
5
+ */
6
+ import { _isFunction, _isObject } from '@webqit/util/js/index.js';
7
+ import { Observer } from './Runtime.js';
8
+
9
+ export default class Workport {
10
+
11
+ constructor(file, params = {}) {
12
+ this.ready = navigator.serviceWorker.ready;
13
+
14
+ // --------
15
+ // Registration and lifecycle
16
+ // --------
17
+ this.registration = new Promise((resolve, reject) => {
18
+ const register = () => {
19
+ navigator.serviceWorker.register(file, { scope: params.scope || '/' }).then(async registration => {
20
+
21
+ // Helper that updates instance's state
22
+ const state = target => {
23
+ // instance2.state can be any of: "installing", "installed", "activating", "activated", "redundant"
24
+ const equivState = target.state === 'installed' ? 'waiting' :
25
+ (target.state === 'activating' || target.state === 'activated' ? 'active' : target.state)
26
+ Observer.set(this, equivState, target);
27
+ }
28
+
29
+ // We're always installing at first for a new service worker.
30
+ // An existing service would immediately be active
31
+ const worker = registration.active || registration.waiting || registration.installing;
32
+ state(worker);
33
+ worker.addEventListener('statechange', e => state(e.target));
34
+
35
+ // "updatefound" event - a new worker that will control
36
+ // this page is installing somewhere
37
+ registration.addEventListener('updatefound', () => {
38
+ // If updatefound is fired, it means that there's
39
+ // a new service worker being installed.
40
+ state(registration.installing);
41
+ registration.installing.addEventListener('statechange', e => state(e.target));
42
+ });
43
+
44
+ resolve(registration);
45
+ }).catch(e => reject(e));
46
+ };
47
+ if (params.onWondowLoad) {
48
+ window.addEventListener('load', register);
49
+ } else {
50
+ register();
51
+ }
52
+ if (params.startMessages) {
53
+ navigator.serviceWorker.startMessages();
54
+ }
55
+ });
56
+
57
+ // --------
58
+ // Post messaging
59
+ // --------
60
+ const postSendCallback = (message, callback, onAvailability = 1) => {
61
+ if (this.active) {
62
+ if (_isFunction(message)) message = message();
63
+ callback(this.active, message);
64
+ } else if (onAvailability) {
65
+ // Availability Handling
66
+ const availabilityHandler = entry => {
67
+ if (_isFunction(message)) message = message();
68
+ callback(entry.value, message);
69
+ if (onAvailability !== 2) {
70
+ Observer.unobserve(this, 'active', availabilityHandler);
71
+ }
72
+ };
73
+ Observer.observe(this, 'active', availabilityHandler);
74
+ }
75
+ };
76
+ this.messaging = {
77
+ post: (message, onAvailability = 1) => {
78
+ postSendCallback(message, (active, message) => {
79
+ active.postMessage(message);
80
+ }, onAvailability);
81
+ return this.post;
82
+ },
83
+ listen: callback => {
84
+ navigator.serviceWorker.addEventListener('message', callback);
85
+ return this.post;
86
+ },
87
+ request: (message, onAvailability = 1) => {
88
+ return new Promise(res => {
89
+ postSendCallback(message, (active, message) => {
90
+ let messageChannel = new MessageChannel();
91
+ active.postMessage(message, [ messageChannel.port2 ]);
92
+ messageChannel.port1.onmessage = e => res(e.data);
93
+ }, onAvailability);
94
+ });
95
+ },
96
+ channel(channelId) {
97
+ if (!this.channels.has(channelId)) { this.channels.set(channelId, new BroadcastChannel(channel)); }
98
+ let channel = this.channels.get(channelId);
99
+ return {
100
+ broadcast: message => channel.postMessage(message),
101
+ listen: callback => channel.addEventListener('message', callback),
102
+ };
103
+ },
104
+ channels: new Map,
105
+ };
106
+
107
+ // --------
108
+ // Notifications
109
+ // --------
110
+ this.notifications = {
111
+ fire: (title, params = {}) => {
112
+ return new Promise((res, rej) => {
113
+ if (typeof Notification === 'undefined' || Notification.permission !== 'granted') {
114
+ return rej(typeof Notification !== 'undefined' && Notification && Notification.permission);
115
+ }
116
+ notification.addEventListener('error', rej);
117
+ let notification = new Notification(title, params);
118
+ notification.addEventListener('click', res);
119
+ notification.addEventListener('close', res);
120
+ });
121
+ },
122
+ };
123
+
124
+ // --------
125
+ // Push notifications
126
+ // --------
127
+ this.push = {
128
+ getSubscription: async () => {
129
+ return (await this.registration).pushManager.getSubscription();
130
+ },
131
+ subscribe: async (publicKey, params = {}) => {
132
+ var subscription = await this.push.getSubscription();
133
+ return subscription ? subscription : (await this.registration).pushManager.subscribe(
134
+ _isObject(publicKey) ? publicKey : {
135
+ applicationServerKey: urlBase64ToUint8Array(publicKey),
136
+ ...params,
137
+ }
138
+ );
139
+ },
140
+ unsubscribe: async () => {
141
+ var subscription = await this.push.getSubscription();
142
+ return !subscription ? null : subscription.unsubscribe();
143
+ },
144
+ };
145
+ }
146
+
147
+ }
148
+
149
+ // Public base64 to Uint
150
+ function urlBase64ToUint8Array(base64String) {
151
+ var padding = '='.repeat((4 - base64String.length % 4) % 4);
152
+ var base64 = (base64String + padding)
153
+ .replace(/\-/g, '+')
154
+ .replace(/_/g, '/');
155
+
156
+ var rawData = window.atob(base64);
157
+ var outputArray = new Uint8Array(rawData.length);
158
+
159
+ for (var i = 0; i < rawData.length; ++i) {
160
+ outputArray[i] = rawData.charCodeAt(i);
161
+ }
162
+ return outputArray;
163
+ }
@@ -5,11 +5,13 @@
5
5
  import Fs from 'fs';
6
6
  import Url from 'url';
7
7
  import Path from 'path';
8
- import { _beforeLast } from '@webqit/util/str/index.js';
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
12
  import { jsFile } from '@webqit/backpack/src/dotfile/index.js';
11
13
  import { gzipSync, brotliCompressSync } from 'zlib';
12
- import EsBuild from 'esbuild';
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
- let genClient = getGen.call(cx, dirSelf, layoutConfig.CLIENT_DIR, clientConfig, `The Client Build.`);
38
- if (clientConfig.support_oohtml) {
39
- genClient.imports = { [`${dirSelf}/generate.oohtml.js`]: null, ...genClient.imports };
40
- }
41
- await bundle.call(cx, genClient, `${dirPublic}/${clientConfig.bundle_filename}`, true/* asModule */);
42
- cx.logger && cx.logger.log('');
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
- if (clientConfig.support_service_worker) {
46
- let genWorker = getGen.call(cx, `${dirSelf}/worker`, layoutConfig.WORKER_DIR, workerConfig, `The Worker Build.`);
47
- await bundle.call(cx, genWorker, `${dirPublic}/${clientConfig.worker_filename}`);
48
- cx.logger && cx.logger.log('');
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 string modulesDir
56
- * @param string routesDir
57
- * @param object paramsObj
58
- * @param string desc
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 getGen(modulesDir, routesDir, paramsObj, desc) {
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 gen
97
- * @param string routesDir
98
- * @param string varName
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
- cx.logger && cx.logger.log(`> Declaring routes...`);
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 ext = Path.extname(resource) || '';
114
- callback(resource, ext);
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 (routesDir && Fs.existsSync(routesDir)) {
123
- let clientDirname = routesDir.replace(/\\/g, '/').split('/').pop();
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[`../${clientDirname}/${relativePath}`] = '* as ' + routeName;
282
+ gen.imports[relativePath] = '* as ' + routeName;
133
283
  // Definition code
134
- let routePath = _beforeLast('/' + relativePath, '/index.js');
135
- gen.code.push(`${varName}['${routePath || '/'}'] = ${routeName};`);
284
+ gen.code.push(`${varName}['${namespace}'] = ${routeName};`);
136
285
  // Show
137
- cx.logger && cx.logger.log(`> ./${relativePath}`);
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(`> (none)`);
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 varName
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.compress;
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
  // ------------------
@@ -199,7 +349,6 @@ async function bundle(gen, outfile, asModule = false) {
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
  }
@@ -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
- // Run
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
- const gzip = gzipSync(contents, {});
251
- const brotli = brotliCompressSync(contents, {});
252
- Fs.writeFileSync(`${bundlingConfig.outfile}.gz`, gzip);
253
- Fs.writeFileSync(`${bundlingConfig.outfile}.br`, brotli);
254
- if (waiting) {
255
- waiting.stop();
256
- cx.logger.log('');
257
- cx.logger.log('> Compression: .gz, .br');
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
  }
@@ -0,0 +1,7 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { NamespacedHTML } from '@webqit/oohtml-ssr/apis.js';
6
+
7
+ NamespacedHTML.call( window );
@@ -0,0 +1,8 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { StateAPI, Subscript } from '@webqit/oohtml-ssr/apis.js';
6
+
7
+ StateAPI.call( window );
8
+ Subscript.call( window );
@@ -0,0 +1,8 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { HTMLModules, HTMLImports } from '@webqit/oohtml-ssr/apis.js';
6
+
7
+ HTMLModules.call( window );
8
+ HTMLImports.call( window );