@webqit/oohtml 2.1.6 → 2.1.8

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/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "wicg-proposal"
15
15
  ],
16
16
  "homepage": "https://webqit.io/tooling/oohtml",
17
- "version": "2.1.6",
17
+ "version": "2.1.8",
18
18
  "license": "MIT",
19
19
  "repository": {
20
20
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  "postpublish": "git push && git push --tags"
39
39
  },
40
40
  "dependencies": {
41
- "@webqit/dom": "^2.0.0",
41
+ "@webqit/dom": "^2.0.2",
42
42
  "@webqit/observer": "^2.0.1",
43
43
  "@webqit/subscript": "^2.1.36",
44
44
  "@webqit/util": "^0.8.9"
@@ -0,0 +1,294 @@
1
+
2
+ export default class Compiler {
3
+
4
+ // Unique ID generator
5
+ static hashTable = new Map;
6
+ static uniqId = () => (0|Math.random()*9e6).toString(36);
7
+
8
+ // Hash of anything generator
9
+ static toHash( val ) {
10
+ let hash;
11
+ if ( !( hash = this.hashTable.get( val ) ) ) {
12
+ hash = this.uniqId();
13
+ this.hashTable.set( val, hash );
14
+ }
15
+ return hash;
16
+ }
17
+
18
+ // Value of any hash
19
+ static fromHash( hash ) {
20
+ let val;
21
+ this.hashTable.forEach( ( _hash, _val ) => {
22
+ if ( _hash === hash ) val = _val;
23
+ } );
24
+ return val;
25
+ }
26
+
27
+ // Set window property
28
+ constructor( window, params, executeCallback ) {
29
+ this.window = window;
30
+ this.params = params;
31
+ // This is a global function
32
+ window.wq.oohtml.Script.run = ( execHash ) => {
33
+ const exec = this.constructor.fromHash( execHash );
34
+ if ( !exec ) throw new Error( `Argument must be a valid exec hash.` );
35
+ const { script, compiledScript, thisContext } = exec;
36
+ if ( thisContext instanceof window.Element && script.scoped ) {
37
+ if ( !thisContext.scripts ) { Object.defineProperty( thisContext, 'scripts', { value: new Set } ); }
38
+ thisContext.scripts.add( script );
39
+ }
40
+ switch ( params.script.retention ) {
41
+ case 'dispose':
42
+ script.remove();
43
+ break;
44
+ case 'hidden':
45
+ script.textContent = `"source hidden"`;
46
+ break;
47
+ default:
48
+ script.textContent = compiledScript.function.originalSource;
49
+ }
50
+ return executeCallback.call( window, compiledScript, thisContext, script );
51
+ };
52
+ }
53
+
54
+ // Compile scipt
55
+ compile( script, thisContext ) {
56
+ const _static = this.constructor;
57
+ const { wq: { oohtml, SubscriptFunction } } = this.window;
58
+ const cache = oohtml.Script.compileCache[ script.contract ? 0 : 1 ];
59
+ const sourceHash = _static.toHash( script.textContent );
60
+ // Script instances are parsed only once
61
+ let source = script.textContent, compiledScript;
62
+ if ( !( compiledScript = cache.get( sourceHash ) ) ) {
63
+ // Are there "import" (and "await") statements? Then, we need to rewrite that
64
+ let imports = [], meta = {};
65
+ let targetKeywords = [];
66
+ if ( script.type === 'module' ) targetKeywords.push( 'import ' );
67
+ if ( script.type === 'module' && !script.contract ) targetKeywords.push( 'await ' );
68
+ if ( targetKeywords.length && ( new RegExp( targetKeywords.join( '|' ) ) ).test( source ) ) {
69
+ [ imports, source, meta ] = this.parse( source );
70
+ if ( imports.length ) {
71
+ source = `\n\t${ this.rewriteImportStmts( imports ).join( `\n\t` ) }\n\t${ source }\n`;
72
+ }
73
+ }
74
+ // Let's obtain a material functions
75
+ let _Function, { parserParams, compilerParams, runtimeParams } = this.params.config;
76
+ if ( script.contract ) {
77
+ parserParams = { ...parserParams, allowAwaitOutsideFunction: script.type === 'module' };
78
+ runtimeParams = { ...runtimeParams, async: script.type === 'module' };
79
+ _Function = SubscriptFunction( source, { compilerParams, parserParams, runtimeParams, } );
80
+ Object.defineProperty( script, 'properties', { configurable: true, value: SubscriptFunction.inspect( _Function, 'properties' ) } );
81
+ } else {
82
+ const isAsync = script.type === 'module'//meta.topLevelAwait || imports.length;
83
+ const _FunctionConstructor = isAsync ? Object.getPrototypeOf( async function() {} ).constructor : Function;
84
+ _Function = runtimeParams?.compileFunction
85
+ ? runtimeParams.compileFunction( source )
86
+ : new _FunctionConstructor( source );
87
+ Object.defineProperty( _Function, 'originalSource', { configurable: true, value: script.textContent } );
88
+ }
89
+ // Save material function to compile cache
90
+ compiledScript = Object.defineProperty( script.cloneNode(), 'function', { value: _Function } );
91
+ script.scoped && Object.defineProperty( compiledScript, 'scoped', Object.getOwnPropertyDescriptor( script, 'scoped') );
92
+ script.contract && Object.defineProperty( compiledScript, 'contract', Object.getOwnPropertyDescriptor( script, 'contract') );
93
+ cache.set( sourceHash, compiledScript );
94
+ }
95
+ const execHash = _static.toHash( { script, compiledScript, thisContext } );
96
+ script.textContent = `wq.oohtml.Script.run('${ execHash }');`;
97
+ }
98
+
99
+ // Match import statements
100
+ // and detect top-level await
101
+ parse( source ) {
102
+ const [ tokens, meta ] = this.tokenize( source, ( $tokens, event, char, meta, i, isLastChar ) => {
103
+
104
+ if ( event === 'start-enclosure' && char === '{' && !meta.openAsync?.type && meta.openEnclosures.length === meta.openAsync?.scopeId ) {
105
+ meta.openAsync.type = 'block';
106
+ } else if ( event === 'end-enclosure' && char === '}' && meta.openAsync?.type === 'block' && meta.openEnclosures.length === meta.openAsync.scopeId ) {
107
+ meta.openAsync = null;
108
+ } else if ( event === 'start-quote' && !meta.openEnclosures.length && [ 'starting', 'from' ].includes( meta.openImport ) ) {
109
+ meta.openImport = 'url';
110
+ } else if ( event === 'end-quote' && meta.openImport === 'url' ) {
111
+ meta.openImport = 'closing';
112
+ } else if ( event === 'char' ) {
113
+
114
+ if ( meta.openImport === 'closing' && (
115
+ char === ';'/* explicit */ || ![ ' ', `\n` ].includes( char )/* implicit */ || isLastChar
116
+ ) ) {
117
+ if ( char === ';' || isLastChar ) {
118
+ $tokens[ 0 ] += char;
119
+ $tokens.unshift( '' );
120
+ } else { $tokens.unshift( char ); }
121
+ meta.openImport = null;
122
+ return false;
123
+ }
124
+
125
+ let remainder = source.substring( i - 1 );
126
+
127
+ if ( !meta.openImport && /^[\W]?import[ ]*[^\(]/.test( remainder ) ) {
128
+ meta.openImport = 'starting';
129
+ $tokens.unshift( '' );
130
+ return 6;
131
+ }
132
+ if ( meta.openImport === 'starting' && /^[\W]?from /.test( remainder ) ) {
133
+ meta.openImport = 'from';
134
+ return 4;
135
+ }
136
+ if ( !meta.openAsync ) {
137
+ if ( /^[\W]?async /.test( remainder ) ) {
138
+ meta.openAsync = { scopeId: meta.openEnclosures.length };
139
+ return 5;
140
+ }
141
+ if ( /^[\W]?await /.test( remainder ) ) {
142
+ meta.topLevelAwait = true;
143
+ return 5;
144
+ }
145
+ }
146
+ if ( meta.openAsync ) {
147
+ if ( !meta.openAsync.type && /.?\=\>[ ]*?\{/.test( remainder ) ) {
148
+ meta.openAsync.type = 'inline-arrow';
149
+ } else if ( meta.openAsync.type === 'inline-arrow' && [ `\n`, ';' ].includes( char ) && meta.openEnclosures.length === meta.openAsync.scopeId ) {
150
+ meta.openAsync = null;
151
+ }
152
+ }
153
+
154
+ }
155
+
156
+ } );
157
+ // Hoist all import statements
158
+ let imports = [], body = '', _str;
159
+ for ( const str of tokens.reverse() ) {
160
+ if ( ( _str = str.trim() ).startsWith( 'import ' ) ) {
161
+ imports.push( str );
162
+ } else if ( _str ) { body += str; }
163
+ }
164
+
165
+ return [ imports, body, meta ];
166
+ }
167
+
168
+ // Rewrite import statements
169
+ rewriteImportStmts( imports ) {
170
+ const importSpecs = [], importPromises = [];
171
+ imports.forEach( ( $import, i ) => {
172
+ $import = parseImportStmt( $import );
173
+ // Identify whole imports and individual imports
174
+ const [ wholeImport, individualImports ] = $import.items.reduce( ( [ whole, parts ], item ) => {
175
+ return item.id === '*' ? [ item.alias, parts ] : [ whole, parts.concat( item ) ];
176
+ }, [ null, [] ] );
177
+ if ( wholeImport ) {
178
+ // const main = await import("url");
179
+ importSpecs.push( `const ${ wholeImport } = __$imports$__[${ i }];` );
180
+ }
181
+ if ( individualImports.length ) {
182
+ // const { aa: bb, cc } = await import("url");
183
+ const individualImportsSpec = individualImports.map( item => `${ item.id }${ item.id !== item.alias ? `: ${ item.alias }` : '' }` ).join( ', ' );
184
+ importSpecs.push( `const { ${ individualImportsSpec } } = __$imports$__[${ i }];` );
185
+ }
186
+ importPromises.push( `import("${ $import.url }")` );
187
+ } );
188
+ return [
189
+ `\n\tconst __$imports$__ = await Promise.all([\n\t\t${ importPromises.join( `,\n\t\t` ) }\n\t]);`,
190
+ importSpecs.join( `\n\t` ),
191
+ ];
192
+ }
193
+
194
+ // Parse import statements
195
+ parseImportStmt( str ) {
196
+ const getUrl = str => {
197
+ let quo = /^[`'"]/.exec( str );
198
+ return quo && str.substring( 1, str.lastIndexOf( quo[ 0 ] ) );
199
+ }
200
+ let $import = { items: [ { id: '' } ] }, _str = str.replace( 'import', '' ).trim();
201
+ if ( !( $import.url = getUrl( _str ) ) ) {
202
+ this.tokenize( _str, ( $tokens, event, char, meta, i, isLastChar ) => {
203
+ if ( [ 'start-quote', 'ongoing-quote', 'end-quote', 'char' ].includes( event ) ) {
204
+ if ( $import.url ) return;
205
+ if ( !meta.openQuote ) {
206
+ let remainder = _str.substring( i );
207
+ if ( char === ',' ) {
208
+ $import.items.unshift( { id: '' } );
209
+ return;
210
+ }
211
+ if ( remainder.startsWith( ' as ' ) ) {
212
+ $import.items[ 0 ].alias = '';
213
+ return 3;
214
+ }
215
+ if ( remainder.startsWith( ' from ' ) ) {
216
+ $import.url = getUrl( remainder.replace( 'from', '' ).trim() );
217
+ return remainder.length;
218
+ }
219
+ }
220
+ if ( 'alias' in $import.items[ 0 ] ) {
221
+ $import.items[ 0 ].alias += char;
222
+ } else {
223
+ $import.items[ 0 ].id += char;
224
+ if ( meta.openEnclosures.length ) {
225
+ $import.items[ 0 ].enclosed = true;
226
+ }
227
+ }
228
+ }
229
+ } );
230
+ }
231
+ $import.items = $import.items
232
+ .map( item => ( {
233
+ id: item.id && !item.alias && !item.enclosed ? 'default' : item.id.trim(),
234
+ alias: item.alias ? item.alias.trim() : item.id.trim(),
235
+ } ) )
236
+ .filter( item => item.id )
237
+ .reverse();
238
+ return $import;
239
+ }
240
+
241
+ // Token JavaScript source
242
+ tokenize( source, _callback ) {
243
+ const lastI = source.length - 1;
244
+ return [ ...source ].reduce( ( [ $tokens, meta, skip ], char, i ) => {
245
+
246
+ if ( skip ) {
247
+ $tokens[ 0 ] += char;
248
+ return [ $tokens, meta, --skip ];
249
+ }
250
+ let callbackReturn;
251
+
252
+ if ( meta.openQuote || meta.openComment ) {
253
+ if ( char === meta.openQuote ) {
254
+ meta.openQuote = null;
255
+ callbackReturn = _callback( $tokens, 'end-quote', char, meta, i, i === lastI );
256
+ } else if ( meta.openQuote ) {
257
+ callbackReturn = _callback( $tokens, 'ongoing-quote', char, meta, i, i === lastI );
258
+ } else if ( meta.openComment ) {
259
+ if ( ( meta.openComment === '//' && char === `\n` ) || ( meta.openComment === '/*' && $tokens[ 0 ].substr( -1 ) === '*' && char === '/' ) ) {
260
+ meta.openComment = null;
261
+ callbackReturn = _callback( $tokens, 'end-comment', char, meta, i, i === lastI );
262
+ }
263
+ }
264
+ if ( callbackReturn !== false ) {
265
+ $tokens[ 0 ] += char;
266
+ }
267
+ return [ $tokens, meta, typeof callbackReturn === 'number' ? callbackReturn : skip ];
268
+ }
269
+
270
+ let enclosure;
271
+ if ( enclosure = [ '()', '{}', '[]' ].filter( pair => char === pair[ 0 ] )[ 0 ] ) {
272
+ callbackReturn = _callback( $tokens, 'start-enclosure', char, meta, i, i === lastI );
273
+ meta.openEnclosures.unshift( enclosure );
274
+ } else if ( meta.openEnclosures.length && char === meta.openEnclosures[ 0 ][ 1 ] ) {
275
+ meta.openEnclosures.shift();
276
+ callbackReturn = _callback( $tokens, 'end-enclosure', char, meta, i, i === lastI );
277
+ } else if ( [ '"', "'", "`" ].includes( char ) ) {
278
+ callbackReturn = _callback( $tokens, 'start-quote', char, meta, i, i === lastI );
279
+ meta.openQuote = char;
280
+ } else if ( !meta.openComment && [ '/*', '//' ].includes( source.substr( i, 2 ) ) ) {
281
+ callbackReturn = _callback( $tokens, 'start-comment', char, meta, i, i === lastI );
282
+ meta.openComment = source.substr( i, 2 );
283
+ } else {
284
+ callbackReturn = _callback( $tokens, 'char', char, meta, i, i === lastI );
285
+ }
286
+
287
+ if ( callbackReturn !== false ) {
288
+ $tokens[ 0 ] += char;
289
+ }
290
+ return [ $tokens, meta, typeof callbackReturn === 'number' ? callbackReturn : skip ];
291
+
292
+ }, [ [ '' ], { openEnclosures: [], }, 0 ] );
293
+ }
294
+ }
@@ -5,6 +5,7 @@
5
5
  import { resolveParams } from '@webqit/subscript/src/params.js';
6
6
  import SubscriptFunction from '@webqit/subscript/src/SubscriptFunctionLite.js';
7
7
  import Observer from '@webqit/observer';
8
+ import Compiler from './Compiler.js';
8
9
  import wqDom from '@webqit/dom';
9
10
 
10
11
  /**
@@ -41,50 +42,6 @@ export {
41
42
  Observer,
42
43
  }
43
44
 
44
- /**
45
- * Performs realtime capture of elements and builds their relationships.
46
- *
47
- * @param Object params
48
- *
49
- * @return Void
50
- */
51
- function realtime( params ) {
52
- const window = this, { dom } = window.wq;
53
- const handled = () => {};
54
- dom.realtime( window.document ).observe( params.scriptSelector, record => {
55
- record.entrants.forEach( script => {
56
- if ( 'contract' in script ) return handled( script );
57
- Object.defineProperty( script, 'contract', { value: script.hasAttribute( 'contract' ) } );
58
- if ( 'scoped' in script ) return handled( script );
59
- Object.defineProperty( script, 'scoped', { value: script.hasAttribute( 'scoped' ) } );
60
- // ---
61
- const thisContext = script.scoped ? record.target : ( script.type === 'module' ? undefined : window );
62
- compile.call( this, script, thisContext, params );
63
- } );
64
- }, { subtree: true, timing: 'intercept', generation: 'entrants' } );
65
- // ---
66
- window.wq.oohtml.Script.run = ( execHash ) => {
67
- const exec = fromHash( execHash );
68
- if ( !exec ) throw new Error( `Argument must be a valid exec hash.` );
69
- const { script, compiledScript, thisContext } = exec;
70
- if ( thisContext instanceof window.Element && script.scoped ) {
71
- if ( !thisContext.scripts ) { Object.defineProperty( thisContext, 'scripts', { value: new Set } ); }
72
- thisContext.scripts.add( script );
73
- }
74
- switch ( params.script.retention ) {
75
- case 'dispose':
76
- script.remove();
77
- break;
78
- case 'hidden':
79
- script.textContent = `"source hidden"`;
80
- break;
81
- default:
82
- script.textContent = compiledScript.function.originalSource;
83
- }
84
- return execute.call( this, compiledScript, thisContext, script );
85
- };
86
- }
87
-
88
45
  // ------------------
89
46
  // Script runner
90
47
  export function execute( compiledScript, thisContext, script ) {
@@ -122,274 +79,28 @@ export function execute( compiledScript, thisContext, script ) {
122
79
  return script;
123
80
  }
124
81
 
125
- // ------------------
126
- // JAVASCRIPT::[SCOPED|CONTRACT]
127
- export function compile( script, thisContext, params = {} ) {
128
- const { wq: { oohtml, SubscriptFunction } } = this;
129
- const cache = oohtml.Script.compileCache[ script.contract ? 0 : 1 ];
130
- const sourceHash = toHash( script.textContent );
131
- // Script instances are parsed only once
132
- let source = script.textContent, compiledScript;
133
- if ( !( compiledScript = cache.get( sourceHash ) ) ) {
134
- // Are there "import" (and "await") statements? Then, we need to rewrite that
135
- let imports = [], meta = {};
136
- let targetKeywords = [];
137
- if ( script.type === 'module' ) targetKeywords.push( 'import ' );
138
- if ( script.type === 'module' && !script.contract ) targetKeywords.push( 'await ' );
139
- if ( targetKeywords.length && ( new RegExp( targetKeywords.join( '|' ) ) ).test( source ) ) {
140
- [ imports, source, meta ] = parse( source );
141
- if ( imports.length ) {
142
- source = `\n\t${ rewriteImportStmts( imports ).join( `\n\t` ) }\n\t${ source }\n`;
143
- }
144
- }
145
- // Let's obtain a material functions
146
- let _Function, { parserParams, compilerParams, runtimeParams } = params.config;
147
- if ( script.contract ) {
148
- parserParams = { ...parserParams, allowAwaitOutsideFunction: script.type === 'module' };
149
- runtimeParams = { ...runtimeParams, async: script.type === 'module' };
150
- _Function = SubscriptFunction( source, { compilerParams, parserParams, runtimeParams, } );
151
- Object.defineProperty( script, 'properties', { configurable: true, value: SubscriptFunction.inspect( _Function, 'properties' ) } );
152
- } else {
153
- const isAsync = script.type === 'module'//meta.topLevelAwait || imports.length;
154
- const _FunctionConstructor = isAsync ? Object.getPrototypeOf( async function() {} ).constructor : Function;
155
- _Function = runtimeParams?.compileFunction
156
- ? runtimeParams.compileFunction( source )
157
- : new _FunctionConstructor( source );
158
- Object.defineProperty( _Function, 'originalSource', { configurable: true, value: script.textContent } );
159
- }
160
- // Save material function to compile cache
161
- compiledScript = Object.defineProperty( script.cloneNode(), 'function', { value: _Function } );
162
- script.scoped && Object.defineProperty( compiledScript, 'scoped', Object.getOwnPropertyDescriptor( script, 'scoped') );
163
- script.contract && Object.defineProperty( compiledScript, 'contract', Object.getOwnPropertyDescriptor( script, 'contract') );
164
- cache.set( sourceHash, compiledScript );
165
- }
166
- const execHash = toHash( { script, compiledScript, thisContext } );
167
- script.textContent = `wq.oohtml.Script.run('${ execHash }');`;
168
- }
169
-
170
- // ------------------
171
- // Match import statements
172
- // and detect top-level await
173
- export function parse( source ) {
174
- const [ tokens, meta ] = tokenize( source, ( $tokens, event, char, meta, i, isLastChar ) => {
175
-
176
- if ( event === 'start-enclosure' && char === '{' && !meta.openAsync?.type && meta.openEnclosures.length === meta.openAsync?.scopeId ) {
177
- meta.openAsync.type = 'block';
178
- } else if ( event === 'end-enclosure' && char === '}' && meta.openAsync?.type === 'block' && meta.openEnclosures.length === meta.openAsync.scopeId ) {
179
- meta.openAsync = null;
180
- } else if ( event === 'start-quote' && !meta.openEnclosures.length && [ 'starting', 'from' ].includes( meta.openImport ) ) {
181
- meta.openImport = 'url';
182
- } else if ( event === 'end-quote' && meta.openImport === 'url' ) {
183
- meta.openImport = 'closing';
184
- } else if ( event === 'char' ) {
185
-
186
- if ( meta.openImport === 'closing' && (
187
- char === ';'/* explicit */ || ![ ' ', `\n` ].includes( char )/* implicit */ || isLastChar
188
- ) ) {
189
- if ( char === ';' || isLastChar ) {
190
- $tokens[ 0 ] += char;
191
- $tokens.unshift( '' );
192
- } else { $tokens.unshift( char ); }
193
- meta.openImport = null;
194
- return false;
195
- }
196
-
197
- let remainder = source.substring( i - 1 );
198
-
199
- if ( !meta.openImport && /^[\W]?import[ ]*[^\(]/.test( remainder ) ) {
200
- meta.openImport = 'starting';
201
- $tokens.unshift( '' );
202
- return 6;
203
- }
204
- if ( meta.openImport === 'starting' && /^[\W]?from /.test( remainder ) ) {
205
- meta.openImport = 'from';
206
- return 4;
207
- }
208
- if ( !meta.openAsync ) {
209
- if ( /^[\W]?async /.test( remainder ) ) {
210
- meta.openAsync = { scopeId: meta.openEnclosures.length };
211
- return 5;
212
- }
213
- if ( /^[\W]?await /.test( remainder ) ) {
214
- meta.topLevelAwait = true;
215
- return 5;
216
- }
217
- }
218
- if ( meta.openAsync ) {
219
- if ( !meta.openAsync.type && /.?\=\>[ ]*?\{/.test( remainder ) ) {
220
- meta.openAsync.type = 'inline-arrow';
221
- } else if ( meta.openAsync.type === 'inline-arrow' && [ `\n`, ';' ].includes( char ) && meta.openEnclosures.length === meta.openAsync.scopeId ) {
222
- meta.openAsync = null;
223
- }
224
- }
225
-
226
- }
227
-
228
- } );
229
- // Hoist all import statements
230
- let imports = [], body = '', _str;
231
- for ( const str of tokens.reverse() ) {
232
- if ( ( _str = str.trim() ).startsWith( 'import ' ) ) {
233
- imports.push( str );
234
- } else if ( _str ) { body += str; }
235
- }
236
-
237
- return [ imports, body, meta ];
238
- }
239
-
240
- // ------------------
241
- // Rewrite import statements
242
- export function rewriteImportStmts( imports ) {
243
- const importSpecs = [], importPromises = [];
244
- imports.forEach( ( $import, i ) => {
245
- $import = parseImportStmt( $import );
246
- // Identify whole imports and individual imports
247
- const [ wholeImport, individualImports ] = $import.items.reduce( ( [ whole, parts ], item ) => {
248
- return item.id === '*' ? [ item.alias, parts ] : [ whole, parts.concat( item ) ];
249
- }, [ null, [] ] );
250
- if ( wholeImport ) {
251
- // const main = await import("url");
252
- importSpecs.push( `const ${ wholeImport } = __$imports$__[${ i }];` );
253
- }
254
- if ( individualImports.length ) {
255
- // const { aa: bb, cc } = await import("url");
256
- const individualImportsSpec = individualImports.map( item => `${ item.id }${ item.id !== item.alias ? `: ${ item.alias }` : '' }` ).join( ', ' );
257
- importSpecs.push( `const { ${ individualImportsSpec } } = __$imports$__[${ i }];` );
258
- }
259
- importPromises.push( `import("${ $import.url }")` );
260
- } );
261
- return [
262
- `\n\tconst __$imports$__ = await Promise.all([\n\t\t${ importPromises.join( `,\n\t\t` ) }\n\t]);`,
263
- importSpecs.join( `\n\t` ),
264
- ];
265
- }
266
-
267
- // ------------------
268
- // Parse import statements
269
- export function parseImportStmt( str ) {
270
- const getUrl = str => {
271
- let quo = /^[`'"]/.exec( str );
272
- return quo && str.substring( 1, str.lastIndexOf( quo[ 0 ] ) );
273
- }
274
- let $import = { items: [ { id: '' } ] }, _str = str.replace( 'import', '' ).trim();
275
- if ( !( $import.url = getUrl( _str ) ) ) {
276
- tokenize( _str, ( $tokens, event, char, meta, i, isLastChar ) => {
277
- if ( [ 'start-quote', 'ongoing-quote', 'end-quote', 'char' ].includes( event ) ) {
278
- if ( $import.url ) return;
279
- if ( !meta.openQuote ) {
280
- let remainder = _str.substring( i );
281
- if ( char === ',' ) {
282
- $import.items.unshift( { id: '' } );
283
- return;
284
- }
285
- if ( remainder.startsWith( ' as ' ) ) {
286
- $import.items[ 0 ].alias = '';
287
- return 3;
288
- }
289
- if ( remainder.startsWith( ' from ' ) ) {
290
- $import.url = getUrl( remainder.replace( 'from', '' ).trim() );
291
- return remainder.length;
292
- }
293
- }
294
- if ( 'alias' in $import.items[ 0 ] ) {
295
- $import.items[ 0 ].alias += char;
296
- } else {
297
- $import.items[ 0 ].id += char;
298
- if ( meta.openEnclosures.length ) {
299
- $import.items[ 0 ].enclosed = true;
300
- }
301
- }
302
- }
82
+ /**
83
+ * Performs realtime capture of elements and builds their relationships.
84
+ *
85
+ * @param Object params
86
+ *
87
+ * @return Void
88
+ */
89
+ function realtime( params ) {
90
+ const window = this, { dom } = window.wq;
91
+ const compiler = new Compiler( window, params, execute ), handled = () => {};
92
+ dom.realtime( window.document ).observe( params.scriptSelector, record => {
93
+ record.entrants.forEach( script => {
94
+ if ( 'contract' in script ) return handled( script );
95
+ Object.defineProperty( script, 'contract', { value: script.hasAttribute( 'contract' ) } );
96
+ if ( 'scoped' in script ) return handled( script );
97
+ Object.defineProperty( script, 'scoped', { value: script.hasAttribute( 'scoped' ) } );
98
+ // ---
99
+ const thisContext = script.scoped ? record.target : ( script.type === 'module' ? undefined : window );
100
+ compiler.compile( script, thisContext );
303
101
  } );
304
- }
305
- $import.items = $import.items
306
- .map( item => ( {
307
- id: item.id && !item.alias && !item.enclosed ? 'default' : item.id.trim(),
308
- alias: item.alias ? item.alias.trim() : item.id.trim(),
309
- } ) )
310
- .filter( item => item.id )
311
- .reverse();
312
- return $import;
313
- }
314
-
315
- // ------------------
316
- // Token JavaScript source
317
- export function tokenize( source, _callback ) {
318
- const lastI = source.length - 1;
319
- return [ ...source ].reduce( ( [ $tokens, meta, skip ], char, i ) => {
320
-
321
- if ( skip ) {
322
- $tokens[ 0 ] += char;
323
- return [ $tokens, meta, --skip ];
324
- }
325
- let callbackReturn;
326
-
327
- if ( meta.openQuote || meta.openComment ) {
328
- if ( char === meta.openQuote ) {
329
- meta.openQuote = null;
330
- callbackReturn = _callback( $tokens, 'end-quote', char, meta, i, i === lastI );
331
- } else if ( meta.openQuote ) {
332
- callbackReturn = _callback( $tokens, 'ongoing-quote', char, meta, i, i === lastI );
333
- } else if ( meta.openComment ) {
334
- if ( ( meta.openComment === '//' && char === `\n` ) || ( meta.openComment === '/*' && $tokens[ 0 ].substr( -1 ) === '*' && char === '/' ) ) {
335
- meta.openComment = null;
336
- callbackReturn = _callback( $tokens, 'end-comment', char, meta, i, i === lastI );
337
- }
338
- }
339
- if ( callbackReturn !== false ) {
340
- $tokens[ 0 ] += char;
341
- }
342
- return [ $tokens, meta, typeof callbackReturn === 'number' ? callbackReturn : skip ];
343
- }
344
-
345
- let enclosure;
346
- if ( enclosure = [ '()', '{}', '[]' ].filter( pair => char === pair[ 0 ] )[ 0 ] ) {
347
- callbackReturn = _callback( $tokens, 'start-enclosure', char, meta, i, i === lastI );
348
- meta.openEnclosures.unshift( enclosure );
349
- } else if ( meta.openEnclosures.length && char === meta.openEnclosures[ 0 ][ 1 ] ) {
350
- meta.openEnclosures.shift();
351
- callbackReturn = _callback( $tokens, 'end-enclosure', char, meta, i, i === lastI );
352
- } else if ( [ '"', "'", "`" ].includes( char ) ) {
353
- callbackReturn = _callback( $tokens, 'start-quote', char, meta, i, i === lastI );
354
- meta.openQuote = char;
355
- } else if ( !meta.openComment && [ '/*', '//' ].includes( source.substr( i, 2 ) ) ) {
356
- callbackReturn = _callback( $tokens, 'start-comment', char, meta, i, i === lastI );
357
- meta.openComment = source.substr( i, 2 );
358
- } else {
359
- callbackReturn = _callback( $tokens, 'char', char, meta, i, i === lastI );
360
- }
361
-
362
- if ( callbackReturn !== false ) {
363
- $tokens[ 0 ] += char;
364
- }
365
- return [ $tokens, meta, typeof callbackReturn === 'number' ? callbackReturn : skip ];
366
-
367
- }, [ [ '' ], { openEnclosures: [], }, 0 ] );
102
+ }, { subtree: true, timing: 'intercept', generation: 'entrants' } );
103
+ // ---
368
104
  }
369
105
 
370
- // ------------------
371
- // Unique ID generator
372
- const hashTable = new Map;
373
- const uniqId = () => (0|Math.random()*9e6).toString(36);
374
106
  const _await = ( value, callback ) => value instanceof Promise ? value.then( callback ) : callback( value );
375
-
376
- // ------------------
377
- // Hash of anything generator
378
- export function toHash( val ) {
379
- let hash;
380
- if ( !( hash = hashTable.get( val ) ) ) {
381
- hash = uniqId();
382
- hashTable.set( val, hash );
383
- }
384
- return hash;
385
- }
386
-
387
- // ------------------
388
- // Value of any hash
389
- export function fromHash( hash ) {
390
- let val;
391
- hashTable.forEach( ( _hash, _val ) => {
392
- if ( _hash === hash ) val = _val;
393
- } );
394
- return val;
395
- }