@poveste/plugin-vue 0.19.1 → 0.21.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/README.md +7 -0
- 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 +62 -60
- package/dist/bundled/client/app/global-components.js +5 -3
- package/dist/bundled/client/app/host.js +9 -5
- package/dist/bundled/client/app/util.js +46 -33
- package/dist/bundled/client/server/Story.js +13 -0
- package/dist/bundled/client/server/collect.js +41 -0
- package/dist/bundled/client/server/run.js +10 -39
- package/dist/bundled/client/server.js +1 -2
- package/dist/bundled/testing/index.js +168 -0
- package/dist/client/app/Story.d.ts +19 -1
- package/dist/client/app/Variant.d.ts +2 -2
- package/dist/client/app/global-components.d.ts +1 -0
- package/dist/client/app/host.d.ts +8 -0
- package/dist/client/app/util.d.ts +28 -0
- package/dist/client/server/Story.d.ts +21 -3
- package/dist/client/server/collect.d.ts +14 -0
- package/dist/client-dependencies.d.ts +2 -0
- package/dist/client-dependencies.js +2 -0
- package/dist/docgen/events.d.ts +9 -0
- package/dist/docgen/events.js +45 -0
- package/dist/docgen/extractor.d.ts +7 -0
- package/dist/docgen/extractor.js +60 -0
- package/dist/docgen/generic.d.ts +5 -0
- package/dist/docgen/generic.js +19 -0
- package/dist/docgen/meta.d.ts +12 -0
- package/dist/docgen/meta.js +76 -0
- package/dist/docgen/slots.d.ts +6 -0
- package/dist/docgen/slots.js +21 -0
- package/dist/index.node.d.ts +2 -0
- package/dist/index.node.js +24 -11
- package/dist/testing/index.d.ts +28 -0
- package/dist/testing/vite.d.ts +18 -0
- package/dist/testing/vite.js +171 -0
- package/dist/util/project-vue.d.ts +1 -0
- package/dist/util/project-vue.js +12 -0
- package/dist/util/vue-plugin.d.ts +10 -0
- package/dist/util/vue-plugin.js +28 -0
- package/package.json +33 -15
- package/src/client/app/MountStory.ts +2 -0
- package/src/client/app/RenderStory.ts +2 -1
- package/src/client/app/Story.ts +15 -3
- package/src/client/app/global-components.ts +4 -0
- package/src/client/app/host.ts +21 -2
- package/src/client/app/util.ts +65 -0
- package/src/client/server/Story.ts +15 -2
- package/src/client/server/collect.ts +83 -0
- package/src/client/server/run.ts +3 -63
- package/src/client-dependencies.ts +2 -0
- package/src/docgen/events.ts +55 -0
- package/src/docgen/extractor.ts +67 -0
- package/src/docgen/generic.ts +20 -0
- package/src/docgen/meta.ts +93 -0
- package/src/docgen/slots.ts +33 -0
- package/src/index.node.ts +27 -12
- package/src/testing/index.ts +286 -0
- package/src/testing/vite.ts +194 -0
- package/src/util/project-vue.ts +13 -0
- package/src/util/vue-plugin.ts +32 -0
- package/testing.d.ts +2 -0
|
@@ -1,44 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { addStoryContext as t, storyFileContext as n } from "./context.js";
|
|
3
|
-
import r from "./Story.js";
|
|
4
|
-
import i from "./Variant.js";
|
|
5
|
-
import { createApp as a, h as o } from "vue";
|
|
6
|
-
import { getSetupHook as s } from "@poveste/shared";
|
|
7
|
-
import * as c from "virtual:$poveste-generated-global-setup";
|
|
8
|
-
import * as l from "virtual:$poveste-setup";
|
|
1
|
+
import { collectComponent as e } from "./collect.js";
|
|
9
2
|
//#region src/client/server/run.ts
|
|
10
|
-
async function
|
|
11
|
-
let { default:
|
|
3
|
+
async function t({ file: t, storyData: n, el: r }) {
|
|
4
|
+
let { default: i } = await import(/* @vite-ignore */
|
|
12
5
|
/* @vite-ignore */
|
|
13
|
-
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
render() {
|
|
21
|
-
return o(p);
|
|
22
|
-
}
|
|
6
|
+
t.moduleId
|
|
7
|
+
);
|
|
8
|
+
await e(i, {
|
|
9
|
+
file: t,
|
|
10
|
+
storyData: n,
|
|
11
|
+
el: r
|
|
23
12
|
});
|
|
24
|
-
m.component("Story", r), m.component("Variant", i);
|
|
25
|
-
let h = {
|
|
26
|
-
app: m,
|
|
27
|
-
story: void 0,
|
|
28
|
-
variant: void 0,
|
|
29
|
-
addWrapper: () => {}
|
|
30
|
-
}, g = s(c, e);
|
|
31
|
-
g && await g(h);
|
|
32
|
-
let _ = s(l, e);
|
|
33
|
-
if (_ && await _(h), m.mount(f), p.doc) {
|
|
34
|
-
let e = document.createElement("div");
|
|
35
|
-
e.innerHTML = p.doc;
|
|
36
|
-
let t = e.textContent;
|
|
37
|
-
d.forEach((e) => {
|
|
38
|
-
e.docsText = t;
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
m.unmount();
|
|
42
13
|
}
|
|
43
14
|
//#endregion
|
|
44
|
-
export {
|
|
15
|
+
export { t as run };
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { e as run };
|
|
1
|
+
export { run } from "./server/run.js";
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { syncStateBundledAndExternal as e } from "../client/app/util.js";
|
|
2
|
+
import { registerControlComponents as t } from "../client/app/global-components.js";
|
|
3
|
+
import { createPreviewHost as n } from "../client/app/host.js";
|
|
4
|
+
import { collectComponent as r } from "../client/server/collect.js";
|
|
5
|
+
import { markRaw as i, nextTick as a, reactive as o } from "vue";
|
|
6
|
+
import { environmentApp as s, environmentReady as c } from "virtual:$poveste-testing-environment";
|
|
7
|
+
import { describe as l, it as u } from "vitest";
|
|
8
|
+
//#region src/testing/index.ts
|
|
9
|
+
function d(e, t = {}) {
|
|
10
|
+
let n = t.name ?? x(e.__file) ?? "stories", r = t.timeout ?? 5e3;
|
|
11
|
+
l(n, async () => {
|
|
12
|
+
await c();
|
|
13
|
+
let { stories: t, problems: i } = await f(e, n);
|
|
14
|
+
if (i.length || !t.length) u("collects", () => {
|
|
15
|
+
throw y(`collecting ${n}`, i.length ? i : ["it has no <Story>"]);
|
|
16
|
+
});
|
|
17
|
+
else for (let e of t) l(e.title, () => {
|
|
18
|
+
for (let t of e.variants) u(t.title, async () => {
|
|
19
|
+
let n = await m(e, t, r);
|
|
20
|
+
if (n.length) throw y(`${e.title} › ${t.title}`, n);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async function f(e, t) {
|
|
26
|
+
let n = e.__file ?? t, a = x(n) ?? t, o = {
|
|
27
|
+
id: t.replace(/\W+/g, "-").toLowerCase(),
|
|
28
|
+
path: n,
|
|
29
|
+
moduleId: n,
|
|
30
|
+
fileName: a.includes(".") ? a.slice(0, a.indexOf(".")) : a
|
|
31
|
+
}, s = [], c = [], l = document.createElement("div");
|
|
32
|
+
try {
|
|
33
|
+
await r(e, {
|
|
34
|
+
file: o,
|
|
35
|
+
storyData: s,
|
|
36
|
+
el: l
|
|
37
|
+
}, {
|
|
38
|
+
beforeSetup(e) {
|
|
39
|
+
e.config.errorHandler = (e) => {
|
|
40
|
+
c.push(e);
|
|
41
|
+
}, e.config.warnHandler = (e, t, n) => {
|
|
42
|
+
c.push(b(e, n));
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
afterSetup(e) {
|
|
46
|
+
_(e._context.components, h()), g(e);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
} catch (e) {
|
|
50
|
+
c.push(e);
|
|
51
|
+
}
|
|
52
|
+
let u = {
|
|
53
|
+
id: o.id,
|
|
54
|
+
supportPluginId: "vue3",
|
|
55
|
+
component: i(e),
|
|
56
|
+
path: [],
|
|
57
|
+
filePath: o.path,
|
|
58
|
+
source: async () => ({ default: "" })
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
stories: s.map((e) => {
|
|
62
|
+
let t = {
|
|
63
|
+
...e,
|
|
64
|
+
file: i(u),
|
|
65
|
+
variants: e.variants.map((e) => p(e)),
|
|
66
|
+
slots: () => ({})
|
|
67
|
+
};
|
|
68
|
+
return u.story = t, t;
|
|
69
|
+
}),
|
|
70
|
+
problems: c
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function p(e) {
|
|
74
|
+
return {
|
|
75
|
+
...e,
|
|
76
|
+
state: o({
|
|
77
|
+
_hPropState: {},
|
|
78
|
+
_hPropDefs: {}
|
|
79
|
+
}),
|
|
80
|
+
setupApp: void 0,
|
|
81
|
+
slots: () => ({}),
|
|
82
|
+
previewReady: !1
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function m(t, r, i) {
|
|
86
|
+
let a = [], s = (e) => {
|
|
87
|
+
a.push(e);
|
|
88
|
+
}, c = (e, t) => {
|
|
89
|
+
a.push(b(e, t));
|
|
90
|
+
}, l = !1, u = document.createElement("div");
|
|
91
|
+
document.body.appendChild(u);
|
|
92
|
+
let d = n({
|
|
93
|
+
name: "StoryTestMount",
|
|
94
|
+
el: u,
|
|
95
|
+
getStory: () => t,
|
|
96
|
+
getVariant: () => null,
|
|
97
|
+
renderContext: o({
|
|
98
|
+
mode: "mount",
|
|
99
|
+
slotName: "default",
|
|
100
|
+
currentVariant: null,
|
|
101
|
+
targetVariantId: r.id,
|
|
102
|
+
externalState: null,
|
|
103
|
+
nextVariantIndex: { value: 0 }
|
|
104
|
+
}),
|
|
105
|
+
onError: s,
|
|
106
|
+
onWarn: c,
|
|
107
|
+
configureApp: g
|
|
108
|
+
}), f = o({}), p = e(r.state, f), m = n({
|
|
109
|
+
name: "StoryTestRender",
|
|
110
|
+
el: u,
|
|
111
|
+
getStory: () => t,
|
|
112
|
+
getVariant: () => r,
|
|
113
|
+
renderContext: o({
|
|
114
|
+
mode: "render",
|
|
115
|
+
slotName: "default",
|
|
116
|
+
currentVariant: r,
|
|
117
|
+
externalState: f,
|
|
118
|
+
nextVariantIndex: { value: 0 }
|
|
119
|
+
}),
|
|
120
|
+
wrapInDiv: !0,
|
|
121
|
+
onError: s,
|
|
122
|
+
onWarn: c,
|
|
123
|
+
onResolve: () => {
|
|
124
|
+
l = !0;
|
|
125
|
+
},
|
|
126
|
+
configureApp: g
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
await d.mount(), await v(() => !!r.configReady || a.length > 0, i) || a.push(/* @__PURE__ */ Error(`its setup did not settle within ${i} ms`)), a.length || (await m.mount(), await v(() => l || a.length > 0, i) || a.push(/* @__PURE__ */ Error(`its render did not settle within ${i} ms`)));
|
|
130
|
+
} catch (e) {
|
|
131
|
+
a.push(e);
|
|
132
|
+
} finally {
|
|
133
|
+
m.unmount(), d.unmount(), p.stop(), u.remove();
|
|
134
|
+
}
|
|
135
|
+
return a;
|
|
136
|
+
}
|
|
137
|
+
function h() {
|
|
138
|
+
let e = {};
|
|
139
|
+
return t({ component: (t, n) => {
|
|
140
|
+
e[t] = n;
|
|
141
|
+
} }), e;
|
|
142
|
+
}
|
|
143
|
+
function g(e) {
|
|
144
|
+
let t = s();
|
|
145
|
+
t && (_(e, t), _(e._context.provides, t._context.provides), _(e.config.globalProperties, t.config.globalProperties), _(e._context.components, t._context.components), _(e._context.directives, t._context.directives));
|
|
146
|
+
}
|
|
147
|
+
function _(e, t) {
|
|
148
|
+
for (let n of Reflect.ownKeys(t)) n in e || Object.defineProperty(e, n, Object.getOwnPropertyDescriptor(t, n));
|
|
149
|
+
}
|
|
150
|
+
async function v(e, t) {
|
|
151
|
+
let n = Date.now() + t;
|
|
152
|
+
do
|
|
153
|
+
if (await a(), await new Promise((e) => setTimeout(e)), e()) return !0;
|
|
154
|
+
while (Date.now() < n);
|
|
155
|
+
return e();
|
|
156
|
+
}
|
|
157
|
+
function y(e, t) {
|
|
158
|
+
let n = t[0], r = t.map((e) => e instanceof Error ? e.stack ?? e.message : String(e)), i = /* @__PURE__ */ Error(`${e}: ${t.length === 1 ? "one problem" : `${t.length} problems`}\n\n${r.join("\n\n")}`);
|
|
159
|
+
return n instanceof Error && (i.cause = n), i;
|
|
160
|
+
}
|
|
161
|
+
function b(e, t) {
|
|
162
|
+
return `[Vue warn]: ${e}${t ? `\n${t}` : ""}`;
|
|
163
|
+
}
|
|
164
|
+
function x(e) {
|
|
165
|
+
return e?.split(/[\\/]/).pop();
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { d as testStories };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Story } from '@poveste/shared';
|
|
2
|
-
import type { PropType, VNode } from 'vue';
|
|
2
|
+
import type { Component, PropType, VNode } from 'vue';
|
|
3
3
|
import type { PreviewRenderContext } from './render-context.js';
|
|
4
4
|
declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
5
5
|
initState: {
|
|
@@ -10,6 +10,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
10
10
|
type: PropType<Story["meta"]>;
|
|
11
11
|
default: undefined;
|
|
12
12
|
};
|
|
13
|
+
autoDocs: {
|
|
14
|
+
type: PropType<Story["autoDocs"]>;
|
|
15
|
+
default: undefined;
|
|
16
|
+
};
|
|
17
|
+
component: {
|
|
18
|
+
type: PropType<Component | Component[]>;
|
|
19
|
+
default: undefined;
|
|
20
|
+
};
|
|
13
21
|
}>, {
|
|
14
22
|
renderContext: PreviewRenderContext | null;
|
|
15
23
|
story: import("vue").ComputedRef<Story>;
|
|
@@ -29,8 +37,18 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
29
37
|
type: PropType<Story["meta"]>;
|
|
30
38
|
default: undefined;
|
|
31
39
|
};
|
|
40
|
+
autoDocs: {
|
|
41
|
+
type: PropType<Story["autoDocs"]>;
|
|
42
|
+
default: undefined;
|
|
43
|
+
};
|
|
44
|
+
component: {
|
|
45
|
+
type: PropType<Component | Component[]>;
|
|
46
|
+
default: undefined;
|
|
47
|
+
};
|
|
32
48
|
}>> & Readonly<{}>, {
|
|
49
|
+
component: Component | Component[];
|
|
33
50
|
meta: import("poveste").StoryMeta | undefined;
|
|
51
|
+
autoDocs: boolean | import("poveste").AutoDocsSection[] | undefined;
|
|
34
52
|
initState: () => any | Promise<any>;
|
|
35
53
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
36
54
|
export default _default;
|
|
@@ -69,11 +69,11 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
69
69
|
};
|
|
70
70
|
}>> & Readonly<{}>, {
|
|
71
71
|
meta: import("poveste").VariantMeta | undefined;
|
|
72
|
-
|
|
72
|
+
setupApp: Function;
|
|
73
73
|
source: string;
|
|
74
74
|
responsiveDisabled: boolean;
|
|
75
75
|
autoPropsDisabled: boolean;
|
|
76
|
-
|
|
76
|
+
initState: () => any | Promise<any>;
|
|
77
77
|
implicit: boolean;
|
|
78
78
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
79
79
|
export default _default;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Story, Variant } from '@poveste/shared';
|
|
2
|
+
import type { App } from 'vue';
|
|
2
3
|
import type { PreviewRenderContext } from './render-context.js';
|
|
3
4
|
interface PreviewHostOptions {
|
|
4
5
|
name: string;
|
|
@@ -7,6 +8,13 @@ interface PreviewHostOptions {
|
|
|
7
8
|
getVariant: () => Variant | null;
|
|
8
9
|
renderContext: PreviewRenderContext;
|
|
9
10
|
wrapInDiv?: boolean;
|
|
11
|
+
/** A story test's: what the app would only log, it fails on. */
|
|
12
|
+
onError?: (err: unknown) => void;
|
|
13
|
+
onWarn?: (message: string, trace: string) => void;
|
|
14
|
+
/** A story test's: when the render's async setups have settled. */
|
|
15
|
+
onResolve?: () => void;
|
|
16
|
+
/** A story test's, run after the app's own registrations and setup hooks. */
|
|
17
|
+
configureApp?: (app: App) => void;
|
|
10
18
|
}
|
|
11
19
|
export declare function createPreviewHost(options: PreviewHostOptions): {
|
|
12
20
|
mount: () => Promise<void>;
|
|
@@ -49,4 +49,32 @@ export declare function syncStateBundledAndExternal(bundledState: Record<string,
|
|
|
49
49
|
* it appeared in the panel and warned when written.
|
|
50
50
|
*/
|
|
51
51
|
export declare function isWritableBinding(value: unknown): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* A component a story holds in its setup, which implicit state skips.
|
|
54
|
+
*
|
|
55
|
+
* Each key is asked with `in` before it is read. Nuxt's runtime config is, in
|
|
56
|
+
* dev, a proxy that warns on any key it does not carry, and reading `__file`,
|
|
57
|
+
* `render` and `setup` off it printed `NUXT_E1003` three times a mount (#1454).
|
|
58
|
+
*/
|
|
59
|
+
export declare function isComponentBinding(value: unknown): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* A Vue app, or an object holding one in a data property of its own, as the
|
|
62
|
+
* Nuxt app `useNuxtApp()` returns does. Implicit state skips it: the state sync
|
|
63
|
+
* deep-watches what it holds, so it walked the whole app on every sync, as far
|
|
64
|
+
* as the dev-only `app.config.compilerOptions` getter that warns when read
|
|
65
|
+
* (#1457). Read through descriptors, so no getter runs to find out.
|
|
66
|
+
*/
|
|
67
|
+
export declare function holdsVueApp(value: unknown): boolean;
|
|
52
68
|
export declare function useInstance(name: string): ComponentInternalInstance;
|
|
69
|
+
/**
|
|
70
|
+
* Warns, once, when a component built against `@poveste/vendors/vue` is
|
|
71
|
+
* rendered by another copy of Vue.
|
|
72
|
+
*
|
|
73
|
+
* The other copy runs the component's `setup`, so the vendored one has no
|
|
74
|
+
* current instance and drops every hook: the story renders nothing, and no build
|
|
75
|
+
* of Vue says why. A test mounting `MountStory` with `createApp` from `vue` read
|
|
76
|
+
* it as a story with no variants (#940).
|
|
77
|
+
*/
|
|
78
|
+
export declare function warnIfForeignVue(component: string): void;
|
|
79
|
+
/** For specs: lets the warning be seen again. */
|
|
80
|
+
export declare function resetForeignVueWarning(): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ServerStory } from '@poveste/shared';
|
|
2
|
-
import type { PropType } from 'vue';
|
|
1
|
+
import type { AutoDocsSection, ServerStory } from '@poveste/shared';
|
|
2
|
+
import type { Component, PropType } from 'vue';
|
|
3
3
|
declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
4
|
title: {
|
|
5
5
|
type: StringConstructor;
|
|
@@ -29,6 +29,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
29
29
|
type: BooleanConstructor;
|
|
30
30
|
default: boolean;
|
|
31
31
|
};
|
|
32
|
+
autoDocs: {
|
|
33
|
+
type: PropType<boolean | AutoDocsSection[]>;
|
|
34
|
+
default: undefined;
|
|
35
|
+
};
|
|
36
|
+
component: {
|
|
37
|
+
type: PropType<Component | Component[]>;
|
|
38
|
+
default: undefined;
|
|
39
|
+
};
|
|
32
40
|
meta: {
|
|
33
41
|
type: PropType<ServerStory["meta"]>;
|
|
34
42
|
default: undefined;
|
|
@@ -64,18 +72,28 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
64
72
|
type: BooleanConstructor;
|
|
65
73
|
default: boolean;
|
|
66
74
|
};
|
|
75
|
+
autoDocs: {
|
|
76
|
+
type: PropType<boolean | AutoDocsSection[]>;
|
|
77
|
+
default: undefined;
|
|
78
|
+
};
|
|
79
|
+
component: {
|
|
80
|
+
type: PropType<Component | Component[]>;
|
|
81
|
+
default: undefined;
|
|
82
|
+
};
|
|
67
83
|
meta: {
|
|
68
84
|
type: PropType<ServerStory["meta"]>;
|
|
69
85
|
default: undefined;
|
|
70
86
|
};
|
|
71
87
|
}>> & Readonly<{}>, {
|
|
88
|
+
component: Component | Component[];
|
|
72
89
|
meta: import("poveste").StoryMeta | undefined;
|
|
90
|
+
autoDocs: boolean | AutoDocsSection[];
|
|
73
91
|
id: string;
|
|
74
92
|
title: string;
|
|
75
93
|
group: string;
|
|
76
94
|
layout: import("poveste").StoryLayout | undefined;
|
|
95
|
+
docsOnly: boolean;
|
|
77
96
|
icon: string;
|
|
78
97
|
iconColor: string;
|
|
79
|
-
docsOnly: boolean;
|
|
80
98
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
81
99
|
export default _default;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ServerRunPayload } from '@poveste/shared';
|
|
2
|
+
import type { App, Component } from 'vue';
|
|
3
|
+
/**
|
|
4
|
+
* Collects a story file's component that is already imported. Story tests get
|
|
5
|
+
* it from their own import, so no `moduleId` has to be a URL in the browser.
|
|
6
|
+
*/
|
|
7
|
+
export declare function collectComponent(Comp: Component & {
|
|
8
|
+
doc?: string;
|
|
9
|
+
}, { file, storyData, el }: ServerRunPayload, hooks?: {
|
|
10
|
+
/** Before the setup hooks, so what they report is seen. */
|
|
11
|
+
beforeSetup?: (app: App) => void;
|
|
12
|
+
/** After them, so what it adds fills gaps rather than replacing the app's own. */
|
|
13
|
+
afterSetup?: (app: App) => void;
|
|
14
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type ts from 'typescript';
|
|
2
|
+
export interface EventDoc {
|
|
3
|
+
description: string;
|
|
4
|
+
tags: {
|
|
5
|
+
name: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
}[];
|
|
8
|
+
}
|
|
9
|
+
export declare function readEventDocs(typescript: typeof ts, program: ts.Program | undefined, file: string): Map<string, EventDoc>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* vue-component-meta reads an event's JSDoc off the component's `$emit` signatures,
|
|
3
|
+
* and those are Vue's own `EmitFn` in `runtime-core.d.ts`, built from the declared
|
|
4
|
+
* type: the comment is never on them, so every event came back without a
|
|
5
|
+
* description or a tag (#1462). Volar keeps the `defineEmits` type argument, comments
|
|
6
|
+
* and all, as a `__VLS_Emit` alias in the generated code, and this reads it there,
|
|
7
|
+
* through the same program, so an emits type imported from another file resolves too.
|
|
8
|
+
*/
|
|
9
|
+
// Not only at the top: a `generic` setup, or a `<script>` block beside `<script setup>`,
|
|
10
|
+
// puts the setup, and the alias with it, inside the component's initializer.
|
|
11
|
+
function findEmitAlias(typescript, node) {
|
|
12
|
+
if (typescript.isTypeAliasDeclaration(node) && node.name.text === '__VLS_Emit') {
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
return typescript.forEachChild(node, child => findEmitAlias(typescript, child));
|
|
16
|
+
}
|
|
17
|
+
export function readEventDocs(typescript, program, file) {
|
|
18
|
+
const docs = new Map();
|
|
19
|
+
const source = program?.getSourceFile(file);
|
|
20
|
+
const alias = source && findEmitAlias(typescript, source);
|
|
21
|
+
if (!program || !alias) {
|
|
22
|
+
return docs;
|
|
23
|
+
}
|
|
24
|
+
const checker = program.getTypeChecker();
|
|
25
|
+
const type = checker.getTypeAtLocation(alias.name);
|
|
26
|
+
const tagsOf = (tags) => tags.map(({ name, text }) => text?.length ? { name, text: typescript.displayPartsToString(text) } : { name });
|
|
27
|
+
// `change: [value: string]`
|
|
28
|
+
for (const property of type.getProperties()) {
|
|
29
|
+
docs.set(property.name, {
|
|
30
|
+
description: typescript.displayPartsToString(property.getDocumentationComment(checker)),
|
|
31
|
+
tags: tagsOf(property.getJsDocTags(checker)),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// `(event: 'change', value: string): void`
|
|
35
|
+
for (const signature of type.getCallSignatures()) {
|
|
36
|
+
const event = signature.parameters[0] && checker.getTypeOfSymbol(signature.parameters[0]);
|
|
37
|
+
if (event?.isStringLiteral()) {
|
|
38
|
+
docs.set(event.value, {
|
|
39
|
+
description: typescript.displayPartsToString(signature.getDocumentationComment(checker)),
|
|
40
|
+
tags: tagsOf(signature.getJsDocTags()),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return docs;
|
|
45
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DocgenExtractor, DocgenExtractorContext } from '@poveste/shared';
|
|
2
|
+
/**
|
|
3
|
+
* Runs in the docgen worker. `vue-component-meta` is imported here rather than at
|
|
4
|
+
* the top, so a book without TypeScript fails this one request, answered with its
|
|
5
|
+
* error, and keeps its runtime props.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createExtractor(context: DocgenExtractorContext): Promise<DocgenExtractor>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { findTsconfig } from 'poveste/docgen';
|
|
4
|
+
import { readEventDocs } from './events.js';
|
|
5
|
+
import { readGeneric } from './generic.js';
|
|
6
|
+
import { toComponentDoc } from './meta.js';
|
|
7
|
+
import { readSlotProps } from './slots.js';
|
|
8
|
+
const SOURCE = /\.(?:vue|[cm]?[jt]sx?)$/;
|
|
9
|
+
/**
|
|
10
|
+
* Runs in the docgen worker. `vue-component-meta` is imported here rather than at
|
|
11
|
+
* the top, so a book without TypeScript fails this one request, answered with its
|
|
12
|
+
* error, and keeps its runtime props.
|
|
13
|
+
*/
|
|
14
|
+
export async function createExtractor(context) {
|
|
15
|
+
const { createChecker, createCheckerByJson, TypeMeta } = await import('vue-component-meta');
|
|
16
|
+
const options = (context.options ?? {});
|
|
17
|
+
const checkerOptions = { forceUseTs: true, printer: { newLine: 1 } };
|
|
18
|
+
const tsconfig = findTsconfig(context.root);
|
|
19
|
+
// The copy vue-component-meta loaded, so the syntax kinds match the program it builds.
|
|
20
|
+
const typescript = createRequire(createRequire(import.meta.url).resolve('vue-component-meta'))('typescript');
|
|
21
|
+
const checker = tsconfig
|
|
22
|
+
? createChecker(tsconfig, checkerOptions)
|
|
23
|
+
: createCheckerByJson(context.root, { include: ['**/*'], exclude: ['node_modules'] }, checkerOptions);
|
|
24
|
+
// A script file is offered for its default export, which is a component only when it is
|
|
25
|
+
// `defineComponent`'s: a function, a helper or a plain object throws or reads as Unknown.
|
|
26
|
+
function scriptComponent(file) {
|
|
27
|
+
try {
|
|
28
|
+
const meta = checker.getComponentMeta(file, 'default');
|
|
29
|
+
return meta.type === TypeMeta.Unknown ? undefined : meta;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
extract(file) {
|
|
37
|
+
const meta = file.endsWith('.vue') ? checker.getComponentMeta(file) : scriptComponent(file);
|
|
38
|
+
if (!meta) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const doc = toComponentDoc(meta, options, meta.events.length ? readEventDocs(typescript, checker.getProgram(), file) : undefined, slot => readSlotProps(typescript, checker.getProgram(), slot));
|
|
42
|
+
const generic = file.endsWith('.vue') ? readGeneric(readFileSync(file, 'utf8')) : undefined;
|
|
43
|
+
return generic ? { ...doc, generic } : doc;
|
|
44
|
+
},
|
|
45
|
+
update(file) {
|
|
46
|
+
if (!SOURCE.test(file)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (existsSync(file)) {
|
|
50
|
+
checker.updateFile(file, readFileSync(file, 'utf8'));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
checker.deleteFile(file);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
sources: () => checker.getProgram()?.getSourceFiles().map(sourceFile => sourceFile.fileName).filter(file => !file.includes('/node_modules/')) ?? [],
|
|
57
|
+
// The program is released with the checker; this drops what it cached.
|
|
58
|
+
dispose: () => checker.clearCache(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// A tag's attributes may hold a `>`, as `generic="T extends Record<string, unknown>"` does,
|
|
2
|
+
// so a quoted value is read whole rather than ending the tag.
|
|
3
|
+
const SCRIPT_TAG = /<script\b((?:[^>"']|"[^"]*"|'[^']*')*)>/g;
|
|
4
|
+
const SETUP = /(?:^|\s)setup(?=[\s=]|$)/;
|
|
5
|
+
const GENERIC = /(?:^|\s)generic\s*=\s*(?:"([^"]*)"|'([^']*)')/;
|
|
6
|
+
/**
|
|
7
|
+
* The type parameters a `<script setup generic="…">` declares, constraints and defaults
|
|
8
|
+
* included, so a table that prints `T | T[]` can say what `T` may be (#1436).
|
|
9
|
+
*/
|
|
10
|
+
export function readGeneric(source) {
|
|
11
|
+
for (const [, attributes] of source.matchAll(SCRIPT_TAG)) {
|
|
12
|
+
if (SETUP.test(attributes)) {
|
|
13
|
+
const found = GENERIC.exec(attributes);
|
|
14
|
+
const generic = (found?.[1] ?? found?.[2])?.replace(/\s+/g, ' ').trim();
|
|
15
|
+
return generic || undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentDoc, DocSlot } from '@poveste/shared';
|
|
2
|
+
import type { DocgenOptions } from 'poveste/docgen';
|
|
3
|
+
import type { ComponentMeta, SlotMeta } from 'vue-component-meta';
|
|
4
|
+
import type { EventDoc } from './events.js';
|
|
5
|
+
export type VueDocgenOptions = DocgenOptions;
|
|
6
|
+
/**
|
|
7
|
+
* An event's payload, which vue-component-meta prints as the tuple it is declared as,
|
|
8
|
+
* `[event: KeyboardEvent]`, written as the parameter list a handler takes (#1436). Only
|
|
9
|
+
* when the outer brackets enclose the whole of it: `[a: A] | [b: B]` is left as it is.
|
|
10
|
+
*/
|
|
11
|
+
export declare function asParameters(type: string): string;
|
|
12
|
+
export declare function toComponentDoc(meta: ComponentMeta, options?: VueDocgenOptions, eventDocs?: Map<string, EventDoc>, slotProps?: (slot: SlotMeta) => DocSlot['props'] & {}): ComponentDoc;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { declaredOnlyInPackages, DEFAULT_TAGS, HIDDEN_TAGS, normalizeDefault } from 'poveste/docgen';
|
|
2
|
+
function tagsOf(tags) {
|
|
3
|
+
return tags.map(({ name, text }) => text === undefined ? { name } : { name, text });
|
|
4
|
+
}
|
|
5
|
+
function isHidden(tags) {
|
|
6
|
+
return tags.some(tag => HIDDEN_TAGS.includes(tag.name));
|
|
7
|
+
}
|
|
8
|
+
function isInherited(prop, allow) {
|
|
9
|
+
return declaredOnlyInPackages(prop.getDeclarations().map(declaration => declaration.file), allow);
|
|
10
|
+
}
|
|
11
|
+
function toProp(prop) {
|
|
12
|
+
const defaultTag = prop.tags.find(tag => DEFAULT_TAGS.includes(tag.name))?.text;
|
|
13
|
+
return {
|
|
14
|
+
name: prop.name,
|
|
15
|
+
...prop.description ? { description: prop.description } : {},
|
|
16
|
+
type: prop.type,
|
|
17
|
+
required: prop.required,
|
|
18
|
+
...prop.default !== undefined ? { default: prop.default } : {},
|
|
19
|
+
...defaultTag !== undefined ? { defaultTag } : {},
|
|
20
|
+
// The code's default is what runs, so it is the one shown; the flag is for the reader.
|
|
21
|
+
...prop.default !== undefined && defaultTag !== undefined && normalizeDefault(prop.default) !== normalizeDefault(defaultTag)
|
|
22
|
+
? { defaultConflict: true }
|
|
23
|
+
: {},
|
|
24
|
+
tags: tagsOf(prop.tags),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function toMember(member) {
|
|
28
|
+
return {
|
|
29
|
+
name: member.name,
|
|
30
|
+
...member.description ? { description: member.description } : {},
|
|
31
|
+
...member.type ? { type: member.type } : {},
|
|
32
|
+
tags: tagsOf(member.tags),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* An event's payload, which vue-component-meta prints as the tuple it is declared as,
|
|
37
|
+
* `[event: KeyboardEvent]`, written as the parameter list a handler takes (#1436). Only
|
|
38
|
+
* when the outer brackets enclose the whole of it: `[a: A] | [b: B]` is left as it is.
|
|
39
|
+
*/
|
|
40
|
+
export function asParameters(type) {
|
|
41
|
+
if (!type.startsWith('[')) {
|
|
42
|
+
return type;
|
|
43
|
+
}
|
|
44
|
+
let depth = 0;
|
|
45
|
+
for (let i = 0; i < type.length; i++) {
|
|
46
|
+
if ('[(<{'.includes(type[i])) {
|
|
47
|
+
depth++;
|
|
48
|
+
}
|
|
49
|
+
// The `>` of an arrow, `() => void`, closes nothing.
|
|
50
|
+
else if (']>)}'.includes(type[i]) && !(type[i] === '>' && type[i - 1] === '=') && --depth === 0) {
|
|
51
|
+
return i === type.length - 1 ? `(${type.slice(1, -1)})` : type;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return type;
|
|
55
|
+
}
|
|
56
|
+
// What vue-component-meta returns for an event is empty wherever its own reading is (#1462).
|
|
57
|
+
function withDocs(event, doc) {
|
|
58
|
+
return {
|
|
59
|
+
name: event.name,
|
|
60
|
+
description: event.description || doc?.description || '',
|
|
61
|
+
type: asParameters(event.type),
|
|
62
|
+
tags: event.tags.length ? event.tags : doc?.tags ?? [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function toSlot(slot, slotProps) {
|
|
66
|
+
const props = slotProps(slot);
|
|
67
|
+
return { ...toMember(slot), ...props.length ? { props } : {} };
|
|
68
|
+
}
|
|
69
|
+
export function toComponentDoc(meta, options = {}, eventDocs = new Map(), slotProps = () => []) {
|
|
70
|
+
const allow = options.allow ?? [];
|
|
71
|
+
return {
|
|
72
|
+
props: meta.props.filter(prop => !prop.global && !isHidden(prop.tags) && !isInherited(prop, allow)).map(toProp),
|
|
73
|
+
slots: meta.slots.filter(slot => !isHidden(slot.tags)).map(slot => toSlot(slot, slotProps)),
|
|
74
|
+
events: meta.events.map(event => withDocs(event, eventDocs.get(event.name))).filter(event => !isHidden(event.tags)).map(toMember),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DocSlot } from '@poveste/shared';
|
|
2
|
+
import type ts from 'typescript';
|
|
3
|
+
import type { SlotMeta } from 'vue-component-meta';
|
|
4
|
+
type DocSlotProp = NonNullable<DocSlot['props']>[number];
|
|
5
|
+
export declare function readSlotProps(typescript: typeof ts, program: ts.Program | undefined, slot: SlotMeta): DocSlotProp[];
|
|
6
|
+
export {};
|