@tixxin/nuxt-theme-engine 0.0.2 → 0.0.3

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/README.md CHANGED
@@ -76,6 +76,8 @@ export default defineNuxtConfig({
76
76
  </template>
77
77
  ```
78
78
 
79
+ `ThemeComponent` 在主题切换时会优先保留当前已显示的组件树,等目标主题组件实际加载完成后再原子切换,以减少异步主题组件带来的空白闪屏。
80
+
79
81
  ## 自定义契约
80
82
 
81
83
  引擎本身不强绑定某一套业务契约。
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "0.0.2",
7
+ "version": "0.0.3",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -1,8 +1,54 @@
1
1
  <script>
2
- import { computed, defineAsyncComponent, defineComponent, h, useAttrs, useSlots } from "vue";
2
+ import { computed, defineAsyncComponent, defineComponent, h, onServerPrefetch, shallowRef, useAttrs, useSlots, watch } from "vue";
3
3
  import { themeComponentLoaders, themeComponentRegistry } from "#build/theme-engine.registry.mjs";
4
4
  import { useThemeEngine } from "../composables/useThemeEngine";
5
5
  const asyncComponentCache = /* @__PURE__ */ new Map();
6
+ const loadedComponentCache = /* @__PURE__ */ new Map();
7
+ const loadingTaskCache = /* @__PURE__ */ new Map();
8
+ function resolveThemeComponent(themeName, componentName, firstThemeName) {
9
+ const entry = themeComponentRegistry[themeName]?.[componentName] ?? themeComponentRegistry[firstThemeName]?.[componentName];
10
+ const loader = themeComponentLoaders[themeName]?.[componentName] ?? themeComponentLoaders[firstThemeName]?.[componentName];
11
+ if (!entry || !loader) {
12
+ return null;
13
+ }
14
+ return {
15
+ cacheKey: `${entry.sourceTheme}:${componentName}`,
16
+ loader
17
+ };
18
+ }
19
+ async function loadThemeComponent(cacheKey, loader) {
20
+ const cachedComponent = loadedComponentCache.get(cacheKey);
21
+ if (cachedComponent) {
22
+ return cachedComponent;
23
+ }
24
+ const loadingTask = loadingTaskCache.get(cacheKey);
25
+ if (loadingTask) {
26
+ return loadingTask;
27
+ }
28
+ const task = loader().then((module) => {
29
+ const component = module.default ?? null;
30
+ if (component) {
31
+ loadedComponentCache.set(cacheKey, component);
32
+ }
33
+ return component;
34
+ }).finally(() => {
35
+ loadingTaskCache.delete(cacheKey);
36
+ });
37
+ loadingTaskCache.set(cacheKey, task);
38
+ return task;
39
+ }
40
+ function getAsyncComponent(cacheKey, loader) {
41
+ if (!asyncComponentCache.has(cacheKey)) {
42
+ asyncComponentCache.set(cacheKey, defineAsyncComponent(async () => {
43
+ const component = await loadThemeComponent(cacheKey, loader);
44
+ if (!component) {
45
+ throw new Error(`Theme component "${cacheKey}" failed to load.`);
46
+ }
47
+ return component;
48
+ }));
49
+ }
50
+ return asyncComponentCache.get(cacheKey) ?? null;
51
+ }
6
52
  export default defineComponent({
7
53
  name: "ThemeComponent",
8
54
  inheritAttrs: false,
@@ -17,29 +63,53 @@ export default defineComponent({
17
63
  const slots = useSlots();
18
64
  const { currentTheme } = useThemeEngine();
19
65
  const firstThemeName = Object.keys(themeComponentRegistry)[0] ?? "";
20
- const entry = computed(() => {
21
- return themeComponentRegistry[currentTheme.value]?.[props.name] ?? themeComponentRegistry[firstThemeName]?.[props.name];
22
- });
23
- const resolvedComponent = computed(() => {
24
- const resolvedEntry = entry.value;
25
- if (!resolvedEntry) {
26
- return null;
66
+ const displayedComponent = shallowRef(null);
67
+ const displayedCacheKey = shallowRef(null);
68
+ const targetComponent = computed(() => resolveThemeComponent(currentTheme.value, props.name, firstThemeName));
69
+ let activeRequestId = 0;
70
+ async function syncDisplayedComponent() {
71
+ const requestId = ++activeRequestId;
72
+ const resolvedTarget = targetComponent.value;
73
+ if (!resolvedTarget) {
74
+ displayedComponent.value = null;
75
+ displayedCacheKey.value = null;
76
+ return;
27
77
  }
28
- const loader = themeComponentLoaders[currentTheme.value]?.[props.name] ?? themeComponentLoaders[firstThemeName]?.[props.name];
29
- if (!loader) {
30
- return null;
78
+ if (displayedCacheKey.value === resolvedTarget.cacheKey && displayedComponent.value) {
79
+ return;
31
80
  }
32
- const cacheKey = `${resolvedEntry.sourceTheme}:${props.name}`;
33
- if (!asyncComponentCache.has(cacheKey)) {
34
- asyncComponentCache.set(cacheKey, defineAsyncComponent(loader));
81
+ const cachedComponent = loadedComponentCache.get(resolvedTarget.cacheKey);
82
+ if (cachedComponent) {
83
+ displayedComponent.value = cachedComponent;
84
+ displayedCacheKey.value = resolvedTarget.cacheKey;
85
+ return;
35
86
  }
36
- return asyncComponentCache.get(cacheKey) ?? null;
87
+ const hasDisplayedComponent = Boolean(displayedComponent.value);
88
+ if (!hasDisplayedComponent) {
89
+ displayedComponent.value = getAsyncComponent(resolvedTarget.cacheKey, resolvedTarget.loader);
90
+ displayedCacheKey.value = resolvedTarget.cacheKey;
91
+ }
92
+ const loadedComponent = await loadThemeComponent(resolvedTarget.cacheKey, resolvedTarget.loader);
93
+ if (!loadedComponent || requestId !== activeRequestId) {
94
+ return;
95
+ }
96
+ displayedComponent.value = loadedComponent;
97
+ displayedCacheKey.value = resolvedTarget.cacheKey;
98
+ }
99
+ watch([
100
+ () => currentTheme.value,
101
+ () => props.name
102
+ ], () => {
103
+ void syncDisplayedComponent();
104
+ }, {
105
+ immediate: true
37
106
  });
107
+ onServerPrefetch(syncDisplayedComponent);
38
108
  return () => {
39
- if (!resolvedComponent.value) {
109
+ if (!displayedComponent.value) {
40
110
  return null;
41
111
  }
42
- return h(resolvedComponent.value, attrs, slots);
112
+ return h(displayedComponent.value, attrs, slots);
43
113
  };
44
114
  }
45
115
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tixxin/nuxt-theme-engine",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "description": "A Nuxt 4 theme engine with layered themes, typed contracts, and runtime theme switching.",
6
6
  "license": "MIT",