@@ -18,6 +18,7 @@ import {
18
18
defineComponent ,
19
19
nextTick ,
20
20
warn ,
21
+ ConcreteComponent ,
21
22
ComponentOptions
22
23
} from '@vue/runtime-core'
23
24
import { camelize , extend , hyphenate , isArray , toNumber } from '@vue/shared'
@@ -124,32 +125,13 @@ export function defineCustomElement(
124
125
hydate ?: RootHydrateFunction
125
126
) : VueElementConstructor {
126
127
const Comp = defineComponent ( options as any )
127
- const { props } = options
128
- const rawKeys = props ? ( isArray ( props ) ? props : Object . keys ( props ) ) : [ ]
129
- const attrKeys = rawKeys . map ( hyphenate )
130
- const propKeys = rawKeys . map ( camelize )
131
-
132
128
class VueCustomElement extends VueElement {
133
129
static def = Comp
134
- static get observedAttributes ( ) {
135
- return attrKeys
136
- }
137
130
constructor ( initialProps ?: Record < string , any > ) {
138
- super ( Comp , initialProps , attrKeys , propKeys , hydate )
131
+ super ( Comp , initialProps , hydate )
139
132
}
140
133
}
141
134
142
- for ( const key of propKeys ) {
143
- Object . defineProperty ( VueCustomElement . prototype , key , {
144
- get ( ) {
145
- return this . _getProp ( key )
146
- } ,
147
- set ( val ) {
148
- this . _setProp ( key , val )
149
- }
150
- } )
151
- }
152
-
153
135
return VueCustomElement
154
136
}
155
137
@@ -162,20 +144,21 @@ const BaseClass = (
162
144
typeof HTMLElement !== 'undefined' ? HTMLElement : class { }
163
145
) as typeof HTMLElement
164
146
147
+ type InnerComponentDef = ConcreteComponent & { styles ?: string [ ] }
148
+
165
149
export class VueElement extends BaseClass {
166
150
/**
167
151
* @internal
168
152
*/
169
153
_instance : ComponentInternalInstance | null = null
170
154
171
155
private _connected = false
156
+ private _resolved = false
172
157
private _styles ?: HTMLStyleElement [ ]
173
158
174
159
constructor (
175
- private _def : ComponentOptions & { styles ?: string [ ] } ,
160
+ private _def : InnerComponentDef ,
176
161
private _props : Record < string , any > = { } ,
177
- private _attrKeys : string [ ] ,
178
- private _propKeys : string [ ] ,
179
162
hydrate ?: RootHydrateFunction
180
163
) {
181
164
super ( )
@@ -189,27 +172,25 @@ export class VueElement extends BaseClass {
189
172
)
190
173
}
191
174
this . attachShadow ( { mode : 'open' } )
192
- this . _applyStyles ( )
193
175
}
194
- }
195
176
196
- attributeChangedCallback ( name : string , _oldValue : string , newValue : string ) {
197
- if ( this . _attrKeys . includes ( name ) ) {
198
- this . _setProp ( camelize ( name ) , toNumber ( newValue ) , false )
177
+ // set initial attrs
178
+ for ( let i = 0 ; i < this . attributes . length ; i ++ ) {
179
+ this . _setAttr ( this . attributes [ i ] . name )
199
180
}
181
+ // watch future attr changes
182
+ const observer = new MutationObserver ( mutations => {
183
+ for ( const m of mutations ) {
184
+ this . _setAttr ( m . attributeName ! )
185
+ }
186
+ } )
187
+ observer . observe ( this , { attributes : true } )
200
188
}
201
189
202
190
connectedCallback ( ) {
203
191
this . _connected = true
204
192
if ( ! this . _instance ) {
205
- // check if there are props set pre-upgrade
206
- for ( const key of this . _propKeys ) {
207
- if ( this . hasOwnProperty ( key ) ) {
208
- const value = ( this as any ) [ key ]
209
- delete ( this as any ) [ key ]
210
- this . _setProp ( key , value )
211
- }
212
- }
193
+ this . _resolveDef ( )
213
194
render ( this . _createVNode ( ) , this . shadowRoot ! )
214
195
}
215
196
}
@@ -224,6 +205,50 @@ export class VueElement extends BaseClass {
224
205
} )
225
206
}
226
207
208
+ /**
209
+ * resolve inner component definition (handle possible async component)
210
+ */
211
+ private _resolveDef ( ) {
212
+ if ( this . _resolved ) {
213
+ return
214
+ }
215
+
216
+ const resolve = ( def : InnerComponentDef ) => {
217
+ this . _resolved = true
218
+ // check if there are props set pre-upgrade or connect
219
+ for ( const key of Object . keys ( this ) ) {
220
+ if ( key [ 0 ] !== '_' ) {
221
+ this . _setProp ( key , this [ key as keyof this] )
222
+ }
223
+ }
224
+ const { props, styles } = def
225
+ // defining getter/setters on prototype
226
+ const rawKeys = props ? ( isArray ( props ) ? props : Object . keys ( props ) ) : [ ]
227
+ for ( const key of rawKeys . map ( camelize ) ) {
228
+ Object . defineProperty ( this , key , {
229
+ get ( ) {
230
+ return this . _getProp ( key )
231
+ } ,
232
+ set ( val ) {
233
+ this . _setProp ( key , val )
234
+ }
235
+ } )
236
+ }
237
+ this . _applyStyles ( styles )
238
+ }
239
+
240
+ const asyncDef = ( this . _def as ComponentOptions ) . __asyncLoader
241
+ if ( asyncDef ) {
242
+ asyncDef ( ) . then ( resolve )
243
+ } else {
244
+ resolve ( this . _def )
245
+ }
246
+ }
247
+
248
+ protected _setAttr ( key : string ) {
249
+ this . _setProp ( camelize ( key ) , toNumber ( this . getAttribute ( key ) ) , false )
250
+ }
251
+
227
252
/**
228
253
* @internal
229
254
*/
@@ -261,16 +286,20 @@ export class VueElement extends BaseClass {
261
286
instance . isCE = true
262
287
// HMR
263
288
if ( __DEV__ ) {
264
- instance . ceReload = ( ) => {
265
- this . _instance = null
266
- // reset styles
289
+ instance . ceReload = newStyles => {
290
+ // alawys reset styles
267
291
if ( this . _styles ) {
268
292
this . _styles . forEach ( s => this . shadowRoot ! . removeChild ( s ) )
269
293
this . _styles . length = 0
270
294
}
271
- this . _applyStyles ( )
272
- // reload
273
- render ( this . _createVNode ( ) , this . shadowRoot ! )
295
+ this . _applyStyles ( newStyles )
296
+ // if this is an async component, ceReload is called from the inner
297
+ // component so no need to reload the async wrapper
298
+ if ( ! ( this . _def as ComponentOptions ) . __asyncLoader ) {
299
+ // reload
300
+ this . _instance = null
301
+ render ( this . _createVNode ( ) , this . shadowRoot ! )
302
+ }
274
303
}
275
304
}
276
305
@@ -299,9 +328,9 @@ export class VueElement extends BaseClass {
299
328
return vnode
300
329
}
301
330
302
- private _applyStyles ( ) {
303
- if ( this . _def . styles ) {
304
- this . _def . styles . forEach ( css => {
331
+ private _applyStyles ( styles : string [ ] | undefined ) {
332
+ if ( styles ) {
333
+ styles . forEach ( css => {
305
334
const s = document . createElement ( 'style' )
306
335
s . textContent = css
307
336
this . shadowRoot ! . appendChild ( s )
0 commit comments