@webqit/oohtml 4.1.6 → 4.1.9

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.6",
17
+ "version": "4.1.9",
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
 
@@ -97,21 +98,21 @@ function cleanup( ...entries ) {
97
98
  }
98
99
  }
99
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
+
100
111
  async function mountDiscreteBindings( config, ...entries ) {
101
112
  const window = this;
102
- const patternMatch = str => {
103
- const tagStart = config.tokens.tagStart.split( '' ).map( x => `\\${ x }` ).join( '' );
104
- const tagEnd = config.tokens.tagEnd.split( '' ).map( x => `\\${ x }` ).join( '' );
105
- const stateStart = config.tokens.stateStart.split( '' ).map( x => x === ' ' ? `(?:\\s+)?` : `\\${ x }` ).join( '' );
106
- const stateEnd = config.tokens.stateEnd.split( '' ).map( x => `\\${ x }` ).join( '' );
107
- const pattern = `^${ tagStart }(.*?)(?:${ stateStart }(\\d+)${ stateEnd }(?:\\s+)?)?${ tagEnd }$`;
108
- const [ /*raw*/, expr, span ] = str.match( new RegExp( pattern ) );
109
- return { raw: str, expr, span: parseInt( span ?? 0 ) };
110
- };
111
-
112
113
  const instances = entries.reduce( ( instances, node ) => {
113
114
  if ( node.isBound ) return instances;
114
- const template = patternMatch( node.nodeValue );
115
+ const template = patternMatch( config, node.nodeValue );
115
116
  let textNode = node;
116
117
  if ( template.span ) {
117
118
  textNode = node.nextSibling;
@@ -131,7 +132,7 @@ async function mountDiscreteBindings( config, ...entries ) {
131
132
  }, [] );
132
133
 
133
134
  for ( const { textNode, template, anchorNode } of instances ) {
134
- const compiled = compileDiscreteBindings( config, template.expr );
135
+ const compiled = compileDiscreteBindings.call( window, config, template.expr );
135
136
  const { scope, bindings } = createDynamicScope.call( this, config, textNode.parentNode );
136
137
  Object.defineProperty( textNode, '$oohtml_internal_databinding_anchorNode', { value: anchorNode, configurable: true } );
137
138
  try {
@@ -156,7 +157,7 @@ function compileDiscreteBindings( config, str ) {
156
157
 
157
158
  async function mountInlineBindings( config, ...entries ) {
158
159
  for ( const node of entries ) {
159
- const compiled = compileInlineBindings( config, node.getAttribute( config.attr.render ) );
160
+ const compiled = compileInlineBindings.call( this, config, node.getAttribute( config.attr.render ) );
160
161
  const { scope, bindings } = createDynamicScope.call( this, config, node );
161
162
  const signals = [];
162
163
  Object.defineProperty( node, '$oohtml_internal_databinding_signals', { value: signals, configurable: true } );
@@ -234,6 +235,13 @@ function compileInlineBindings( config, str ) {
234
235
  $existing__.clear();
235
236
  }`;
236
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
+ `;
237
245
  }
238
246
  if ( str.trim() ) throw new Error( `Invalid binding: ${ str }.` );
239
247
  } ).join( `\n` );
@@ -244,3 +252,14 @@ function compileInlineBindings( config, str ) {
244
252
  }
245
253
 
246
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 );
@@ -126,7 +126,7 @@ export default function() {
126
126
  priv.disconnectedCallback = () => {
127
127
  priv.hydrationImportRequest?.abort();
128
128
  priv.hydrationImportRequest = null;
129
- if ( priv.anchorNode.isConnected ) return;
129
+ if ( priv.anchorNode?.isConnected ) return;
130
130
  priv.moduleRefRealtime?.disconnect();
131
131
  priv.moduleRefRealtime = null;
132
132
  };
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
  }
package/test/index.js CHANGED
@@ -27,8 +27,8 @@ export function createDocumentPrefixed( prefix, head = '', body = '', callback =
27
27
  <head>
28
28
  <meta name="$q-compiler-url" content="../quantum-js/dist/compiler.js">
29
29
  ${ prefix ? `<meta name="webqit" content="prefix=${ prefix };">` : `` }
30
- <script ssr src="/dist/main.lite.js"></script>
31
30
  ${ head }
31
+ <script ssr src="/dist/main.lite.js"></script>
32
32
  </head>
33
33
  <body>${ body }</body>
34
34
  </html>`;
@@ -10,7 +10,8 @@ describe(`Test: Scoped JS`, function() {
10
10
  describe(`Scripts`, function() {
11
11
 
12
12
  it(`Should do basic observe`, async function() {
13
- const head = '', body = `
13
+ const head = ``;
14
+ const body = `
14
15
  <h1>Hello World!</h1>
15
16
  <script scoped quantum>
16
17
  testRecords.push( this );
@@ -19,7 +20,7 @@ describe(`Test: Scoped JS`, function() {
19
20
 
20
21
  const window = createDocument( head, body );
21
22
  window.testRecords = [];
22
- await delay( 300 ); // Takes time to dynamically load Reflex compiler
23
+ await delay( 500 ); // Takes time to dynamically load Reflex compiler
23
24
 
24
25
  expect( window.testRecords ).to.have.length( 1 );
25
26
  expect( window.testRecords[ 0 ] ).to.eql( window.document.body );