poveste 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/bin.js +31 -3
- package/dist/node/build.js +220 -190
- package/dist/node/collect/index.js +11 -3
- package/package.json +5 -5
package/dist/node/bin.js
CHANGED
|
@@ -7,6 +7,34 @@ const { version } = JSON.parse(fs.readFileSync(resolve(__dirname, '../../package
|
|
|
7
7
|
process.env.NODE_ENV = process.argv.includes('build') || process.argv.includes('preview') ? 'production' : 'development';
|
|
8
8
|
process.env.HISTOIRE = 'true';
|
|
9
9
|
process.env.POVESTE = 'true';
|
|
10
|
+
// sade does not await an action, so a rejected command was terminated only by
|
|
11
|
+
// Node's default handling of the unhandled rejection it became. That is not the
|
|
12
|
+
// CLI's to rely on: any dependency can install an `unhandledRejection` listener
|
|
13
|
+
// and take it away, which Vike does — and a failed build then ran forever with
|
|
14
|
+
// its error already printed (#405).
|
|
15
|
+
//
|
|
16
|
+
// So failure is reported here, the exit code set, and the process forced out if
|
|
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 nuxt4 and sveltekit books,
|
|
19
|
+
// holding `FSEventWrap` and `FSReqCallback` handles — watchers nothing closes on
|
|
20
|
+
// the error path. And `dev` and `preview` hold a listening server by design, so
|
|
21
|
+
// for them no amount of leak-fixing would ever let the loop drain.
|
|
22
|
+
//
|
|
23
|
+
// The timer is unref'd, so it never fires for a process that ends by itself and
|
|
24
|
+
// costs nothing when there is nothing to force. It is 500ms rather than
|
|
25
|
+
// immediate because exiting on the same tick as `console.error` truncates it:
|
|
26
|
+
// writes to a piped stderr are asynchronous, and a framed compiler error is long.
|
|
27
|
+
//
|
|
28
|
+
// It does mean a leak reads as a clean failure from the outside, so it hides the
|
|
29
|
+
// class of bug #426 was. Removing it again needs those watcher handles closed
|
|
30
|
+
// first, and a guard that measures the resource list rather than the exit code.
|
|
31
|
+
function run(command) {
|
|
32
|
+
return command().then(() => undefined, (error) => {
|
|
33
|
+
console.error(error);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
setTimeout(() => process.exit(1), 500).unref();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
10
38
|
const program = sade('poveste');
|
|
11
39
|
program.version(version);
|
|
12
40
|
program.command('dev')
|
|
@@ -17,14 +45,14 @@ program.command('dev')
|
|
|
17
45
|
.option('--host [host]', '[string] specify hostname (omit value or pass `0.0.0.0` to expose to all network interfaces)')
|
|
18
46
|
.action(async (options) => {
|
|
19
47
|
const { devCommand } = await import('./commands/dev.js');
|
|
20
|
-
return devCommand(options);
|
|
48
|
+
return run(() => devCommand(options));
|
|
21
49
|
});
|
|
22
50
|
program.command('build')
|
|
23
51
|
.describe('build the poveste final app you can deploy')
|
|
24
52
|
.option('-c, --config <file>', `[string] use specified config file`)
|
|
25
53
|
.action(async (options) => {
|
|
26
54
|
const { buildCommand } = await import('./commands/build.js');
|
|
27
|
-
return buildCommand(options);
|
|
55
|
+
return run(() => buildCommand(options));
|
|
28
56
|
});
|
|
29
57
|
program.command('preview')
|
|
30
58
|
.describe('preview the built directory')
|
|
@@ -33,6 +61,6 @@ program.command('preview')
|
|
|
33
61
|
.option('--host [host]', '[string] specify hostname (omit value or pass `0.0.0.0` to expose to all network interfaces)')
|
|
34
62
|
.action(async (options) => {
|
|
35
63
|
const { previewCommand } = await import('./commands/preview.js');
|
|
36
|
-
return previewCommand(options);
|
|
64
|
+
return run(() => previewCommand(options));
|
|
37
65
|
});
|
|
38
66
|
program.parse(process.argv);
|
package/dist/node/build.js
CHANGED
|
@@ -14,6 +14,7 @@ import { startPreview } from './preview.js';
|
|
|
14
14
|
import { findAllStories } from './stories.js';
|
|
15
15
|
import { chromeCssScopePlugin, entryCssMergerPlugin, userCssScopePlugin, } from './style-isolation/index.js';
|
|
16
16
|
import { applyHeadTransform } from './util/head.js';
|
|
17
|
+
import { wrapLogError } from './util/log.js';
|
|
17
18
|
import { getViteConfigWithPlugins } from './vite.js';
|
|
18
19
|
const PRELOAD_MODULES = [
|
|
19
20
|
'vendor',
|
|
@@ -53,208 +54,237 @@ export async function build(ctx) {
|
|
|
53
54
|
optimizeDeps: { include: [], noDiscovery: true },
|
|
54
55
|
}));
|
|
55
56
|
await server.pluginContainer.buildStart({});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
// Everything from here is wrapped so the server is closed on the way out
|
|
58
|
+
// whichever way that is — it is the other handle that kept a failed build
|
|
59
|
+
// alive (#405). `serverClosed` keeps the existing order rather than moving the
|
|
60
|
+
// buildEnd callbacks either side of the close.
|
|
61
|
+
let serverClosed = false;
|
|
62
|
+
try {
|
|
63
|
+
const moduleLoader = useModuleLoader({
|
|
64
|
+
server,
|
|
65
|
+
throws: true,
|
|
66
|
+
});
|
|
67
|
+
const changeViteConfigCallbacks = [];
|
|
68
|
+
const buildEndCallbacks = [];
|
|
69
|
+
const previewStoryCallbacks = [];
|
|
70
|
+
for (const plugin of ctx.config.plugins) {
|
|
71
|
+
if (plugin.onBuild) {
|
|
72
|
+
const api = new BuildPluginApi(ctx, plugin, moduleLoader);
|
|
73
|
+
await plugin.onBuild(api);
|
|
74
|
+
changeViteConfigCallbacks.push(...api.changeViteConfigCallbacks);
|
|
75
|
+
buildEndCallbacks.push(...api.buildEndCallbacks);
|
|
76
|
+
previewStoryCallbacks.push(...api.previewStoryCallbacks);
|
|
77
|
+
}
|
|
70
78
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
79
|
+
// Collect story data
|
|
80
|
+
const { executeStoryFile, destroy: destroyCollectStories } = useCollectStories({
|
|
81
|
+
server,
|
|
82
|
+
throws: true,
|
|
83
|
+
}, ctx);
|
|
84
|
+
// `throws: true`, so one bad story rejects here. Without the `finally` the
|
|
85
|
+
// teardown below never ran, eleven worker threads stayed alive, and the process
|
|
86
|
+
// hung instead of failing — the error was printed and then nothing (#405).
|
|
87
|
+
try {
|
|
88
|
+
await Promise.all(ctx.storyFiles.map(storyFile => executeStoryFile(storyFile)));
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
// Logged, not thrown: a teardown that rejects would replace the error that
|
|
92
|
+
// caused the teardown, which is the failure this whole change is about.
|
|
93
|
+
// Same wrapper the dev path uses for these exact calls (server.ts).
|
|
94
|
+
await wrapLogError('destroyCollectStories', () => destroyCollectStories());
|
|
95
|
+
}
|
|
96
|
+
const storyCount = ctx.storyFiles.reduce((sum, file) => sum + (file.story?.variants.length ? 1 : 0), 0);
|
|
97
|
+
const variantCount = ctx.storyFiles.reduce((sum, file) => sum + (file.story?.variants.length ?? 0), 0);
|
|
98
|
+
const emptyStoryCount = ctx.storyFiles.length - storyCount;
|
|
99
|
+
const { viteConfig: buildViteConfigRaw } = await getViteConfigWithPlugins(false, ctx);
|
|
100
|
+
const buildViteConfig = mergeViteConfig(buildViteConfigRaw, {
|
|
101
|
+
mode: 'development',
|
|
102
|
+
build: {
|
|
103
|
+
lib: false,
|
|
104
|
+
rollupOptions: {
|
|
105
|
+
// Named, not an array. With an array the bundler derives `[name]` from the
|
|
106
|
+
// path, and `APP_PATH` is absolute — so any framework whose `entryFileNames`
|
|
107
|
+
// refuses a path-shaped `[name]` fails the build outright (#369, and
|
|
108
|
+
// histoire-dev/histoire#802 before it). The keys make `[name]` the key.
|
|
109
|
+
input: {
|
|
110
|
+
'bundle-main': join(APP_PATH, 'bundle-main.js'),
|
|
111
|
+
'bundle-sandbox': join(APP_PATH, 'bundle-sandbox.js'),
|
|
104
112
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// Nuxt: replaces the Nuxt vite dev server
|
|
113
|
-
buildViteConfig.plugins.push({
|
|
114
|
-
name: 'poveste-vue-plugin-override',
|
|
115
|
-
config(config) {
|
|
116
|
-
const vuePlugin = config.plugins.find((p) => p.name === 'vite:vue');
|
|
117
|
-
if (vuePlugin) {
|
|
118
|
-
// @ts-expect-error vue plugin use function form
|
|
119
|
-
const original = vuePlugin.configureServer.bind(vuePlugin);
|
|
120
|
-
vuePlugin.configureServer = () => {
|
|
121
|
-
original({
|
|
122
|
-
...server,
|
|
123
|
-
config: {
|
|
124
|
-
...server.config,
|
|
125
|
-
server: {
|
|
126
|
-
...server.config.server,
|
|
127
|
-
hmr: false,
|
|
113
|
+
plugins: [
|
|
114
|
+
{
|
|
115
|
+
name: 'poveste-build-rollup-options-override',
|
|
116
|
+
enforce: 'post',
|
|
117
|
+
options(options) {
|
|
118
|
+
// Don't externalize
|
|
119
|
+
options.external = [];
|
|
128
120
|
},
|
|
129
121
|
},
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
// For @vite/plugin-vue: Always put our vite server
|
|
127
|
+
// Disable template inlining
|
|
128
|
+
// (so that we no longer need defineExpose)
|
|
129
|
+
// Nuxt: replaces the Nuxt vite dev server
|
|
130
|
+
buildViteConfig.plugins.push({
|
|
131
|
+
name: 'poveste-vue-plugin-override',
|
|
132
|
+
config(config) {
|
|
133
|
+
const vuePlugin = config.plugins.find((p) => p.name === 'vite:vue');
|
|
134
|
+
if (vuePlugin) {
|
|
135
|
+
// @ts-expect-error vue plugin use function form
|
|
136
|
+
const original = vuePlugin.configureServer.bind(vuePlugin);
|
|
137
|
+
vuePlugin.configureServer = () => {
|
|
138
|
+
original({
|
|
139
|
+
...server,
|
|
140
|
+
config: {
|
|
141
|
+
...server.config,
|
|
142
|
+
server: {
|
|
143
|
+
...server.config.server,
|
|
144
|
+
hmr: false,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
// @ts-expect-error vue plugin use function form
|
|
150
|
+
vuePlugin.configureServer(server);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
buildViteConfig.plugins.push({
|
|
155
|
+
name: 'poveste-build-config-override',
|
|
156
|
+
enforce: 'post',
|
|
157
|
+
config(config) {
|
|
158
|
+
// Don't externalize
|
|
159
|
+
config.build.rollupOptions.external = [];
|
|
160
|
+
// Force chunk strategy
|
|
161
|
+
config.build.rollupOptions.output = {
|
|
162
|
+
manualChunks(id) {
|
|
163
|
+
// Vite's runtime helpers (`\0vite/preload-helper.js` and friends) are
|
|
164
|
+
// virtual modules, so the node_modules routing below never sees them
|
|
165
|
+
// and their placement is left to the bundler — which parked
|
|
166
|
+
// `__vitePreload` inside the 10 MB `highlighter` chunk. Every chunk
|
|
167
|
+
// importing the helper then statically imported the whole highlighter,
|
|
168
|
+
// and the sandbox was back to evaluating it once per grid cell (#197),
|
|
169
|
+
// undoing exactly what `APP_ONLY_VENDORS` is for. Pin them somewhere
|
|
170
|
+
// harmless instead of leaving the choice to chunking heuristics.
|
|
171
|
+
if (id.replace(/^\0/, '').startsWith('vite/')) {
|
|
172
|
+
return 'app-runtime';
|
|
160
173
|
}
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
if (!id.includes('@poveste/app') && id.includes('node_modules')) {
|
|
175
|
+
if (APP_ONLY_VENDORS.some(test => test.test(id))) {
|
|
176
|
+
return 'highlighter';
|
|
177
|
+
}
|
|
178
|
+
for (const test of ctx.config.build?.excludeFromVendorsChunk ?? []) {
|
|
179
|
+
if ((typeof test === 'string' && id.includes(test)) || (test instanceof RegExp && test.test(id))) {
|
|
180
|
+
// Excluded from vendor chunk
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
165
183
|
}
|
|
184
|
+
return 'vendor';
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
// A framework that declares Vite environments gives each one its own
|
|
189
|
+
// `build.outDir`, and that wins over the top-level value for the environment's
|
|
190
|
+
// output — so the book was written to the framework's directory (Vike: `dist/client`)
|
|
191
|
+
// while poveste kept looking in `outDir` (#369). Only the client environment
|
|
192
|
+
// builds the book. Where no framework declared environments there is nothing to
|
|
193
|
+
// override, and where one did this puts the book back where poveste reads it.
|
|
194
|
+
if (config.environments?.client?.build) {
|
|
195
|
+
config.environments.client.build.outDir = ctx.config.outDir;
|
|
196
|
+
}
|
|
197
|
+
// Force vite build options
|
|
198
|
+
Object.assign(config.build, {
|
|
199
|
+
outDir: ctx.config.outDir,
|
|
200
|
+
emptyOutDir: true,
|
|
201
|
+
// Re-merged per-entry by entry-css-merger.
|
|
202
|
+
cssCodeSplit: true,
|
|
203
|
+
minify: false,
|
|
204
|
+
// Don't build in SSR mode
|
|
205
|
+
ssr: false,
|
|
206
|
+
});
|
|
207
|
+
config.define.__HST_COLLECT__ = false;
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
const isolate = ctx.config.isolateStyles !== false;
|
|
211
|
+
buildViteConfig.plugins.push(userCssScopePlugin({ enabled: isolate }));
|
|
212
|
+
buildViteConfig.plugins.push(chromeCssScopePlugin({ enabled: isolate }));
|
|
213
|
+
buildViteConfig.plugins.push(entryCssMergerPlugin({ isolateStyles: isolate }));
|
|
214
|
+
for (const cb of changeViteConfigCallbacks) {
|
|
215
|
+
console.log('vite config hook', cb);
|
|
216
|
+
await cb(buildViteConfig);
|
|
217
|
+
}
|
|
218
|
+
const results = await viteBuild(buildViteConfig);
|
|
219
|
+
const result = Array.isArray(results) ? results[0] : results;
|
|
220
|
+
function findEntryCss(entryName) {
|
|
221
|
+
return result.output.find(o => o.type === 'asset' && o.fileName === `${entryName}.css`);
|
|
222
|
+
}
|
|
223
|
+
const mainStyleOutput = findEntryCss('bundle-main')
|
|
224
|
+
?? result.output.find(o => o.name === 'style.css' && o.type === 'asset');
|
|
225
|
+
const sandboxStyleOutput = findEntryCss('bundle-sandbox') ?? mainStyleOutput;
|
|
226
|
+
// Preload
|
|
227
|
+
const preloadOutputs = result.output.filter(o => PRELOAD_MODULES.includes(o.name) && o.type === 'chunk');
|
|
228
|
+
const preloadHtml = generateScriptLinks(preloadOutputs.map(o => o.fileName), 'preload', ctx);
|
|
229
|
+
// Prefetch
|
|
230
|
+
const prefetchOutputs = result.output.filter(o => PREFETCHED_MODULES.includes(o.name) && o.type === 'chunk');
|
|
231
|
+
const prefetchHtml = generateScriptLinks(prefetchOutputs.map(o => o.fileName), 'prefetch', ctx);
|
|
232
|
+
// Index
|
|
233
|
+
const indexOutput = result.output.find(o => o.name === 'bundle-main' && o.type === 'chunk');
|
|
234
|
+
let indexHtml = generateEntryHtml(indexOutput.fileName, mainStyleOutput.fileName, {
|
|
235
|
+
HEAD: `${preloadHtml}${prefetchHtml}`,
|
|
236
|
+
}, ctx);
|
|
237
|
+
indexHtml = await applyHeadTransform(indexHtml, ctx.config.head);
|
|
238
|
+
await writeFile('index.html', indexHtml, ctx);
|
|
239
|
+
// Sandbox
|
|
240
|
+
const sandboxOutput = result.output.find(o => o.name === 'bundle-sandbox' && o.type === 'chunk');
|
|
241
|
+
let sandboxHtml = generateEntryHtml(sandboxOutput.fileName, sandboxStyleOutput.fileName, {}, ctx);
|
|
242
|
+
sandboxHtml = await applyHeadTransform(sandboxHtml, ctx.config.head);
|
|
243
|
+
await writeFile('__sandbox.html', sandboxHtml, ctx);
|
|
244
|
+
await writeFile('poveste.json', JSON.stringify(getSerializedStoryData(ctx), null, 2), ctx);
|
|
245
|
+
const duration = performance.now() - startTime;
|
|
246
|
+
if (emptyStoryCount) {
|
|
247
|
+
console.warn(pc.yellow(`⚠️ ${emptyStoryCount} empty story file${emptyStoryCount === 1 ? '' : 's'}`));
|
|
248
|
+
}
|
|
249
|
+
console.log(pc.green(`✅ Built ${storyCount} stor${storyCount === 1 ? 'y' : 'ies'} (${variantCount} variant${variantCount === 1 ? '' : 's'}) in ${Math.round(duration / 1000 * 100) / 100}s`));
|
|
250
|
+
// Render
|
|
251
|
+
if (previewStoryCallbacks.length) {
|
|
252
|
+
const { baseUrl, close } = await startPreview({}, ctx);
|
|
253
|
+
try {
|
|
254
|
+
for (const storyFile of ctx.storyFiles) {
|
|
255
|
+
const story = storyFile.story;
|
|
256
|
+
for (const variant of story.variants) {
|
|
257
|
+
const query = new URLSearchParams();
|
|
258
|
+
query.append('storyId', story.id);
|
|
259
|
+
query.append('variantId', variant.id);
|
|
260
|
+
const url = `${baseUrl}__sandbox.html?${query.toString()}`;
|
|
261
|
+
for (const fn of previewStoryCallbacks) {
|
|
262
|
+
await fn({
|
|
263
|
+
file: storyFile.path,
|
|
264
|
+
story,
|
|
265
|
+
variant,
|
|
266
|
+
url,
|
|
267
|
+
});
|
|
166
268
|
}
|
|
167
|
-
return 'vendor';
|
|
168
269
|
}
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
// A framework that declares Vite environments gives each one its own
|
|
172
|
-
// `build.outDir`, and that wins over the top-level value for the environment's
|
|
173
|
-
// output — so the book was written to the framework's directory (Vike: `dist/client`)
|
|
174
|
-
// while poveste kept looking in `outDir` (#369). Only the client environment
|
|
175
|
-
// builds the book. Where no framework declared environments there is nothing to
|
|
176
|
-
// override, and where one did this puts the book back where poveste reads it.
|
|
177
|
-
if (config.environments?.client?.build) {
|
|
178
|
-
config.environments.client.build.outDir = ctx.config.outDir;
|
|
179
|
-
}
|
|
180
|
-
// Force vite build options
|
|
181
|
-
Object.assign(config.build, {
|
|
182
|
-
outDir: ctx.config.outDir,
|
|
183
|
-
emptyOutDir: true,
|
|
184
|
-
// Re-merged per-entry by entry-css-merger.
|
|
185
|
-
cssCodeSplit: true,
|
|
186
|
-
minify: false,
|
|
187
|
-
// Don't build in SSR mode
|
|
188
|
-
ssr: false,
|
|
189
|
-
});
|
|
190
|
-
config.define.__HST_COLLECT__ = false;
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
const isolate = ctx.config.isolateStyles !== false;
|
|
194
|
-
buildViteConfig.plugins.push(userCssScopePlugin({ enabled: isolate }));
|
|
195
|
-
buildViteConfig.plugins.push(chromeCssScopePlugin({ enabled: isolate }));
|
|
196
|
-
buildViteConfig.plugins.push(entryCssMergerPlugin({ isolateStyles: isolate }));
|
|
197
|
-
for (const cb of changeViteConfigCallbacks) {
|
|
198
|
-
console.log('vite config hook', cb);
|
|
199
|
-
await cb(buildViteConfig);
|
|
200
|
-
}
|
|
201
|
-
const results = await viteBuild(buildViteConfig);
|
|
202
|
-
const result = Array.isArray(results) ? results[0] : results;
|
|
203
|
-
function findEntryCss(entryName) {
|
|
204
|
-
return result.output.find(o => o.type === 'asset' && o.fileName === `${entryName}.css`);
|
|
205
|
-
}
|
|
206
|
-
const mainStyleOutput = findEntryCss('bundle-main')
|
|
207
|
-
?? result.output.find(o => o.name === 'style.css' && o.type === 'asset');
|
|
208
|
-
const sandboxStyleOutput = findEntryCss('bundle-sandbox') ?? mainStyleOutput;
|
|
209
|
-
// Preload
|
|
210
|
-
const preloadOutputs = result.output.filter(o => PRELOAD_MODULES.includes(o.name) && o.type === 'chunk');
|
|
211
|
-
const preloadHtml = generateScriptLinks(preloadOutputs.map(o => o.fileName), 'preload', ctx);
|
|
212
|
-
// Prefetch
|
|
213
|
-
const prefetchOutputs = result.output.filter(o => PREFETCHED_MODULES.includes(o.name) && o.type === 'chunk');
|
|
214
|
-
const prefetchHtml = generateScriptLinks(prefetchOutputs.map(o => o.fileName), 'prefetch', ctx);
|
|
215
|
-
// Index
|
|
216
|
-
const indexOutput = result.output.find(o => o.name === 'bundle-main' && o.type === 'chunk');
|
|
217
|
-
let indexHtml = generateEntryHtml(indexOutput.fileName, mainStyleOutput.fileName, {
|
|
218
|
-
HEAD: `${preloadHtml}${prefetchHtml}`,
|
|
219
|
-
}, ctx);
|
|
220
|
-
indexHtml = await applyHeadTransform(indexHtml, ctx.config.head);
|
|
221
|
-
await writeFile('index.html', indexHtml, ctx);
|
|
222
|
-
// Sandbox
|
|
223
|
-
const sandboxOutput = result.output.find(o => o.name === 'bundle-sandbox' && o.type === 'chunk');
|
|
224
|
-
let sandboxHtml = generateEntryHtml(sandboxOutput.fileName, sandboxStyleOutput.fileName, {}, ctx);
|
|
225
|
-
sandboxHtml = await applyHeadTransform(sandboxHtml, ctx.config.head);
|
|
226
|
-
await writeFile('__sandbox.html', sandboxHtml, ctx);
|
|
227
|
-
await writeFile('poveste.json', JSON.stringify(getSerializedStoryData(ctx), null, 2), ctx);
|
|
228
|
-
const duration = performance.now() - startTime;
|
|
229
|
-
if (emptyStoryCount) {
|
|
230
|
-
console.warn(pc.yellow(`⚠️ ${emptyStoryCount} empty story file${emptyStoryCount === 1 ? '' : 's'}`));
|
|
231
|
-
}
|
|
232
|
-
console.log(pc.green(`✅ Built ${storyCount} stor${storyCount === 1 ? 'y' : 'ies'} (${variantCount} variant${variantCount === 1 ? '' : 's'}) in ${Math.round(duration / 1000 * 100) / 100}s`));
|
|
233
|
-
// Render
|
|
234
|
-
if (previewStoryCallbacks.length) {
|
|
235
|
-
const { baseUrl, close } = await startPreview({}, ctx);
|
|
236
|
-
for (const storyFile of ctx.storyFiles) {
|
|
237
|
-
const story = storyFile.story;
|
|
238
|
-
for (const variant of story.variants) {
|
|
239
|
-
const query = new URLSearchParams();
|
|
240
|
-
query.append('storyId', story.id);
|
|
241
|
-
query.append('variantId', variant.id);
|
|
242
|
-
const url = `${baseUrl}__sandbox.html?${query.toString()}`;
|
|
243
|
-
for (const fn of previewStoryCallbacks) {
|
|
244
|
-
await fn({
|
|
245
|
-
file: storyFile.path,
|
|
246
|
-
story,
|
|
247
|
-
variant,
|
|
248
|
-
url,
|
|
249
|
-
});
|
|
250
270
|
}
|
|
251
271
|
}
|
|
272
|
+
finally {
|
|
273
|
+
// A screenshot or percy callback that throws would otherwise leave this
|
|
274
|
+
// server bound — the same leak as the two above, one level down.
|
|
275
|
+
await wrapLogError('startPreview.close', () => close());
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
await server.close();
|
|
279
|
+
serverClosed = true;
|
|
280
|
+
for (const fn of buildEndCallbacks) {
|
|
281
|
+
await fn();
|
|
252
282
|
}
|
|
253
|
-
await close();
|
|
254
283
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
284
|
+
finally {
|
|
285
|
+
if (!serverClosed) {
|
|
286
|
+
await wrapLogError('server.close', () => server.close());
|
|
287
|
+
}
|
|
258
288
|
}
|
|
259
289
|
}
|
|
260
290
|
function generateBaseHtml(head, body, ctx) {
|
|
@@ -79,18 +79,23 @@ export function useCollectStories(options, ctx) {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
async function executeStoryFile(storyFile) {
|
|
82
|
+
// The channel belongs to this execution, and nothing used to close it. On a
|
|
83
|
+
// build that fails, the executions still in flight are abandoned mid-run and
|
|
84
|
+
// their main-thread ports stay open and listening — enough live handles to
|
|
85
|
+
// keep the process alive with nothing left to do (#426).
|
|
86
|
+
let channel;
|
|
82
87
|
try {
|
|
83
|
-
|
|
88
|
+
channel = createChannel();
|
|
84
89
|
const payload = {
|
|
85
90
|
root: server.config.root,
|
|
86
91
|
base: server.config.base,
|
|
87
92
|
storyFile,
|
|
88
|
-
port: workerPort,
|
|
93
|
+
port: channel.workerPort,
|
|
89
94
|
defineGlobals,
|
|
90
95
|
};
|
|
91
96
|
const { storyData } = await threadPool.run(payload, {
|
|
92
97
|
transferList: [
|
|
93
|
-
workerPort,
|
|
98
|
+
channel.workerPort,
|
|
94
99
|
],
|
|
95
100
|
});
|
|
96
101
|
if (storyData.length === 0) {
|
|
@@ -125,6 +130,9 @@ export function useCollectStories(options, ctx) {
|
|
|
125
130
|
throw e;
|
|
126
131
|
}
|
|
127
132
|
}
|
|
133
|
+
finally {
|
|
134
|
+
channel?.port.close();
|
|
135
|
+
}
|
|
128
136
|
}
|
|
129
137
|
async function destroy() {
|
|
130
138
|
await threadPool.destroy();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poveste",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"description": "Interactive component playgrounds for Vue, Svelte and Nuxt — a maintained, drop-in successor to histoire",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sorin Gitlan"
|
|
@@ -98,10 +98,10 @@
|
|
|
98
98
|
"sirv": "^3.0.0",
|
|
99
99
|
"unhead": "^3.1.0",
|
|
100
100
|
"vite-node": "^6.0.0",
|
|
101
|
-
"@poveste/app": "^0.
|
|
102
|
-
"@poveste/controls": "^0.
|
|
103
|
-
"@poveste/shared": "^0.
|
|
104
|
-
"@poveste/vendors": "^0.
|
|
101
|
+
"@poveste/app": "^0.9.0",
|
|
102
|
+
"@poveste/controls": "^0.9.0",
|
|
103
|
+
"@poveste/shared": "^0.9.0",
|
|
104
|
+
"@poveste/vendors": "^0.9.0"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@types/fs-extra": "^11.0.4",
|