poveste 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/README.md +9 -7
- package/dist/node/build.js +17 -3
- package/dist/node/collect/index.js +21 -2
- package/dist/node/collect/module-server.js +9 -3
- package/dist/node/collect/runner.d.ts +7 -0
- package/dist/node/collect/runner.js +20 -1
- package/dist/node/collect/types.d.ts +2 -0
- package/dist/node/collect/worker.js +28 -7
- package/dist/node/config-validation.js +3 -0
- package/dist/node/context.d.ts +3 -0
- package/dist/node/docgen/build.d.ts +8 -0
- package/dist/node/docgen/build.js +49 -0
- package/dist/node/docgen/components-of.d.ts +7 -0
- package/dist/node/docgen/components-of.js +17 -0
- package/dist/node/docgen/engine.d.ts +50 -0
- package/dist/node/docgen/engine.js +90 -0
- package/dist/node/docgen/protocol.d.ts +32 -0
- package/dist/node/docgen/protocol.js +1 -0
- package/dist/node/docgen/service.d.ts +52 -0
- package/dist/node/docgen/service.js +128 -0
- package/dist/node/docgen/worker.d.ts +1 -0
- package/dist/node/docgen/worker.js +20 -0
- package/dist/node/dom/env.d.ts +1 -1
- package/dist/node/dom/env.js +3 -2
- package/dist/node/server.js +28 -0
- package/dist/node/stories.d.ts +2 -1
- package/dist/node/stories.js +24 -4
- package/dist/node/util/head.d.ts +8 -1
- package/dist/node/util/head.js +13 -3
- package/dist/node/util/vendors.d.ts +5 -0
- package/dist/node/util/vendors.js +11 -0
- package/dist/node/virtual/index.d.ts +2 -0
- package/dist/node/virtual/index.js +2 -0
- package/dist/node/virtual/resolved-config.d.ts +24 -0
- package/dist/node/virtual/resolved-config.js +30 -1
- package/dist/node/virtual/story-source.js +2 -1
- package/dist/node/virtual/vite-plugin.js +7 -0
- package/dist/node/vite.js +2 -2
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -16,17 +16,17 @@ panel.
|
|
|
16
16
|
Install `poveste` alongside the plugin for your framework:
|
|
17
17
|
|
|
18
18
|
```shell
|
|
19
|
-
pnpm i -D poveste @poveste/plugin-vue
|
|
20
|
-
pnpm i -D poveste @poveste/plugin-svelte
|
|
21
|
-
pnpm i -D poveste @poveste/plugin-nuxt # Nuxt 4
|
|
22
|
-
pnpm i -D poveste @poveste/plugin-quasar # Quasar
|
|
23
|
-
pnpm i -D poveste @poveste/plugin-solid
|
|
24
|
-
pnpm i -D poveste @poveste/plugin-react
|
|
19
|
+
pnpm i -D poveste @poveste/plugin-vue # Vue 3
|
|
20
|
+
pnpm i -D poveste @poveste/plugin-svelte # Svelte 5 and SvelteKit
|
|
21
|
+
pnpm i -D poveste @poveste/plugin-vue @poveste/plugin-nuxt # Nuxt 4
|
|
22
|
+
pnpm i -D poveste @poveste/plugin-vue @poveste/plugin-quasar # Quasar
|
|
23
|
+
pnpm i -D poveste @poveste/plugin-solid # Solid, renderer only
|
|
24
|
+
pnpm i -D poveste @poveste/plugin-react # React 19, renderer only
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
> **Installed an older version than you expected?** For about a day after a release, pnpm installs the previous one with no error and no hint, and for a plugin that's new in that release, its empty `0.0.1` placeholder. That's pnpm's release-age cooldown, not a broken publish. `package.json` keeps the old `^x.y.z`, so waiting doesn't fix it: install again with `npm`, with `--config.minimum-release-age=0` (kebab-case: pnpm 12.4 ignores the camelCase spelling), or with exact versions.
|
|
28
28
|
|
|
29
|
-
Then create a `poveste.config.ts` in your project root:
|
|
29
|
+
Then create a `poveste.config.ts` in your project root. For a Vue project it is this:
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
32
|
import { HstVue } from '@poveste/plugin-vue'
|
|
@@ -39,6 +39,8 @@ export default defineConfig({
|
|
|
39
39
|
})
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
Every other framework uses its own plugin, and Quasar also needs a setup file, so take the config from its guide: [Svelte](https://poveste.dev/guide/svelte/getting-started), [SvelteKit](https://poveste.dev/guide/sveltekit/getting-started), [Nuxt](https://poveste.dev/guide/nuxt/getting-started), [Quasar](https://poveste.dev/guide/quasar/getting-started), [Solid](https://poveste.dev/guide/solid/getting-started) or [React](https://poveste.dev/guide/react/getting-started).
|
|
43
|
+
|
|
42
44
|
## Commands
|
|
43
45
|
|
|
44
46
|
| | |
|
package/dist/node/build.js
CHANGED
|
@@ -8,13 +8,14 @@ import { APP_PATH } from './alias.js';
|
|
|
8
8
|
import { buildCounts, buildSummary, builtSomething, storyGuideUrl } from './build-counts.js';
|
|
9
9
|
import { getSerializedStoryData } from './build-serialize.js';
|
|
10
10
|
import { useCollectStories } from './collect/index.js';
|
|
11
|
+
import { extractComponentDocs } from './docgen/build.js';
|
|
11
12
|
import { useModuleLoader } from './load.js';
|
|
12
13
|
import { createMarkdownFilesWatcher } from './markdown.js';
|
|
13
14
|
import { BuildPluginApi } from './plugin.js';
|
|
14
15
|
import { startPreview } from './preview.js';
|
|
15
16
|
import { findAllStories } from './stories.js';
|
|
16
17
|
import { chromeCssScopePlugin, entryCssMergerPlugin, userCssScopePlugin, } from './style-isolation/index.js';
|
|
17
|
-
import { applyHeadTransform } from './util/head.js';
|
|
18
|
+
import { applyHeadTransform, defaultDescription } from './util/head.js';
|
|
18
19
|
import { wrapLogError } from './util/log.js';
|
|
19
20
|
import { viteMode } from './util/vite-mode.js';
|
|
20
21
|
import { getViteConfigWithPlugins } from './vite.js';
|
|
@@ -38,6 +39,12 @@ const APP_ONLY_VENDORS = [
|
|
|
38
39
|
/[\\/]node_modules[\\/]shiki[\\/]/,
|
|
39
40
|
/[\\/]node_modules[\\/]@shikijs[\\/]/,
|
|
40
41
|
];
|
|
42
|
+
// Left to the bundler wherever they are installed, rather than sent to `vendor`.
|
|
43
|
+
// `@poveste/controls` loads three controls lazily, and in a book installed from
|
|
44
|
+
// npm `vendor` took them in, so every reader downloaded them before anything
|
|
45
|
+
// rendered. The workspace book never showed it: linked source has no
|
|
46
|
+
// `node_modules` in its path (#1343).
|
|
47
|
+
const LEFT_TO_BUNDLER = ['@poveste/app', '@poveste/controls'];
|
|
41
48
|
const PREFETCHED_MODULES = [
|
|
42
49
|
'StoryView',
|
|
43
50
|
'reactivity',
|
|
@@ -117,6 +124,7 @@ export async function build(ctx) {
|
|
|
117
124
|
// Same wrapper the dev path uses for these exact calls (server.ts).
|
|
118
125
|
await wrapLogError('destroyCollectStories', () => destroyCollectStories());
|
|
119
126
|
}
|
|
127
|
+
ctx.componentDocs = await extractComponentDocs(ctx, server.moduleGraph);
|
|
120
128
|
const counts = buildCounts(ctx.storyFiles);
|
|
121
129
|
const { viteConfig: buildViteConfigRaw } = await getViteConfigWithPlugins(false, ctx);
|
|
122
130
|
const buildViteConfig = mergeViteConfig(buildViteConfigRaw, {
|
|
@@ -198,7 +206,7 @@ export async function build(ctx) {
|
|
|
198
206
|
if (id.replace(/^\0/, '').startsWith('vite/')) {
|
|
199
207
|
return 'app-runtime';
|
|
200
208
|
}
|
|
201
|
-
if (!id.includes(
|
|
209
|
+
if (!LEFT_TO_BUNDLER.some(name => id.includes(name)) && id.includes('node_modules')) {
|
|
202
210
|
if (APP_ONLY_VENDORS.some(test => test.test(id))) {
|
|
203
211
|
return 'highlighter';
|
|
204
212
|
}
|
|
@@ -271,13 +279,19 @@ export async function build(ctx) {
|
|
|
271
279
|
let indexHtml = generateEntryHtml(indexOutput.fileName, mainStyleOutput.fileName, {
|
|
272
280
|
HEAD: `${preloadHtml}${prefetchHtml}`,
|
|
273
281
|
}, ctx);
|
|
274
|
-
indexHtml = await applyHeadTransform(indexHtml, ctx.config.head);
|
|
282
|
+
indexHtml = await applyHeadTransform(indexHtml, ctx.config.head, defaultDescription(ctx.config.theme.title ?? 'Poveste'));
|
|
275
283
|
await writeFile('index.html', indexHtml, ctx);
|
|
276
284
|
// Sandbox
|
|
277
285
|
let sandboxHtml = generateEntryHtml(sandboxOutput.fileName, sandboxStyleOutput.fileName, {}, ctx);
|
|
278
286
|
sandboxHtml = await applyHeadTransform(sandboxHtml, ctx.config.head);
|
|
279
287
|
await writeFile('__sandbox.html', sandboxHtml, ctx);
|
|
280
288
|
await writeFile('poveste.json', JSON.stringify(getSerializedStoryData(ctx), null, 2), ctx);
|
|
289
|
+
// A host's SPA fallback answers a missing /robots.txt with index.html, which a
|
|
290
|
+
// crawler reads as a malformed robots file rather than as none (#316). One in
|
|
291
|
+
// the public directory is the book's own, and was copied here already.
|
|
292
|
+
if (!await fs.pathExists(join(ctx.config.outDir, 'robots.txt'))) {
|
|
293
|
+
await writeFile('robots.txt', 'User-agent: *\nAllow: /\n', ctx);
|
|
294
|
+
}
|
|
281
295
|
const duration = performance.now() - startTime;
|
|
282
296
|
const { empty } = counts;
|
|
283
297
|
if (empty.length) {
|
|
@@ -5,6 +5,7 @@ import { TEMP_PATH } from '../alias.js';
|
|
|
5
5
|
import { createPath } from '../tree.js';
|
|
6
6
|
import { escapeRegExp } from '../util/escape-regexp.js';
|
|
7
7
|
import { slash } from '../util/fs.js';
|
|
8
|
+
import { NOOP_ID, SETUP_ID } from '../virtual/index.js';
|
|
8
9
|
import { globalsFromDefine } from './define-globals.js';
|
|
9
10
|
import { strayLayoutKeys } from './layout-keys.js';
|
|
10
11
|
import { createModuleServer } from './module-server.js';
|
|
@@ -41,7 +42,16 @@ export function useCollectStories(options, ctx) {
|
|
|
41
42
|
threads: threadsCount,
|
|
42
43
|
invoke: (name, data) => node.invoke(name, data),
|
|
43
44
|
});
|
|
45
|
+
// Resolved once: a change to the config restarts the server.
|
|
46
|
+
let setupFile;
|
|
47
|
+
function resolveSetupFile() {
|
|
48
|
+
return setupFile ??= server.pluginContainer.resolveId(SETUP_ID, undefined, { ssr: true })
|
|
49
|
+
.then(resolved => resolved && resolved.id !== NOOP_ID ? resolved.id : undefined);
|
|
50
|
+
}
|
|
51
|
+
// Every story the setup file reaches fails with it, so it is reported once a pass.
|
|
52
|
+
let setupFailureReported = false;
|
|
44
53
|
function clearCache() {
|
|
54
|
+
setupFailureReported = false;
|
|
45
55
|
server.moduleGraph.invalidateAll();
|
|
46
56
|
node.clearCache();
|
|
47
57
|
// Both of those make every module read again, and a worker told nothing would
|
|
@@ -66,6 +76,7 @@ export function useCollectStories(options, ctx) {
|
|
|
66
76
|
base: server.config.base,
|
|
67
77
|
storyFile,
|
|
68
78
|
defineGlobals,
|
|
79
|
+
setupFile: await resolveSetupFile(),
|
|
69
80
|
};
|
|
70
81
|
const { storyData } = await threadPool.run(payload);
|
|
71
82
|
const finalData = storyData[0];
|
|
@@ -101,12 +112,20 @@ export function useCollectStories(options, ctx) {
|
|
|
101
112
|
catch (e) {
|
|
102
113
|
// Work `destroy` cut short fails with whatever the pool or a closing server
|
|
103
114
|
// throws. That is the shutdown, not the story, so it is not reported (#878).
|
|
104
|
-
|
|
115
|
+
// A build destroys only once a story has failed, which already threw.
|
|
116
|
+
if (destroying) {
|
|
105
117
|
return;
|
|
106
118
|
}
|
|
107
119
|
// A Vite transform error carries the code frame on `frame`.
|
|
108
120
|
const error = (e instanceof Error ? e : new Error(String(e)));
|
|
109
|
-
|
|
121
|
+
const detail = error.frame ? `${pc.bold(error.message)}\n${error.frame}` : error.stack;
|
|
122
|
+
if (!error.setupFile) {
|
|
123
|
+
console.error(pc.red(`Error while collecting story ${storyFile.path}:\n${detail}`));
|
|
124
|
+
}
|
|
125
|
+
else if (!setupFailureReported) {
|
|
126
|
+
setupFailureReported = true;
|
|
127
|
+
console.error(pc.red(`Error while loading the setup file ${error.setupFile}, so no story that needs it was collected:\n${detail}`));
|
|
128
|
+
}
|
|
110
129
|
if (options.throws) {
|
|
111
130
|
throw e;
|
|
112
131
|
}
|
|
@@ -152,6 +152,13 @@ export function createModuleServer(server, options) {
|
|
|
152
152
|
? true
|
|
153
153
|
: [...options.inline, ...(Array.isArray(noExternal) ? noExternal : noExternal ? [noExternal] : [])];
|
|
154
154
|
const externalizeCache = new Map();
|
|
155
|
+
// Each entry keeps the module's invalidation time it was transformed under, in
|
|
156
|
+
// Vite's clock rather than `Date.now()`. That clock adds a millisecond for every
|
|
157
|
+
// call within one, so a burst of transforms runs it ahead of the wall clock, and
|
|
158
|
+
// `invalidateAll` then stamps every module seconds in the future. Against
|
|
159
|
+
// `Date.now()` the whole cache read as stale until real time caught up, each
|
|
160
|
+
// re-transform pushed the clock further ahead, and one reload re-collected a
|
|
161
|
+
// large book a hundred times over (#1352).
|
|
155
162
|
const fetchCache = new Map();
|
|
156
163
|
function getTransformMode(id) {
|
|
157
164
|
const path = cleanUrl(id);
|
|
@@ -227,12 +234,11 @@ export function createModuleServer(server, options) {
|
|
|
227
234
|
const moduleNode = server.moduleGraph.getModuleById(id) ?? [...server.moduleGraph.getModulesByFile(path) ?? []][0];
|
|
228
235
|
const invalidatedAt = moduleNode ? Math.max(moduleNode.lastHMRTimestamp, moduleNode.lastInvalidationTimestamp) : 0;
|
|
229
236
|
const cached = fetchCache.get(path);
|
|
230
|
-
if (cached &&
|
|
237
|
+
if (cached && invalidatedAt <= cached.invalidatedAt) {
|
|
231
238
|
// Unchanged since the runner last read it: it keeps what it has. A runner
|
|
232
239
|
// that never had it gets the code, with nothing of its own to invalidate.
|
|
233
240
|
return cachedByRunner ? { cache: true } : { ...cached.result, invalidate: false };
|
|
234
241
|
}
|
|
235
|
-
const timestamp = Date.now();
|
|
236
242
|
const result = await transform(id, getTransformMode(id));
|
|
237
243
|
if (!result) {
|
|
238
244
|
throw new Error(`[poveste] could not transform ${id}${importer ? `, imported from ${importer}` : ''}`);
|
|
@@ -244,7 +250,7 @@ export function createModuleServer(server, options) {
|
|
|
244
250
|
url: id,
|
|
245
251
|
invalidate: true,
|
|
246
252
|
};
|
|
247
|
-
fetchCache.set(path, {
|
|
253
|
+
fetchCache.set(path, { invalidatedAt, result: fetched });
|
|
248
254
|
return fetched;
|
|
249
255
|
}
|
|
250
256
|
/** The two calls a `ModuleRunner` makes, as its transport's `invoke` carries them. */
|
|
@@ -30,5 +30,12 @@ export type Invoke = (name: string, data: unknown[]) => Promise<unknown>;
|
|
|
30
30
|
* that branch, and no id or file in either book carried the prefix.
|
|
31
31
|
*/
|
|
32
32
|
export declare function filenameOf(module: Readonly<EvaluatedModuleNode>, root: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The keys an externalised module added to `globalThis` when Node ran it. Node
|
|
35
|
+
* caches that module, so once a worker's reset deletes them nothing installs them
|
|
36
|
+
* again. Vue's development build sets `__VUE_HMR_RUNTIME__` this way, and a story
|
|
37
|
+
* compiled with HMR code reads it at its top level (#1320).
|
|
38
|
+
*/
|
|
39
|
+
export declare const globalsFromExternals: Set<string>;
|
|
33
40
|
/** A runner over a module server, reached through `invoke`. `root` is the Vite root. */
|
|
34
41
|
export declare function createRunner(invoke: Invoke, root: string, evaluatedModules?: EvaluatedModules): ModuleRunner;
|
|
@@ -67,6 +67,25 @@ export function filenameOf(module, root) {
|
|
|
67
67
|
}
|
|
68
68
|
return join(root, 'virtual-module');
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The keys an externalised module added to `globalThis` when Node ran it. Node
|
|
72
|
+
* caches that module, so once a worker's reset deletes them nothing installs them
|
|
73
|
+
* again. Vue's development build sets `__VUE_HMR_RUNTIME__` this way, and a story
|
|
74
|
+
* compiled with HMR code reads it at its top level (#1320).
|
|
75
|
+
*/
|
|
76
|
+
export const globalsFromExternals = new Set();
|
|
77
|
+
const ranExternals = new Set();
|
|
78
|
+
async function importRecordingGlobals(file) {
|
|
79
|
+
const before = new Set(Object.keys(globalThis));
|
|
80
|
+
const imported = await import(file);
|
|
81
|
+
ranExternals.add(file);
|
|
82
|
+
for (const key of Object.keys(globalThis)) {
|
|
83
|
+
if (!before.has(key)) {
|
|
84
|
+
globalsFromExternals.add(key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return imported;
|
|
88
|
+
}
|
|
70
89
|
/** As vite-node did: a CJS package's named exports are read through its default. */
|
|
71
90
|
class InteropEvaluator {
|
|
72
91
|
root;
|
|
@@ -130,7 +149,7 @@ class InteropEvaluator {
|
|
|
130
149
|
await run(context[ssrModuleExportsKey], context[ssrImportMetaKey], context[ssrImportKey], context[ssrDynamicImportKey], context[ssrExportAllKey], context[ssrExportNameKey], createRequire(filename), cjsExports, moduleProxy, filename, dirname(filename));
|
|
131
150
|
}
|
|
132
151
|
async runExternalModule(file) {
|
|
133
|
-
const imported = await import(file);
|
|
152
|
+
const imported = ranExternals.has(file) ? await import(file) : await importRecordingGlobals(file);
|
|
134
153
|
if (file.endsWith('.mjs') || !('default' in imported)) {
|
|
135
154
|
return imported;
|
|
136
155
|
}
|
|
@@ -4,6 +4,8 @@ export interface Payload {
|
|
|
4
4
|
base: string;
|
|
5
5
|
storyFile: ServerStoryFile;
|
|
6
6
|
defineGlobals?: Record<string, unknown>;
|
|
7
|
+
/** The book's setup file as the collect server resolved it, when it has one. */
|
|
8
|
+
setupFile?: string | undefined;
|
|
7
9
|
}
|
|
8
10
|
export interface ReturnData {
|
|
9
11
|
storyData: ServerStory[];
|
|
@@ -6,7 +6,7 @@ import pc from 'picocolors';
|
|
|
6
6
|
import { EvaluatedModules } from 'vite/module-runner';
|
|
7
7
|
import { createDomEnv, resetDomEnv } from '../dom/env.js';
|
|
8
8
|
import { invokeOver } from './rpc.js';
|
|
9
|
-
import { createRunner } from './runner.js';
|
|
9
|
+
import { createRunner, globalsFromExternals } from './runner.js';
|
|
10
10
|
import { serveTasks } from './task.js';
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const _evaluatedModules = new EvaluatedModules();
|
|
@@ -41,6 +41,22 @@ function invalidateAll() {
|
|
|
41
41
|
_evaluatedModules.invalidateModule(node);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
// A framework's collect module imports the setup file, so a setup file that fails
|
|
45
|
+
// to load fails every story with an error that names neither of them (#1324). The
|
|
46
|
+
// runner keeps a module that failed as its rejected promise, which is how it is told
|
|
47
|
+
// apart from a story that failed on its own. Matched on the fetched file, because
|
|
48
|
+
// the module's id is however its importer wrote it, root-relative inside the root.
|
|
49
|
+
async function blameSetupFile(error, setupFile) {
|
|
50
|
+
const nodes = setupFile
|
|
51
|
+
? [..._evaluatedModules.idToModuleMap.values()].filter(node => node.meta && 'file' in node.meta && node.meta.file === setupFile)
|
|
52
|
+
: [];
|
|
53
|
+
const failed = await Promise.all(nodes.map(node => node.promise?.then(() => false, () => true)));
|
|
54
|
+
if (!failed.includes(true)) {
|
|
55
|
+
return error;
|
|
56
|
+
}
|
|
57
|
+
const blamed = typeof error === 'object' && error !== null ? error : new Error(String(error));
|
|
58
|
+
return Object.assign(blamed, { setupFile });
|
|
59
|
+
}
|
|
44
60
|
if (!parentPort) {
|
|
45
61
|
throw new Error('[poveste] the collection worker was started outside a worker thread');
|
|
46
62
|
}
|
|
@@ -63,7 +79,7 @@ async function collect(payload) {
|
|
|
63
79
|
// belongs to one server, so every task it is sent carries that server's root.
|
|
64
80
|
const runner = _runner ?? (_runner = createRunner((name, data) => _invoke(name, data), payload.root, _evaluatedModules));
|
|
65
81
|
if (_domEnv) {
|
|
66
|
-
resetDomEnv(_domEnv);
|
|
82
|
+
resetDomEnv(_domEnv, globalsFromExternals);
|
|
67
83
|
}
|
|
68
84
|
else {
|
|
69
85
|
_domEnv = createDomEnv();
|
|
@@ -73,11 +89,16 @@ async function collect(payload) {
|
|
|
73
89
|
const { run } = (await runner.import(resolve(__dirname, './run.js')));
|
|
74
90
|
const afterExecuteTime = performance.now();
|
|
75
91
|
const storyData = [];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
try {
|
|
93
|
+
await run({
|
|
94
|
+
file: payload.storyFile,
|
|
95
|
+
storyData,
|
|
96
|
+
el,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw await blameSetupFile(error, payload.setupFile);
|
|
101
|
+
}
|
|
81
102
|
const afterRunTime = performance.now();
|
|
82
103
|
if (payload.storyFile.markdownFile) {
|
|
83
104
|
const el = document.createElement('div');
|
|
@@ -7,6 +7,9 @@ const RULES = [
|
|
|
7
7
|
{ path: 'setupCode', kind: 'string[]' },
|
|
8
8
|
{ path: 'globalStyles', kind: 'string[]' },
|
|
9
9
|
{ path: 'isolateStyles', kind: 'boolean' },
|
|
10
|
+
{ path: 'autoDocs', kind: ['boolean', 'object'] },
|
|
11
|
+
{ path: 'autoDocs.allow', kind: 'string[]' },
|
|
12
|
+
{ path: 'autoDocs.exclude', kind: 'string[]' },
|
|
10
13
|
{ path: 'autoApplyContrastColor', kind: 'boolean' },
|
|
11
14
|
{ path: 'defaultBackgroundColor', kind: 'string' },
|
|
12
15
|
{ path: 'sandboxDarkClass', kind: 'string' },
|
package/dist/node/context.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ConfigMode, FinalSupportPlugin, PluginCommand, PovesteConfig, ServerMarkdownFile, ServerStoryFile } from '@poveste/shared';
|
|
2
2
|
import type { ResolvedConfig } from 'vite';
|
|
3
|
+
import type { StoryDocsResult } from './docgen/protocol.js';
|
|
3
4
|
export interface Context {
|
|
4
5
|
root: string;
|
|
5
6
|
config: PovesteConfig;
|
|
@@ -9,6 +10,8 @@ export interface Context {
|
|
|
9
10
|
supportPlugins: FinalSupportPlugin[];
|
|
10
11
|
markdownFiles: ServerMarkdownFile[];
|
|
11
12
|
registeredCommands: PluginCommand[];
|
|
13
|
+
/** What `poveste build` extracted for auto-docs, by story id (#1159). */
|
|
14
|
+
componentDocs?: Record<string, StoryDocsResult['components']>;
|
|
12
15
|
}
|
|
13
16
|
export interface CreateContextOptions {
|
|
14
17
|
mode: Context['mode'];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ModuleGraph } from 'vite';
|
|
2
|
+
import type { Context } from '../context.js';
|
|
3
|
+
import type { StoryDocsResult } from './protocol.js';
|
|
4
|
+
/**
|
|
5
|
+
* The build's pass (#1159): with no reader to wait for, every story's named
|
|
6
|
+
* components are extracted once, after collection, into what the book ships.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractComponentDocs(ctx: Context, moduleGraph: ModuleGraph): Promise<Record<string, StoryDocsResult['components']>>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { componentsOfStory } from './components-of.js';
|
|
2
|
+
import { createDocgenService } from './service.js';
|
|
3
|
+
/**
|
|
4
|
+
* The build's pass (#1159): with no reader to wait for, every story's named
|
|
5
|
+
* components are extracted once, after collection, into what the book ships.
|
|
6
|
+
*/
|
|
7
|
+
export async function extractComponentDocs(ctx, moduleGraph) {
|
|
8
|
+
const docgen = createDocgenService({
|
|
9
|
+
root: ctx.root,
|
|
10
|
+
plugins: ctx.config.plugins,
|
|
11
|
+
enabled: ctx.config.autoDocs !== false,
|
|
12
|
+
bookOptions: typeof ctx.config.autoDocs === 'object' ? ctx.config.autoDocs : undefined,
|
|
13
|
+
collected: Promise.resolve(),
|
|
14
|
+
componentsOf: storyId => componentsOfStory(ctx.storyFiles, moduleGraph, storyId),
|
|
15
|
+
});
|
|
16
|
+
if (!docgen.enabled) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
const docs = {};
|
|
20
|
+
const failed = new Map();
|
|
21
|
+
try {
|
|
22
|
+
for (const storyFile of ctx.storyFiles) {
|
|
23
|
+
const storyId = storyFile.story?.id;
|
|
24
|
+
if (!storyId) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const { components } = await docgen.request(storyId);
|
|
28
|
+
// A built book is published, so an extractor's error stays on the build
|
|
29
|
+
// machine: the component keeps its runtime props, as with auto-docs off (#1382).
|
|
30
|
+
const read = Object.entries(components).filter(([file, result]) => {
|
|
31
|
+
if (!result.doc) {
|
|
32
|
+
failed.set(file, result.error);
|
|
33
|
+
}
|
|
34
|
+
return result.doc;
|
|
35
|
+
});
|
|
36
|
+
if (read.length) {
|
|
37
|
+
docs[storyId] = Object.fromEntries(read);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await docgen.dispose();
|
|
43
|
+
}
|
|
44
|
+
if (failed.size) {
|
|
45
|
+
const [file, error] = [...failed][0];
|
|
46
|
+
console.warn(`[poveste] auto-docs could not read ${failed.size} component${failed.size === 1 ? '' : 's'}, which show their runtime props instead. The first, ${file}: ${error}`);
|
|
47
|
+
}
|
|
48
|
+
return docs;
|
|
49
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ServerStoryFile } from '@poveste/shared';
|
|
2
|
+
import type { ModuleGraph } from 'vite';
|
|
3
|
+
/**
|
|
4
|
+
* The files a story's own module imports, as collection resolved them: the
|
|
5
|
+
* components that story names. Other story files are left out.
|
|
6
|
+
*/
|
|
7
|
+
export declare function componentsOfStory(storyFiles: ServerStoryFile[], moduleGraph: ModuleGraph, storyId: string): string[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files a story's own module imports, as collection resolved them: the
|
|
3
|
+
* components that story names. Other story files are left out.
|
|
4
|
+
*/
|
|
5
|
+
export function componentsOfStory(storyFiles, moduleGraph, storyId) {
|
|
6
|
+
const storyFile = storyFiles.find(file => file.story?.id === storyId);
|
|
7
|
+
const storyPaths = new Set(storyFiles.map(file => file.path));
|
|
8
|
+
const files = new Set();
|
|
9
|
+
for (const mod of moduleGraph.getModulesByFile(storyFile?.path ?? '') ?? []) {
|
|
10
|
+
for (const imported of mod.importedModules) {
|
|
11
|
+
if (imported.file && !storyPaths.has(imported.file)) {
|
|
12
|
+
files.add(imported.file);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return [...files];
|
|
17
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ComponentDoc, DocgenExtractorModule } from '@poveste/shared';
|
|
2
|
+
export interface EngineExtractorSpec {
|
|
3
|
+
/** The plugin's name, which keys its extractor. */
|
|
4
|
+
name: string;
|
|
5
|
+
module: string;
|
|
6
|
+
options?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export type ExtractResult = {
|
|
9
|
+
doc: ComponentDoc;
|
|
10
|
+
} | {
|
|
11
|
+
doc?: undefined;
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
|
14
|
+
export interface DocgenEngineOptions {
|
|
15
|
+
root: string;
|
|
16
|
+
extractors: EngineExtractorSpec[];
|
|
17
|
+
/**
|
|
18
|
+
* Dispose every extractor once the heap passes this share of its limit, and
|
|
19
|
+
* build afresh on the next request. Storybook recycles at 0.7, and laziness
|
|
20
|
+
* alone did not cap its memory (storybookjs/storybook#35260).
|
|
21
|
+
*/
|
|
22
|
+
heapRatio?: number;
|
|
23
|
+
heapLimit?: number;
|
|
24
|
+
heapUsed?: () => number;
|
|
25
|
+
load?: (module: string) => Promise<DocgenExtractorModule>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* What a reader is shown of an extractor's failure: its first line, with the
|
|
29
|
+
* book's root made relative and any other absolute path cut to its file name.
|
|
30
|
+
* Node's `MODULE_NOT_FOUND` message carries a require stack of absolute paths,
|
|
31
|
+
* which a built book published under every component (#1382).
|
|
32
|
+
*/
|
|
33
|
+
export declare function describeError(error: unknown, root: string): string;
|
|
34
|
+
/** The extraction loop that runs inside the docgen worker, kept free of the thread. */
|
|
35
|
+
/** `import()` takes a URL, and an absolute Windows path is not one: `D:` reads as its scheme. */
|
|
36
|
+
export declare function importable(module: string): string;
|
|
37
|
+
export declare function createDocgenEngine(options: DocgenEngineOptions): {
|
|
38
|
+
stats: {
|
|
39
|
+
created: number;
|
|
40
|
+
recycled: number;
|
|
41
|
+
};
|
|
42
|
+
extract(requests: {
|
|
43
|
+
name: string;
|
|
44
|
+
file: string;
|
|
45
|
+
}[]): Promise<Record<string, ExtractResult>>;
|
|
46
|
+
/** Every live extractor's sources, for the dev server to watch. */
|
|
47
|
+
sources(): Promise<string[]>;
|
|
48
|
+
update(file: string): Promise<void>;
|
|
49
|
+
dispose: () => Promise<void>;
|
|
50
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { getHeapStatistics } from 'node:v8';
|
|
4
|
+
// An absolute path, POSIX or Windows, of two segments or more; its last segment is kept.
|
|
5
|
+
const ABSOLUTE_PATH = /(?:(?<![\w.])\/|\b[A-Z]:[\\/])(?:[^\s'"`/\\]+[\\/])+([^\s'"`/\\]+)/gi;
|
|
6
|
+
/**
|
|
7
|
+
* What a reader is shown of an extractor's failure: its first line, with the
|
|
8
|
+
* book's root made relative and any other absolute path cut to its file name.
|
|
9
|
+
* Node's `MODULE_NOT_FOUND` message carries a require stack of absolute paths,
|
|
10
|
+
* which a built book published under every component (#1382).
|
|
11
|
+
*/
|
|
12
|
+
export function describeError(error, root) {
|
|
13
|
+
const firstLine = (error instanceof Error ? error.message : String(error)).split('\n')[0].trim();
|
|
14
|
+
return firstLine.replaceAll(`${root}/`, '').replaceAll(root, '.').replace(ABSOLUTE_PATH, '$1');
|
|
15
|
+
}
|
|
16
|
+
/** The extraction loop that runs inside the docgen worker, kept free of the thread. */
|
|
17
|
+
/** `import()` takes a URL, and an absolute Windows path is not one: `D:` reads as its scheme. */
|
|
18
|
+
export function importable(module) {
|
|
19
|
+
return isAbsolute(module) ? pathToFileURL(module).href : module;
|
|
20
|
+
}
|
|
21
|
+
export function createDocgenEngine(options) {
|
|
22
|
+
const heapRatio = options.heapRatio ?? 0.7;
|
|
23
|
+
const heapLimit = options.heapLimit ?? getHeapStatistics().heap_size_limit;
|
|
24
|
+
const heapUsed = options.heapUsed ?? (() => process.memoryUsage().heapUsed);
|
|
25
|
+
const load = options.load ?? (module => import(importable(module)));
|
|
26
|
+
const live = new Map();
|
|
27
|
+
const stats = { created: 0, recycled: 0 };
|
|
28
|
+
function extractorFor(name) {
|
|
29
|
+
let extractor = live.get(name);
|
|
30
|
+
if (!extractor) {
|
|
31
|
+
const spec = options.extractors.find(e => e.name === name);
|
|
32
|
+
if (!spec) {
|
|
33
|
+
throw new Error(`no docgen extractor named "${name}"`);
|
|
34
|
+
}
|
|
35
|
+
extractor = load(spec.module).then((module) => {
|
|
36
|
+
stats.created++;
|
|
37
|
+
return module.createExtractor({ root: options.root, options: spec.options });
|
|
38
|
+
});
|
|
39
|
+
// A failed start is not cached: the next request tries again.
|
|
40
|
+
extractor.catch(() => live.delete(name));
|
|
41
|
+
live.set(name, extractor);
|
|
42
|
+
}
|
|
43
|
+
return extractor;
|
|
44
|
+
}
|
|
45
|
+
async function disposeAll() {
|
|
46
|
+
const extractors = [...live.values()];
|
|
47
|
+
live.clear();
|
|
48
|
+
await Promise.all(extractors.map(async (extractor) => (await extractor.catch(() => undefined))?.dispose()));
|
|
49
|
+
}
|
|
50
|
+
async function recycleIfNeeded() {
|
|
51
|
+
if (live.size > 0 && heapUsed() > heapLimit * heapRatio) {
|
|
52
|
+
await disposeAll();
|
|
53
|
+
stats.recycled++;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
stats,
|
|
58
|
+
async extract(requests) {
|
|
59
|
+
const results = {};
|
|
60
|
+
for (const { name, file } of requests) {
|
|
61
|
+
try {
|
|
62
|
+
const extractor = await extractorFor(name);
|
|
63
|
+
const doc = await extractor.extract(file);
|
|
64
|
+
results[file] = doc ? { doc } : { error: 'no component found' };
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
results[file] = { error: describeError(error, options.root) };
|
|
68
|
+
}
|
|
69
|
+
await recycleIfNeeded();
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
},
|
|
73
|
+
/** Every live extractor's sources, for the dev server to watch. */
|
|
74
|
+
async sources() {
|
|
75
|
+
const files = new Set();
|
|
76
|
+
for (const extractor of live.values()) {
|
|
77
|
+
for (const file of await (await extractor.catch(() => undefined))?.sources?.() ?? []) {
|
|
78
|
+
files.add(file);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [...files];
|
|
82
|
+
},
|
|
83
|
+
async update(file) {
|
|
84
|
+
for (const extractor of live.values()) {
|
|
85
|
+
await (await extractor.catch(() => undefined))?.update?.(file);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
dispose: disposeAll,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ExtractResult } from './engine.js';
|
|
2
|
+
export type DocgenRequest = {
|
|
3
|
+
id: number;
|
|
4
|
+
type: 'extract';
|
|
5
|
+
requests: {
|
|
6
|
+
name: string;
|
|
7
|
+
file: string;
|
|
8
|
+
}[];
|
|
9
|
+
} | {
|
|
10
|
+
id: number;
|
|
11
|
+
type: 'update';
|
|
12
|
+
file: string;
|
|
13
|
+
} | {
|
|
14
|
+
id: number;
|
|
15
|
+
type: 'dispose';
|
|
16
|
+
};
|
|
17
|
+
export interface DocgenResponse {
|
|
18
|
+
id: number;
|
|
19
|
+
results?: Record<string, ExtractResult>;
|
|
20
|
+
stats?: {
|
|
21
|
+
created: number;
|
|
22
|
+
recycled: number;
|
|
23
|
+
};
|
|
24
|
+
/** What the extractors read, sent with each extraction. */
|
|
25
|
+
sources?: string[];
|
|
26
|
+
}
|
|
27
|
+
/** What the dev server sends a client that asked for a story's docs. */
|
|
28
|
+
export interface StoryDocsResult {
|
|
29
|
+
storyId: string;
|
|
30
|
+
/** Keyed by component file, relative to the book's root. */
|
|
31
|
+
components: Record<string, ExtractResult>;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AutoDocsOptions, Plugin } from '@poveste/shared';
|
|
2
|
+
import type { EngineExtractorSpec } from './engine.js';
|
|
3
|
+
import type { DocgenRequest, DocgenResponse, StoryDocsResult } from './protocol.js';
|
|
4
|
+
type Outgoing = DocgenRequest extends infer R ? R extends {
|
|
5
|
+
id: number;
|
|
6
|
+
} ? Omit<R, 'id'> : never : never;
|
|
7
|
+
export interface DocgenRunner {
|
|
8
|
+
send: (request: Outgoing) => Promise<DocgenResponse>;
|
|
9
|
+
terminate: () => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface DocgenWorkerData {
|
|
12
|
+
root: string;
|
|
13
|
+
extractors: EngineExtractorSpec[];
|
|
14
|
+
}
|
|
15
|
+
export declare function createWorkerRunner(data: DocgenWorkerData): DocgenRunner;
|
|
16
|
+
export interface DocgenServiceOptions {
|
|
17
|
+
root: string;
|
|
18
|
+
plugins: Plugin[];
|
|
19
|
+
/** The config's off-switch. */
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
/** `autoDocs` when the config gives an object: merged into every extractor's options. */
|
|
22
|
+
bookOptions?: AutoDocsOptions | undefined;
|
|
23
|
+
/** Settles once the first full collection has finished. */
|
|
24
|
+
collected: Promise<void>;
|
|
25
|
+
/** The component files a story imports, resolved to absolute paths. */
|
|
26
|
+
componentsOf: (storyId: string) => string[] | Promise<string[]>;
|
|
27
|
+
createRunner?: (data: DocgenWorkerData) => DocgenRunner;
|
|
28
|
+
/** Called with files the extractors read that nothing was watching yet. */
|
|
29
|
+
watch?: (files: string[]) => void;
|
|
30
|
+
/** Where a plugin's reason for being unavailable is said. */
|
|
31
|
+
warn?: (message: string) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* When extraction runs, for every framework (#159). Nothing starts until a reader
|
|
35
|
+
* first asks for a story's docs: no worker, no checker. Then only the components
|
|
36
|
+
* that story imports are extracted, after collection has finished, and a change
|
|
37
|
+
* on disk drops what was extracted rather than re-extracting it unasked.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createDocgenService(options: DocgenServiceOptions): {
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
/** Whether a worker exists, and so whether a checker could. */
|
|
42
|
+
readonly started: boolean;
|
|
43
|
+
request(storyId: string): Promise<StoryDocsResult>;
|
|
44
|
+
/**
|
|
45
|
+
* A file changed. Returns whether anything extracted went stale, so the
|
|
46
|
+
* caller can tell an open panel to ask again. Before the first request there
|
|
47
|
+
* is nothing to drop and nothing is started.
|
|
48
|
+
*/
|
|
49
|
+
fileChanged(file: string): Promise<boolean>;
|
|
50
|
+
dispose(): Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads';
|
|
2
|
+
import { relative } from 'pathe';
|
|
3
|
+
export function createWorkerRunner(data) {
|
|
4
|
+
const worker = new Worker(new URL('./worker.js', import.meta.url), { workerData: data });
|
|
5
|
+
const pending = new Map();
|
|
6
|
+
let nextId = 0;
|
|
7
|
+
worker.on('message', (response) => {
|
|
8
|
+
pending.get(response.id)?.(response);
|
|
9
|
+
pending.delete(response.id);
|
|
10
|
+
});
|
|
11
|
+
// The engine already answers a failed extraction; this is the thread dying.
|
|
12
|
+
worker.on('error', (error) => {
|
|
13
|
+
for (const resolve of pending.values()) {
|
|
14
|
+
resolve({ id: -1, results: {} });
|
|
15
|
+
}
|
|
16
|
+
pending.clear();
|
|
17
|
+
console.error('[poveste] the docgen worker stopped:', error);
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
send: request => new Promise((resolve) => {
|
|
21
|
+
const id = nextId++;
|
|
22
|
+
pending.set(id, resolve);
|
|
23
|
+
worker.postMessage({ ...request, id });
|
|
24
|
+
}),
|
|
25
|
+
terminate: async () => {
|
|
26
|
+
await worker.terminate();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* When extraction runs, for every framework (#159). Nothing starts until a reader
|
|
32
|
+
* first asks for a story's docs: no worker, no checker. Then only the components
|
|
33
|
+
* that story imports are extracted, after collection has finished, and a change
|
|
34
|
+
* on disk drops what was extracted rather than re-extracting it unasked.
|
|
35
|
+
*/
|
|
36
|
+
export function createDocgenService(options) {
|
|
37
|
+
const warn = options.warn ?? (message => console.warn(message));
|
|
38
|
+
const plugins = options.plugins.filter((plugin) => {
|
|
39
|
+
if (!plugin.docgen) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const reason = options.enabled ? plugin.docgen.unavailable?.(options.root) : undefined;
|
|
43
|
+
if (reason) {
|
|
44
|
+
warn(`[poveste] ${reason}`);
|
|
45
|
+
}
|
|
46
|
+
return !reason;
|
|
47
|
+
});
|
|
48
|
+
const enabled = options.enabled && plugins.length > 0;
|
|
49
|
+
const createRunner = options.createRunner ?? createWorkerRunner;
|
|
50
|
+
let runner;
|
|
51
|
+
const cache = new Map();
|
|
52
|
+
const watched = new Set();
|
|
53
|
+
const exclude = options.bookOptions?.exclude ?? [];
|
|
54
|
+
function pluginFor(file) {
|
|
55
|
+
if (exclude.some(pattern => file.includes(pattern))) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return plugins.find(plugin => plugin.docgen.match(file));
|
|
59
|
+
}
|
|
60
|
+
function start() {
|
|
61
|
+
runner ??= createRunner({
|
|
62
|
+
root: options.root,
|
|
63
|
+
extractors: plugins.map(plugin => ({
|
|
64
|
+
name: plugin.name,
|
|
65
|
+
module: plugin.docgen.module,
|
|
66
|
+
options: { ...plugin.docgen.options, ...options.bookOptions },
|
|
67
|
+
})),
|
|
68
|
+
});
|
|
69
|
+
return runner;
|
|
70
|
+
}
|
|
71
|
+
function extract(files) {
|
|
72
|
+
const missing = files.filter(file => !cache.has(file));
|
|
73
|
+
if (missing.length) {
|
|
74
|
+
const response = start().send({
|
|
75
|
+
type: 'extract',
|
|
76
|
+
requests: missing.map(file => ({ name: pluginFor(file).name, file })),
|
|
77
|
+
});
|
|
78
|
+
for (const file of missing) {
|
|
79
|
+
cache.set(file, response.then(({ results }) => results?.[file] ?? { error: 'the docgen worker gave no answer' }));
|
|
80
|
+
}
|
|
81
|
+
void response.then(({ sources }) => {
|
|
82
|
+
const unseen = (sources ?? []).filter(file => !watched.has(file));
|
|
83
|
+
if (unseen.length && options.watch) {
|
|
84
|
+
unseen.forEach(file => watched.add(file));
|
|
85
|
+
options.watch(unseen);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return Promise.all(files.map(async (file) => [relative(options.root, file), await cache.get(file)]));
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
enabled,
|
|
93
|
+
/** Whether a worker exists, and so whether a checker could. */
|
|
94
|
+
get started() {
|
|
95
|
+
return runner !== undefined;
|
|
96
|
+
},
|
|
97
|
+
async request(storyId) {
|
|
98
|
+
if (!enabled) {
|
|
99
|
+
return { storyId, components: {} };
|
|
100
|
+
}
|
|
101
|
+
await options.collected;
|
|
102
|
+
const files = (await options.componentsOf(storyId)).filter(file => pluginFor(file));
|
|
103
|
+
return { storyId, components: Object.fromEntries(await extract(files)) };
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* A file changed. Returns whether anything extracted went stale, so the
|
|
107
|
+
* caller can tell an open panel to ask again. Before the first request there
|
|
108
|
+
* is nothing to drop and nothing is started.
|
|
109
|
+
*/
|
|
110
|
+
async fileChanged(file) {
|
|
111
|
+
if (!runner || cache.size === 0 || file.includes('/node_modules/')) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
await runner.send({ type: 'update', file });
|
|
115
|
+
cache.clear();
|
|
116
|
+
return true;
|
|
117
|
+
},
|
|
118
|
+
async dispose() {
|
|
119
|
+
const current = runner;
|
|
120
|
+
runner = undefined;
|
|
121
|
+
cache.clear();
|
|
122
|
+
if (current) {
|
|
123
|
+
await current.send({ type: 'dispose' });
|
|
124
|
+
await current.terminate();
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
2
|
+
import { createDocgenEngine } from './engine.js';
|
|
3
|
+
const engine = createDocgenEngine(workerData);
|
|
4
|
+
parentPort.on('message', async (message) => {
|
|
5
|
+
let response;
|
|
6
|
+
switch (message.type) {
|
|
7
|
+
case 'extract':
|
|
8
|
+
response = { id: message.id, results: await engine.extract(message.requests), stats: { ...engine.stats }, sources: await engine.sources() };
|
|
9
|
+
break;
|
|
10
|
+
case 'update':
|
|
11
|
+
await engine.update(message.file);
|
|
12
|
+
response = { id: message.id };
|
|
13
|
+
break;
|
|
14
|
+
case 'dispose':
|
|
15
|
+
await engine.dispose();
|
|
16
|
+
response = { id: message.id };
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
parentPort.postMessage(response);
|
|
20
|
+
});
|
package/dist/node/dom/env.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export declare function createDomEnv(): {
|
|
|
6
6
|
storages: Storage[];
|
|
7
7
|
};
|
|
8
8
|
/** Returns a reused environment to the state a fresh one would have been in. */
|
|
9
|
-
export declare function resetDomEnv(env: Pick<ReturnType<typeof createDomEnv>, 'window' | 'pristineKeys' | 'storages'>): void;
|
|
9
|
+
export declare function resetDomEnv(env: Pick<ReturnType<typeof createDomEnv>, 'window' | 'pristineKeys' | 'storages'>, kept?: ReadonlySet<string>): void;
|
package/dist/node/dom/env.js
CHANGED
|
@@ -134,7 +134,7 @@ export function createDomEnv() {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
/** Returns a reused environment to the state a fresh one would have been in. */
|
|
137
|
-
export function resetDomEnv(env) {
|
|
137
|
+
export function resetDomEnv(env, kept = new Set()) {
|
|
138
138
|
const doc = env.window.document;
|
|
139
139
|
for (const el of [doc.documentElement, doc.head, doc.body]) {
|
|
140
140
|
if (!el)
|
|
@@ -149,8 +149,9 @@ export function resetDomEnv(env) {
|
|
|
149
149
|
storage.clear();
|
|
150
150
|
}
|
|
151
151
|
// Additions only: the modules that installed the rest are cached, not re-run.
|
|
152
|
+
// So are the ones that installed `kept`, after this environment was made.
|
|
152
153
|
for (const key of Object.keys(globalThis)) {
|
|
153
|
-
if (!env.pristineKeys.has(key)) {
|
|
154
|
+
if (!env.pristineKeys.has(key) && !kept.has(key)) {
|
|
154
155
|
delete globalThis[key];
|
|
155
156
|
}
|
|
156
157
|
}
|
package/dist/node/server.js
CHANGED
|
@@ -4,6 +4,8 @@ import { createServer as createViteServer, mergeConfig as mergeViteConfig } from
|
|
|
4
4
|
import { useCollectStories } from './collect/index.js';
|
|
5
5
|
import { socketOnBookPort } from './commands/port.js';
|
|
6
6
|
import { answerDevEvent } from './dev-event.js';
|
|
7
|
+
import { componentsOfStory } from './docgen/components-of.js';
|
|
8
|
+
import { createDocgenService } from './docgen/service.js';
|
|
7
9
|
import { useModuleLoader } from './load.js';
|
|
8
10
|
import { createMarkdownFilesWatcher, onMarkdownFileChange, onMarkdownListChange } from './markdown.js';
|
|
9
11
|
import { DevEventPluginApi, DevPluginApi } from './plugin.js';
|
|
@@ -143,6 +145,31 @@ async function startServer(ctx, options, onOpen, close) {
|
|
|
143
145
|
},
|
|
144
146
|
],
|
|
145
147
|
});
|
|
148
|
+
// Auto-docs (#1159). Nothing here starts a worker: the first request does.
|
|
149
|
+
let markCollected;
|
|
150
|
+
const collected = new Promise((resolve) => {
|
|
151
|
+
markCollected = resolve;
|
|
152
|
+
});
|
|
153
|
+
const docgen = createDocgenService({
|
|
154
|
+
root: ctx.root,
|
|
155
|
+
plugins: ctx.config.plugins,
|
|
156
|
+
enabled: ctx.config.autoDocs !== false,
|
|
157
|
+
bookOptions: typeof ctx.config.autoDocs === 'object' ? ctx.config.autoDocs : undefined,
|
|
158
|
+
collected,
|
|
159
|
+
componentsOf: storyId => componentsOfStory(ctx.storyFiles, nodeServer.moduleGraph, storyId),
|
|
160
|
+
watch: files => server.watcher.add(files),
|
|
161
|
+
});
|
|
162
|
+
onOpen('docgen', () => docgen.dispose());
|
|
163
|
+
if (docgen.enabled) {
|
|
164
|
+
server.ws.on('poveste:docgen-request', async ({ storyId }) => {
|
|
165
|
+
server.ws.send('poveste:docgen-result', await docgen.request(storyId));
|
|
166
|
+
});
|
|
167
|
+
server.watcher.on('change', async (file) => {
|
|
168
|
+
if (await docgen.fileChanged(file)) {
|
|
169
|
+
server.ws.send('poveste:docgen-stale', {});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
146
173
|
// Invalidate modules
|
|
147
174
|
const invalidateModule = (id) => {
|
|
148
175
|
const mod = server.moduleGraph.getModuleById(id);
|
|
@@ -248,6 +275,7 @@ async function startServer(ctx, options, onOpen, close) {
|
|
|
248
275
|
return;
|
|
249
276
|
}
|
|
250
277
|
didAllStoriesYet = true;
|
|
278
|
+
markCollected();
|
|
251
279
|
server.ws.send('poveste:all-stories-loaded', {});
|
|
252
280
|
}
|
|
253
281
|
if (closed) {
|
package/dist/node/stories.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ServerStoryFile } from '@poveste/shared';
|
|
1
|
+
import type { ServerStoryFile, SupportMatchPattern } from '@poveste/shared';
|
|
2
2
|
import type { Context } from './context.js';
|
|
3
3
|
type StoryChangeHandler = (file?: ServerStoryFile) => unknown;
|
|
4
4
|
/**
|
|
@@ -16,6 +16,7 @@ type StoryListChangeHandler = () => unknown;
|
|
|
16
16
|
export declare function onStoryListChange(handler: StoryListChangeHandler): () => void;
|
|
17
17
|
export declare function notifyStoryListChange(): void;
|
|
18
18
|
export declare function watchStories(newContext: Context): Promise<import("chokidar").FSWatcher>;
|
|
19
|
+
export declare function noSupportPluginMessage(file: string, supportMatch: SupportMatchPattern[]): string;
|
|
19
20
|
export declare function addStory(relativeFilePath: string, virtualModuleCode?: string): ServerStoryFile;
|
|
20
21
|
export declare function removeStory(relativeFilePath: string): void;
|
|
21
22
|
export declare function findAllStories(newContext: Context): Promise<void>;
|
package/dist/node/stories.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { kebabCase } from 'change-case';
|
|
2
2
|
import chokidar from 'chokidar';
|
|
3
|
-
import { basename, resolve } from 'pathe';
|
|
3
|
+
import { basename, extname, relative, resolve } from 'pathe';
|
|
4
4
|
import picomatch from 'picomatch';
|
|
5
5
|
import { glob } from 'tinyglobby';
|
|
6
6
|
import { unregister } from './util/unregister.js';
|
|
@@ -65,6 +65,23 @@ export async function watchStories(newContext) {
|
|
|
65
65
|
function getAbsoluteFilePath(relativeFilePath) {
|
|
66
66
|
return resolve(context.root, relativeFilePath);
|
|
67
67
|
}
|
|
68
|
+
// The plugin a story's extension needs, where one plugin alone collects it. A
|
|
69
|
+
// Quasar book without `HstVue()` was told only that nothing claimed its story,
|
|
70
|
+
// which named neither the file's plugin nor which ones had looked (#1337).
|
|
71
|
+
const PLUGIN_FOR_EXTENSION = {
|
|
72
|
+
'.vue': '`HstVue()` from `@poveste/plugin-vue`',
|
|
73
|
+
'.svelte': '`HstSvelte()` from `@poveste/plugin-svelte`',
|
|
74
|
+
};
|
|
75
|
+
export function noSupportPluginMessage(file, supportMatch) {
|
|
76
|
+
const claimed = supportMatch.map(p => `${p.id} (${p.patterns.join(', ')})`).join(', ');
|
|
77
|
+
const extension = extname(file);
|
|
78
|
+
const needed = PLUGIN_FOR_EXTENSION[extension];
|
|
79
|
+
return [
|
|
80
|
+
`No support plugin found for file ${file}.`,
|
|
81
|
+
`What this book's plugins collect: ${claimed || 'nothing'}.`,
|
|
82
|
+
...needed ? [`A \`${extension}\` story needs ${needed} in the config's \`plugins\`.`] : [],
|
|
83
|
+
].join(' ');
|
|
84
|
+
}
|
|
68
85
|
export function addStory(relativeFilePath, virtualModuleCode) {
|
|
69
86
|
const absoluteFilePath = getAbsoluteFilePath(relativeFilePath);
|
|
70
87
|
for (const file of context.storyFiles) {
|
|
@@ -87,14 +104,17 @@ export function addStory(relativeFilePath, virtualModuleCode) {
|
|
|
87
104
|
}
|
|
88
105
|
}
|
|
89
106
|
if (!supportPluginId) {
|
|
90
|
-
throw new Error(
|
|
107
|
+
throw new Error(noSupportPluginMessage(absoluteFilePath, context.config.supportMatch));
|
|
91
108
|
}
|
|
109
|
+
// A plugin adds its story by absolute path, and `relativePath` and a virtual
|
|
110
|
+
// module's id are published in a built book (#1383).
|
|
111
|
+
const relativePath = relative(context.root, absoluteFilePath);
|
|
92
112
|
const file = {
|
|
93
113
|
id: fileId, // The file id will be changed by the story id after it is collected
|
|
94
114
|
path: absoluteFilePath,
|
|
95
|
-
relativePath
|
|
115
|
+
relativePath,
|
|
96
116
|
fileName,
|
|
97
|
-
moduleId: virtualModuleCode ? `virtual:story:${
|
|
117
|
+
moduleId: virtualModuleCode ? `virtual:story:${relativePath}` : absoluteFilePath,
|
|
98
118
|
supportPluginId,
|
|
99
119
|
virtual: !!virtualModuleCode,
|
|
100
120
|
moduleCode: virtualModuleCode,
|
package/dist/node/util/head.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { UseHeadInput } from 'unhead/types';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The description a book's index page gets when its `head` sets none. Without
|
|
4
|
+
* one, a search result and Lighthouse's SEO audit have nothing to read (#316).
|
|
5
|
+
* Unhead keeps one `description` per page and a later entry wins, so a book's own
|
|
6
|
+
* replaces this.
|
|
7
|
+
*/
|
|
8
|
+
export declare function defaultDescription(title: string): UseHeadInput;
|
|
9
|
+
export declare function applyHeadTransform(html: string, head: UseHeadInput | undefined, defaults?: UseHeadInput): Promise<string>;
|
package/dist/node/util/head.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { createHead, transformHtmlTemplate } from 'unhead/server';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The description a book's index page gets when its `head` sets none. Without
|
|
4
|
+
* one, a search result and Lighthouse's SEO audit have nothing to read (#316).
|
|
5
|
+
* Unhead keeps one `description` per page and a later entry wins, so a book's own
|
|
6
|
+
* replaces this.
|
|
7
|
+
*/
|
|
8
|
+
export function defaultDescription(title) {
|
|
9
|
+
return { meta: [{ name: 'description', content: `${title}, a book of component stories.` }] };
|
|
10
|
+
}
|
|
11
|
+
export async function applyHeadTransform(html, head, defaults) {
|
|
12
|
+
const init = [defaults, head].filter((entry) => entry !== undefined);
|
|
13
|
+
if (init.length === 0) {
|
|
4
14
|
return html;
|
|
5
15
|
}
|
|
6
16
|
// disableDefaults: don't inject unhead's `lang="en"` / viewport tags — Poveste's
|
|
7
17
|
// own HTML template already provides viewport, and we shouldn't override it.
|
|
8
|
-
const instance = createHead({ init
|
|
18
|
+
const instance = createHead({ init, disableDefaults: true });
|
|
9
19
|
return await transformHtmlTemplate(instance, html);
|
|
10
20
|
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export declare const __dirname: string;
|
|
2
|
+
/**
|
|
3
|
+
* Source as a reader is shown it: an import `getInjectedImport` wrote as a file
|
|
4
|
+
* URL on this machine reads as the package it stands for (#1383).
|
|
5
|
+
*/
|
|
6
|
+
export declare function withPackageImports(source: string): string;
|
|
2
7
|
export declare function getInjectedImport(request: string): string;
|
|
@@ -8,6 +8,17 @@ const alias = {
|
|
|
8
8
|
'@poveste/vendors/vue': path.resolve(__dirname, '../vendors/vue.js'),
|
|
9
9
|
'@poveste/controls': path.resolve(__dirname, '../vendors/controls.js'),
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Source as a reader is shown it: an import `getInjectedImport` wrote as a file
|
|
13
|
+
* URL on this machine reads as the package it stands for (#1383).
|
|
14
|
+
*/
|
|
15
|
+
export function withPackageImports(source) {
|
|
16
|
+
let result = source;
|
|
17
|
+
for (const [request, file] of Object.entries(alias)) {
|
|
18
|
+
result = result.replaceAll(JSON.stringify(pathToFileURL(file).href), JSON.stringify(request));
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
11
22
|
export function getInjectedImport(request) {
|
|
12
23
|
let id = request;
|
|
13
24
|
id = alias[id] ?? id;
|
|
@@ -10,6 +10,8 @@ export declare const SEARCH_TITLE_DATA_ID = "virtual:$poveste-search-title-data"
|
|
|
10
10
|
export declare const RESOLVED_SEARCH_TITLE_DATA_ID = "\0virtual:$poveste-search-title-data";
|
|
11
11
|
export declare const SEARCH_DOCS_DATA_ID = "virtual:$poveste-search-docs-data";
|
|
12
12
|
export declare const RESOLVED_SEARCH_DOCS_DATA_ID = "\0virtual:$poveste-search-docs-data";
|
|
13
|
+
export declare const COMPONENT_DOCS_ID = "virtual:$poveste-component-docs";
|
|
14
|
+
export declare const RESOLVED_COMPONENT_DOCS_ID = "\0virtual:$poveste-component-docs";
|
|
13
15
|
export declare const GENERATED_GLOBAL_SETUP = "virtual:$poveste-generated-global-setup";
|
|
14
16
|
export declare const RESOLVED_GENERATED_GLOBAL_SETUP = "\0virtual:$poveste-generated-global-setup";
|
|
15
17
|
export declare const GENERATED_SETUP_CODE = "virtual:$poveste-generated-setup-code";
|
|
@@ -17,6 +17,8 @@ export const RESOLVED_SEARCH_TITLE_DATA_ID = `\0${SEARCH_TITLE_DATA_ID}`;
|
|
|
17
17
|
export const SEARCH_DOCS_DATA_ID = 'virtual:$poveste-search-docs-data';
|
|
18
18
|
// export const RESOLVED_SEARCH_DOCS_DATA_ID = `/__resolved__${SEARCH_DOCS_DATA_ID}`
|
|
19
19
|
export const RESOLVED_SEARCH_DOCS_DATA_ID = `\0${SEARCH_DOCS_DATA_ID}`;
|
|
20
|
+
export const COMPONENT_DOCS_ID = 'virtual:$poveste-component-docs';
|
|
21
|
+
export const RESOLVED_COMPONENT_DOCS_ID = `\0${COMPONENT_DOCS_ID}`;
|
|
20
22
|
export const GENERATED_GLOBAL_SETUP = 'virtual:$poveste-generated-global-setup';
|
|
21
23
|
// export const RESOLVED_GENERATED_GLOBAL_SETUP = `/__resolved__${GENERATED_GLOBAL_SETUP}`
|
|
22
24
|
export const RESOLVED_GENERATED_GLOBAL_SETUP = `\0${GENERATED_GLOBAL_SETUP}`;
|
|
@@ -1,2 +1,26 @@
|
|
|
1
1
|
import type { Context } from '../context.js';
|
|
2
|
+
/**
|
|
3
|
+
* The config as the browser gets it: the fields `@poveste/app` reads, and no
|
|
4
|
+
* others. The rest is build-side, and holds paths on the machine that built the
|
|
5
|
+
* book (`outDir`, each plugin's modules, the logo files), which a built book
|
|
6
|
+
* published in full (#1383). The logos reach the browser as `logos`, below.
|
|
7
|
+
*/
|
|
8
|
+
export declare function clientConfig(config: Context['config']): {
|
|
9
|
+
theme: {
|
|
10
|
+
title: string | undefined;
|
|
11
|
+
logoHref: string | undefined;
|
|
12
|
+
defaultColorScheme: "auto" | "light" | "dark" | undefined;
|
|
13
|
+
hideColorSchemeSwitch: boolean | undefined;
|
|
14
|
+
storeColorScheme: boolean | undefined;
|
|
15
|
+
darkClass: string | undefined;
|
|
16
|
+
};
|
|
17
|
+
routerMode: "hash" | "history" | undefined;
|
|
18
|
+
autoApplyContrastColor: boolean | undefined;
|
|
19
|
+
responsivePresets: import("@poveste/shared").ResponsivePreset[] | undefined;
|
|
20
|
+
backgroundPresets: import("@poveste/shared").BackgroundPreset[] | undefined;
|
|
21
|
+
defaultBackgroundColor: string | undefined;
|
|
22
|
+
preview: import("@poveste/shared").PreviewConfig | undefined;
|
|
23
|
+
isolateStyles: boolean | undefined;
|
|
24
|
+
sandboxDarkClass: string | undefined;
|
|
25
|
+
};
|
|
2
26
|
export declare function resolvedConfig(ctx: Context): string;
|
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
import { jsIdentifier, jsString } from './codegen.js';
|
|
2
|
+
/**
|
|
3
|
+
* The config as the browser gets it: the fields `@poveste/app` reads, and no
|
|
4
|
+
* others. The rest is build-side, and holds paths on the machine that built the
|
|
5
|
+
* book (`outDir`, each plugin's modules, the logo files), which a built book
|
|
6
|
+
* published in full (#1383). The logos reach the browser as `logos`, below.
|
|
7
|
+
*/
|
|
8
|
+
export function clientConfig(config) {
|
|
9
|
+
const theme = config.theme ?? {};
|
|
10
|
+
return {
|
|
11
|
+
theme: {
|
|
12
|
+
title: theme.title,
|
|
13
|
+
logoHref: theme.logoHref,
|
|
14
|
+
defaultColorScheme: theme.defaultColorScheme,
|
|
15
|
+
hideColorSchemeSwitch: theme.hideColorSchemeSwitch,
|
|
16
|
+
storeColorScheme: theme.storeColorScheme,
|
|
17
|
+
darkClass: theme.darkClass,
|
|
18
|
+
},
|
|
19
|
+
routerMode: config.routerMode,
|
|
20
|
+
autoApplyContrastColor: config.autoApplyContrastColor,
|
|
21
|
+
responsivePresets: config.responsivePresets,
|
|
22
|
+
backgroundPresets: config.backgroundPresets,
|
|
23
|
+
// eslint-disable-next-line ts/no-deprecated -- the app still reads it as a fallback
|
|
24
|
+
defaultBackgroundColor: config.defaultBackgroundColor,
|
|
25
|
+
preview: config.preview,
|
|
26
|
+
isolateStyles: config.isolateStyles,
|
|
27
|
+
// eslint-disable-next-line ts/no-deprecated -- the app still reads it as a fallback
|
|
28
|
+
sandboxDarkClass: config.sandboxDarkClass,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
2
31
|
export function resolvedConfig(ctx) {
|
|
3
|
-
let js = `export const config = ${JSON.stringify(ctx.config)}\n`;
|
|
32
|
+
let js = `export const config = ${JSON.stringify(clientConfig(ctx.config))}\n`;
|
|
4
33
|
const logos = Object.entries(ctx.config.theme?.logo ?? {}).flatMap(([key, file]) => file === undefined ? [] : [[key, file]]);
|
|
5
34
|
for (const [key, file] of logos) {
|
|
6
35
|
js += `import Logo_${jsIdentifier(key, `theme.logo key '${key}'`)} from ${jsString(file)}\n`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import { resolve } from 'pathe';
|
|
3
|
+
import { withPackageImports } from '../util/vendors.js';
|
|
3
4
|
export async function storySource(ctx, id) {
|
|
4
5
|
const storyId = id.slice('/__resolved__virtual:story-source:'.length);
|
|
5
6
|
const storyFile = ctx.storyFiles.find(f => f.story?.id === storyId);
|
|
@@ -11,7 +12,7 @@ export async function storySource(ctx, id) {
|
|
|
11
12
|
else {
|
|
12
13
|
source = await fs.readFile(resolve(ctx.root, storyFile.relativePath), 'utf-8');
|
|
13
14
|
}
|
|
14
|
-
return `export default ${JSON.stringify(source)}`;
|
|
15
|
+
return `export default ${JSON.stringify(withPackageImports(source))}`;
|
|
15
16
|
}
|
|
16
17
|
return undefined;
|
|
17
18
|
}
|
|
@@ -42,6 +42,9 @@ export function createVirtualFilesPlugin(ctx, isServer) {
|
|
|
42
42
|
if (id.startsWith(VirtualFiles.SEARCH_DOCS_DATA_ID)) {
|
|
43
43
|
return VirtualFiles.RESOLVED_SEARCH_DOCS_DATA_ID;
|
|
44
44
|
}
|
|
45
|
+
if (id === VirtualFiles.COMPONENT_DOCS_ID) {
|
|
46
|
+
return VirtualFiles.RESOLVED_COMPONENT_DOCS_ID;
|
|
47
|
+
}
|
|
45
48
|
if (id.startsWith(VirtualFiles.GENERATED_GLOBAL_SETUP)) {
|
|
46
49
|
return VirtualFiles.RESOLVED_GENERATED_GLOBAL_SETUP;
|
|
47
50
|
}
|
|
@@ -95,6 +98,10 @@ export function createVirtualFilesPlugin(ctx, isServer) {
|
|
|
95
98
|
if (id === VirtualFiles.RESOLVED_SEARCH_DOCS_DATA_ID) {
|
|
96
99
|
return getSearchDataJS(await generateDocSearchData(ctx));
|
|
97
100
|
}
|
|
101
|
+
// Filled by `poveste build`; in dev the docs come over the dev socket instead.
|
|
102
|
+
if (id === VirtualFiles.RESOLVED_COMPONENT_DOCS_ID) {
|
|
103
|
+
return `export const componentDocs = ${JSON.stringify(ctx.componentDocs ?? {})}\n`;
|
|
104
|
+
}
|
|
98
105
|
if (id === VirtualFiles.RESOLVED_GENERATED_GLOBAL_SETUP) {
|
|
99
106
|
return VirtualFiles.resolvedGeneratedGlobalSetup(ctx);
|
|
100
107
|
}
|
package/dist/node/vite.js
CHANGED
|
@@ -11,7 +11,7 @@ import { optimizeEntries } from './optimize-entries.js';
|
|
|
11
11
|
import { notifyStoryChange } from './stories.js';
|
|
12
12
|
import { chromeCssScopePlugin, globalStylesPlugin, userCssScopePlugin, } from './style-isolation/index.js';
|
|
13
13
|
import { escapeRegExp } from './util/escape-regexp.js';
|
|
14
|
-
import { applyHeadTransform } from './util/head.js';
|
|
14
|
+
import { applyHeadTransform, defaultDescription } from './util/head.js';
|
|
15
15
|
import { viteCommand, viteMode } from './util/vite-mode.js';
|
|
16
16
|
import { chromePrebundles, collapseVendoredVue, vendorsFromPoveste } from './vendors.js';
|
|
17
17
|
import { createVirtualFilesPlugin } from './virtual/vite-plugin.js';
|
|
@@ -330,7 +330,7 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
330
330
|
<script type="module" src="/@fs/${APP_PATH}/bundle-main${process.env['POVESTE_DEV'] ? '-dev' : ''}.js"></script>
|
|
331
331
|
</body>
|
|
332
332
|
</html>`;
|
|
333
|
-
html = await applyHeadTransform(html, ctx.config.head);
|
|
333
|
+
html = await applyHeadTransform(html, ctx.config.head, defaultDescription(ctx.config.theme?.title ?? 'Poveste'));
|
|
334
334
|
// Apply Vite HTML transforms. This injects the Vite HMR client, and
|
|
335
335
|
// also applies HTML transforms from Vite plugins
|
|
336
336
|
html = await server.transformIndexHtml(req.url ?? '/', html);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poveste",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.20.0",
|
|
5
5
|
"description": "Interactive component playgrounds for Vue, Nuxt, Svelte, SvelteKit and Quasar, plus Solid and React (renderer only) — a drop-in fork of histoire, with Quasar support histoire does not ship.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sorin Gitlan",
|
|
@@ -78,19 +78,19 @@
|
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"@11ty/gray-matter": "^3.0.0",
|
|
81
|
-
"@poveste/app": "^0.
|
|
82
|
-
"@poveste/controls": "^0.
|
|
83
|
-
"@poveste/shared": "^0.
|
|
84
|
-
"@poveste/vendors": "^0.
|
|
81
|
+
"@poveste/app": "^0.20.0",
|
|
82
|
+
"@poveste/controls": "^0.20.0",
|
|
83
|
+
"@poveste/shared": "^0.20.0",
|
|
84
|
+
"@poveste/vendors": "^0.20.0",
|
|
85
85
|
"change-case": "^5.4.4",
|
|
86
86
|
"chokidar": "^5.0.0",
|
|
87
87
|
"defu": "^6.1.4",
|
|
88
88
|
"diacritics": "^1.3.0",
|
|
89
|
-
"es-module-lexer": "^3.0.
|
|
89
|
+
"es-module-lexer": "^3.0.3",
|
|
90
90
|
"fs-extra": "^11.4.1",
|
|
91
|
-
"jsdom": "^30.1.
|
|
91
|
+
"jsdom": "^30.1.2",
|
|
92
92
|
"lightningcss": "^1.32.0",
|
|
93
|
-
"markdown-it": "^15.0.
|
|
93
|
+
"markdown-it": "^15.0.3",
|
|
94
94
|
"markdown-it-anchor": "^10.0.0",
|
|
95
95
|
"markdown-it-attrs": "^5.0.1",
|
|
96
96
|
"markdown-it-emoji": "^3.1.0",
|
|
@@ -99,19 +99,19 @@
|
|
|
99
99
|
"picocolors": "^1.1.1",
|
|
100
100
|
"picomatch": "^4.0.7",
|
|
101
101
|
"sade": "^1.8.1",
|
|
102
|
-
"shiki": "^4.
|
|
102
|
+
"shiki": "^4.5.0",
|
|
103
103
|
"tinyglobby": "^0.2.17",
|
|
104
|
-
"unhead": "^3.4.
|
|
104
|
+
"unhead": "^3.4.3"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@types/diacritics": "^1.3.3",
|
|
108
108
|
"@types/fs-extra": "^11.0.4",
|
|
109
109
|
"@types/jsdom": "^30.0.0",
|
|
110
|
-
"@types/node": "^26.6.
|
|
110
|
+
"@types/node": "^26.6.5",
|
|
111
111
|
"@types/picomatch": "^4.0.3",
|
|
112
112
|
"typescript": "6.0.3",
|
|
113
|
-
"vite": "^8.3.
|
|
114
|
-
"vitest": "^5.0.
|
|
113
|
+
"vite": "^8.3.4",
|
|
114
|
+
"vitest": "^5.0.4"
|
|
115
115
|
},
|
|
116
116
|
"scripts": {
|
|
117
117
|
"build": "rimraf dist && tsc",
|