poveste 0.13.1 → 0.15.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 +4 -2
- package/client.d.ts +9 -1
- package/dist/node/__tests__/dev-cleanup/poveste.config.d.ts +7 -0
- package/dist/node/__tests__/dev-cleanup/poveste.config.js +15 -0
- package/dist/node/alias.js +1 -1
- package/dist/node/bin.js +5 -5
- package/dist/node/build-counts.d.ts +56 -0
- package/dist/node/build-counts.js +83 -0
- package/dist/node/build-serialize.d.ts +3 -3
- package/dist/node/build.js +56 -28
- package/dist/node/builtin-plugins/vanilla-support/MountStory.js +9 -9
- package/dist/node/builtin-plugins/vanilla-support/RenderStory.d.ts +1 -1
- package/dist/node/builtin-plugins/vanilla-support/RenderStory.js +5 -5
- package/dist/node/builtin-plugins/vanilla-support/collect.js +2 -2
- package/dist/node/builtin-plugins/vanilla-support/types.d.ts +2 -2
- package/dist/node/builtin-plugins/vanilla-support/vanilla.story.js +4 -4
- package/dist/node/collect/index.js +41 -39
- package/dist/node/collect/module-server.d.ts +14 -0
- package/dist/node/collect/module-server.js +268 -0
- package/dist/node/collect/rpc.d.ts +6 -0
- package/dist/node/collect/rpc.js +51 -0
- package/dist/node/collect/runner.d.ts +4 -0
- package/dist/node/collect/runner.js +130 -0
- package/dist/node/collect/worker.js +22 -25
- package/dist/node/colors.d.ts +4 -4
- package/dist/node/colors.js +6 -6
- package/dist/node/commands/dev.d.ts +13 -1
- package/dist/node/commands/dev.js +106 -45
- package/dist/node/config.d.ts +2 -2
- package/dist/node/config.js +50 -20
- package/dist/node/context.d.ts +2 -2
- package/dist/node/context.js +9 -4
- package/dist/node/dom/env.js +11 -8
- package/dist/node/dom/util.js +1 -1
- package/dist/node/load.js +10 -13
- package/dist/node/markdown.d.ts +3 -4
- package/dist/node/markdown.js +85 -36
- package/dist/node/optimize-entries.js +2 -2
- package/dist/node/plugin.d.ts +13 -8
- package/dist/node/plugin.js +24 -7
- package/dist/node/preview.d.ts +3 -3
- package/dist/node/preview.js +2 -2
- package/dist/node/search.d.ts +8 -2
- package/dist/node/server.d.ts +9 -4
- package/dist/node/server.js +70 -30
- package/dist/node/stories.d.ts +5 -4
- package/dist/node/stories.js +10 -6
- package/dist/node/style-isolation/entry-css-merger.js +2 -3
- package/dist/node/style-isolation/global-styles.js +1 -0
- package/dist/node/style-isolation/scope-chrome.js +2 -2
- package/dist/node/tree.js +29 -21
- package/dist/node/util/find-up.d.ts +1 -1
- package/dist/node/util/managed-watches.d.ts +14 -0
- package/dist/node/util/managed-watches.js +39 -0
- package/dist/node/util/unregister.d.ts +2 -0
- package/dist/node/util/unregister.js +9 -0
- package/dist/node/util/vendors.js +1 -3
- package/dist/node/util/watch-ignore.js +3 -3
- package/dist/node/virtual/resolved-config.js +4 -5
- package/dist/node/virtual/resolved-generated-setup-code.d.ts +1 -1
- package/dist/node/virtual/resolved-generated-setup-code.js +1 -1
- package/dist/node/virtual/resolved-markdown-files.js +1 -1
- package/dist/node/virtual/resolved-support-plugins-client.js +1 -1
- package/dist/node/virtual/resolved-support-plugins-collect.js +1 -1
- package/dist/node/virtual/resolved-theme.js +8 -3
- package/dist/node/virtual/story-source.d.ts +1 -1
- package/dist/node/virtual/story-source.js +2 -1
- package/dist/node/virtual/story.d.ts +1 -1
- package/dist/node/virtual/story.js +1 -0
- package/dist/node/virtual/util.d.ts +1 -1
- package/dist/node/virtual/vite-plugin.js +2 -0
- package/dist/node/vite.d.ts +1 -1
- package/dist/node/vite.js +50 -12
- package/package.json +22 -24
- package/plugin.d.ts +11 -0
- package/plugin.js +5 -0
- package/dist/node/builtin-plugins/pinceau-tokens-style.d.ts +0 -1
- package/dist/node/builtin-plugins/pinceau-tokens-style.js +0 -85
- package/dist/node/builtin-plugins/pinceau-tokens.d.ts +0 -6
- package/dist/node/builtin-plugins/pinceau-tokens.js +0 -424
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ pnpm i -D poveste @poveste/plugin-nuxt # Nuxt 4
|
|
|
20
20
|
pnpm i -D poveste @poveste/plugin-quasar # Quasar
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
> **Installed an older version than you expected?** For about a day after a release, pnpm installs the previous one — `(x.y.z is available)`, no error. That is pnpm's release-age cooldown, not a broken publish. Use `npm`, wait it out, or pass `--config.minimum-release-age=0` (kebab-case — pnpm 12 silently ignores the camelCase spelling). Asking for the exact version does not help: pnpm 12 refuses one inside the window too.
|
|
24
|
+
|
|
23
25
|
Then create a `poveste.config.ts` in your project root:
|
|
24
26
|
|
|
25
27
|
```ts
|
|
@@ -46,11 +48,11 @@ Run `npx poveste --help` for the full list of options.
|
|
|
46
48
|
## Requirements
|
|
47
49
|
|
|
48
50
|
Node `^22.22.2 || ^24.15.0 || >=26.0.0` and Vite `^8.0.0`. See
|
|
49
|
-
[supported versions](https://poveste.dev/guide/getting-started
|
|
51
|
+
[supported versions](https://poveste.dev/guide/getting-started#supported-versions) for
|
|
50
52
|
what proves each range.
|
|
51
53
|
|
|
52
54
|
## Links
|
|
53
55
|
|
|
54
56
|
- [Documentation](https://poveste.dev)
|
|
55
|
-
- [Migrating from histoire](https://poveste.dev/guide/migration-from-histoire
|
|
57
|
+
- [Migrating from histoire](https://poveste.dev/guide/migration-from-histoire)
|
|
56
58
|
- [GitHub](https://github.com/poveste-dev/poveste)
|
package/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @deprecated
|
|
2
|
+
* @deprecated Use {@link logEvent} instead. This forwards to it and warns.
|
|
3
3
|
*/
|
|
4
4
|
export function hstEvent(name: string, argument): void
|
|
5
5
|
|
|
@@ -15,5 +15,13 @@ export function logEvent(name: string, argument): void
|
|
|
15
15
|
*/
|
|
16
16
|
export function isCollecting(): boolean
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Switches the book between light and dark, or to `value` when one is given.
|
|
20
|
+
* Returns the mode it ended up in.
|
|
21
|
+
*/
|
|
18
22
|
export function toggleDark(value?: boolean): boolean
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether the book is currently in dark mode.
|
|
26
|
+
*/
|
|
19
27
|
export function isDark(): boolean
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { appendFileSync } from 'node:fs';
|
|
2
|
+
import { onPovesteCleanup } from 'poveste';
|
|
3
|
+
const marker = process.env['POVESTE_CLEANUP_MARKER'] ?? '';
|
|
4
|
+
// Registers the way @poveste/plugin-nuxt does, while the config resolves, and
|
|
5
|
+
// leaves a line behind each time the cleanup runs.
|
|
6
|
+
// `.ts`, not `.js`: the restart specs edit this file, and a `.js` config can be
|
|
7
|
+
// served from Node's module cache, so the edit would never be read.
|
|
8
|
+
export default {
|
|
9
|
+
plugins: [{
|
|
10
|
+
name: 'cleanup-marker',
|
|
11
|
+
defaultConfig() {
|
|
12
|
+
onPovesteCleanup(() => appendFileSync(marker, 'ran\n'));
|
|
13
|
+
},
|
|
14
|
+
}],
|
|
15
|
+
};
|
package/dist/node/alias.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import path from 'pathe';
|
|
3
3
|
const require = createRequire(import.meta.url);
|
|
4
|
-
export const APP_PATH = path.join(path.dirname(require.resolve('@poveste/app/package.json')), process.env
|
|
4
|
+
export const APP_PATH = path.join(path.dirname(require.resolve('@poveste/app/package.json')), process.env['POVESTE_DEV'] ? 'src' : 'dist');
|
|
5
5
|
export const TEMP_PATH = path.join(process.cwd(), 'node_modules', '.poveste');
|
package/dist/node/bin.js
CHANGED
|
@@ -4,9 +4,9 @@ import { dirname, resolve } from 'pathe';
|
|
|
4
4
|
import sade from 'sade';
|
|
5
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const { version } = JSON.parse(fs.readFileSync(resolve(__dirname, '../../package.json'), 'utf8'));
|
|
7
|
-
process.env
|
|
8
|
-
process.env
|
|
9
|
-
process.env
|
|
7
|
+
process.env['NODE_ENV'] = process.argv.includes('build') || process.argv.includes('preview') ? 'production' : 'development';
|
|
8
|
+
process.env['HISTOIRE'] = 'true';
|
|
9
|
+
process.env['POVESTE'] = 'true';
|
|
10
10
|
// sade does not await an action, so a rejected command was terminated only by
|
|
11
11
|
// Node's default handling of the unhandled rejection it became. That is not the
|
|
12
12
|
// CLI's to rely on: any dependency can install an `unhandledRejection` listener
|
|
@@ -15,7 +15,7 @@ process.env.POVESTE = 'true';
|
|
|
15
15
|
//
|
|
16
16
|
// So failure is reported here, the exit code set, and the process forced out if
|
|
17
17
|
// it does not leave on its own. Closing the collection ports (#426) was not
|
|
18
|
-
// enough on its own: a failed build still hangs in the
|
|
18
|
+
// enough on its own: a failed build still hangs in the nuxt and sveltekit books,
|
|
19
19
|
// holding `FSEventWrap` and `FSReqCallback` handles — watchers nothing closes on
|
|
20
20
|
// the error path. And `dev` and `preview` hold a listening server by design, so
|
|
21
21
|
// for them no amount of leak-fixing would ever let the loop drain.
|
|
@@ -47,7 +47,7 @@ process.env.POVESTE = 'true';
|
|
|
47
47
|
* that ends and one that does not.
|
|
48
48
|
*/
|
|
49
49
|
function proveItDrains() {
|
|
50
|
-
return !!process.env
|
|
50
|
+
return !!process.env['POVESTE_NO_FORCE_EXIT'];
|
|
51
51
|
}
|
|
52
52
|
function run(command) {
|
|
53
53
|
return command().then(() => undefined, (error) => {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ServerStoryFile } from '@poveste/shared';
|
|
2
|
+
export interface BuildCounts {
|
|
3
|
+
/** Story files that produced a story with variants. */
|
|
4
|
+
stories: number;
|
|
5
|
+
variants: number;
|
|
6
|
+
/** Standalone `.story.md` pages, which the book's home page calls Documents. */
|
|
7
|
+
docs: number;
|
|
8
|
+
/** Relative paths of the files that produced neither, so the warning can name them. */
|
|
9
|
+
empty: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Splits the collected story files three ways, the way the book's own home page
|
|
13
|
+
* does: stories, documents, and files that produced nothing.
|
|
14
|
+
*
|
|
15
|
+
* "No variants" is not the question. A standalone `.story.md` is collected into a
|
|
16
|
+
* story with `docsOnly: true` and `variants: []` by construction (`markdown.ts`),
|
|
17
|
+
* so counting empty by variants alone reported every documentation page in the
|
|
18
|
+
* book as a problem — three of the four `examples/vue` warned about are
|
|
19
|
+
* `SHARED_STORY_TITLES` entries that a required spec fails if you delete them,
|
|
20
|
+
* which left the one real empty file hidden inside them (#670).
|
|
21
|
+
*
|
|
22
|
+
* Documents are counted rather than folded in with stories because `HomeView.vue`
|
|
23
|
+
* already makes exactly this split, and a build that reports 60 stories over a
|
|
24
|
+
* home page reading 57 Stories and 3 Documents disagrees with itself.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildCounts(storyFiles: ServerStoryFile[]): BuildCounts;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the build produced anything a reader can open.
|
|
29
|
+
*
|
|
30
|
+
* Stated once because two callers ask it: the message below, and the colour
|
|
31
|
+
* `build.ts` prints that message in. Reading it back off the message instead —
|
|
32
|
+
* one line means green — ties the colour to the shape of an unrelated decision,
|
|
33
|
+
* and a second success line would turn a good build yellow.
|
|
34
|
+
*/
|
|
35
|
+
export declare function builtSomething(counts: BuildCounts): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* What a finished build says it produced.
|
|
38
|
+
*
|
|
39
|
+
* A separate function because the interesting case is a claim rather than a
|
|
40
|
+
* number: `✅ Built 0 stories (0 variants)` is true of a working build in a
|
|
41
|
+
* project with nothing to build, and it is what a first-time reader gets from
|
|
42
|
+
* the first command the guide tells them to run (#624). Nothing in it says
|
|
43
|
+
* where the build looked, so there is no next step in it either.
|
|
44
|
+
*
|
|
45
|
+
* Nothing built is two states, not one, and the likelier is the second: a
|
|
46
|
+
* reader who followed the guide has a story file and has not put a `<Story>`
|
|
47
|
+
* in it yet. Those files matched, so "nothing matched" is false of them, and
|
|
48
|
+
* "write a story file" asks for the one they already wrote.
|
|
49
|
+
*
|
|
50
|
+
* Not an error. A book with no stories is a valid thing to build — scaffolding
|
|
51
|
+
* is exactly that state — so this changes what is said, not the exit code.
|
|
52
|
+
*
|
|
53
|
+
* Documents count as content: a book of `.story.md` pages with no variants has
|
|
54
|
+
* something in it, and telling its author nothing was found would be wrong.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildSummary(counts: BuildCounts, seconds: number, storyMatch: string[]): string[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits the collected story files three ways, the way the book's own home page
|
|
3
|
+
* does: stories, documents, and files that produced nothing.
|
|
4
|
+
*
|
|
5
|
+
* "No variants" is not the question. A standalone `.story.md` is collected into a
|
|
6
|
+
* story with `docsOnly: true` and `variants: []` by construction (`markdown.ts`),
|
|
7
|
+
* so counting empty by variants alone reported every documentation page in the
|
|
8
|
+
* book as a problem — three of the four `examples/vue` warned about are
|
|
9
|
+
* `SHARED_STORY_TITLES` entries that a required spec fails if you delete them,
|
|
10
|
+
* which left the one real empty file hidden inside them (#670).
|
|
11
|
+
*
|
|
12
|
+
* Documents are counted rather than folded in with stories because `HomeView.vue`
|
|
13
|
+
* already makes exactly this split, and a build that reports 60 stories over a
|
|
14
|
+
* home page reading 57 Stories and 3 Documents disagrees with itself.
|
|
15
|
+
*/
|
|
16
|
+
export function buildCounts(storyFiles) {
|
|
17
|
+
const counts = { stories: 0, variants: 0, docs: 0, empty: [] };
|
|
18
|
+
for (const file of storyFiles) {
|
|
19
|
+
if (file.story?.docsOnly) {
|
|
20
|
+
counts.docs++;
|
|
21
|
+
}
|
|
22
|
+
else if (file.story?.variants.length) {
|
|
23
|
+
counts.stories++;
|
|
24
|
+
counts.variants += file.story.variants.length;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
counts.empty.push(file.relativePath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return counts;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether the build produced anything a reader can open.
|
|
34
|
+
*
|
|
35
|
+
* Stated once because two callers ask it: the message below, and the colour
|
|
36
|
+
* `build.ts` prints that message in. Reading it back off the message instead —
|
|
37
|
+
* one line means green — ties the colour to the shape of an unrelated decision,
|
|
38
|
+
* and a second success line would turn a good build yellow.
|
|
39
|
+
*/
|
|
40
|
+
export function builtSomething(counts) {
|
|
41
|
+
return counts.stories > 0 || counts.docs > 0;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* What a finished build says it produced.
|
|
45
|
+
*
|
|
46
|
+
* A separate function because the interesting case is a claim rather than a
|
|
47
|
+
* number: `✅ Built 0 stories (0 variants)` is true of a working build in a
|
|
48
|
+
* project with nothing to build, and it is what a first-time reader gets from
|
|
49
|
+
* the first command the guide tells them to run (#624). Nothing in it says
|
|
50
|
+
* where the build looked, so there is no next step in it either.
|
|
51
|
+
*
|
|
52
|
+
* Nothing built is two states, not one, and the likelier is the second: a
|
|
53
|
+
* reader who followed the guide has a story file and has not put a `<Story>`
|
|
54
|
+
* in it yet. Those files matched, so "nothing matched" is false of them, and
|
|
55
|
+
* "write a story file" asks for the one they already wrote.
|
|
56
|
+
*
|
|
57
|
+
* Not an error. A book with no stories is a valid thing to build — scaffolding
|
|
58
|
+
* is exactly that state — so this changes what is said, not the exit code.
|
|
59
|
+
*
|
|
60
|
+
* Documents count as content: a book of `.story.md` pages with no variants has
|
|
61
|
+
* something in it, and telling its author nothing was found would be wrong.
|
|
62
|
+
*/
|
|
63
|
+
export function buildSummary(counts, seconds, storyMatch) {
|
|
64
|
+
const took = `in ${seconds}s`;
|
|
65
|
+
const globs = storyMatch.join(', ');
|
|
66
|
+
if (!builtSomething(counts)) {
|
|
67
|
+
if (counts.empty.length) {
|
|
68
|
+
const files = `${counts.empty.length} file${counts.empty.length === 1 ? '' : 's'}`;
|
|
69
|
+
return [
|
|
70
|
+
`Built 0 stories ${took} — ${files} matched ${globs} and produced no story`,
|
|
71
|
+
'A story file needs a <Story> in it: https://poveste.dev/guide/',
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
return [
|
|
75
|
+
`Built 0 stories ${took} — nothing matched ${globs}`,
|
|
76
|
+
'Write a story file and run this again: https://poveste.dev/guide/',
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
const stories = `${counts.stories} stor${counts.stories === 1 ? 'y' : 'ies'}`;
|
|
80
|
+
const variants = `${counts.variants} variant${counts.variants === 1 ? '' : 's'}`;
|
|
81
|
+
const docs = counts.docs ? ` and ${counts.docs} document${counts.docs === 1 ? '' : 's'}` : '';
|
|
82
|
+
return [`✅ Built ${stories} (${variants})${docs} ${took}`];
|
|
83
|
+
}
|
|
@@ -3,9 +3,9 @@ import type { Context } from './context.js';
|
|
|
3
3
|
interface SerializedStory extends Omit<ServerStory, 'docsText'> {
|
|
4
4
|
relativePath: string;
|
|
5
5
|
supportPluginId: string;
|
|
6
|
-
treePath?: string[];
|
|
7
|
-
virtual?: boolean;
|
|
8
|
-
markdownFile
|
|
6
|
+
treePath?: string[] | undefined;
|
|
7
|
+
virtual?: boolean | undefined;
|
|
8
|
+
markdownFile: SerializedMarkdownFile | null;
|
|
9
9
|
}
|
|
10
10
|
interface SerializedMarkdownFile {
|
|
11
11
|
id: string;
|
package/dist/node/build.js
CHANGED
|
@@ -5,6 +5,7 @@ import { join } from 'pathe';
|
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { createServer as createViteServer, mergeConfig as mergeViteConfig, build as viteBuild, } from 'vite';
|
|
7
7
|
import { APP_PATH } from './alias.js';
|
|
8
|
+
import { buildCounts, buildSummary, builtSomething } from './build-counts.js';
|
|
8
9
|
import { getSerializedStoryData } from './build-serialize.js';
|
|
9
10
|
import { useCollectStories } from './collect/index.js';
|
|
10
11
|
import { useModuleLoader } from './load.js';
|
|
@@ -57,6 +58,17 @@ export async function build(ctx) {
|
|
|
57
58
|
// bundle built from it.
|
|
58
59
|
mode: viteMode(ctx.mode),
|
|
59
60
|
optimizeDeps: { include: [], noDiscovery: true },
|
|
61
|
+
plugins: [{
|
|
62
|
+
// Collection reads each file once, so this server needs no watcher, and
|
|
63
|
+
// one leaked from it: a transform still in flight when a failed build
|
|
64
|
+
// closes the server re-adds a file, Vite's chokidar reopens on `add`, and
|
|
65
|
+
// the native watcher that starts is never closed (#434). A `config` hook
|
|
66
|
+
// cannot say this, because merging a config skips `null`.
|
|
67
|
+
name: 'poveste:collect-without-watcher',
|
|
68
|
+
configResolved(config) {
|
|
69
|
+
Object.assign(config.server, { watch: null });
|
|
70
|
+
},
|
|
71
|
+
}],
|
|
60
72
|
}));
|
|
61
73
|
await server.pluginContainer.buildStart({});
|
|
62
74
|
// Cleanup a plugin registers runs however this ends. `onBuildEnd` cannot be that
|
|
@@ -105,9 +117,7 @@ export async function build(ctx) {
|
|
|
105
117
|
// Same wrapper the dev path uses for these exact calls (server.ts).
|
|
106
118
|
await wrapLogError('destroyCollectStories', () => destroyCollectStories());
|
|
107
119
|
}
|
|
108
|
-
const
|
|
109
|
-
const variantCount = ctx.storyFiles.reduce((sum, file) => sum + (file.story?.variants.length ?? 0), 0);
|
|
110
|
-
const emptyStoryCount = ctx.storyFiles.length - storyCount;
|
|
120
|
+
const counts = buildCounts(ctx.storyFiles);
|
|
111
121
|
const { viteConfig: buildViteConfigRaw } = await getViteConfigWithPlugins(false, ctx);
|
|
112
122
|
const buildViteConfig = mergeViteConfig(buildViteConfigRaw, {
|
|
113
123
|
// Vite picks the `.env` file from `mode`, not from NODE_ENV — this said
|
|
@@ -115,7 +125,7 @@ export async function build(ctx) {
|
|
|
115
125
|
mode: viteMode(ctx.mode),
|
|
116
126
|
build: {
|
|
117
127
|
lib: false,
|
|
118
|
-
|
|
128
|
+
rolldownOptions: {
|
|
119
129
|
// Named, not an array. With an array the bundler derives `[name]` from the
|
|
120
130
|
// path, and `APP_PATH` is absolute — so any framework whose `entryFileNames`
|
|
121
131
|
// refuses a path-shaped `[name]` fails the build outright (#369, and
|
|
@@ -137,14 +147,15 @@ export async function build(ctx) {
|
|
|
137
147
|
},
|
|
138
148
|
},
|
|
139
149
|
});
|
|
150
|
+
const buildPlugins = buildViteConfig.plugins ??= [];
|
|
140
151
|
// For @vite/plugin-vue: Always put our vite server
|
|
141
152
|
// Disable template inlining
|
|
142
153
|
// (so that we no longer need defineExpose)
|
|
143
154
|
// Nuxt: replaces the Nuxt vite dev server
|
|
144
|
-
|
|
155
|
+
buildPlugins.push({
|
|
145
156
|
name: 'poveste-vue-plugin-override',
|
|
146
157
|
config(config) {
|
|
147
|
-
const vuePlugin = config.plugins
|
|
158
|
+
const vuePlugin = config.plugins?.find((p) => p.name === 'vite:vue');
|
|
148
159
|
if (vuePlugin) {
|
|
149
160
|
// @ts-expect-error vue plugin use function form
|
|
150
161
|
const original = vuePlugin.configureServer.bind(vuePlugin);
|
|
@@ -165,14 +176,16 @@ export async function build(ctx) {
|
|
|
165
176
|
}
|
|
166
177
|
},
|
|
167
178
|
});
|
|
168
|
-
|
|
179
|
+
buildPlugins.push({
|
|
169
180
|
name: 'poveste-build-config-override',
|
|
170
181
|
enforce: 'post',
|
|
171
182
|
config(config) {
|
|
183
|
+
const build = config.build ??= {};
|
|
184
|
+
const rolldownOptions = build.rolldownOptions ??= {};
|
|
172
185
|
// Don't externalize
|
|
173
|
-
|
|
186
|
+
rolldownOptions.external = [];
|
|
174
187
|
// Force chunk strategy
|
|
175
|
-
|
|
188
|
+
rolldownOptions.output = {
|
|
176
189
|
manualChunks(id) {
|
|
177
190
|
// Vite's runtime helpers (`\0vite/preload-helper.js` and friends) are
|
|
178
191
|
// virtual modules, so the node_modules routing below never sees them
|
|
@@ -197,6 +210,7 @@ export async function build(ctx) {
|
|
|
197
210
|
}
|
|
198
211
|
return 'vendor';
|
|
199
212
|
}
|
|
213
|
+
return undefined;
|
|
200
214
|
},
|
|
201
215
|
};
|
|
202
216
|
// A framework that declares Vite environments gives each one its own
|
|
@@ -205,72 +219,86 @@ export async function build(ctx) {
|
|
|
205
219
|
// while poveste kept looking in `outDir` (#369). Only the client environment
|
|
206
220
|
// builds the book. Where no framework declared environments there is nothing to
|
|
207
221
|
// override, and where one did this puts the book back where poveste reads it.
|
|
208
|
-
if (config.environments?.client?.build) {
|
|
209
|
-
config.environments
|
|
222
|
+
if (config.environments?.['client']?.build) {
|
|
223
|
+
config.environments['client'].build.outDir = ctx.config.outDir;
|
|
210
224
|
}
|
|
211
225
|
// Force vite build options
|
|
212
|
-
Object.assign(
|
|
226
|
+
Object.assign(build, {
|
|
213
227
|
outDir: ctx.config.outDir,
|
|
214
228
|
emptyOutDir: true,
|
|
215
229
|
// Re-merged per-entry by entry-css-merger.
|
|
216
230
|
cssCodeSplit: true,
|
|
217
231
|
// Not an oversight: the source pane prints story code by reading
|
|
218
232
|
// functions back at runtime, and `Function.prototype.toString()`
|
|
219
|
-
// returns whatever the minifier left — `codegen-
|
|
233
|
+
// returns whatever the minifier left — `codegen-vue.spec.ts` fails
|
|
220
234
|
// on three cases with this on (#329).
|
|
221
235
|
minify: false,
|
|
222
236
|
// Don't build in SSR mode
|
|
223
237
|
ssr: false,
|
|
224
238
|
});
|
|
225
|
-
config.define.__HST_COLLECT__
|
|
239
|
+
config.define = { ...config.define, __HST_COLLECT__: false };
|
|
226
240
|
},
|
|
227
241
|
});
|
|
228
242
|
const isolate = ctx.config.isolateStyles !== false;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
243
|
+
buildPlugins.push(userCssScopePlugin({ enabled: isolate }));
|
|
244
|
+
buildPlugins.push(chromeCssScopePlugin({ enabled: isolate }));
|
|
245
|
+
buildPlugins.push(entryCssMergerPlugin({ isolateStyles: isolate }));
|
|
232
246
|
for (const cb of changeViteConfigCallbacks) {
|
|
233
|
-
console.log('vite config hook', cb);
|
|
234
247
|
await cb(buildViteConfig);
|
|
235
248
|
}
|
|
236
249
|
const results = await viteBuild(buildViteConfig);
|
|
237
250
|
const result = Array.isArray(results) ? results[0] : results;
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
if (!result) {
|
|
252
|
+
throw new Error('[poveste] the book build produced no output');
|
|
240
253
|
}
|
|
254
|
+
const findEntryCss = (entryName) => result.output.find(o => o.type === 'asset' && o.fileName === `${entryName}.css`);
|
|
241
255
|
const mainStyleOutput = findEntryCss('bundle-main')
|
|
242
|
-
?? result.output.find(o => o.
|
|
256
|
+
?? result.output.find(o => o.type === 'asset' && o.names.includes('style.css'));
|
|
243
257
|
const sandboxStyleOutput = findEntryCss('bundle-sandbox') ?? mainStyleOutput;
|
|
244
258
|
// Preload
|
|
245
|
-
const preloadOutputs = result.output.filter(o =>
|
|
259
|
+
const preloadOutputs = result.output.filter(o => o.type === 'chunk' && PRELOAD_MODULES.includes(o.name));
|
|
246
260
|
const preloadHtml = generateScriptLinks(preloadOutputs.map(o => o.fileName), 'preload', ctx);
|
|
247
261
|
// Prefetch
|
|
248
|
-
const prefetchOutputs = result.output.filter(o =>
|
|
262
|
+
const prefetchOutputs = result.output.filter(o => o.type === 'chunk' && PREFETCHED_MODULES.includes(o.name));
|
|
249
263
|
const prefetchHtml = generateScriptLinks(prefetchOutputs.map(o => o.fileName), 'prefetch', ctx);
|
|
250
264
|
// Index
|
|
251
|
-
const indexOutput = result.output.find(o => o.
|
|
265
|
+
const indexOutput = result.output.find(o => o.type === 'chunk' && o.name === 'bundle-main');
|
|
266
|
+
const sandboxOutput = result.output.find(o => o.type === 'chunk' && o.name === 'bundle-sandbox');
|
|
267
|
+
if (!indexOutput || !sandboxOutput || !mainStyleOutput || !sandboxStyleOutput) {
|
|
268
|
+
const missing = { 'bundle-main.js': indexOutput, 'bundle-sandbox.js': sandboxOutput, 'bundle-main.css': mainStyleOutput };
|
|
269
|
+
throw new Error(`[poveste] the book build produced no ${Object.entries(missing).filter(([, output]) => !output).map(([name]) => name).join(', ')}`);
|
|
270
|
+
}
|
|
252
271
|
let indexHtml = generateEntryHtml(indexOutput.fileName, mainStyleOutput.fileName, {
|
|
253
272
|
HEAD: `${preloadHtml}${prefetchHtml}`,
|
|
254
273
|
}, ctx);
|
|
255
274
|
indexHtml = await applyHeadTransform(indexHtml, ctx.config.head);
|
|
256
275
|
await writeFile('index.html', indexHtml, ctx);
|
|
257
276
|
// Sandbox
|
|
258
|
-
const sandboxOutput = result.output.find(o => o.name === 'bundle-sandbox' && o.type === 'chunk');
|
|
259
277
|
let sandboxHtml = generateEntryHtml(sandboxOutput.fileName, sandboxStyleOutput.fileName, {}, ctx);
|
|
260
278
|
sandboxHtml = await applyHeadTransform(sandboxHtml, ctx.config.head);
|
|
261
279
|
await writeFile('__sandbox.html', sandboxHtml, ctx);
|
|
262
280
|
await writeFile('poveste.json', JSON.stringify(getSerializedStoryData(ctx), null, 2), ctx);
|
|
263
281
|
const duration = performance.now() - startTime;
|
|
264
|
-
|
|
265
|
-
|
|
282
|
+
const { empty } = counts;
|
|
283
|
+
if (empty.length) {
|
|
284
|
+
console.warn(pc.yellow(`⚠️ ${empty.length} empty story file${empty.length === 1 ? '' : 's'}: ${empty.join(', ')}`));
|
|
285
|
+
}
|
|
286
|
+
// Green only when something was built. A tick over nothing is the claim
|
|
287
|
+
// #624 is about, and it is the first command the guide asks for.
|
|
288
|
+
const paint = builtSomething(counts) ? pc.green : pc.yellow;
|
|
289
|
+
for (const line of buildSummary(counts, Math.round(duration / 1000 * 100) / 100, ctx.config.storyMatch)) {
|
|
290
|
+
console.log(paint(line));
|
|
266
291
|
}
|
|
267
|
-
console.log(pc.green(`✅ Built ${storyCount} stor${storyCount === 1 ? 'y' : 'ies'} (${variantCount} variant${variantCount === 1 ? '' : 's'}) in ${Math.round(duration / 1000 * 100) / 100}s`));
|
|
268
292
|
// Render
|
|
269
293
|
if (previewStoryCallbacks.length) {
|
|
270
294
|
const { baseUrl, close } = await startPreview({}, ctx);
|
|
271
295
|
try {
|
|
272
296
|
for (const storyFile of ctx.storyFiles) {
|
|
273
297
|
const story = storyFile.story;
|
|
298
|
+
// An empty story file collects no story, and there is nothing to render.
|
|
299
|
+
if (!story) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
274
302
|
for (const variant of story.variants) {
|
|
275
303
|
const query = new URLSearchParams();
|
|
276
304
|
query.append('storyId', story.id);
|
|
@@ -8,7 +8,7 @@ export default _defineComponent({
|
|
|
8
8
|
},
|
|
9
9
|
},
|
|
10
10
|
setup(props) {
|
|
11
|
-
const options = props.story.file
|
|
11
|
+
const options = props.story.file?.component;
|
|
12
12
|
let rawVariants = [];
|
|
13
13
|
if (options.onMount) {
|
|
14
14
|
rawVariants = [{
|
|
@@ -19,16 +19,16 @@ export default _defineComponent({
|
|
|
19
19
|
}];
|
|
20
20
|
}
|
|
21
21
|
else {
|
|
22
|
-
rawVariants = options.variants;
|
|
22
|
+
rawVariants = options.variants ?? [];
|
|
23
23
|
}
|
|
24
|
-
for (const index
|
|
24
|
+
for (const [index, variant] of props.story.variants.entries()) {
|
|
25
25
|
const rawVariant = rawVariants[index];
|
|
26
|
-
Object.assign(
|
|
27
|
-
slots: () => ({ default: rawVariant
|
|
28
|
-
source: rawVariant
|
|
29
|
-
responsiveDisabled: rawVariant
|
|
30
|
-
autoPropsDisabled: rawVariant
|
|
31
|
-
setupApp: rawVariant
|
|
26
|
+
Object.assign(variant, {
|
|
27
|
+
slots: () => ({ default: rawVariant?.onMount, controls: rawVariant?.onMountControls }),
|
|
28
|
+
source: rawVariant?.source ?? options.source,
|
|
29
|
+
responsiveDisabled: rawVariant?.responsiveDisabled ?? options.responsiveDisabled,
|
|
30
|
+
autoPropsDisabled: rawVariant?.autoPropsDisabled ?? options.autoPropsDisabled,
|
|
31
|
+
setupApp: rawVariant?.setupApp ?? options.setupApp,
|
|
32
32
|
configReady: true,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
@@ -14,7 +14,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
14
14
|
default: string;
|
|
15
15
|
};
|
|
16
16
|
}>, {
|
|
17
|
-
sandbox: import("vue").Ref<HTMLDivElement, HTMLDivElement>;
|
|
17
|
+
sandbox: import("vue").Ref<HTMLDivElement | undefined, HTMLDivElement | undefined>;
|
|
18
18
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
19
|
ready: () => true;
|
|
20
20
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
@@ -26,7 +26,7 @@ export default _defineComponent({
|
|
|
26
26
|
setup(props, { emit }) {
|
|
27
27
|
const sandbox = _ref();
|
|
28
28
|
let mounting = false;
|
|
29
|
-
let app;
|
|
29
|
+
let app = null;
|
|
30
30
|
let appHooks;
|
|
31
31
|
async function unmountVariant() {
|
|
32
32
|
if (app) {
|
|
@@ -36,7 +36,7 @@ export default _defineComponent({
|
|
|
36
36
|
await hook();
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
app.el.
|
|
39
|
+
app.el.remove();
|
|
40
40
|
app = null;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -86,9 +86,9 @@ export default _defineComponent({
|
|
|
86
86
|
appHooks.onUnmount.push(cb);
|
|
87
87
|
},
|
|
88
88
|
};
|
|
89
|
-
const onMount = props.variant.slots()
|
|
90
|
-
await onMount(api);
|
|
91
|
-
sandbox.value
|
|
89
|
+
const onMount = Reflect.get(props.variant.slots?.() ?? {}, props.slotName);
|
|
90
|
+
await onMount?.(api);
|
|
91
|
+
sandbox.value?.appendChild(app.el);
|
|
92
92
|
emit('ready');
|
|
93
93
|
}
|
|
94
94
|
_onMounted(async () => {
|
|
@@ -10,7 +10,7 @@ export async function run({ file, storyData }) {
|
|
|
10
10
|
}];
|
|
11
11
|
}
|
|
12
12
|
else {
|
|
13
|
-
rawVariants = options.variants;
|
|
13
|
+
rawVariants = options.variants ?? [];
|
|
14
14
|
}
|
|
15
15
|
const story = {
|
|
16
16
|
id: options.id ?? file.id,
|
|
@@ -20,7 +20,7 @@ export async function run({ file, storyData }) {
|
|
|
20
20
|
icon: options.icon,
|
|
21
21
|
iconColor: options.iconColor,
|
|
22
22
|
docsOnly: options.docsOnly ?? false,
|
|
23
|
-
variants:
|
|
23
|
+
variants: [],
|
|
24
24
|
};
|
|
25
25
|
const variants = rawVariants.map((v, index) => ({
|
|
26
26
|
id: v.id ?? `${story.id}-${index}`,
|
|
@@ -12,8 +12,8 @@ export interface MountApi {
|
|
|
12
12
|
onUnmount: (cb: () => unknown) => Awaitable<void>;
|
|
13
13
|
}
|
|
14
14
|
export interface VanillaApi {
|
|
15
|
-
onMount?: (api: MountApi) => Awaitable<void
|
|
16
|
-
onMountControls?: (api: MountApi) => Awaitable<void
|
|
15
|
+
onMount?: ((api: MountApi) => Awaitable<void>) | undefined;
|
|
16
|
+
onMountControls?: ((api: MountApi) => Awaitable<void>) | undefined;
|
|
17
17
|
}
|
|
18
18
|
export type VanillaStorySetupHandler = (payload: {
|
|
19
19
|
app: App;
|
|
@@ -5,20 +5,20 @@ export default {
|
|
|
5
5
|
{
|
|
6
6
|
title: 'Hello',
|
|
7
7
|
onMount: ({ el, state, onUpdate }) => {
|
|
8
|
-
state
|
|
8
|
+
state['count'] = 0;
|
|
9
9
|
const div = document.createElement('div');
|
|
10
10
|
div.innerHTML = '<h1>Hello World</h1>';
|
|
11
11
|
el.appendChild(div);
|
|
12
12
|
const counter = document.createElement('pre');
|
|
13
|
-
counter.innerHTML = `Counter: ${state
|
|
13
|
+
counter.innerHTML = `Counter: ${state['counter']}`;
|
|
14
14
|
div.appendChild(counter);
|
|
15
15
|
onUpdate(() => {
|
|
16
|
-
counter.innerHTML = `Counter: ${state
|
|
16
|
+
counter.innerHTML = `Counter: ${state['count']}`;
|
|
17
17
|
});
|
|
18
18
|
},
|
|
19
19
|
onMountControls: ({ el, state, onUnmount }) => {
|
|
20
20
|
const add = () => {
|
|
21
|
-
state
|
|
21
|
+
state['count']++;
|
|
22
22
|
};
|
|
23
23
|
Object.assign(el.style, {
|
|
24
24
|
padding: '8px',
|