1
1
/**
2
- * vue-meta v3.0.0-alpha.1
2
+ * vue-meta v3.0.0-alpha.2
3
3
* (c) 2021
4
4
* - Pim (@pimlie)
5
5
* - All the amazing contributors
@@ -612,7 +612,7 @@ function renderAttributes(context, key, data, config) {
612
612
}
613
613
function getSlotContent ( { metainfo, slots } , slotName , content , groupConfig ) {
614
614
const slot = slots && slots [ slotName ] ;
615
- if ( ! slot ) {
615
+ if ( ! slot || ! isFunction ( slot ) ) {
616
616
return content ;
617
617
}
618
618
const slotScopeProps = {
@@ -635,9 +635,34 @@ const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag ===
635
635
const PolySymbol = ( name ) =>
636
636
// vm = vue meta
637
637
hasSymbol
638
- ? Symbol ( '[vue-meta]: ' + name )
639
- : ( '[vue-meta]: ' ) + name ;
640
- const metaActiveKey = /*#__PURE__*/ PolySymbol ( 'active_meta' ) ;
638
+ ? Symbol ( '[vue-meta]: ' + name )
639
+ : ( '[vue-meta]: ' ) + name ;
640
+ const metaActiveKey = /*#__PURE__*/ PolySymbol ( 'meta_active' ) ;
641
+
642
+ /**
643
+ * Apply the differences between newSource & oldSource to target
644
+ */
645
+ function applyDifference ( target , newSource , oldSource ) {
646
+ for ( const key in newSource ) {
647
+ if ( ! ( key in oldSource ) ) {
648
+ target [ key ] = newSource [ key ] ;
649
+ continue ;
650
+ }
651
+ // We dont care about nested objects here , these changes
652
+ // should already have been tracked by the MergeProxy
653
+ if ( isObject ( target [ key ] ) ) {
654
+ continue ;
655
+ }
656
+ if ( newSource [ key ] !== oldSource [ key ] ) {
657
+ target [ key ] = newSource [ key ] ;
658
+ }
659
+ }
660
+ for ( const key in oldSource ) {
661
+ if ( ! ( key in newSource ) ) {
662
+ delete target [ key ] ;
663
+ }
664
+ }
665
+ }
641
666
642
667
function getCurrentManager ( vm ) {
643
668
if ( ! vm ) {
@@ -649,15 +674,22 @@ function getCurrentManager(vm) {
649
674
return vm . appContext . config . globalProperties . $metaManager ;
650
675
}
651
676
function useMeta ( source , manager ) {
652
- const vm = vue . getCurrentInstance ( ) ;
677
+ const vm = vue . getCurrentInstance ( ) || undefined ;
653
678
if ( ! manager && vm ) {
654
679
manager = getCurrentManager ( vm ) ;
655
680
}
656
681
if ( ! manager ) {
657
- // oopsydoopsy
658
682
throw new Error ( 'No manager or current instance' ) ;
659
683
}
660
- return manager . addMeta ( source , vm || undefined ) ;
684
+ if ( vue . isProxy ( source ) ) {
685
+ vue . watch ( source , ( newSource , oldSource ) => {
686
+ // We only care about first level props, second+ level will already be changed by the merge proxy
687
+ applyDifference ( metaProxy . meta , newSource , oldSource ) ;
688
+ } ) ;
689
+ source = source . value ;
690
+ }
691
+ const metaProxy = manager . addMeta ( source , vm ) ;
692
+ return metaProxy ;
661
693
}
662
694
function useActiveMeta ( ) {
663
695
return vue . inject ( metaActiveKey ) ;
@@ -695,83 +727,128 @@ function addVnode(teleports, to, vnodes) {
695
727
}
696
728
teleports [ to ] . push ( ...nodes ) ;
697
729
}
698
- function createMetaManager ( config , resolver ) {
699
- const resolve = ( options , contexts , active , key , pathSegments ) => {
700
- if ( isFunction ( resolver ) ) {
701
- return resolver ( options , contexts , active , key , pathSegments ) ;
730
+ const createMetaManager = ( config , resolver ) => MetaManager . create ( config , resolver ) ;
731
+ class MetaManager {
732
+ constructor ( config , target , resolver ) {
733
+ this . ssrCleanedUp = false ;
734
+ this . config = config ;
735
+ this . target = target ;
736
+ if ( resolver && 'setup' in resolver && isFunction ( resolver . setup ) ) {
737
+ this . resolver = resolver ;
702
738
}
703
- return resolver . resolve ( options , contexts , active , key , pathSegments ) ;
704
- } ;
705
- const { addSource, delSource } = createMergedObject ( resolve , active ) ;
706
- // TODO: validate resolver
707
- const manager = {
708
- config,
709
- install ( app ) {
710
- app . component ( 'Metainfo' , Metainfo ) ;
711
- app . config . globalProperties . $metaManager = manager ;
712
- app . provide ( metaActiveKey , active ) ;
713
- } ,
714
- addMeta ( metaObj , vm ) {
715
- if ( ! vm ) {
716
- vm = vue . getCurrentInstance ( ) || undefined ;
717
- }
718
- const resolveContext = { vm } ;
719
- if ( resolver && 'setup' in resolver && isFunction ( resolver . setup ) ) {
720
- resolver . setup ( resolveContext ) ;
721
- }
722
- // TODO: optimize initial compute
723
- const meta = addSource ( metaObj , resolveContext , true ) ;
724
- const unmount = ( ) => delSource ( meta ) ;
725
- if ( vm ) {
726
- vue . onUnmounted ( unmount ) ;
727
- }
728
- return {
729
- meta,
730
- unmount
731
- } ;
732
- } ,
733
- render ( { slots } = { } ) {
734
- const teleports = { } ;
735
- for ( const key in active ) {
736
- const config = this . config [ key ] || { } ;
737
- let renderedNodes = renderMeta ( { metainfo : active , slots } , key , active [ key ] , config ) ;
738
- if ( ! renderedNodes ) {
739
+ }
740
+ install ( app ) {
741
+ app . component ( 'Metainfo' , Metainfo ) ;
742
+ app . config . globalProperties . $metaManager = this ;
743
+ app . provide ( metaActiveKey , active ) ;
744
+ }
745
+ addMeta ( metadata , vm ) {
746
+ if ( ! vm ) {
747
+ vm = vue . getCurrentInstance ( ) || undefined ;
748
+ }
749
+ const metaGuards = ( {
750
+ removed : [ ]
751
+ } ) ;
752
+ const resolveContext = { vm } ;
753
+ if ( this . resolver ) {
754
+ this . resolver . setup ( resolveContext ) ;
755
+ }
756
+ // TODO: optimize initial compute (once)
757
+ const meta = this . target . addSource ( metadata , resolveContext , true ) ;
758
+ const onRemoved = ( removeGuard ) => metaGuards . removed . push ( removeGuard ) ;
759
+ const unmount = ( ignoreGuards ) => this . unmount ( ! ! ignoreGuards , meta , metaGuards , vm ) ;
760
+ if ( vm ) {
761
+ vue . onUnmounted ( unmount ) ;
762
+ }
763
+ return {
764
+ meta,
765
+ onRemoved,
766
+ unmount
767
+ } ;
768
+ }
769
+ unmount ( ignoreGuards , meta , metaGuards , vm ) {
770
+ if ( vm ) {
771
+ const { $el } = vm . proxy ;
772
+ // Wait for element to be removed from DOM
773
+ if ( $el && $el . offsetParent ) {
774
+ let observer = new MutationObserver ( ( records ) => {
775
+ for ( const { removedNodes } of records ) {
776
+ if ( ! removedNodes ) {
777
+ continue ;
778
+ }
779
+ removedNodes . forEach ( ( el ) => {
780
+ if ( el === $el && observer ) {
781
+ observer . disconnect ( ) ;
782
+ observer = undefined ;
783
+ this . reallyUnmount ( ignoreGuards , meta , metaGuards ) ;
784
+ }
785
+ } ) ;
786
+ }
787
+ } ) ;
788
+ observer . observe ( $el . parentNode , { childList : true } ) ;
789
+ return ;
790
+ }
791
+ }
792
+ this . reallyUnmount ( ignoreGuards , meta , metaGuards ) ;
793
+ }
794
+ async reallyUnmount ( ignoreGuards , meta , metaGuards ) {
795
+ this . target . delSource ( meta ) ;
796
+ if ( ! ignoreGuards && metaGuards ) {
797
+ await Promise . all ( metaGuards . removed . map ( removeGuard => removeGuard ( ) ) ) ;
798
+ }
799
+ }
800
+ render ( { slots } = { } ) {
801
+ const teleports = { } ;
802
+ for ( const key in active ) {
803
+ const config = this . config [ key ] || { } ;
804
+ let renderedNodes = renderMeta ( { metainfo : active , slots } , key , active [ key ] , config ) ;
805
+ if ( ! renderedNodes ) {
806
+ continue ;
807
+ }
808
+ if ( ! isArray ( renderedNodes ) ) {
809
+ renderedNodes = [ renderedNodes ] ;
810
+ }
811
+ let defaultTo = key !== 'base' && active [ key ] . to ;
812
+ if ( ! defaultTo && 'to' in config ) {
813
+ defaultTo = config . to ;
814
+ }
815
+ if ( ! defaultTo && 'attributesFor' in config ) {
816
+ defaultTo = key ;
817
+ }
818
+ for ( const { to, vnode } of renderedNodes ) {
819
+ addVnode ( teleports , to || defaultTo || 'head' , vnode ) ;
820
+ }
821
+ }
822
+ if ( slots ) {
823
+ for ( const slotName in slots ) {
824
+ const tagName = slotName === 'default' ? 'head' : slotName ;
825
+ // Only teleport the contents of head/body slots
826
+ if ( tagName !== 'head' && tagName !== 'body' ) {
739
827
continue ;
740
828
}
741
- if ( ! isArray ( renderedNodes ) ) {
742
- renderedNodes = [ renderedNodes ] ;
743
- }
744
- let defaultTo = key !== 'base' && active [ key ] . to ;
745
- if ( ! defaultTo && 'to' in config ) {
746
- defaultTo = config . to ;
747
- }
748
- if ( ! defaultTo && 'attributesFor' in config ) {
749
- defaultTo = key ;
750
- }
751
- for ( const { to, vnode } of renderedNodes ) {
752
- addVnode ( teleports , to || defaultTo || 'head' , vnode ) ;
753
- }
754
- }
755
- if ( slots ) {
756
- for ( const slotName in slots ) {
757
- const tagName = slotName === 'default' ? 'head' : slotName ;
758
- // Only teleport the contents of head/body slots
759
- if ( tagName !== 'head' && tagName !== 'body' ) {
760
- continue ;
761
- }
762
- const slot = slots [ slotName ] ;
763
- if ( isFunction ( slot ) ) {
764
- addVnode ( teleports , tagName , slot ( { metainfo : active } ) ) ;
765
- }
829
+ const slot = slots [ slotName ] ;
830
+ if ( isFunction ( slot ) ) {
831
+ addVnode ( teleports , tagName , slot ( { metainfo : active } ) ) ;
766
832
}
767
833
}
768
- return Object . keys ( teleports ) . map ( ( to ) => {
769
- return vue . h ( vue . Teleport , { to } , teleports [ to ] ) ;
770
- } ) ;
771
834
}
835
+ return Object . keys ( teleports ) . map ( ( to ) => {
836
+ return vue . h ( vue . Teleport , { to } , teleports [ to ] ) ;
837
+ } ) ;
838
+ }
839
+ }
840
+ MetaManager . create = ( config , resolver ) => {
841
+ const resolve = ( options , contexts , active , key , pathSegments ) => {
842
+ if ( isFunction ( resolver ) ) {
843
+ return resolver ( options , contexts , active , key , pathSegments ) ;
844
+ }
845
+ return resolver . resolve ( options , contexts , active , key , pathSegments ) ;
772
846
} ;
847
+ const mergedObject = createMergedObject ( resolve , active ) ;
848
+ // TODO: validate resolver
849
+ const manager = new MetaManager ( config , mergedObject , resolver ) ;
773
850
return manager ;
774
- }
851
+ } ;
775
852
776
853
// rollup doesnt like an import as it cant find the export so use require
777
854
const { renderToString } = require ( '@vue/server-renderer' ) ;
0 commit comments