poveste 0.1.2 → 0.2.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 +3 -1
- package/dist/node/build.d.ts.map +1 -1
- package/dist/node/build.js +23 -5
- package/dist/node/builtin-plugins/pinceau-tokens.js +35 -35
- package/dist/node/builtin-plugins/tailwind-tokens.js +38 -38
- package/dist/node/builtin-plugins/vanilla-support/RenderStory.d.ts.map +1 -1
- package/dist/node/builtin-plugins/vanilla-support/RenderStory.js +7 -4
- package/dist/node/builtin-plugins/vanilla-support/types.d.ts +6 -5
- package/dist/node/builtin-plugins/vanilla-support/types.d.ts.map +1 -1
- package/dist/node/commands/preview.d.ts +2 -0
- package/dist/node/commands/preview.d.ts.map +1 -1
- package/dist/node/commands/preview.js +6 -3
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/config.js +1 -0
- package/dist/node/context.js +2 -2
- package/dist/node/dom/env.d.ts.map +1 -1
- package/dist/node/dom/env.js +26 -1
- package/dist/node/markdown.js +1 -1
- package/dist/node/preview.d.ts +7 -1
- package/dist/node/preview.d.ts.map +1 -1
- package/dist/node/preview.js +22 -39
- package/dist/node/style-isolation/entry-css-merger.d.ts +9 -0
- package/dist/node/style-isolation/entry-css-merger.d.ts.map +1 -0
- package/dist/node/style-isolation/entry-css-merger.js +128 -0
- package/dist/node/style-isolation/global-styles.d.ts +8 -0
- package/dist/node/style-isolation/global-styles.d.ts.map +1 -0
- package/dist/node/style-isolation/global-styles.js +31 -0
- package/dist/node/style-isolation/index.d.ts +6 -0
- package/dist/node/style-isolation/index.d.ts.map +1 -0
- package/dist/node/style-isolation/index.js +5 -0
- package/dist/node/style-isolation/scope-chrome.d.ts +6 -0
- package/dist/node/style-isolation/scope-chrome.d.ts.map +1 -0
- package/dist/node/style-isolation/scope-chrome.js +37 -0
- package/dist/node/style-isolation/selectors.d.ts +7 -0
- package/dist/node/style-isolation/selectors.d.ts.map +1 -0
- package/dist/node/style-isolation/selectors.js +15 -0
- package/dist/node/style-isolation/transforms.d.ts +11 -0
- package/dist/node/style-isolation/transforms.d.ts.map +1 -0
- package/dist/node/style-isolation/transforms.js +75 -0
- package/dist/node/style-isolation/vite-plugin.d.ts +15 -0
- package/dist/node/style-isolation/vite-plugin.d.ts.map +1 -0
- package/dist/node/style-isolation/vite-plugin.js +62 -0
- package/dist/node/util/head.d.ts +3 -0
- package/dist/node/util/head.d.ts.map +1 -0
- package/dist/node/util/head.js +10 -0
- package/dist/node/virtual/markdown.js +1 -1
- package/dist/node/virtual/noop.d.ts +2 -1
- package/dist/node/virtual/noop.d.ts.map +1 -1
- package/dist/node/virtual/noop.js +7 -1
- package/dist/node/virtual/resolved-generated-global-setup.js +3 -3
- package/dist/node/virtual/resolved-markdown-files.js +2 -2
- package/dist/node/virtual/util.d.ts +15 -0
- package/dist/node/virtual/util.d.ts.map +1 -1
- package/dist/node/virtual/util.js +18 -0
- package/dist/node/virtual/vite-plugin.js +1 -1
- package/dist/node/vite.d.ts +1 -1
- package/dist/node/vite.d.ts.map +1 -1
- package/dist/node/vite.js +51 -9
- package/package.json +7 -5
- package/src/node/__tests__/dom-env.spec.ts +38 -0
- package/src/node/__tests__/head.spec.ts +32 -0
- package/src/node/__tests__/virtual-setup.spec.ts +51 -0
- package/src/node/bin.ts +3 -1
- package/src/node/build.ts +31 -5
- package/src/node/builtin-plugins/pinceau-tokens.ts +35 -35
- package/src/node/builtin-plugins/tailwind-tokens.ts +38 -38
- package/src/node/builtin-plugins/vanilla-support/RenderStory.ts +8 -5
- package/src/node/builtin-plugins/vanilla-support/types.ts +3 -1
- package/src/node/commands/preview.ts +8 -3
- package/src/node/config.ts +1 -0
- package/src/node/context.ts +2 -2
- package/src/node/dom/env.ts +26 -1
- package/src/node/markdown.ts +1 -1
- package/src/node/preview.ts +30 -47
- package/src/node/style-isolation/__tests__/entry-css-merger.spec.ts +126 -0
- package/src/node/style-isolation/__tests__/transforms.spec.ts +92 -0
- package/src/node/style-isolation/__tests__/vite-plugin.spec.ts +39 -0
- package/src/node/style-isolation/entry-css-merger.ts +133 -0
- package/src/node/style-isolation/global-styles.ts +37 -0
- package/src/node/style-isolation/index.ts +5 -0
- package/src/node/style-isolation/scope-chrome.ts +42 -0
- package/src/node/style-isolation/selectors.ts +16 -0
- package/src/node/style-isolation/transforms.ts +90 -0
- package/src/node/style-isolation/vite-plugin.ts +75 -0
- package/src/node/util/head.ts +12 -0
- package/src/node/virtual/markdown.ts +1 -1
- package/src/node/virtual/noop.ts +9 -1
- package/src/node/virtual/resolved-generated-global-setup.ts +3 -3
- package/src/node/virtual/resolved-markdown-files.ts +2 -2
- package/src/node/virtual/util.ts +22 -0
- package/src/node/virtual/vite-plugin.ts +1 -1
- package/src/node/vite.ts +57 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../../../src/node/util/head.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGhD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAQtG"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createHead, transformHtmlTemplate } from 'unhead/server';
|
|
2
|
+
export async function applyHeadTransform(html, head) {
|
|
3
|
+
if (!head) {
|
|
4
|
+
return html;
|
|
5
|
+
}
|
|
6
|
+
// disableDefaults: don't inject unhead's `lang="en"` / viewport tags — Poveste's
|
|
7
|
+
// own HTML template already provides viewport, and we shouldn't override it.
|
|
8
|
+
const instance = createHead({ init: [head], disableDefaults: true });
|
|
9
|
+
return await transformHtmlTemplate(instance, html);
|
|
10
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noop.d.ts","sourceRoot":"","sources":["../../../src/node/virtual/noop.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"noop.d.ts","sourceRoot":"","sources":["../../../src/node/virtual/noop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAG5C,wBAAgB,IAAI,CAAC,GAAG,EAAE,OAAO,UAKhC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GENERATED_SETUP_CODE } from './index.js';
|
|
2
|
-
import { ID_SEPARATOR } from './util.js';
|
|
2
|
+
import { declareEmptySetupFns, getSetupFnNames, ID_SEPARATOR } from './util.js';
|
|
3
3
|
export function resolvedGeneratedGlobalSetup(ctx) {
|
|
4
4
|
if (ctx.config.setupCode) {
|
|
5
5
|
return [
|
|
@@ -8,7 +8,7 @@ export function resolvedGeneratedGlobalSetup(ctx) {
|
|
|
8
8
|
// List
|
|
9
9
|
`const setupList = [${ctx.config.setupCode.map((c, index) => `setup_${index}`).join(', ')}]`,
|
|
10
10
|
// Setups
|
|
11
|
-
...ctx
|
|
11
|
+
...getSetupFnNames(ctx).map(fnName => `export async function ${fnName} (payload) {
|
|
12
12
|
for (const setup of setupList) {
|
|
13
13
|
if (setup?.${fnName}) {
|
|
14
14
|
await setup.${fnName}(payload)
|
|
@@ -18,6 +18,6 @@ export function resolvedGeneratedGlobalSetup(ctx) {
|
|
|
18
18
|
].join('\n');
|
|
19
19
|
}
|
|
20
20
|
else {
|
|
21
|
-
return
|
|
21
|
+
return declareEmptySetupFns(ctx);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -4,8 +4,8 @@ export function resolvedMarkdownFiles(ctx) {
|
|
|
4
4
|
return `import { reactive } from ${process.env.POVESTE_DEV ? `'vue'` : getInjectedImport('@poveste/vendors/vue')}
|
|
5
5
|
export const markdownFiles = reactive({${filesJs}})
|
|
6
6
|
if (import.meta.hot) {
|
|
7
|
-
if (!window.
|
|
8
|
-
window.
|
|
7
|
+
if (!window.__pvt_md_hmr) {
|
|
8
|
+
window.__pvt_md_hmr = (newModule) => {
|
|
9
9
|
markdownFiles[newModule.relativePath] = () => newModule
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import type { Context } from '../context.js';
|
|
1
2
|
export declare const ID_SEPARATOR = "__-__";
|
|
3
|
+
/**
|
|
4
|
+
* Names of every setup hook contributed by the enabled support plugins, e.g.
|
|
5
|
+
* `setupVanilla`, `setupVue3`, `setupSvelte4`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getSetupFnNames(ctx: Context): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Declares the setup hooks as named exports set to `undefined`.
|
|
10
|
+
*
|
|
11
|
+
* Consumers read them off a namespace import (`generatedSetup.setupVue3`) behind
|
|
12
|
+
* a `typeof === 'function'` guard. Without the declaration Rollup can't resolve
|
|
13
|
+
* the member access statically and warns `"setupVue3" is not exported by ...`
|
|
14
|
+
* in every consumer build.
|
|
15
|
+
*/
|
|
16
|
+
export declare function declareEmptySetupFns(ctx: Context): string;
|
|
2
17
|
export declare const PLUGINS_HAVE_DEV: string[];
|
|
3
18
|
//# sourceMappingURL=util.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/node/virtual/util.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,UAAU,CAAA;AAEnC,eAAO,MAAM,gBAAgB,UAE5B,CAAA"}
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/node/virtual/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAE5C,eAAO,MAAM,YAAY,UAAU,CAAA;AAEnC;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,YAE3C;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,UAEhD;AAED,eAAO,MAAM,gBAAgB,UAE5B,CAAA"}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
export const ID_SEPARATOR = '__-__';
|
|
2
|
+
/**
|
|
3
|
+
* Names of every setup hook contributed by the enabled support plugins, e.g.
|
|
4
|
+
* `setupVanilla`, `setupVue3`, `setupSvelte4`.
|
|
5
|
+
*/
|
|
6
|
+
export function getSetupFnNames(ctx) {
|
|
7
|
+
return [...new Set(ctx.supportPlugins.flatMap(p => p.setupFn))];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Declares the setup hooks as named exports set to `undefined`.
|
|
11
|
+
*
|
|
12
|
+
* Consumers read them off a namespace import (`generatedSetup.setupVue3`) behind
|
|
13
|
+
* a `typeof === 'function'` guard. Without the declaration Rollup can't resolve
|
|
14
|
+
* the member access statically and warns `"setupVue3" is not exported by ...`
|
|
15
|
+
* in every consumer build.
|
|
16
|
+
*/
|
|
17
|
+
export function declareEmptySetupFns(ctx) {
|
|
18
|
+
return getSetupFnNames(ctx).map(fnName => `export const ${fnName} = undefined`).join('\n');
|
|
19
|
+
}
|
|
2
20
|
export const PLUGINS_HAVE_DEV = [
|
|
3
21
|
'@poveste/plugin-vue',
|
|
4
22
|
];
|
|
@@ -80,7 +80,7 @@ export function createVirtualFilesPlugin(ctx, isServer) {
|
|
|
80
80
|
return VirtualFiles.resolvedStories(ctx);
|
|
81
81
|
}
|
|
82
82
|
if (id === VirtualFiles.NOOP_ID) {
|
|
83
|
-
return VirtualFiles.noop();
|
|
83
|
+
return VirtualFiles.noop(ctx);
|
|
84
84
|
}
|
|
85
85
|
if (id === VirtualFiles.RESOLVED_CONFIG_ID) {
|
|
86
86
|
return VirtualFiles.resolvedConfig(ctx);
|
package/dist/node/vite.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { InlineConfig } from 'vite';
|
|
2
2
|
import type { Context } from './context.js';
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function mergePovesteViteConfig(viteConfig: InlineConfig, ctx: Context): Promise<InlineConfig>;
|
|
4
4
|
export interface ViteConfigWithPlugins {
|
|
5
5
|
viteConfig: InlineConfig;
|
|
6
6
|
viteConfigFile: string | null;
|
package/dist/node/vite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../src/node/vite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,MAAM,CAAA;AACb,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../src/node/vite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,MAAM,CAAA;AACb,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAsB3C,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,yBAmClF;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,YAAY,CAAA;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAqW9G"}
|
package/dist/node/vite.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { createRequire } from 'node:module';
|
|
2
3
|
import { lookup as lookupMime } from 'mrmime';
|
|
3
4
|
import { dirname, join, relative } from 'pathe';
|
|
@@ -5,9 +6,11 @@ import { loadConfigFromFile as loadViteConfigFromFile, mergeConfig as mergeViteC
|
|
|
5
6
|
import { APP_PATH, TEMP_PATH } from './alias.js';
|
|
6
7
|
import { createMarkdownPlugins } from './markdown.js';
|
|
7
8
|
import { notifyStoryChange } from './stories.js';
|
|
9
|
+
import { chromeCssScopePlugin, globalStylesPlugin, userCssScopePlugin, } from './style-isolation/index.js';
|
|
10
|
+
import { applyHeadTransform } from './util/head.js';
|
|
8
11
|
import { createVirtualFilesPlugin } from './virtual/vite-plugin.js';
|
|
9
12
|
const require = createRequire(import.meta.url);
|
|
10
|
-
export async function
|
|
13
|
+
export async function mergePovesteViteConfig(viteConfig, ctx) {
|
|
11
14
|
if (ctx.config.vite) {
|
|
12
15
|
const command = ctx.mode === 'dev' ? 'serve' : 'build';
|
|
13
16
|
const overrides = typeof ctx.config.vite === 'function'
|
|
@@ -42,7 +45,7 @@ export async function mergeHistoireViteConfig(viteConfig, ctx) {
|
|
|
42
45
|
export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
43
46
|
const userViteConfigFile = await loadViteConfigFromFile({ command: ctx.mode === 'dev' ? 'serve' : 'build', mode: ctx.mode });
|
|
44
47
|
const userViteConfig = mergeViteConfig(userViteConfigFile?.config ?? {}, { server: { port: 6006 } });
|
|
45
|
-
const inlineConfig = await
|
|
48
|
+
const inlineConfig = await mergePovesteViteConfig(userViteConfig, ctx);
|
|
46
49
|
const plugins = [];
|
|
47
50
|
const supportPluginAllowPaths = ctx.supportPlugins.flatMap((plugin) => {
|
|
48
51
|
const paths = [ctx.root, import.meta.url];
|
|
@@ -60,14 +63,40 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
60
63
|
}
|
|
61
64
|
return Array.from(result);
|
|
62
65
|
});
|
|
66
|
+
// Mirror how Vite resolves `optimizeDeps.include`: walk `node_modules` up from
|
|
67
|
+
// the project root. Deliberately not `require.resolve(dep, { paths: [root] })`
|
|
68
|
+
// — Node always folds NODE_PATH into resolution even when `paths` is given, and
|
|
69
|
+
// pnpm's bin shims point NODE_PATH at the hidden `.pnpm/node_modules` store, so
|
|
70
|
+
// every package would look reachable from every root. Vite ignores NODE_PATH.
|
|
71
|
+
function isResolvableFromRoot(dep) {
|
|
72
|
+
let dir = ctx.root;
|
|
73
|
+
while (true) {
|
|
74
|
+
if (existsSync(join(dir, 'node_modules', dep, 'package.json'))) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
const parent = dirname(dir);
|
|
78
|
+
if (parent === dir) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
dir = parent;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
63
84
|
function optimizeDeps(deps) {
|
|
64
85
|
const result = [];
|
|
65
86
|
for (const dep of deps) {
|
|
66
|
-
|
|
87
|
+
// The bare specifier is what hoisted installs (npm, yarn, pnpm with
|
|
88
|
+
// `shamefully-hoist`) need — see histoire@a1014ab. Under strict pnpm it is
|
|
89
|
+
// unreachable from the root and Vite only logs "Failed to resolve
|
|
90
|
+
// dependency", so emit it just when the root can actually resolve it.
|
|
91
|
+
if (isResolvableFromRoot(dep)) {
|
|
92
|
+
result.push(dep);
|
|
93
|
+
}
|
|
67
94
|
try {
|
|
95
|
+
// Absolute path, valid regardless of layout — this is what makes the
|
|
96
|
+
// dep optimizable under strict pnpm.
|
|
68
97
|
result.push(dirname(require.resolve(`${dep}/package.json`)));
|
|
69
98
|
}
|
|
70
|
-
catch
|
|
99
|
+
catch {
|
|
71
100
|
// Noop
|
|
72
101
|
}
|
|
73
102
|
}
|
|
@@ -97,11 +126,7 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
97
126
|
`${APP_PATH}/bundle-sandbox.js`,
|
|
98
127
|
],
|
|
99
128
|
include: optimizeDeps([
|
|
100
|
-
'flexsearch',
|
|
101
129
|
'shiki',
|
|
102
|
-
// Shiki dependencies
|
|
103
|
-
'vscode-oniguruma',
|
|
104
|
-
'vscode-textmate',
|
|
105
130
|
]),
|
|
106
131
|
exclude: [
|
|
107
132
|
'poveste',
|
|
@@ -141,7 +166,7 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
141
166
|
: {},
|
|
142
167
|
'__HST_COLLECT__': isServer,
|
|
143
168
|
},
|
|
144
|
-
cacheDir: isServer ? 'node_modules/.
|
|
169
|
+
cacheDir: isServer ? 'node_modules/.pvt-vite-server' : 'node_modules/.pvt-vite',
|
|
145
170
|
};
|
|
146
171
|
},
|
|
147
172
|
options() {
|
|
@@ -189,6 +214,7 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
189
214
|
<script type="module" src="/@fs/${APP_PATH}/bundle-sandbox${process.env.POVESTE_DEV ? '-dev' : ''}.js"></script>
|
|
190
215
|
</body>
|
|
191
216
|
</html>`;
|
|
217
|
+
html = await applyHeadTransform(html, ctx.config.head);
|
|
192
218
|
// Apply Vite HTML transforms. This injects the Vite HMR client, and
|
|
193
219
|
// also applies HTML transforms from Vite plugins
|
|
194
220
|
html = await server.transformIndexHtml(req.url, html);
|
|
@@ -213,12 +239,14 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
213
239
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
214
240
|
<meta name="description" content="">
|
|
215
241
|
${ctx.config.theme?.favicon ? `<link rel="icon" type="${lookupMime(ctx.config.theme.favicon)}" href="${server.config.base}${ctx.config.theme.favicon}"/>` : ''}
|
|
242
|
+
<style>html,body{margin:0;padding:0}</style>
|
|
216
243
|
</head>
|
|
217
244
|
<body>
|
|
218
245
|
<div id="app"></div>
|
|
219
246
|
<script type="module" src="/@fs/${APP_PATH}/bundle-main${process.env.POVESTE_DEV ? '-dev' : ''}.js"></script>
|
|
220
247
|
</body>
|
|
221
248
|
</html>`;
|
|
249
|
+
html = await applyHeadTransform(html, ctx.config.head);
|
|
222
250
|
// Apply Vite HTML transforms. This injects the Vite HMR client, and
|
|
223
251
|
// also applies HTML transforms from Vite plugins
|
|
224
252
|
html = await server.transformIndexHtml(req.url, html);
|
|
@@ -232,6 +260,20 @@ export async function getViteConfigWithPlugins(isServer, ctx) {
|
|
|
232
260
|
},
|
|
233
261
|
});
|
|
234
262
|
plugins.push(createVirtualFilesPlugin(ctx, isServer));
|
|
263
|
+
plugins.push(globalStylesPlugin({
|
|
264
|
+
files: ctx.config.globalStyles ?? [],
|
|
265
|
+
rootDir: ctx.root,
|
|
266
|
+
}));
|
|
267
|
+
// Dev-mode isolation. User CSS is wrapped in `@scope (.__poveste-render-story)`
|
|
268
|
+
// so it cannot leak into chrome (popovers, tooltips, dropdowns). The build
|
|
269
|
+
// path emits stronger per-bundle isolation that ALSO keeps the user's
|
|
270
|
+
// teleporting-component styling working inside the sandbox iframe — in dev
|
|
271
|
+
// that case is a known trade-off, see docs/guide/css.md.
|
|
272
|
+
const isolate = ctx.config.isolateStyles !== false;
|
|
273
|
+
if (ctx.mode === 'dev' && !isServer) {
|
|
274
|
+
plugins.push(userCssScopePlugin({ enabled: isolate, mode: 'wrap' }));
|
|
275
|
+
plugins.push(chromeCssScopePlugin({ enabled: isolate }));
|
|
276
|
+
}
|
|
235
277
|
// Replace dev flag
|
|
236
278
|
const flags = {
|
|
237
279
|
'_ctx.__POVESTE_DEV__': JSON.stringify(ctx.mode === 'dev'),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poveste",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "A fast story book powered by Vite",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Sorin Gitlan"
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"gray-matter": "^4.0.3",
|
|
55
55
|
"jiti": "^2.4.1",
|
|
56
56
|
"jsdom": "^27.4.0",
|
|
57
|
+
"lightningcss": "^1.32.0",
|
|
57
58
|
"markdown-it": "^14.1.0",
|
|
58
59
|
"markdown-it-anchor": "^9.2.0",
|
|
59
60
|
"markdown-it-attrs": "^4.3.0",
|
|
@@ -65,11 +66,12 @@
|
|
|
65
66
|
"sade": "^1.8.1",
|
|
66
67
|
"shiki": "^3.0.0",
|
|
67
68
|
"sirv": "^3.0.0",
|
|
69
|
+
"unhead": "^3.1.0",
|
|
68
70
|
"vite-node": "3.2.4",
|
|
69
|
-
"@poveste/
|
|
70
|
-
"@poveste/
|
|
71
|
-
"@poveste/
|
|
72
|
-
"@poveste/
|
|
71
|
+
"@poveste/app": "^0.2.0",
|
|
72
|
+
"@poveste/controls": "^0.2.0",
|
|
73
|
+
"@poveste/shared": "^0.2.0",
|
|
74
|
+
"@poveste/vendors": "^0.2.0"
|
|
73
75
|
},
|
|
74
76
|
"devDependencies": {
|
|
75
77
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { createDomEnv } from '../dom/env.js'
|
|
3
|
+
|
|
4
|
+
describe('createDomEnv', () => {
|
|
5
|
+
let env: ReturnType<typeof createDomEnv>
|
|
6
|
+
let errorSpy: ReturnType<typeof vi.spyOn>
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
env?.destroy()
|
|
10
|
+
errorSpy?.mockRestore()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
describe('when a stylesheet contains unparseable directives (e.g. Tailwind v4 @theme)', () => {
|
|
14
|
+
it('does not log a css-parsing jsdomError', () => {
|
|
15
|
+
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
16
|
+
env = createDomEnv()
|
|
17
|
+
const style = env.window.document.createElement('style')
|
|
18
|
+
style.textContent = '@theme { --color-brand: oklch(0.7 0.1 200); }\n.btn { color: var(--color-brand) }'
|
|
19
|
+
|
|
20
|
+
env.window.document.head.appendChild(style)
|
|
21
|
+
|
|
22
|
+
expect(errorSpy).not.toHaveBeenCalled()
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('when a script throws an unhandled exception', () => {
|
|
27
|
+
it('forwards the error to console.error', () => {
|
|
28
|
+
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
29
|
+
env = createDomEnv()
|
|
30
|
+
const script = env.window.document.createElement('script')
|
|
31
|
+
script.textContent = 'throw new Error("boom from story script")'
|
|
32
|
+
|
|
33
|
+
env.window.document.head.appendChild(script)
|
|
34
|
+
|
|
35
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('boom from story script'))
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { applyHeadTransform } from '../util/head.js'
|
|
3
|
+
|
|
4
|
+
const BASE_HTML = '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body></body></html>'
|
|
5
|
+
|
|
6
|
+
describe('applyHeadTransform', () => {
|
|
7
|
+
it('returns the html untouched when head is undefined', async () => {
|
|
8
|
+
expect(await applyHeadTransform(BASE_HTML, undefined)).toBe(BASE_HTML)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('injects user-supplied link tags into the head', async () => {
|
|
12
|
+
const out = await applyHeadTransform(BASE_HTML, {
|
|
13
|
+
link: [{ rel: 'preconnect', href: 'https://fonts.googleapis.com' }],
|
|
14
|
+
})
|
|
15
|
+
expect(out).toContain('<link rel="preconnect" href="https://fonts.googleapis.com">')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('injects user-supplied meta tags into the head', async () => {
|
|
19
|
+
const out = await applyHeadTransform(BASE_HTML, {
|
|
20
|
+
meta: [{ name: 'theme-color', content: '#10b981' }],
|
|
21
|
+
})
|
|
22
|
+
expect(out).toContain('<meta name="theme-color" content="#10b981">')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('skips unhead default lang and viewport tags when head is provided', async () => {
|
|
26
|
+
const out = await applyHeadTransform(BASE_HTML, {
|
|
27
|
+
meta: [{ name: 'theme-color', content: '#10b981' }],
|
|
28
|
+
})
|
|
29
|
+
expect(out).not.toContain('lang="en"')
|
|
30
|
+
expect(out).not.toMatch(/<meta[^>]*name="viewport"/)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Context } from '../context.js'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { noop } from '../virtual/noop.js'
|
|
4
|
+
import { resolvedGeneratedGlobalSetup } from '../virtual/resolved-generated-global-setup.js'
|
|
5
|
+
|
|
6
|
+
function contextFactory(config: Partial<Context['config']> = {}): Context {
|
|
7
|
+
return {
|
|
8
|
+
config,
|
|
9
|
+
supportPlugins: [
|
|
10
|
+
{ setupFn: 'setupVanilla' },
|
|
11
|
+
{ setupFn: 'setupVue3' },
|
|
12
|
+
{ setupFn: ['setupSvelte4', 'setupSvelte5'] },
|
|
13
|
+
],
|
|
14
|
+
} as Context
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SETUP_FN_NAMES = ['setupVanilla', 'setupVue3', 'setupSvelte4', 'setupSvelte5']
|
|
18
|
+
|
|
19
|
+
// Consumers read these hooks off a namespace import behind a `typeof === 'function'`
|
|
20
|
+
// guard. If the module doesn't declare them, Rollup can't resolve the member access
|
|
21
|
+
// statically and warns `"setupVue3" is not exported by ...` in every consumer build.
|
|
22
|
+
describe('virtual setup modules', () => {
|
|
23
|
+
describe('generated global setup', () => {
|
|
24
|
+
it('should declare every support plugin setup hook when there is no setup code', () => {
|
|
25
|
+
const code = resolvedGeneratedGlobalSetup(contextFactory())
|
|
26
|
+
|
|
27
|
+
for (const fnName of SETUP_FN_NAMES) {
|
|
28
|
+
expect(code).toContain(`export const ${fnName} = undefined`)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should export a real hook per support plugin when there is setup code', () => {
|
|
33
|
+
const code = resolvedGeneratedGlobalSetup(contextFactory({ setupCode: ['export function setupVue3() {}'] }))
|
|
34
|
+
|
|
35
|
+
for (const fnName of SETUP_FN_NAMES) {
|
|
36
|
+
expect(code).toContain(`export async function ${fnName} (payload)`)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('noop', () => {
|
|
42
|
+
it('should keep the default export and declare every setup hook', () => {
|
|
43
|
+
const code = noop(contextFactory())
|
|
44
|
+
|
|
45
|
+
expect(code).toContain('export default () => {}')
|
|
46
|
+
for (const fnName of SETUP_FN_NAMES) {
|
|
47
|
+
expect(code).toContain(`export const ${fnName} = undefined`)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
})
|
package/src/node/bin.ts
CHANGED
|
@@ -18,7 +18,7 @@ program.command('dev')
|
|
|
18
18
|
.option('-p, --port <port>', 'Listening port of the server')
|
|
19
19
|
.option('-c, --config <file>', `[string] use specified config file`)
|
|
20
20
|
.option('--open', 'Open in your default browser')
|
|
21
|
-
.option('--host
|
|
21
|
+
.option('--host [host]', '[string] specify hostname (omit value or pass `0.0.0.0` to expose to all network interfaces)')
|
|
22
22
|
.action(async (options) => {
|
|
23
23
|
const { devCommand } = await import('./commands/dev.js')
|
|
24
24
|
return devCommand(options)
|
|
@@ -35,6 +35,8 @@ program.command('build')
|
|
|
35
35
|
program.command('preview')
|
|
36
36
|
.describe('preview the built directory')
|
|
37
37
|
.option('-p, --port <port>', 'Listening port of the server')
|
|
38
|
+
.option('--open', 'Open in your default browser')
|
|
39
|
+
.option('--host [host]', '[string] specify hostname (omit value or pass `0.0.0.0` to expose to all network interfaces)')
|
|
38
40
|
.action(async (options) => {
|
|
39
41
|
const { previewCommand } = await import('./commands/preview.js')
|
|
40
42
|
return previewCommand(options)
|
package/src/node/build.ts
CHANGED
|
@@ -27,6 +27,12 @@ import { createMarkdownFilesWatcher } from './markdown.js'
|
|
|
27
27
|
import { BuildPluginApi } from './plugin.js'
|
|
28
28
|
import { startPreview } from './preview.js'
|
|
29
29
|
import { findAllStories } from './stories.js'
|
|
30
|
+
import {
|
|
31
|
+
chromeCssScopePlugin,
|
|
32
|
+
entryCssMergerPlugin,
|
|
33
|
+
userCssScopePlugin,
|
|
34
|
+
} from './style-isolation/index.js'
|
|
35
|
+
import { applyHeadTransform } from './util/head.js'
|
|
30
36
|
import { getViteConfigWithPlugins } from './vite.js'
|
|
31
37
|
|
|
32
38
|
const PRELOAD_MODULES = [
|
|
@@ -169,7 +175,8 @@ export async function build(ctx: Context) {
|
|
|
169
175
|
Object.assign(config.build, {
|
|
170
176
|
outDir: ctx.config.outDir,
|
|
171
177
|
emptyOutDir: true,
|
|
172
|
-
|
|
178
|
+
// Re-merged per-entry by entry-css-merger.
|
|
179
|
+
cssCodeSplit: true,
|
|
173
180
|
minify: false,
|
|
174
181
|
// Don't build in SSR mode
|
|
175
182
|
ssr: false,
|
|
@@ -179,6 +186,11 @@ export async function build(ctx: Context) {
|
|
|
179
186
|
},
|
|
180
187
|
})
|
|
181
188
|
|
|
189
|
+
const isolate = ctx.config.isolateStyles !== false
|
|
190
|
+
buildViteConfig.plugins.push(userCssScopePlugin({ enabled: isolate }))
|
|
191
|
+
buildViteConfig.plugins.push(chromeCssScopePlugin({ enabled: isolate }))
|
|
192
|
+
buildViteConfig.plugins.push(entryCssMergerPlugin({ isolateStyles: isolate }))
|
|
193
|
+
|
|
182
194
|
for (const cb of changeViteConfigCallbacks) {
|
|
183
195
|
console.log('vite config hook', cb)
|
|
184
196
|
await cb(buildViteConfig)
|
|
@@ -187,7 +199,14 @@ export async function build(ctx: Context) {
|
|
|
187
199
|
const results = await viteBuild(buildViteConfig)
|
|
188
200
|
const result = Array.isArray(results) ? results[0] : results as RollupOutput
|
|
189
201
|
|
|
190
|
-
|
|
202
|
+
function findEntryCss(entryName: string) {
|
|
203
|
+
return result.output.find(
|
|
204
|
+
o => o.type === 'asset' && o.fileName === `${entryName}.css`,
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
const mainStyleOutput = findEntryCss('bundle-main')
|
|
208
|
+
?? result.output.find(o => o.name === 'style.css' && o.type === 'asset')
|
|
209
|
+
const sandboxStyleOutput = findEntryCss('bundle-sandbox') ?? mainStyleOutput
|
|
191
210
|
|
|
192
211
|
// Preload
|
|
193
212
|
const preloadOutputs = result.output.filter(o => PRELOAD_MODULES.includes(o.name) && o.type === 'chunk')
|
|
@@ -199,14 +218,16 @@ export async function build(ctx: Context) {
|
|
|
199
218
|
|
|
200
219
|
// Index
|
|
201
220
|
const indexOutput = result.output.find(o => o.name === 'bundle-main' && o.type === 'chunk')
|
|
202
|
-
|
|
221
|
+
let indexHtml = generateEntryHtml(indexOutput.fileName, mainStyleOutput.fileName, {
|
|
203
222
|
HEAD: `${preloadHtml}${prefetchHtml}`,
|
|
204
223
|
}, ctx)
|
|
224
|
+
indexHtml = await applyHeadTransform(indexHtml, ctx.config.head)
|
|
205
225
|
await writeFile('index.html', indexHtml, ctx)
|
|
206
226
|
|
|
207
227
|
// Sandbox
|
|
208
228
|
const sandboxOutput = result.output.find(o => o.name === 'bundle-sandbox' && o.type === 'chunk')
|
|
209
|
-
|
|
229
|
+
let sandboxHtml = generateEntryHtml(sandboxOutput.fileName, sandboxStyleOutput.fileName, {}, ctx)
|
|
230
|
+
sandboxHtml = await applyHeadTransform(sandboxHtml, ctx.config.head)
|
|
210
231
|
await writeFile('__sandbox.html', sandboxHtml, ctx)
|
|
211
232
|
|
|
212
233
|
await writeFile('poveste.json', JSON.stringify(getSerializedStoryData(ctx), null, 2), ctx)
|
|
@@ -219,7 +240,7 @@ export async function build(ctx: Context) {
|
|
|
219
240
|
|
|
220
241
|
// Render
|
|
221
242
|
if (previewStoryCallbacks.length) {
|
|
222
|
-
const { baseUrl, close } = await startPreview(
|
|
243
|
+
const { baseUrl, close } = await startPreview({}, ctx)
|
|
223
244
|
for (const storyFile of ctx.storyFiles) {
|
|
224
245
|
const story = storyFile.story
|
|
225
246
|
for (const variant of story.variants) {
|
|
@@ -255,6 +276,7 @@ function generateBaseHtml(head: string, body: string, ctx: Context) {
|
|
|
255
276
|
<meta charset="utf-8">
|
|
256
277
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
257
278
|
<meta name="description" content="">
|
|
279
|
+
<style>html,body{margin:0;padding:0}</style>
|
|
258
280
|
${head}
|
|
259
281
|
</head>
|
|
260
282
|
<body>
|
|
@@ -281,3 +303,7 @@ async function writeFile(fileName: string, content: string, ctx: Context) {
|
|
|
281
303
|
function generateScriptLinks(prefetchScripts: string[], rel: string, ctx: Context) {
|
|
282
304
|
return prefetchScripts.map(s => `<link rel="${rel}" href="${ctx.resolvedViteConfig.base}${s}" as="script" crossOrigin="anonymous">`).join('')
|
|
283
305
|
}
|
|
306
|
+
|
|
307
|
+
function generateStyleLinks(styleFiles: string[], rel: string, ctx: Context) {
|
|
308
|
+
return styleFiles.map(s => `<link rel="${rel}" href="${ctx.resolvedViteConfig.base}${s}" as="style">`).join('')
|
|
309
|
+
}
|