@webqit/oohtml 4.1.5 → 4.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": "4.1.5",
17
+ "version": "4.1.8",
18
18
  "license": "MIT",
19
19
  "repository": {
20
20
  "type": "git",
@@ -2,6 +2,7 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
+ import { xpathQuery } from '@webqit/realdom/src/realtime/Util.js';
5
6
  import { _, _init, _splitOuter } from '../util.js';
6
7
 
7
8
  /**
@@ -38,12 +39,12 @@ function realtime( config ) {
38
39
  const window = this, { webqit: { realdom } } = window;
39
40
  // ----------------
40
41
  realdom.realtime( window.document ).query( `(${ config.discreteBindingsSelector })`, record => {
41
- cleanup.call( this, ...record.exits );
42
- mountDiscreteBindings.call( this, config, ...record.entrants );
42
+ cleanup.call( this, ...record.exits );
43
+ mountDiscreteBindings.call( window, config, ...record.entrants );
43
44
  }, { live: true, subtree: 'cross-roots', timing: 'sync' } );
44
45
  realdom.realtime( window.document ).query( config.attrSelector, record => {
45
- cleanup.call( this, ...record.exits );
46
- mountInlineBindings.call( this, config, ...record.entrants );
46
+ cleanup.call( this, ...record.exits );
47
+ mountInlineBindings.call( window, config, ...record.entrants );
47
48
  }, { live: true, subtree: 'cross-roots', timing: 'sync', staticSensitivity: true } );
48
49
  }
49
50
 
@@ -52,10 +53,18 @@ function createDynamicScope( config, root ) {
52
53
  if ( _( root ).has( 'data-binding' ) ) return _( root ).get( 'data-binding' );
53
54
  const scope = Object.create( null ), abortController = new AbortController;
54
55
  scope[ '$exec__' ] = ( target, prop, ...args ) => {
55
- realdom.schedule( 'write', () => target[ prop ]( ...args ) );
56
+ const exec = () => {
57
+ try { target[ prop ]( ...args ); }
58
+ catch( e ) { console.error( `${ e.message } at ${ e.cause }` ); }
59
+ };
60
+ realdom.schedule( 'write', exec );
56
61
  };
57
62
  scope[ '$assign__' ] = ( target, prop, val ) => {
58
- realdom.schedule( 'write', () => (target[ prop ] = val) );
63
+ const exec = () => {
64
+ try { target[ prop ] = val; }
65
+ catch( e ) { console.error( `${ e.message } at ${ e.cause }` ); }
66
+ };
67
+ realdom.schedule( 'write', exec );
59
68
  };
60
69
  Observer.intercept( scope, {
61
70
  get: ( e, recieved, next ) => {
@@ -89,21 +98,21 @@ function cleanup( ...entries ) {
89
98
  }
90
99
  }
91
100
 
101
+ function patternMatch( config, str ) {
102
+ const tagStart = config.tokens.tagStart.split( '' ).map( x => `\\${ x }` ).join( '' );
103
+ const tagEnd = config.tokens.tagEnd.split( '' ).map( x => `\\${ x }` ).join( '' );
104
+ const stateStart = config.tokens.stateStart.split( '' ).map( x => x === ' ' ? `(?:\\s+)?` : `\\${ x }` ).join( '' );
105
+ const stateEnd = config.tokens.stateEnd.split( '' ).map( x => `\\${ x }` ).join( '' );
106
+ const pattern = `^${ tagStart }(.*?)(?:${ stateStart }(\\d+)${ stateEnd }(?:\\s+)?)?${ tagEnd }$`;
107
+ const [ /*raw*/, expr, span ] = str.match( new RegExp( pattern ) );
108
+ return { raw: str, expr, span: parseInt( span ?? 0 ) };
109
+ }
110
+
92
111
  async function mountDiscreteBindings( config, ...entries ) {
93
112
  const window = this;
94
- const patternMatch = str => {
95
- const tagStart = config.tokens.tagStart.split( '' ).map( x => `\\${ x }` ).join( '' );
96
- const tagEnd = config.tokens.tagEnd.split( '' ).map( x => `\\${ x }` ).join( '' );
97
- const stateStart = config.tokens.stateStart.split( '' ).map( x => x === ' ' ? `(?:\\s+)?` : `\\${ x }` ).join( '' );
98
- const stateEnd = config.tokens.stateEnd.split( '' ).map( x => `\\${ x }` ).join( '' );
99
- const pattern = `^${ tagStart }(.*?)(?:${ stateStart }(\\d+)${ stateEnd }(?:\\s+)?)?${ tagEnd }$`;
100
- const [ /*raw*/, expr, span ] = str.match( new RegExp( pattern ) );
101
- return { raw: str, expr, span: parseInt( span ?? 0 ) };
102
- };
103
-
104
113
  const instances = entries.reduce( ( instances, node ) => {
105
114
  if ( node.isBound ) return instances;
106
- const template = patternMatch( node.nodeValue );
115
+ const template = patternMatch( config, node.nodeValue );
107
116
  let textNode = node;
108
117
  if ( template.span ) {
109
118
  textNode = node.nextSibling;
@@ -123,10 +132,14 @@ async function mountDiscreteBindings( config, ...entries ) {
123
132
  }, [] );
124
133
 
125
134
  for ( const { textNode, template, anchorNode } of instances ) {
126
- const compiled = compileDiscreteBindings( config, template.expr );
135
+ const compiled = compileDiscreteBindings.call( window, config, template.expr );
127
136
  const { scope, bindings } = createDynamicScope.call( this, config, textNode.parentNode );
128
137
  Object.defineProperty( textNode, '$oohtml_internal_databinding_anchorNode', { value: anchorNode, configurable: true } );
129
- bindings.set( textNode, { state: await ( await compiled.bind( textNode, scope ) ).execute(), } );
138
+ try {
139
+ bindings.set( textNode, { state: await ( await compiled.bind( textNode, scope ) ).execute(), } );
140
+ } catch( e ) {
141
+ console.log(e);
142
+ }
130
143
  }
131
144
  }
132
145
 
@@ -144,11 +157,15 @@ function compileDiscreteBindings( config, str ) {
144
157
 
145
158
  async function mountInlineBindings( config, ...entries ) {
146
159
  for ( const node of entries ) {
147
- const compiled = compileInlineBindings( config, node.getAttribute( config.attr.render ) );
160
+ const compiled = compileInlineBindings.call( this, config, node.getAttribute( config.attr.render ) );
148
161
  const { scope, bindings } = createDynamicScope.call( this, config, node );
149
162
  const signals = [];
150
163
  Object.defineProperty( node, '$oohtml_internal_databinding_signals', { value: signals, configurable: true } );
151
- bindings.set( node, { signals, state: await ( await compiled.bind( node, scope ) ).execute(), } );
164
+ try {
165
+ bindings.set( node, { signals, state: await ( await compiled.bind( node, scope ) ).execute(), } );
166
+ } catch( e ) {
167
+ console.log(e);
168
+ }
152
169
  }
153
170
  }
154
171
 
@@ -218,6 +235,13 @@ function compileInlineBindings( config, str ) {
218
235
  $existing__.clear();
219
236
  }`;
220
237
  }
238
+ // Treat other "@" directives as events
239
+ return `
240
+ const handler = () => ${ arg };
241
+ this.addEventListener( '${ param }', handler );
242
+ const abort = () => this.removeEventListener( '${ param }', handler );
243
+ this.$oohtml_internal_databinding_signals?.push( { abort } );
244
+ `;
221
245
  }
222
246
  if ( str.trim() ) throw new Error( `Invalid binding: ${ str }.` );
223
247
  } ).join( `\n` );
@@ -228,3 +252,14 @@ function compileInlineBindings( config, str ) {
228
252
  }
229
253
 
230
254
  const escDouble = str => str.replace(/"/g, '\\"');
255
+
256
+ export function idleCompiler( node ) {
257
+ const window = this, { webqit: { oohtml: { configs: { DATA_BINDING: config } } } } = window;
258
+ xpathQuery( window, node, `(${ config.discreteBindingsSelector })` ).forEach( node => {
259
+ const template = patternMatch( config, node.nodeValue );
260
+ compileDiscreteBindings.call( window, config, template.expr );
261
+ } );
262
+ ( node?.matches( config.attrSelector ) ? [ node ] : [] ).concat([ ...( node?.querySelectorAll( config.attrSelector ) || [] ) ]).forEach( node => {
263
+ compileInlineBindings.call( window, config, node.getAttribute( config.attr.render ) );
264
+ } );
265
+ }
@@ -2,7 +2,6 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
- import { _isNumeric } from '@webqit/util/js/index.js';
6
5
  import { getDefs } from './index.js';
7
6
  import { _, env } from '../util.js';
8
7
 
@@ -22,6 +21,7 @@ export default class HTMLModule {
22
21
  const { window } = env, { webqit: { realdom, oohtml: { configs } } } = window;
23
22
  _( host ).get( `defsmanager::instance` )?.dispose();
24
23
  _( host ).set( `defsmanager::instance`, this );
24
+ this.window = window;
25
25
  this.host = host;
26
26
  this.config = configs.HTML_IMPORTS;
27
27
  this.parent = parent;
@@ -31,8 +31,8 @@ export default class HTMLModule {
31
31
  this.validateDefId( this.defId );
32
32
  // ----------
33
33
  this.realtimeA = realdom.realtime( this.host.content ).children( record => {
34
- this.export( record.entrants, true );
35
- this.export( record.exits, false );
34
+ this.expose( record.entrants, true );
35
+ this.expose( record.exits, false );
36
36
  }, { live: true, timing: 'sync' } );
37
37
  // ----------
38
38
  this.realtimeB = realdom.realtime( this.host ).attr( [ 'src', 'loading' ], ( ...args ) => this.evaluateLoading( ...args ), {
@@ -67,7 +67,7 @@ export default class HTMLModule {
67
67
  *
68
68
  * @returns Void
69
69
  */
70
- export( entries, isConnected ) {
70
+ expose( entries, isConnected ) {
71
71
  const { window } = env, { webqit: { Observer } } = window;
72
72
  let dirty, allFragments = this.defs[ '#' ] || [];
73
73
  Observer.batch( this.defs, () => {
@@ -76,10 +76,16 @@ export default class HTMLModule {
76
76
  const isTemplate = entry.matches( this.config.templateSelector );
77
77
  const defId = ( entry.getAttribute( isTemplate ? this.config.attr.def : this.config.attr.fragmentdef ) || '' ).trim();
78
78
  if ( isConnected ) {
79
- if ( isTemplate && defId ) { new HTMLModule( entry, this.host, this.level + 1 ); }
80
- else {
79
+ if ( isTemplate && defId ) {
80
+ new HTMLModule( entry, this.host, this.level + 1 );
81
+ } else {
81
82
  allFragments.push( entry );
82
83
  dirty = true;
84
+ if ( typeof requestIdleCallback === 'function' ) {
85
+ requestIdleCallback( () => {
86
+ this.config.idleCompilers?.forEach( callback => callback.call( this.window, entry ) );
87
+ } );
88
+ }
83
89
  }
84
90
  if ( defId ) {
85
91
  this.validateDefId( defId );
package/src/init.js CHANGED
@@ -3,12 +3,12 @@
3
3
  * @imports
4
4
  */
5
5
  import NamespacedHTML from './namespaced-html/index.js';
6
- import ScopedCSS from './scoped-css/index.js';
7
- import ScopedJS from './scoped-js/index.js';
8
- import ContextAPI from './context-api/index.js';
6
+ import ScopedJS, { idleCompiler as idleCompiler1 } from './scoped-js/index.js';
7
+ import DataBinding, { idleCompiler as idleCompiler2 } from './data-binding/index.js';
9
8
  import BindingsAPI from './bindings-api/index.js';
10
9
  import HTMLImports from './html-imports/index.js';
11
- import DataBinding from './data-binding/index.js';
10
+ import ContextAPI from './context-api/index.js';
11
+ import ScopedCSS from './scoped-css/index.js';
12
12
 
13
13
  /**
14
14
  * @init
@@ -20,7 +20,7 @@ export default function init( QuantumJS, configs = {} ) {
20
20
  ContextAPI.call( this, ( configs.CONTEXT_API || {} ) );
21
21
  BindingsAPI.call( this, ( configs.BINDINGS_API || {} ) ); // Depends on ContextAPI
22
22
  NamespacedHTML.call( this, ( configs.NAMESPACED_HTML || {} ) ); // Depends on ContextAPI
23
- HTMLImports.call( this, ( configs.HTML_IMPORTS || {} ) ); // Depends on ContextAPI
23
+ HTMLImports.call( this, { ...( configs.HTML_IMPORTS || {} ), idleCompilers: [ idleCompiler1, idleCompiler2 ] } ); // Depends on ContextAPI
24
24
  DataBinding.call( this, ( configs.DATA_BINDING || {} ) ); // Depends on ContextAPI, BindingsAPI, HTMLImports
25
25
  ScopedCSS.call( this, ( configs.SCOPED_CSS || {} ) ); // Depends on NamespacedHTML
26
26
  ScopedJS.call( this, ( configs.SCOPED_JS || {} ) );
@@ -91,30 +91,16 @@ async function execute( config, execHash ) {
91
91
  * @return Void
92
92
  */
93
93
  function realtime( config ) {
94
- const window = this, { webqit: { oohtml, realdom, QuantumScript, AsyncQuantumScript, QuantumModule } } = window;
94
+ const window = this, { webqit: { oohtml, realdom } } = window;
95
95
  if ( !window.HTMLScriptElement.supports ) { window.HTMLScriptElement.supports = type => [ 'text/javascript', 'application/javascript' ].includes( type ); }
96
96
  const handled = new WeakSet;
97
97
  realdom.realtime( window.document ).query( config.scriptSelector, record => {
98
98
  record.entrants.forEach( script => {
99
99
  if ( handled.has( script ) ) return;
100
- const textContent = ( script._ = script.textContent.trim() ) && script._.startsWith( '/*@oohtml*/if(false){' ) && script._.endsWith( '}/*@oohtml*/' ) ? script._.slice( 21, -12 ) : script.textContent;
101
- if ( !script.scoped && !script.quantum && !textContent.includes( 'quantum' ) ) return;
102
- handled.add( script );
103
100
  // Do compilation
104
- const sourceHash = _toHash( textContent );
105
- const compileCache = oohtml.Script.compileCache[ script.quantum ? 0 : 1 ];
106
- let compiledScript;
107
- if ( !( compiledScript = compileCache.get( sourceHash ) ) ) {
108
- const { parserParams, compilerParams, runtimeParams } = config.advanced;
109
- compiledScript = new ( script.type === 'module' ? QuantumModule : ( QuantumScript || AsyncQuantumScript ) )( textContent, {
110
- exportNamespace: `#${ script.id }`,
111
- fileName: `${ window.document.url?.split( '#' )?.[ 0 ] || '' }#${ script.id }`,
112
- parserParams: { ...parserParams, quantumMode: script.quantum },
113
- compilerParams,
114
- runtimeParams,
115
- } );
116
- compileCache.set( sourceHash, compiledScript );
117
- }
101
+ const compiledScript = compileScript.call( window, config, script );
102
+ if ( !compiledScript ) return;
103
+ handled.add( script );
118
104
  // Run now!!!
119
105
  const thisContext = script.scoped ? script.parentNode || record.target : ( script.type === 'module' ? undefined : window );
120
106
  if ( script.scoped ) { thisContext[ config.api.scripts ].push( script ); }
@@ -126,4 +112,32 @@ function realtime( config ) {
126
112
  } );
127
113
  }, { live: true, subtree: 'cross-roots', timing: 'intercept', generation: 'entrants', eventDetails: true } );
128
114
  // ---
115
+ }
116
+
117
+ function compileScript( config, script ) {
118
+ const window = this, { webqit: { oohtml, QuantumScript, AsyncQuantumScript, QuantumModule } } = window;
119
+ const textContent = ( script._ = script.textContent.trim() ) && script._.startsWith( '/*@oohtml*/if(false){' ) && script._.endsWith( '}/*@oohtml*/' ) ? script._.slice( 21, -12 ) : script.textContent;
120
+ if ( !script.scoped && !script.quantum && !textContent.includes( 'quantum' ) ) return;
121
+ const sourceHash = _toHash( textContent );
122
+ const compileCache = oohtml.Script.compileCache[ script.quantum ? 0 : 1 ];
123
+ let compiledScript;
124
+ if ( !( compiledScript = compileCache.get( sourceHash ) ) ) {
125
+ const { parserParams, compilerParams, runtimeParams } = config.advanced;
126
+ compiledScript = new ( script.type === 'module' ? QuantumModule : ( QuantumScript || AsyncQuantumScript ) )( textContent, {
127
+ exportNamespace: `#${ script.id }`,
128
+ fileName: `${ window.document.url?.split( '#' )?.[ 0 ] || '' }#${ script.id }`,
129
+ parserParams: { ...parserParams, quantumMode: script.quantum },
130
+ compilerParams,
131
+ runtimeParams,
132
+ } );
133
+ compileCache.set( sourceHash, compiledScript );
134
+ }
135
+ return compiledScript;
136
+ }
137
+
138
+ export function idleCompiler( node ) {
139
+ const window = this, { webqit: { oohtml: { configs: { SCOPED_JS: config } } } } = window;
140
+ [ ...( node?.querySelectorAll( config.scriptSelector ) || [] ) ].forEach( script => {
141
+ compileScript.call( window, config, script );
142
+ } );
129
143
  }