@@ -8,11 +8,16 @@ import ReactDOM from "../web_modules/react-dom.js";
88import htm from "../web_modules/htm.js" ;
99
1010import serializeEvent from "./event-to-object.js" ;
11- import lazyComponent from "./lazy-component.js" ;
1211
1312const html = htm . bind ( React . createElement ) ;
13+ const alreadyImported = { } ;
1414
15- function Layout ( { endpoint } ) {
15+ export function renderLayout ( mountElement , endpoint ) {
16+ const cmpt = html `< ${ Layout } endpoint =${ endpoint } /> ` ;
17+ return ReactDOM . render ( cmpt , mountElement ) ;
18+ }
19+
20+ export default function Layout ( { endpoint } ) {
1621 // handle relative endpoint URI
1722 if ( endpoint . startsWith ( "." ) || endpoint . startsWith ( "/" ) ) {
1823 let loc = window . location ;
@@ -48,36 +53,48 @@ function Layout({ endpoint }) {
4853 body : { event : event } ,
4954 } ) ;
5055 } ;
51-
5256 if ( modelState . root && modelState . models [ modelState . root ] ) {
5357 return html `< ${ Element }
5458 modelState =${ modelState }
55- model=${ modelState . models [ modelState . root ] }
5659 sendEvent=${ sendEvent }
60+ model=${ modelState . models [ modelState . root ] }
5761 /> ` ;
5862 } else {
5963 return html `< div /> ` ;
6064 }
6165}
6266
63- function Element ( { modelState, model, sendEvent } ) {
64- const children = elementChildren ( modelState , model , sendEvent ) ;
65- const attributes = elementAttributes ( model , sendEvent ) ;
67+ function Element ( { modelState, sendEvent, model } ) {
6668 if ( model . importSource ) {
67- const cmpt = lazyComponent ( model ) ;
68- return html `
69- < ${ Suspense } fallback ="${ model . importSource . fallback } ">
70- < ${ cmpt } ...${ attributes } > ${ children } </ />
71- </ />
72- ` ;
73- } else if ( model . children && model . children . length ) {
74- return html `< ${ model . tagName } ...${ attributes } > ${ children } </ /> ` ;
69+ return html `< ${ LazyElement }
70+ modelState =${ modelState }
71+ sendEvent=${ sendEvent }
72+ model=${ model }
73+ /> ` ;
74+ } else {
75+ const children = elementChildren ( modelState , sendEvent , model ) ;
76+ const attributes = elementAttributes ( sendEvent , model ) ;
77+ if ( model . children && model . children . length ) {
78+ return html `< ${ model . tagName } ...${ attributes } > ${ children } </ /> ` ;
79+ } else {
80+ return html `< ${ model . tagName } ...${ attributes } /> ` ;
81+ }
82+ }
83+ }
84+
85+ function LazyElement ( { modelState, sendEvent, model } ) {
86+ const module = useLazyModule ( model . importSource . source ) ;
87+ if ( module ) {
88+ const cmpt = getPathProperty ( module , model . tagName ) ;
89+ const children = elementChildren ( modelState , sendEvent , model ) ;
90+ const attributes = elementAttributes ( sendEvent , model ) ;
91+ return html `< ${ cmpt } ...${ attributes } > ${ children } </ /> ` ;
7592 } else {
76- return html `< ${ model . tagName } ... ${ attributes } /> ` ;
93+ return html `< div > ${ model . importSource . fallback } </ /> ` ;
7794 }
7895}
7996
80- function elementChildren ( modelState , model , sendEvent ) {
97+ function elementChildren ( modelState , sendEvent , model ) {
8198 if ( ! model . children ) {
8299 return [ ] ;
83100 } else {
@@ -106,45 +123,84 @@ function elementChildren(modelState, model, sendEvent) {
106123 }
107124}
108125
109- function elementAttributes ( model , sendEvent ) {
126+ function elementAttributes ( sendEvent , model ) {
110127 const attributes = Object . assign ( { } , model . attributes ) ;
111128
112129 if ( model . eventHandlers ) {
113130 Object . keys ( model . eventHandlers ) . forEach ( ( eventName ) => {
114131 const eventSpec = model . eventHandlers [ eventName ] ;
115- attributes [ eventName ] = function eventHandler ( event ) {
116- const data = Array . from ( arguments ) . map ( ( value ) => {
117- if ( typeof value === "object" && value . nativeEvent ) {
118- if ( eventSpec [ "preventDefault" ] ) {
119- value . preventDefault ( ) ;
120- }
121- if ( eventSpec [ "stopPropagation" ] ) {
122- value . stopPropagation ( ) ;
123- }
124- return serializeEvent ( value ) ;
125- } else {
126- return value ;
127- }
128- } ) ;
129- const sentEvent = new Promise ( ( resolve , reject ) => {
130- const msg = {
131- data : data ,
132- target : eventSpec [ "target" ] ,
133- } ;
134- sendEvent ( msg ) ;
135- resolve ( msg ) ;
136- } ) ;
137- } ;
132+ attributes [ eventName ] = eventHandler ( sendEvent , eventSpec ) ;
138133 } ) ;
139134 }
140135
141136 return attributes ;
142137}
143138
144- function renderLayout ( mountElement , endpoint ) {
145- const cmpt = html `< ${ Layout } endpoint =${ endpoint } /> ` ;
146- return ReactDOM . render ( cmpt , mountElement ) ;
139+ function eventHandler ( sendEvent , eventSpec ) {
140+ return function ( ) {
141+ const data = Array . from ( arguments ) . map ( ( value ) => {
142+ if ( typeof value === "object" && value . nativeEvent ) {
143+ if ( eventSpec [ "preventDefault" ] ) {
144+ value . preventDefault ( ) ;
145+ }
146+ if ( eventSpec [ "stopPropagation" ] ) {
147+ value . stopPropagation ( ) ;
148+ }
149+ return serializeEvent ( value ) ;
150+ } else {
151+ return value ;
152+ }
153+ } ) ;
154+ const sentEvent = new Promise ( ( resolve , reject ) => {
155+ const msg = {
156+ data : data ,
157+ target : eventSpec [ "target" ] ,
158+ } ;
159+ sendEvent ( msg ) ;
160+ resolve ( msg ) ;
161+ } ) ;
162+ } ;
163+ }
164+
165+ function useLazyModule ( source ) {
166+ const [ module , setModule ] = useState ( alreadyImported [ source ] ) ;
167+ if ( ! module ) {
168+ dynamicImport ( source ) . then ( setModule ) ;
169+ }
170+ return module ;
171+ }
172+
173+ function dynamicImport ( source ) {
174+ return eval ( `import('${ source } ')` ) . then (
175+ ( pkg ) => ( pkg . default ? pkg . default : pkg ) ,
176+ ( error ) => {
177+ if ( ! error . stack ) {
178+ throw error ;
179+ } else {
180+ console . log ( error ) ;
181+ return {
182+ default : function Catch ( ) {
183+ return html `
184+ < pre >
185+ < h1 > Error</ h1 >
186+ < code > ${ [ error . stack , error . message ] } </ code >
187+ </ pre
188+ >
189+ ` ;
190+ } ,
191+ } ;
192+ }
193+ }
194+ ) ;
147195}
148196
149- export default Layout ;
150- export { renderLayout } ;
197+ function getPathProperty ( obj , prop ) {
198+ // properties may be dot seperated strings
199+ const path = prop . split ( "." ) ;
200+ const firstProp = path . shift ( ) ;
201+ let value = obj [ firstProp ] ;
202+ for ( let i = 0 ; i < path . length ; i ++ ) {
203+ value = value [ path [ i ] ] ;
204+ }
205+ return value ;
206+ }
0 commit comments