@poveste/plugin-vue 0.19.0 → 0.20.0
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/dist/bundled/client/app/MountStory.js +32 -30
- package/dist/bundled/client/app/RenderStory.js +39 -44
- package/dist/bundled/client/app/Story.js +7 -17
- package/dist/bundled/client/app/util.js +37 -33
- package/dist/bundled/client/server.js +1 -2
- package/dist/client/app/util.d.ts +12 -0
- package/dist/docgen/extractor.d.ts +8 -0
- package/dist/docgen/extractor.js +41 -0
- package/dist/docgen/meta.d.ts +9 -0
- package/dist/docgen/meta.js +50 -0
- package/dist/docgen/typescript.d.ts +9 -0
- package/dist/docgen/typescript.js +36 -0
- package/dist/index.node.js +10 -0
- package/dist/util/vue-plugin.d.ts +10 -0
- package/dist/util/vue-plugin.js +28 -0
- package/package.json +14 -13
- package/src/client/app/MountStory.ts +2 -0
- package/src/client/app/RenderStory.ts +2 -1
- package/src/client/app/util.ts +25 -0
- package/src/docgen/extractor.ts +47 -0
- package/src/docgen/meta.ts +68 -0
- package/src/docgen/typescript.ts +41 -0
- package/src/index.node.ts +11 -0
- package/src/util/vue-plugin.ts +32 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { warnIfForeignVue as e } from "./util.js";
|
|
2
|
+
import { createPreviewHost as t } from "./host.js";
|
|
3
|
+
import { defineComponent as n, h as r, onMounted as i, onUnmounted as a, ref as o, watch as s } from "@poveste/vendors/vue";
|
|
4
|
+
import { reactive as c } from "vue";
|
|
4
5
|
//#region src/client/app/MountStory.ts
|
|
5
|
-
var
|
|
6
|
+
var l = n({
|
|
6
7
|
name: "MountStory",
|
|
7
8
|
props: {
|
|
8
9
|
story: {
|
|
@@ -18,43 +19,44 @@ var c = t({
|
|
|
18
19
|
default: !0
|
|
19
20
|
}
|
|
20
21
|
},
|
|
21
|
-
setup(
|
|
22
|
-
|
|
22
|
+
setup(n) {
|
|
23
|
+
e("MountStory");
|
|
24
|
+
let r = o(), l = c({
|
|
23
25
|
mode: "mount",
|
|
24
26
|
slotName: "default",
|
|
25
27
|
currentVariant: null,
|
|
26
|
-
targetVariantId:
|
|
27
|
-
syncState:
|
|
28
|
+
targetVariantId: n.targetVariantId,
|
|
29
|
+
syncState: n.syncState,
|
|
28
30
|
externalState: null,
|
|
29
31
|
nextVariantIndex: { value: 0 }
|
|
30
|
-
}),
|
|
31
|
-
async function
|
|
32
|
-
|
|
32
|
+
}), u = null;
|
|
33
|
+
async function d() {
|
|
34
|
+
r.value && (u = t({
|
|
33
35
|
name: "MountStorySubApp",
|
|
34
|
-
el:
|
|
35
|
-
getStory: () =>
|
|
36
|
+
el: r.value,
|
|
37
|
+
getStory: () => n.story,
|
|
36
38
|
getVariant: () => null,
|
|
37
|
-
renderContext:
|
|
38
|
-
}), await
|
|
39
|
+
renderContext: l
|
|
40
|
+
}), await u.mount());
|
|
39
41
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
+
function f() {
|
|
43
|
+
u?.unmount(), u = null;
|
|
42
44
|
}
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
}),
|
|
46
|
-
await
|
|
47
|
-
}),
|
|
48
|
-
|
|
49
|
-
}),
|
|
50
|
-
|
|
51
|
-
}),
|
|
52
|
-
|
|
53
|
-
}), { el:
|
|
45
|
+
return s(() => n.story.id, async () => {
|
|
46
|
+
f(), await d();
|
|
47
|
+
}), i(async () => {
|
|
48
|
+
await d();
|
|
49
|
+
}), s(() => n.story.variants, () => {
|
|
50
|
+
u?.forceUpdate();
|
|
51
|
+
}), s(() => n.targetVariantId, (e) => {
|
|
52
|
+
l.targetVariantId = e;
|
|
53
|
+
}), a(() => {
|
|
54
|
+
f();
|
|
55
|
+
}), { el: r };
|
|
54
56
|
},
|
|
55
57
|
render() {
|
|
56
|
-
return
|
|
58
|
+
return r("div", { ref: "el" });
|
|
57
59
|
}
|
|
58
60
|
});
|
|
59
61
|
//#endregion
|
|
60
|
-
export {
|
|
62
|
+
export { l as default };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { syncStateBundledAndExternal as e } from "./util.js";
|
|
2
|
-
import { createPreviewHost as
|
|
3
|
-
import { defineComponent as
|
|
4
|
-
import { reactive as
|
|
1
|
+
import { syncStateBundledAndExternal as e, warnIfForeignVue as t } from "./util.js";
|
|
2
|
+
import { createPreviewHost as n } from "./host.js";
|
|
3
|
+
import { defineComponent as r, h as i, onBeforeUnmount as a, onMounted as o, ref as s, watch as c } from "@poveste/vendors/vue";
|
|
4
|
+
import { reactive as l } from "vue";
|
|
5
5
|
//#region src/client/app/RenderStory.ts
|
|
6
|
-
var
|
|
6
|
+
var u = r({
|
|
7
7
|
name: "RenderStory",
|
|
8
8
|
props: {
|
|
9
9
|
variant: {
|
|
@@ -20,54 +20,49 @@ var l = n({
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
emits: { ready: () => !0 },
|
|
23
|
-
setup(
|
|
24
|
-
|
|
23
|
+
setup(r, { emit: i }) {
|
|
24
|
+
t("RenderStory");
|
|
25
|
+
let u = s(), d = null, f = !1, p = l({}), m = e(r.variant.state, p), h = l({
|
|
25
26
|
mode: "render",
|
|
26
|
-
slotName:
|
|
27
|
-
currentVariant:
|
|
28
|
-
externalState:
|
|
27
|
+
slotName: r.slotName,
|
|
28
|
+
currentVariant: r.variant,
|
|
29
|
+
externalState: p,
|
|
29
30
|
nextVariantIndex: { value: 0 }
|
|
30
31
|
});
|
|
31
|
-
function h() {
|
|
32
|
-
u?.unmount(), u = null;
|
|
33
|
-
}
|
|
34
32
|
function g() {
|
|
35
|
-
|
|
33
|
+
d?.unmount(), d = null;
|
|
34
|
+
}
|
|
35
|
+
function _() {
|
|
36
|
+
m?.stop(), m = e(r.variant.state, p);
|
|
36
37
|
}
|
|
37
|
-
async function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
getStory: () => n.story,
|
|
47
|
-
getVariant: () => n.variant,
|
|
48
|
-
renderContext: m,
|
|
49
|
-
wrapInDiv: !0
|
|
50
|
-
}), await u.mount(), d = !1, r("ready");
|
|
51
|
-
}
|
|
38
|
+
async function v() {
|
|
39
|
+
f || (f = !0, g(), h.currentVariant = r.variant, h.slotName = r.slotName, u.value ? (d = n({
|
|
40
|
+
name: "RenderStorySubApp",
|
|
41
|
+
el: u.value,
|
|
42
|
+
getStory: () => r.story,
|
|
43
|
+
getVariant: () => r.variant,
|
|
44
|
+
renderContext: h,
|
|
45
|
+
wrapInDiv: !0
|
|
46
|
+
}), await d.mount(), f = !1, i("ready")) : f = !1);
|
|
52
47
|
}
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
}),
|
|
56
|
-
|
|
57
|
-
}),
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
return o(async () => {
|
|
49
|
+
r.variant.configReady && await v();
|
|
50
|
+
}), c(() => r.variant, () => {
|
|
51
|
+
h.currentVariant = r.variant, _();
|
|
52
|
+
}), c(() => [
|
|
53
|
+
r.story.id,
|
|
54
|
+
r.slotName,
|
|
55
|
+
r.variant.id,
|
|
56
|
+
r.variant.configReady
|
|
62
57
|
], async ([, e]) => {
|
|
63
|
-
|
|
64
|
-
}),
|
|
65
|
-
|
|
66
|
-
}), { sandbox:
|
|
58
|
+
h.currentVariant = r.variant, h.slotName = e, r.variant.configReady && !f && (d ? d.forceUpdate() : await v());
|
|
59
|
+
}), a(() => {
|
|
60
|
+
m?.stop(), g();
|
|
61
|
+
}), { sandbox: u };
|
|
67
62
|
},
|
|
68
63
|
render() {
|
|
69
|
-
return
|
|
64
|
+
return i("div", { ref: "sandbox" });
|
|
70
65
|
}
|
|
71
66
|
});
|
|
72
67
|
//#endregion
|
|
73
|
-
export {
|
|
68
|
+
export { u as default };
|
|
@@ -69,16 +69,12 @@ var m = c({
|
|
|
69
69
|
function x() {
|
|
70
70
|
let e = 0, t = m?.targetVariantId, n = t ? l.story.variants.findIndex((e) => e.id === t) : -1, r = (t) => {
|
|
71
71
|
let i = [];
|
|
72
|
-
for (let s of t) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
Array.isArray(s.children) && (s.children = r(s.children)), i.push(s);
|
|
81
|
-
}
|
|
72
|
+
for (let s of t) if (s.type?.__povesteType === "variant") {
|
|
73
|
+
let t = l.story.variants[e];
|
|
74
|
+
if (e++, !t || n !== -1 && e - 1 !== n) continue;
|
|
75
|
+
let r = { variant: t };
|
|
76
|
+
s.key ?? (r.key = t.id), !s.props?.initState && !s.props?.["init-state"] && (r.initState = a.initState), Object.assign(r, b(s)), i.push(o(s, r));
|
|
77
|
+
} else Array.isArray(s.children) && (s.children = r(s.children)), i.push(s);
|
|
82
78
|
return i;
|
|
83
79
|
};
|
|
84
80
|
return r(c.slots.default?.() ?? []);
|
|
@@ -98,13 +94,7 @@ var m = c({
|
|
|
98
94
|
if (t !== -1) {
|
|
99
95
|
let r = 0, i = !1, a = (e) => {
|
|
100
96
|
let n = [];
|
|
101
|
-
for (let s of e)
|
|
102
|
-
if (s?.type?.__povesteType === "variant") {
|
|
103
|
-
r === t && (n.push(o(s, b(s))), i = !0), r++;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
Array.isArray(s?.children) && s.children.length && (s.children = a(s.children)), n.push(s);
|
|
107
|
-
}
|
|
97
|
+
for (let s of e) s?.type?.__povesteType === "variant" ? (r === t && (n.push(o(s, b(s))), i = !0), r++) : (Array.isArray(s?.children) && s.children.length && (s.children = a(s.children)), n.push(s));
|
|
108
98
|
return n;
|
|
109
99
|
}, s = a(n);
|
|
110
100
|
if (i) return e.nextVariantIndex.value = t, s;
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getCurrentInstance as
|
|
3
|
-
import { applyState as
|
|
1
|
+
import { getCurrentInstance as e, isRef as t, unref as n, watch as r } from "@poveste/vendors/vue";
|
|
2
|
+
import { getCurrentInstance as i, isReadonly as a, isRef as o, markRaw as s, unref as c, watch as l } from "vue";
|
|
3
|
+
import { applyState as u, createStateBaseline as d, isMarkedRaw as f, isPlainObject as p } from "@poveste/shared";
|
|
4
4
|
//#region src/client/app/util.ts
|
|
5
|
-
var
|
|
6
|
-
function
|
|
7
|
-
let n =
|
|
5
|
+
var m = (e) => typeof e == "object" && !!e;
|
|
6
|
+
function h(e, t = /* @__PURE__ */ new WeakMap()) {
|
|
7
|
+
let n = o(e) ? c(e) : e;
|
|
8
8
|
if (typeof n == "symbol") return n.toString();
|
|
9
|
-
if (!
|
|
9
|
+
if (!m(n) || f(n)) return n;
|
|
10
10
|
if (t.has(n)) return t.get(n);
|
|
11
|
-
if (!Array.isArray(n) && !
|
|
11
|
+
if (!Array.isArray(n) && !p(n)) return s(n);
|
|
12
12
|
if (Array.isArray(n)) {
|
|
13
13
|
let e = [];
|
|
14
|
-
return t.set(n, e), e.push(...n.map((e) =>
|
|
14
|
+
return t.set(n, e), e.push(...n.map((e) => h(e, t))), e;
|
|
15
15
|
}
|
|
16
16
|
{
|
|
17
17
|
let e = {};
|
|
18
|
-
return t.set(n, e),
|
|
18
|
+
return t.set(n, e), g(n, e, t), e;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
function
|
|
21
|
+
function g(e, t, n = /* @__PURE__ */ new WeakMap()) {
|
|
22
22
|
Object.keys(e).forEach((r) => {
|
|
23
|
-
t[r] =
|
|
23
|
+
t[r] = h(e[r], n);
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
let i = e
|
|
26
|
+
function _(e, r = /* @__PURE__ */ new WeakMap()) {
|
|
27
|
+
let i = t(e) ? n(e) : e;
|
|
28
28
|
if (typeof i == "symbol") return i.toString();
|
|
29
|
-
if (!
|
|
29
|
+
if (!m(i) || f(i)) return i;
|
|
30
30
|
if (r.has(i)) return r.get(i);
|
|
31
|
-
if (!Array.isArray(i) && !
|
|
31
|
+
if (!Array.isArray(i) && !p(i)) return s(i);
|
|
32
32
|
if (Array.isArray(i)) {
|
|
33
33
|
let e = [];
|
|
34
|
-
return r.set(i, e), e.push(...i.map((e) =>
|
|
34
|
+
return r.set(i, e), e.push(...i.map((e) => _(e, r))), e;
|
|
35
35
|
}
|
|
36
36
|
{
|
|
37
37
|
let e = {};
|
|
38
|
-
return r.set(i, e),
|
|
38
|
+
return r.set(i, e), v(i, e, r), e;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function v(e, t, n = /* @__PURE__ */ new WeakMap()) {
|
|
42
42
|
Object.keys(e).forEach((r) => {
|
|
43
|
-
t[r] =
|
|
43
|
+
t[r] = _(e[r], n);
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
-
function
|
|
46
|
+
function y(e, t) {
|
|
47
47
|
if (!t.length) return e;
|
|
48
48
|
let n = { ...e };
|
|
49
49
|
for (let e of t) delete n[e];
|
|
50
50
|
return n;
|
|
51
51
|
}
|
|
52
|
-
function
|
|
53
|
-
let i =
|
|
52
|
+
function b(e, t, n = []) {
|
|
53
|
+
let i = d(), a = r(() => e, (e) => {
|
|
54
54
|
if (e == null) return;
|
|
55
|
-
let
|
|
56
|
-
|
|
55
|
+
let r = i.take(y(_(e), n));
|
|
56
|
+
r && u(t, r);
|
|
57
57
|
}, {
|
|
58
58
|
deep: !0,
|
|
59
59
|
immediate: !0
|
|
60
|
-
}), o =
|
|
60
|
+
}), o = l(() => t, (t) => {
|
|
61
61
|
if (t == null) return;
|
|
62
|
-
let
|
|
63
|
-
|
|
62
|
+
let r = i.take(y(h(t), n));
|
|
63
|
+
r && u(e, r);
|
|
64
64
|
}, {
|
|
65
65
|
deep: !0,
|
|
66
66
|
immediate: !0
|
|
@@ -69,13 +69,17 @@ function y(e, t, r = []) {
|
|
|
69
69
|
a(), o();
|
|
70
70
|
} };
|
|
71
71
|
}
|
|
72
|
-
function b(e) {
|
|
73
|
-
return !(a(e) && i(e));
|
|
74
|
-
}
|
|
75
72
|
function x(e) {
|
|
76
|
-
|
|
73
|
+
return !(o(e) && a(e));
|
|
74
|
+
}
|
|
75
|
+
function S(e) {
|
|
76
|
+
let t = i();
|
|
77
77
|
if (!t) throw Error(`[poveste] <${e}> can only be set up inside a component`);
|
|
78
78
|
return t;
|
|
79
79
|
}
|
|
80
|
+
var C = !1;
|
|
81
|
+
function w(t) {
|
|
82
|
+
C || e() || (C = !0, console.warn(`[poveste] ${t} was rendered by a copy of Vue other than \`@poveste/vendors/vue\`, so it renders nothing. Poveste's client components are built against that copy: create the app with \`createApp\` from \`@poveste/vendors/vue\`, not from \`vue\`.`));
|
|
83
|
+
}
|
|
80
84
|
//#endregion
|
|
81
|
-
export {
|
|
85
|
+
export { _ as _toRawDeep, x as isWritableBinding, b as syncStateBundledAndExternal, h as toRawDeep, S as useInstance, w as warnIfForeignVue };
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { e as run };
|
|
1
|
+
export { run } from "./server/run.js";
|
|
@@ -50,3 +50,15 @@ export declare function syncStateBundledAndExternal(bundledState: Record<string,
|
|
|
50
50
|
*/
|
|
51
51
|
export declare function isWritableBinding(value: unknown): boolean;
|
|
52
52
|
export declare function useInstance(name: string): ComponentInternalInstance;
|
|
53
|
+
/**
|
|
54
|
+
* Warns, once, when a component built against `@poveste/vendors/vue` is
|
|
55
|
+
* rendered by another copy of Vue.
|
|
56
|
+
*
|
|
57
|
+
* The other copy runs the component's `setup`, so the vendored one has no
|
|
58
|
+
* current instance and drops every hook: the story renders nothing, and no build
|
|
59
|
+
* of Vue says why. A test mounting `MountStory` with `createApp` from `vue` read
|
|
60
|
+
* it as a story with no variants (#940).
|
|
61
|
+
*/
|
|
62
|
+
export declare function warnIfForeignVue(component: string): void;
|
|
63
|
+
/** For specs: lets the warning be seen again. */
|
|
64
|
+
export declare function resetForeignVueWarning(): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DocgenExtractor, DocgenExtractorContext } from '@poveste/shared';
|
|
2
|
+
export declare function findTsconfig(root: string): string | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Runs in the docgen worker. `vue-component-meta` is imported here rather than at
|
|
5
|
+
* the top, so a book without TypeScript fails this one request, answered with its
|
|
6
|
+
* error, and keeps its runtime props.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createExtractor(context: DocgenExtractorContext): Promise<DocgenExtractor>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'pathe';
|
|
3
|
+
import { toComponentDoc } from './meta.js';
|
|
4
|
+
// `tsconfig.app.json` first: Vite's Vue template splits the app's settings into it
|
|
5
|
+
// and leaves `tsconfig.json` holding only references, which type nothing.
|
|
6
|
+
const TSCONFIGS = ['tsconfig.app.json', 'tsconfig.json'];
|
|
7
|
+
const SOURCE = /\.(?:vue|[cm]?[jt]sx?)$/;
|
|
8
|
+
export function findTsconfig(root) {
|
|
9
|
+
return TSCONFIGS.map(name => join(root, name)).find(file => existsSync(file));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Runs in the docgen worker. `vue-component-meta` is imported here rather than at
|
|
13
|
+
* the top, so a book without TypeScript fails this one request, answered with its
|
|
14
|
+
* error, and keeps its runtime props.
|
|
15
|
+
*/
|
|
16
|
+
export async function createExtractor(context) {
|
|
17
|
+
const { createChecker, createCheckerByJson } = await import('vue-component-meta');
|
|
18
|
+
const options = (context.options ?? {});
|
|
19
|
+
const checkerOptions = { forceUseTs: true, printer: { newLine: 1 } };
|
|
20
|
+
const tsconfig = findTsconfig(context.root);
|
|
21
|
+
const checker = tsconfig
|
|
22
|
+
? createChecker(tsconfig, checkerOptions)
|
|
23
|
+
: createCheckerByJson(context.root, { include: ['**/*'], exclude: ['node_modules'] }, checkerOptions);
|
|
24
|
+
return {
|
|
25
|
+
extract: file => toComponentDoc(checker.getComponentMeta(file), options),
|
|
26
|
+
update(file) {
|
|
27
|
+
if (!SOURCE.test(file)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (existsSync(file)) {
|
|
31
|
+
checker.updateFile(file, readFileSync(file, 'utf8'));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
checker.deleteFile(file);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
sources: () => checker.getProgram()?.getSourceFiles().map(sourceFile => sourceFile.fileName).filter(file => !file.includes('/node_modules/')) ?? [],
|
|
38
|
+
// The program is released with the checker; this drops what it cached.
|
|
39
|
+
dispose: () => checker.clearCache(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentDoc } from '@poveste/shared';
|
|
2
|
+
import type { ComponentMeta } from 'vue-component-meta';
|
|
3
|
+
export interface VueDocgenOptions {
|
|
4
|
+
/** Packages whose props count although they are declared under `node_modules`. */
|
|
5
|
+
allow?: string[];
|
|
6
|
+
}
|
|
7
|
+
/** A default as written, without the quoting a tag or the printer added. */
|
|
8
|
+
export declare function normalizeDefault(value: string): string;
|
|
9
|
+
export declare function toComponentDoc(meta: ComponentMeta, options?: VueDocgenOptions): ComponentDoc;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const HIDDEN_TAGS = ['internal', 'private'];
|
|
2
|
+
// Aliases: bits-ui mixes them about 50/50, reka-ui and nuxt/ui write `@defaultValue`.
|
|
3
|
+
const DEFAULT_TAGS = ['default', 'defaultValue'];
|
|
4
|
+
function tagsOf(tags) {
|
|
5
|
+
return tags.map(({ name, text }) => text === undefined ? { name } : { name, text });
|
|
6
|
+
}
|
|
7
|
+
function isHidden(tags) {
|
|
8
|
+
return tags.some(tag => HIDDEN_TAGS.includes(tag.name));
|
|
9
|
+
}
|
|
10
|
+
/** Declared only in installed packages none of which is allowed: an inherited attribute. */
|
|
11
|
+
function isInherited(prop, allow) {
|
|
12
|
+
const files = prop.getDeclarations().map(declaration => declaration.file.replaceAll('\\', '/'));
|
|
13
|
+
return files.length > 0 && files.every(file => file.includes('/node_modules/') && !allow.some(pkg => file.includes(`/node_modules/${pkg}/`)));
|
|
14
|
+
}
|
|
15
|
+
/** A default as written, without the quoting a tag or the printer added. */
|
|
16
|
+
export function normalizeDefault(value) {
|
|
17
|
+
return value.trim().replace(/^`(.*)`$/s, '$1').replace(/^'(.*)'$/s, '"$1"');
|
|
18
|
+
}
|
|
19
|
+
function toProp(prop) {
|
|
20
|
+
const defaultTag = prop.tags.find(tag => DEFAULT_TAGS.includes(tag.name))?.text;
|
|
21
|
+
return {
|
|
22
|
+
name: prop.name,
|
|
23
|
+
...prop.description ? { description: prop.description } : {},
|
|
24
|
+
type: prop.type,
|
|
25
|
+
required: prop.required,
|
|
26
|
+
...prop.default !== undefined ? { default: prop.default } : {},
|
|
27
|
+
...defaultTag !== undefined ? { defaultTag } : {},
|
|
28
|
+
// The code's default is what runs, so it is the one shown; the flag is for the reader.
|
|
29
|
+
...prop.default !== undefined && defaultTag !== undefined && normalizeDefault(prop.default) !== normalizeDefault(defaultTag)
|
|
30
|
+
? { defaultConflict: true }
|
|
31
|
+
: {},
|
|
32
|
+
tags: tagsOf(prop.tags),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function toMember(member) {
|
|
36
|
+
return {
|
|
37
|
+
name: member.name,
|
|
38
|
+
...member.description ? { description: member.description } : {},
|
|
39
|
+
...member.type ? { type: member.type } : {},
|
|
40
|
+
tags: tagsOf(member.tags),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function toComponentDoc(meta, options = {}) {
|
|
44
|
+
const allow = options.allow ?? [];
|
|
45
|
+
return {
|
|
46
|
+
props: meta.props.filter(prop => !prop.global && !isHidden(prop.tags) && !isInherited(prop, allow)).map(toProp),
|
|
47
|
+
slots: meta.slots.filter(slot => !isHidden(slot.tags)).map(toMember),
|
|
48
|
+
events: meta.events.filter(event => !isHidden(event.tags)).map(toMember),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The version of the first `typescript` found in `dirs`, searched in order as Node searches `node_modules`. */
|
|
2
|
+
export declare function typescriptVersionIn(dirs: string[]): string | undefined;
|
|
3
|
+
/** Why auto-docs cannot read a Vue component's types with the TypeScript in `dirs`, or nothing when it can. */
|
|
4
|
+
export declare function typescriptProblemIn(dirs: string[]): string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* The same question for the TypeScript `vue-component-meta` will load: it requires
|
|
7
|
+
* `typescript` from where it is installed, an optional peer that npm does not add.
|
|
8
|
+
*/
|
|
9
|
+
export declare function typescriptProblem(): string | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { join } from 'pathe';
|
|
4
|
+
// `vue-component-meta` 3.3 calls `ts.readJsonConfigFile`, which TypeScript 7 no
|
|
5
|
+
// longer has, and no release of it reads TypeScript 7 yet (#1382).
|
|
6
|
+
const NEWEST_MAJOR = 6;
|
|
7
|
+
const FIX = 'Install `typescript@6` in the book to turn it on, or set `autoDocs: false` to turn it off without this message.';
|
|
8
|
+
/** The version of the first `typescript` found in `dirs`, searched in order as Node searches `node_modules`. */
|
|
9
|
+
export function typescriptVersionIn(dirs) {
|
|
10
|
+
for (const dir of dirs) {
|
|
11
|
+
const manifest = join(dir, 'typescript', 'package.json');
|
|
12
|
+
if (existsSync(manifest)) {
|
|
13
|
+
return JSON.parse(readFileSync(manifest, 'utf8')).version;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
/** Why auto-docs cannot read a Vue component's types with the TypeScript in `dirs`, or nothing when it can. */
|
|
19
|
+
export function typescriptProblemIn(dirs) {
|
|
20
|
+
const version = typescriptVersionIn(dirs);
|
|
21
|
+
if (!version) {
|
|
22
|
+
return `Auto-docs is off: it reads component types with TypeScript, which this book does not have. ${FIX}`;
|
|
23
|
+
}
|
|
24
|
+
if (Number.parseInt(version, 10) > NEWEST_MAJOR) {
|
|
25
|
+
return `Auto-docs is off: it reads component types with TypeScript ${NEWEST_MAJOR} or earlier, and this book has TypeScript ${version}. ${FIX}`;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The same question for the TypeScript `vue-component-meta` will load: it requires
|
|
31
|
+
* `typescript` from where it is installed, an optional peer that npm does not add.
|
|
32
|
+
*/
|
|
33
|
+
export function typescriptProblem() {
|
|
34
|
+
const meta = createRequire(import.meta.url).resolve('vue-component-meta');
|
|
35
|
+
return typescriptProblemIn(createRequire(meta).resolve.paths('typescript') ?? []);
|
|
36
|
+
}
|
package/dist/index.node.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import generateStoryCommand from './commands/generate-story.server.js';
|
|
6
|
+
import { typescriptProblem } from './docgen/typescript.js';
|
|
5
7
|
import { VUE_SETUP_HOOK_NAMES } from './setup-hooks.js';
|
|
6
8
|
import { listComponentFiles } from './util/list-components.js';
|
|
7
9
|
import { VAPOR_INTEROP_ID, vaporInteropModule, withoutVaporPlugin } from './util/vapor.js';
|
|
10
|
+
import { requireVuePlugin } from './util/vue-plugin.js';
|
|
8
11
|
export function HstVue() {
|
|
9
12
|
return {
|
|
10
13
|
name: '@poveste/plugin-vue',
|
|
@@ -23,6 +26,7 @@ export function HstVue() {
|
|
|
23
26
|
return {
|
|
24
27
|
vite: {
|
|
25
28
|
plugins: [
|
|
29
|
+
requireVuePlugin(),
|
|
26
30
|
withoutVaporPlugin(),
|
|
27
31
|
(() => {
|
|
28
32
|
let root = process.cwd();
|
|
@@ -52,6 +56,12 @@ export function HstVue() {
|
|
|
52
56
|
},
|
|
53
57
|
};
|
|
54
58
|
},
|
|
59
|
+
// A story file documents nothing: it names the components that do.
|
|
60
|
+
docgen: {
|
|
61
|
+
match: file => file.endsWith('.vue') && !file.endsWith('.story.vue'),
|
|
62
|
+
module: fileURLToPath(new URL('./docgen/extractor.js', import.meta.url)),
|
|
63
|
+
unavailable: () => typescriptProblem(),
|
|
64
|
+
},
|
|
55
65
|
supportPlugin: {
|
|
56
66
|
id: 'vue3',
|
|
57
67
|
moduleName: '@poveste/plugin-vue',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Plugin as VitePlugin } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* What to say when a book's Vite config has no Vue plugin. Without one, Vite
|
|
4
|
+
* answers the first `.vue` file with "Install @vitejs/plugin-vue", which sends a
|
|
5
|
+
* Quasar reader to install a package their project does not need: Quasar brings
|
|
6
|
+
* its own Vue plugin, and `HstQuasar()` is what hands it to Poveste (#1337).
|
|
7
|
+
*/
|
|
8
|
+
export declare function noVuePluginMessage(root: string, exists?: (path: string) => boolean): string;
|
|
9
|
+
/** Reports, once per process, a resolved Vite config with no Vue plugin in it. */
|
|
10
|
+
export declare function requireVuePlugin(): VitePlugin;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const QUASAR_CONFIGS = ['quasar.config.js', 'quasar.config.mjs', 'quasar.config.cjs', 'quasar.config.ts'];
|
|
4
|
+
/**
|
|
5
|
+
* What to say when a book's Vite config has no Vue plugin. Without one, Vite
|
|
6
|
+
* answers the first `.vue` file with "Install @vitejs/plugin-vue", which sends a
|
|
7
|
+
* Quasar reader to install a package their project does not need: Quasar brings
|
|
8
|
+
* its own Vue plugin, and `HstQuasar()` is what hands it to Poveste (#1337).
|
|
9
|
+
*/
|
|
10
|
+
export function noVuePluginMessage(root, exists = existsSync) {
|
|
11
|
+
const fix = QUASAR_CONFIGS.some(name => exists(join(root, name)))
|
|
12
|
+
? 'This is a Quasar project: add `HstQuasar()` from `@poveste/plugin-quasar` to the config\'s `plugins`, which brings Quasar\'s own Vue plugin.'
|
|
13
|
+
: 'Add `@vitejs/plugin-vue` to `vite.config`, or in a Nuxt project `HstNuxt()` from `@poveste/plugin-nuxt` to the config\'s `plugins`.';
|
|
14
|
+
return `[poveste] The Vite config has no Vue plugin, so no \`.vue\` story can be compiled. ${fix}`;
|
|
15
|
+
}
|
|
16
|
+
/** Reports, once per process, a resolved Vite config with no Vue plugin in it. */
|
|
17
|
+
export function requireVuePlugin() {
|
|
18
|
+
let reported = false;
|
|
19
|
+
return {
|
|
20
|
+
name: 'poveste-plugin-vue:require-vue-plugin',
|
|
21
|
+
configResolved(config) {
|
|
22
|
+
if (!reported && !config.plugins.some(plugin => plugin.name === 'vite:vue')) {
|
|
23
|
+
reported = true;
|
|
24
|
+
console.error(noVuePluginMessage(config.root));
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poveste/plugin-vue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.20.0",
|
|
5
5
|
"description": "Poveste plugin for Vue.js support",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sorin Gitlan",
|
|
@@ -72,27 +72,28 @@
|
|
|
72
72
|
"node": ">=22.22.2"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"poveste": "^0.
|
|
75
|
+
"poveste": "^0.20.0",
|
|
76
76
|
"vue": "^3.5.43"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@poveste/controls": "^0.
|
|
80
|
-
"@poveste/shared": "^0.
|
|
81
|
-
"@poveste/vendors": "^0.
|
|
79
|
+
"@poveste/controls": "^0.20.0",
|
|
80
|
+
"@poveste/shared": "^0.20.0",
|
|
81
|
+
"@poveste/vendors": "^0.20.0",
|
|
82
82
|
"change-case": "^5.4.4",
|
|
83
83
|
"ignore": "^7.0.10",
|
|
84
|
-
"launch-editor": "^2.
|
|
84
|
+
"launch-editor": "^2.14.2",
|
|
85
85
|
"pathe": "^2.0.3",
|
|
86
|
-
"tinyglobby": "^0.2.17"
|
|
86
|
+
"tinyglobby": "^0.2.17",
|
|
87
|
+
"vue-component-meta": "^3.3.13"
|
|
87
88
|
},
|
|
88
89
|
"devDependencies": {
|
|
89
|
-
"@types/node": "^26.6.
|
|
90
|
-
"concurrently": "^10.0.
|
|
91
|
-
"poveste": "0.
|
|
90
|
+
"@types/node": "^26.6.5",
|
|
91
|
+
"concurrently": "^10.0.6",
|
|
92
|
+
"poveste": "0.20.0",
|
|
92
93
|
"typescript": "6.0.3",
|
|
93
|
-
"vite": "^8.3.
|
|
94
|
-
"vitest": "^5.0.
|
|
95
|
-
"vue": "^3.5.
|
|
94
|
+
"vite": "^8.3.4",
|
|
95
|
+
"vitest": "^5.0.4",
|
|
96
|
+
"vue": "^3.5.44",
|
|
96
97
|
"vue-vapor-rc": "npm:vue@3.6.0-rc.10"
|
|
97
98
|
},
|
|
98
99
|
"scripts": {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '@poveste/vendors/vue'
|
|
13
13
|
import { reactive } from 'vue'
|
|
14
14
|
import { createPreviewHost } from './host.js'
|
|
15
|
+
import { warnIfForeignVue } from './util.js'
|
|
15
16
|
|
|
16
17
|
export default _defineComponent({
|
|
17
18
|
name: 'MountStory',
|
|
@@ -34,6 +35,7 @@ export default _defineComponent({
|
|
|
34
35
|
},
|
|
35
36
|
|
|
36
37
|
setup(props) {
|
|
38
|
+
warnIfForeignVue('MountStory')
|
|
37
39
|
const el = _ref<HTMLDivElement>()
|
|
38
40
|
const renderContext = reactive({
|
|
39
41
|
mode: 'mount' as const,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from '@poveste/vendors/vue'
|
|
13
13
|
import { reactive } from 'vue'
|
|
14
14
|
import { createPreviewHost } from './host.js'
|
|
15
|
-
import { syncStateBundledAndExternal } from './util.js'
|
|
15
|
+
import { syncStateBundledAndExternal, warnIfForeignVue } from './util.js'
|
|
16
16
|
|
|
17
17
|
export default _defineComponent({
|
|
18
18
|
name: 'RenderStory',
|
|
@@ -39,6 +39,7 @@ export default _defineComponent({
|
|
|
39
39
|
},
|
|
40
40
|
|
|
41
41
|
setup(props, { emit }) {
|
|
42
|
+
warnIfForeignVue('RenderStory')
|
|
42
43
|
const sandbox = _ref<HTMLDivElement>()
|
|
43
44
|
let host: ReturnType<typeof createPreviewHost> | null = null
|
|
44
45
|
let mounting = false
|
package/src/client/app/util.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ComponentInternalInstance } from 'vue'
|
|
2
2
|
import { applyState, createStateBaseline, isMarkedRaw, isPlainObject } from '@poveste/shared'
|
|
3
3
|
import {
|
|
4
|
+
getCurrentInstance as _getCurrentInstance,
|
|
4
5
|
isRef as _isRef,
|
|
5
6
|
unref as _unref,
|
|
6
7
|
watch as _watch,
|
|
@@ -241,3 +242,27 @@ export function useInstance(name: string): ComponentInternalInstance {
|
|
|
241
242
|
}
|
|
242
243
|
return vm
|
|
243
244
|
}
|
|
245
|
+
|
|
246
|
+
let warnedForeignVue = false
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Warns, once, when a component built against `@poveste/vendors/vue` is
|
|
250
|
+
* rendered by another copy of Vue.
|
|
251
|
+
*
|
|
252
|
+
* The other copy runs the component's `setup`, so the vendored one has no
|
|
253
|
+
* current instance and drops every hook: the story renders nothing, and no build
|
|
254
|
+
* of Vue says why. A test mounting `MountStory` with `createApp` from `vue` read
|
|
255
|
+
* it as a story with no variants (#940).
|
|
256
|
+
*/
|
|
257
|
+
export function warnIfForeignVue(component: string) {
|
|
258
|
+
if (warnedForeignVue || _getCurrentInstance()) {
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
warnedForeignVue = true
|
|
262
|
+
console.warn(`[poveste] ${component} was rendered by a copy of Vue other than \`@poveste/vendors/vue\`, so it renders nothing. Poveste's client components are built against that copy: create the app with \`createApp\` from \`@poveste/vendors/vue\`, not from \`vue\`.`)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** For specs: lets the warning be seen again. */
|
|
266
|
+
export function resetForeignVueWarning() {
|
|
267
|
+
warnedForeignVue = false
|
|
268
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DocgenExtractor, DocgenExtractorContext } from '@poveste/shared'
|
|
2
|
+
import type { VueDocgenOptions } from './meta.js'
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
import { join } from 'pathe'
|
|
5
|
+
import { toComponentDoc } from './meta.js'
|
|
6
|
+
|
|
7
|
+
// `tsconfig.app.json` first: Vite's Vue template splits the app's settings into it
|
|
8
|
+
// and leaves `tsconfig.json` holding only references, which type nothing.
|
|
9
|
+
const TSCONFIGS = ['tsconfig.app.json', 'tsconfig.json']
|
|
10
|
+
const SOURCE = /\.(?:vue|[cm]?[jt]sx?)$/
|
|
11
|
+
|
|
12
|
+
export function findTsconfig(root: string) {
|
|
13
|
+
return TSCONFIGS.map(name => join(root, name)).find(file => existsSync(file))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs in the docgen worker. `vue-component-meta` is imported here rather than at
|
|
18
|
+
* the top, so a book without TypeScript fails this one request, answered with its
|
|
19
|
+
* error, and keeps its runtime props.
|
|
20
|
+
*/
|
|
21
|
+
export async function createExtractor(context: DocgenExtractorContext): Promise<DocgenExtractor> {
|
|
22
|
+
const { createChecker, createCheckerByJson } = await import('vue-component-meta')
|
|
23
|
+
const options = (context.options ?? {}) as VueDocgenOptions
|
|
24
|
+
const checkerOptions = { forceUseTs: true, printer: { newLine: 1 } }
|
|
25
|
+
const tsconfig = findTsconfig(context.root)
|
|
26
|
+
const checker = tsconfig
|
|
27
|
+
? createChecker(tsconfig, checkerOptions)
|
|
28
|
+
: createCheckerByJson(context.root, { include: ['**/*'], exclude: ['node_modules'] }, checkerOptions)
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
extract: file => toComponentDoc(checker.getComponentMeta(file), options),
|
|
32
|
+
update(file) {
|
|
33
|
+
if (!SOURCE.test(file)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
if (existsSync(file)) {
|
|
37
|
+
checker.updateFile(file, readFileSync(file, 'utf8'))
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
checker.deleteFile(file)
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
sources: () => checker.getProgram()?.getSourceFiles().map(sourceFile => sourceFile.fileName).filter(file => !file.includes('/node_modules/')) ?? [],
|
|
44
|
+
// The program is released with the checker; this drops what it cached.
|
|
45
|
+
dispose: () => checker.clearCache(),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ComponentDoc, DocEvent, DocProp, DocSlot, DocTag } from '@poveste/shared'
|
|
2
|
+
import type { ComponentMeta, EventMeta, PropertyMeta, SlotMeta } from 'vue-component-meta'
|
|
3
|
+
|
|
4
|
+
export interface VueDocgenOptions {
|
|
5
|
+
/** Packages whose props count although they are declared under `node_modules`. */
|
|
6
|
+
allow?: string[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type Tags = { name: string, text?: string }[]
|
|
10
|
+
|
|
11
|
+
const HIDDEN_TAGS = ['internal', 'private']
|
|
12
|
+
// Aliases: bits-ui mixes them about 50/50, reka-ui and nuxt/ui write `@defaultValue`.
|
|
13
|
+
const DEFAULT_TAGS = ['default', 'defaultValue']
|
|
14
|
+
|
|
15
|
+
function tagsOf(tags: Tags): DocTag[] {
|
|
16
|
+
return tags.map(({ name, text }) => text === undefined ? { name } : { name, text })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isHidden(tags: Tags) {
|
|
20
|
+
return tags.some(tag => HIDDEN_TAGS.includes(tag.name))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Declared only in installed packages none of which is allowed: an inherited attribute. */
|
|
24
|
+
function isInherited(prop: PropertyMeta, allow: string[]) {
|
|
25
|
+
const files = prop.getDeclarations().map(declaration => declaration.file.replaceAll('\\', '/'))
|
|
26
|
+
return files.length > 0 && files.every(file =>
|
|
27
|
+
file.includes('/node_modules/') && !allow.some(pkg => file.includes(`/node_modules/${pkg}/`)))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A default as written, without the quoting a tag or the printer added. */
|
|
31
|
+
export function normalizeDefault(value: string) {
|
|
32
|
+
return value.trim().replace(/^`(.*)`$/s, '$1').replace(/^'(.*)'$/s, '"$1"')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toProp(prop: PropertyMeta): DocProp {
|
|
36
|
+
const defaultTag = prop.tags.find(tag => DEFAULT_TAGS.includes(tag.name))?.text
|
|
37
|
+
return {
|
|
38
|
+
name: prop.name,
|
|
39
|
+
...prop.description ? { description: prop.description } : {},
|
|
40
|
+
type: prop.type,
|
|
41
|
+
required: prop.required,
|
|
42
|
+
...prop.default !== undefined ? { default: prop.default } : {},
|
|
43
|
+
...defaultTag !== undefined ? { defaultTag } : {},
|
|
44
|
+
// The code's default is what runs, so it is the one shown; the flag is for the reader.
|
|
45
|
+
...prop.default !== undefined && defaultTag !== undefined && normalizeDefault(prop.default) !== normalizeDefault(defaultTag)
|
|
46
|
+
? { defaultConflict: true }
|
|
47
|
+
: {},
|
|
48
|
+
tags: tagsOf(prop.tags),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function toMember(member: SlotMeta | EventMeta): DocSlot & DocEvent {
|
|
53
|
+
return {
|
|
54
|
+
name: member.name,
|
|
55
|
+
...member.description ? { description: member.description } : {},
|
|
56
|
+
...member.type ? { type: member.type } : {},
|
|
57
|
+
tags: tagsOf(member.tags),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function toComponentDoc(meta: ComponentMeta, options: VueDocgenOptions = {}): ComponentDoc {
|
|
62
|
+
const allow = options.allow ?? []
|
|
63
|
+
return {
|
|
64
|
+
props: meta.props.filter(prop => !prop.global && !isHidden(prop.tags) && !isInherited(prop, allow)).map(toProp),
|
|
65
|
+
slots: meta.slots.filter(slot => !isHidden(slot.tags)).map(toMember),
|
|
66
|
+
events: meta.events.filter(event => !isHidden(event.tags)).map(toMember),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import { join } from 'pathe'
|
|
4
|
+
|
|
5
|
+
// `vue-component-meta` 3.3 calls `ts.readJsonConfigFile`, which TypeScript 7 no
|
|
6
|
+
// longer has, and no release of it reads TypeScript 7 yet (#1382).
|
|
7
|
+
const NEWEST_MAJOR = 6
|
|
8
|
+
|
|
9
|
+
const FIX = 'Install `typescript@6` in the book to turn it on, or set `autoDocs: false` to turn it off without this message.'
|
|
10
|
+
|
|
11
|
+
/** The version of the first `typescript` found in `dirs`, searched in order as Node searches `node_modules`. */
|
|
12
|
+
export function typescriptVersionIn(dirs: string[]): string | undefined {
|
|
13
|
+
for (const dir of dirs) {
|
|
14
|
+
const manifest = join(dir, 'typescript', 'package.json')
|
|
15
|
+
if (existsSync(manifest)) {
|
|
16
|
+
return JSON.parse(readFileSync(manifest, 'utf8')).version
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Why auto-docs cannot read a Vue component's types with the TypeScript in `dirs`, or nothing when it can. */
|
|
23
|
+
export function typescriptProblemIn(dirs: string[]): string | undefined {
|
|
24
|
+
const version = typescriptVersionIn(dirs)
|
|
25
|
+
if (!version) {
|
|
26
|
+
return `Auto-docs is off: it reads component types with TypeScript, which this book does not have. ${FIX}`
|
|
27
|
+
}
|
|
28
|
+
if (Number.parseInt(version, 10) > NEWEST_MAJOR) {
|
|
29
|
+
return `Auto-docs is off: it reads component types with TypeScript ${NEWEST_MAJOR} or earlier, and this book has TypeScript ${version}. ${FIX}`
|
|
30
|
+
}
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The same question for the TypeScript `vue-component-meta` will load: it requires
|
|
36
|
+
* `typescript` from where it is installed, an optional peer that npm does not add.
|
|
37
|
+
*/
|
|
38
|
+
export function typescriptProblem(): string | undefined {
|
|
39
|
+
const meta = createRequire(import.meta.url).resolve('vue-component-meta')
|
|
40
|
+
return typescriptProblemIn(createRequire(meta).resolve.paths('typescript') ?? [])
|
|
41
|
+
}
|
package/src/index.node.ts
CHANGED
|
@@ -3,11 +3,14 @@ import type { Plugin } from 'poveste'
|
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
4
|
import { createRequire } from 'node:module'
|
|
5
5
|
import { join } from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
6
7
|
|
|
7
8
|
import generateStoryCommand from './commands/generate-story.server.js'
|
|
9
|
+
import { typescriptProblem } from './docgen/typescript.js'
|
|
8
10
|
import { VUE_SETUP_HOOK_NAMES } from './setup-hooks.js'
|
|
9
11
|
import { listComponentFiles } from './util/list-components.js'
|
|
10
12
|
import { VAPOR_INTEROP_ID, vaporInteropModule, withoutVaporPlugin } from './util/vapor.js'
|
|
13
|
+
import { requireVuePlugin } from './util/vue-plugin.js'
|
|
11
14
|
|
|
12
15
|
export function HstVue(): Plugin {
|
|
13
16
|
return {
|
|
@@ -29,6 +32,7 @@ export function HstVue(): Plugin {
|
|
|
29
32
|
return {
|
|
30
33
|
vite: {
|
|
31
34
|
plugins: [
|
|
35
|
+
requireVuePlugin(),
|
|
32
36
|
withoutVaporPlugin(),
|
|
33
37
|
(() => {
|
|
34
38
|
let root = process.cwd()
|
|
@@ -59,6 +63,13 @@ export function HstVue(): Plugin {
|
|
|
59
63
|
}
|
|
60
64
|
},
|
|
61
65
|
|
|
66
|
+
// A story file documents nothing: it names the components that do.
|
|
67
|
+
docgen: {
|
|
68
|
+
match: file => file.endsWith('.vue') && !file.endsWith('.story.vue'),
|
|
69
|
+
module: fileURLToPath(new URL('./docgen/extractor.js', import.meta.url)),
|
|
70
|
+
unavailable: () => typescriptProblem(),
|
|
71
|
+
},
|
|
72
|
+
|
|
62
73
|
supportPlugin: {
|
|
63
74
|
id: 'vue3',
|
|
64
75
|
moduleName: '@poveste/plugin-vue',
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Plugin as VitePlugin } from 'vite'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
const QUASAR_CONFIGS = ['quasar.config.js', 'quasar.config.mjs', 'quasar.config.cjs', 'quasar.config.ts']
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What to say when a book's Vite config has no Vue plugin. Without one, Vite
|
|
9
|
+
* answers the first `.vue` file with "Install @vitejs/plugin-vue", which sends a
|
|
10
|
+
* Quasar reader to install a package their project does not need: Quasar brings
|
|
11
|
+
* its own Vue plugin, and `HstQuasar()` is what hands it to Poveste (#1337).
|
|
12
|
+
*/
|
|
13
|
+
export function noVuePluginMessage(root: string, exists: (path: string) => boolean = existsSync): string {
|
|
14
|
+
const fix = QUASAR_CONFIGS.some(name => exists(join(root, name)))
|
|
15
|
+
? 'This is a Quasar project: add `HstQuasar()` from `@poveste/plugin-quasar` to the config\'s `plugins`, which brings Quasar\'s own Vue plugin.'
|
|
16
|
+
: 'Add `@vitejs/plugin-vue` to `vite.config`, or in a Nuxt project `HstNuxt()` from `@poveste/plugin-nuxt` to the config\'s `plugins`.'
|
|
17
|
+
return `[poveste] The Vite config has no Vue plugin, so no \`.vue\` story can be compiled. ${fix}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Reports, once per process, a resolved Vite config with no Vue plugin in it. */
|
|
21
|
+
export function requireVuePlugin(): VitePlugin {
|
|
22
|
+
let reported = false
|
|
23
|
+
return {
|
|
24
|
+
name: 'poveste-plugin-vue:require-vue-plugin',
|
|
25
|
+
configResolved(config) {
|
|
26
|
+
if (!reported && !config.plugins.some(plugin => plugin.name === 'vite:vue')) {
|
|
27
|
+
reported = true
|
|
28
|
+
console.error(noVuePluginMessage(config.root))
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
}
|