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.
Files changed (92) hide show
  1. package/dist/node/bin.js +3 -1
  2. package/dist/node/build.d.ts.map +1 -1
  3. package/dist/node/build.js +23 -5
  4. package/dist/node/builtin-plugins/pinceau-tokens.js +35 -35
  5. package/dist/node/builtin-plugins/tailwind-tokens.js +38 -38
  6. package/dist/node/builtin-plugins/vanilla-support/RenderStory.d.ts.map +1 -1
  7. package/dist/node/builtin-plugins/vanilla-support/RenderStory.js +7 -4
  8. package/dist/node/builtin-plugins/vanilla-support/types.d.ts +6 -5
  9. package/dist/node/builtin-plugins/vanilla-support/types.d.ts.map +1 -1
  10. package/dist/node/commands/preview.d.ts +2 -0
  11. package/dist/node/commands/preview.d.ts.map +1 -1
  12. package/dist/node/commands/preview.js +6 -3
  13. package/dist/node/config.d.ts.map +1 -1
  14. package/dist/node/config.js +1 -0
  15. package/dist/node/context.js +2 -2
  16. package/dist/node/dom/env.d.ts.map +1 -1
  17. package/dist/node/dom/env.js +26 -1
  18. package/dist/node/markdown.js +1 -1
  19. package/dist/node/preview.d.ts +7 -1
  20. package/dist/node/preview.d.ts.map +1 -1
  21. package/dist/node/preview.js +22 -39
  22. package/dist/node/style-isolation/entry-css-merger.d.ts +9 -0
  23. package/dist/node/style-isolation/entry-css-merger.d.ts.map +1 -0
  24. package/dist/node/style-isolation/entry-css-merger.js +128 -0
  25. package/dist/node/style-isolation/global-styles.d.ts +8 -0
  26. package/dist/node/style-isolation/global-styles.d.ts.map +1 -0
  27. package/dist/node/style-isolation/global-styles.js +31 -0
  28. package/dist/node/style-isolation/index.d.ts +6 -0
  29. package/dist/node/style-isolation/index.d.ts.map +1 -0
  30. package/dist/node/style-isolation/index.js +5 -0
  31. package/dist/node/style-isolation/scope-chrome.d.ts +6 -0
  32. package/dist/node/style-isolation/scope-chrome.d.ts.map +1 -0
  33. package/dist/node/style-isolation/scope-chrome.js +37 -0
  34. package/dist/node/style-isolation/selectors.d.ts +7 -0
  35. package/dist/node/style-isolation/selectors.d.ts.map +1 -0
  36. package/dist/node/style-isolation/selectors.js +15 -0
  37. package/dist/node/style-isolation/transforms.d.ts +11 -0
  38. package/dist/node/style-isolation/transforms.d.ts.map +1 -0
  39. package/dist/node/style-isolation/transforms.js +75 -0
  40. package/dist/node/style-isolation/vite-plugin.d.ts +15 -0
  41. package/dist/node/style-isolation/vite-plugin.d.ts.map +1 -0
  42. package/dist/node/style-isolation/vite-plugin.js +62 -0
  43. package/dist/node/util/head.d.ts +3 -0
  44. package/dist/node/util/head.d.ts.map +1 -0
  45. package/dist/node/util/head.js +10 -0
  46. package/dist/node/virtual/markdown.js +1 -1
  47. package/dist/node/virtual/noop.d.ts +2 -1
  48. package/dist/node/virtual/noop.d.ts.map +1 -1
  49. package/dist/node/virtual/noop.js +7 -1
  50. package/dist/node/virtual/resolved-generated-global-setup.js +3 -3
  51. package/dist/node/virtual/resolved-markdown-files.js +2 -2
  52. package/dist/node/virtual/util.d.ts +15 -0
  53. package/dist/node/virtual/util.d.ts.map +1 -1
  54. package/dist/node/virtual/util.js +18 -0
  55. package/dist/node/virtual/vite-plugin.js +1 -1
  56. package/dist/node/vite.d.ts +1 -1
  57. package/dist/node/vite.d.ts.map +1 -1
  58. package/dist/node/vite.js +51 -9
  59. package/package.json +7 -5
  60. package/src/node/__tests__/dom-env.spec.ts +38 -0
  61. package/src/node/__tests__/head.spec.ts +32 -0
  62. package/src/node/__tests__/virtual-setup.spec.ts +51 -0
  63. package/src/node/bin.ts +3 -1
  64. package/src/node/build.ts +31 -5
  65. package/src/node/builtin-plugins/pinceau-tokens.ts +35 -35
  66. package/src/node/builtin-plugins/tailwind-tokens.ts +38 -38
  67. package/src/node/builtin-plugins/vanilla-support/RenderStory.ts +8 -5
  68. package/src/node/builtin-plugins/vanilla-support/types.ts +3 -1
  69. package/src/node/commands/preview.ts +8 -3
  70. package/src/node/config.ts +1 -0
  71. package/src/node/context.ts +2 -2
  72. package/src/node/dom/env.ts +26 -1
  73. package/src/node/markdown.ts +1 -1
  74. package/src/node/preview.ts +30 -47
  75. package/src/node/style-isolation/__tests__/entry-css-merger.spec.ts +126 -0
  76. package/src/node/style-isolation/__tests__/transforms.spec.ts +92 -0
  77. package/src/node/style-isolation/__tests__/vite-plugin.spec.ts +39 -0
  78. package/src/node/style-isolation/entry-css-merger.ts +133 -0
  79. package/src/node/style-isolation/global-styles.ts +37 -0
  80. package/src/node/style-isolation/index.ts +5 -0
  81. package/src/node/style-isolation/scope-chrome.ts +42 -0
  82. package/src/node/style-isolation/selectors.ts +16 -0
  83. package/src/node/style-isolation/transforms.ts +90 -0
  84. package/src/node/style-isolation/vite-plugin.ts +75 -0
  85. package/src/node/util/head.ts +12 -0
  86. package/src/node/virtual/markdown.ts +1 -1
  87. package/src/node/virtual/noop.ts +9 -1
  88. package/src/node/virtual/resolved-generated-global-setup.ts +3 -3
  89. package/src/node/virtual/resolved-markdown-files.ts +2 -2
  90. package/src/node/virtual/util.ts +22 -0
  91. package/src/node/virtual/vite-plugin.ts +1 -1
  92. package/src/node/vite.ts +57 -9
@@ -0,0 +1,75 @@
1
+ import type { Plugin as VitePlugin } from 'vite'
2
+ import { GLOBAL_LAYER_QUERY, STORY_SCOPE_ROOT } from './selectors.js'
3
+ import { wrapUserCss } from './transforms.js'
4
+
5
+ export const USER_CSS_MARK_START = '/*__PVT_USER_CSS_START__*/'
6
+ export const USER_CSS_MARK_END = '/*__PVT_USER_CSS_END__*/'
7
+
8
+ export interface UserCssScopePluginOptions {
9
+ enabled: boolean
10
+ /**
11
+ * 'mark' — insert sentinel comments; entry-css-merger wraps per-entry at
12
+ * build time. 'wrap' — wrap directly in @scope at transform time (dev,
13
+ * no merger).
14
+ */
15
+ mode?: 'mark' | 'wrap'
16
+ }
17
+
18
+ export function userCssScopePlugin(opts: UserCssScopePluginOptions): VitePlugin {
19
+ const mode = opts.mode ?? 'mark'
20
+ return {
21
+ name: 'poveste:style-isolation:user-css',
22
+ transform(code, id) {
23
+ if (!opts.enabled) return null
24
+ const q = id.indexOf('?')
25
+ const path = q === -1 ? id : id.slice(0, q)
26
+ const query = q === -1 ? '' : id.slice(q + 1)
27
+ // Cheap substring rejection of all poveste-internal CSS first.
28
+ if (isChromePath(path)) return null
29
+ if (!isCssPath(path, query)) return null
30
+ if (hasGlobalQuery(query)) return null
31
+ if (mode === 'wrap') {
32
+ return { code: wrapUserCss(code, { scopeRoot: STORY_SCOPE_ROOT }), map: null }
33
+ }
34
+ return {
35
+ code: `${USER_CSS_MARK_START}\n${code}\n${USER_CSS_MARK_END}\n`,
36
+ map: null,
37
+ }
38
+ },
39
+ }
40
+ }
41
+
42
+ const CSS_EXT_RE = /\.(?:css|pcss|postcss|scss|sass|less|styl|stylus)$/
43
+ // Vue / Svelte / Vite virtual style modules carry the source extension as a
44
+ // `lang.<ext>` query, e.g. `Foo.vue?vue&type=style&index=0&lang.css`.
45
+ const CSS_LANG_QUERY_RE = /(?:^|&)lang\.(?:css|pcss|postcss|scss|sass|less|styl|stylus)(?:&|$)/
46
+
47
+ function isCssPath(path: string, query: string): boolean {
48
+ return CSS_EXT_RE.test(path) || CSS_LANG_QUERY_RE.test(query)
49
+ }
50
+
51
+ function hasGlobalQuery(query: string): boolean {
52
+ if (!query) return false
53
+ const parts = query.split('&')
54
+ // `?global` is the per-import escape hatch; the `globalStyles` config marks
55
+ // its files with `?poveste-global-layer` and they get a cascade layer of
56
+ // their own in global-styles.ts.
57
+ return parts.includes('global')
58
+ || parts.some(p => p.startsWith('global='))
59
+ || parts.includes(GLOBAL_LAYER_QUERY)
60
+ }
61
+
62
+ export function isChromeCss(id: string): boolean {
63
+ const q = id.indexOf('?')
64
+ return isChromePath(q === -1 ? id : id.slice(0, q))
65
+ }
66
+
67
+ function isChromePath(path: string): boolean {
68
+ return path.includes('@poveste/app/')
69
+ || path.includes('poveste-app/')
70
+ || path.includes('@poveste/controls/')
71
+ || path.includes('poveste-controls/')
72
+ || path.includes('@poveste/vendors/')
73
+ || path.includes('poveste-vendors/')
74
+ || path.includes('virtual:$poveste-')
75
+ }
@@ -0,0 +1,12 @@
1
+ import type { UseHeadInput } from 'unhead/types'
2
+ import { createHead, transformHtmlTemplate } from 'unhead/server'
3
+
4
+ export async function applyHeadTransform(html: string, head: UseHeadInput | undefined): Promise<string> {
5
+ if (!head) {
6
+ return html
7
+ }
8
+ // disableDefaults: don't inject unhead's `lang="en"` / viewport tags — Poveste's
9
+ // own HTML template already provides viewport, and we shouldn't override it.
10
+ const instance = createHead({ init: [head], disableDefaults: true })
11
+ return await transformHtmlTemplate(instance, html)
12
+ }
@@ -14,7 +14,7 @@ export const relativePath = ${JSON.stringify(relativePath)}
14
14
  if (import.meta.hot) {
15
15
  import.meta.hot.accept(newModule => {
16
16
  if (newModule) {
17
- window.__hst_md_hmr(newModule)
17
+ window.__pvt_md_hmr(newModule)
18
18
  }
19
19
  })
20
20
  }`
@@ -1 +1,9 @@
1
- export const noop = () => `export default () => {}`
1
+ import type { Context } from '../context.js'
2
+ import { declareEmptySetupFns } from './util.js'
3
+
4
+ export function noop(ctx: Context) {
5
+ return [
6
+ `export default () => {}`,
7
+ declareEmptySetupFns(ctx),
8
+ ].join('\n')
9
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Context } from '../context.js'
2
2
  import { GENERATED_SETUP_CODE } from './index.js'
3
- import { ID_SEPARATOR } from './util.js'
3
+ import { declareEmptySetupFns, getSetupFnNames, ID_SEPARATOR } from './util.js'
4
4
 
5
5
  export function resolvedGeneratedGlobalSetup(ctx: Context) {
6
6
  if (ctx.config.setupCode) {
@@ -10,7 +10,7 @@ export function resolvedGeneratedGlobalSetup(ctx: Context) {
10
10
  // List
11
11
  `const setupList = [${ctx.config.setupCode.map((c, index) => `setup_${index}`).join(', ')}]`,
12
12
  // Setups
13
- ...ctx.supportPlugins.map(p => p.setupFn).flat().map(fnName => `export async function ${fnName} (payload) {
13
+ ...getSetupFnNames(ctx).map(fnName => `export async function ${fnName} (payload) {
14
14
  for (const setup of setupList) {
15
15
  if (setup?.${fnName}) {
16
16
  await setup.${fnName}(payload)
@@ -20,6 +20,6 @@ export function resolvedGeneratedGlobalSetup(ctx: Context) {
20
20
  ].join('\n')
21
21
  }
22
22
  else {
23
- return ''
23
+ return declareEmptySetupFns(ctx)
24
24
  }
25
25
  }
@@ -6,8 +6,8 @@ export function resolvedMarkdownFiles(ctx: Context) {
6
6
  return `import { reactive } from ${process.env.POVESTE_DEV ? `'vue'` : getInjectedImport('@poveste/vendors/vue')}
7
7
  export const markdownFiles = reactive({${filesJs}})
8
8
  if (import.meta.hot) {
9
- if (!window.__hst_md_hmr) {
10
- window.__hst_md_hmr = (newModule) => {
9
+ if (!window.__pvt_md_hmr) {
10
+ window.__pvt_md_hmr = (newModule) => {
11
11
  markdownFiles[newModule.relativePath] = () => newModule
12
12
  }
13
13
  }
@@ -1,5 +1,27 @@
1
+ import type { Context } from '../context.js'
2
+
1
3
  export const ID_SEPARATOR = '__-__'
2
4
 
5
+ /**
6
+ * Names of every setup hook contributed by the enabled support plugins, e.g.
7
+ * `setupVanilla`, `setupVue3`, `setupSvelte4`.
8
+ */
9
+ export function getSetupFnNames(ctx: Context) {
10
+ return [...new Set(ctx.supportPlugins.flatMap(p => p.setupFn))]
11
+ }
12
+
13
+ /**
14
+ * Declares the setup hooks as named exports set to `undefined`.
15
+ *
16
+ * Consumers read them off a namespace import (`generatedSetup.setupVue3`) behind
17
+ * a `typeof === 'function'` guard. Without the declaration Rollup can't resolve
18
+ * the member access statically and warns `"setupVue3" is not exported by ...`
19
+ * in every consumer build.
20
+ */
21
+ export function declareEmptySetupFns(ctx: Context) {
22
+ return getSetupFnNames(ctx).map(fnName => `export const ${fnName} = undefined`).join('\n')
23
+ }
24
+
3
25
  export const PLUGINS_HAVE_DEV = [
4
26
  '@poveste/plugin-vue',
5
27
  ]
@@ -87,7 +87,7 @@ export function createVirtualFilesPlugin(ctx: Context, isServer: boolean) {
87
87
  }
88
88
 
89
89
  if (id === VirtualFiles.NOOP_ID) {
90
- return VirtualFiles.noop()
90
+ return VirtualFiles.noop(ctx)
91
91
  }
92
92
 
93
93
  if (id === VirtualFiles.RESOLVED_CONFIG_ID) {
package/src/node/vite.ts CHANGED
@@ -4,6 +4,7 @@ import type {
4
4
  Plugin as VitePlugin,
5
5
  } from 'vite'
6
6
  import type { Context } from './context.js'
7
+ import { existsSync } from 'node:fs'
7
8
  import { createRequire } from 'node:module'
8
9
  import { lookup as lookupMime } from 'mrmime'
9
10
  import { dirname, join, relative } from 'pathe'
@@ -14,11 +15,17 @@ import {
14
15
  import { APP_PATH, TEMP_PATH } from './alias.js'
15
16
  import { createMarkdownPlugins } from './markdown.js'
16
17
  import { notifyStoryChange } from './stories.js'
18
+ import {
19
+ chromeCssScopePlugin,
20
+ globalStylesPlugin,
21
+ userCssScopePlugin,
22
+ } from './style-isolation/index.js'
23
+ import { applyHeadTransform } from './util/head.js'
17
24
  import { createVirtualFilesPlugin } from './virtual/vite-plugin.js'
18
25
 
19
26
  const require = createRequire(import.meta.url)
20
27
 
21
- export async function mergeHistoireViteConfig(viteConfig: InlineConfig, ctx: Context) {
28
+ export async function mergePovesteViteConfig(viteConfig: InlineConfig, ctx: Context) {
22
29
  if (ctx.config.vite) {
23
30
  const command = ctx.mode === 'dev' ? 'serve' : 'build'
24
31
  const overrides = typeof ctx.config.vite === 'function'
@@ -64,7 +71,7 @@ export async function getViteConfigWithPlugins(isServer: boolean, ctx: Context):
64
71
  const userViteConfigFile = await loadViteConfigFromFile({ command: ctx.mode === 'dev' ? 'serve' : 'build', mode: ctx.mode })
65
72
  const userViteConfig = mergeViteConfig(userViteConfigFile?.config ?? {}, { server: { port: 6006 } })
66
73
 
67
- const inlineConfig = await mergeHistoireViteConfig(userViteConfig, ctx)
74
+ const inlineConfig = await mergePovesteViteConfig(userViteConfig, ctx)
68
75
  const plugins: VitePlugin[] = []
69
76
  const supportPluginAllowPaths = ctx.supportPlugins.flatMap((plugin) => {
70
77
  const paths = [ctx.root, import.meta.url]
@@ -85,14 +92,41 @@ export async function getViteConfigWithPlugins(isServer: boolean, ctx: Context):
85
92
  return Array.from(result)
86
93
  })
87
94
 
95
+ // Mirror how Vite resolves `optimizeDeps.include`: walk `node_modules` up from
96
+ // the project root. Deliberately not `require.resolve(dep, { paths: [root] })`
97
+ // — Node always folds NODE_PATH into resolution even when `paths` is given, and
98
+ // pnpm's bin shims point NODE_PATH at the hidden `.pnpm/node_modules` store, so
99
+ // every package would look reachable from every root. Vite ignores NODE_PATH.
100
+ function isResolvableFromRoot(dep: string): boolean {
101
+ let dir = ctx.root
102
+ while (true) {
103
+ if (existsSync(join(dir, 'node_modules', dep, 'package.json'))) {
104
+ return true
105
+ }
106
+ const parent = dirname(dir)
107
+ if (parent === dir) {
108
+ return false
109
+ }
110
+ dir = parent
111
+ }
112
+ }
113
+
88
114
  function optimizeDeps(deps: string[]): string[] {
89
115
  const result = []
90
116
  for (const dep of deps) {
91
- result.push(dep)
117
+ // The bare specifier is what hoisted installs (npm, yarn, pnpm with
118
+ // `shamefully-hoist`) need — see histoire@a1014ab. Under strict pnpm it is
119
+ // unreachable from the root and Vite only logs "Failed to resolve
120
+ // dependency", so emit it just when the root can actually resolve it.
121
+ if (isResolvableFromRoot(dep)) {
122
+ result.push(dep)
123
+ }
92
124
  try {
125
+ // Absolute path, valid regardless of layout — this is what makes the
126
+ // dep optimizable under strict pnpm.
93
127
  result.push(dirname(require.resolve(`${dep}/package.json`)))
94
128
  }
95
- catch (e) {
129
+ catch {
96
130
  // Noop
97
131
  }
98
132
  }
@@ -124,11 +158,7 @@ export async function getViteConfigWithPlugins(isServer: boolean, ctx: Context):
124
158
  `${APP_PATH}/bundle-sandbox.js`,
125
159
  ],
126
160
  include: optimizeDeps([
127
- 'flexsearch',
128
161
  'shiki',
129
- // Shiki dependencies
130
- 'vscode-oniguruma',
131
- 'vscode-textmate',
132
162
  ]),
133
163
  exclude: [
134
164
  'poveste',
@@ -168,7 +198,7 @@ export async function getViteConfigWithPlugins(isServer: boolean, ctx: Context):
168
198
  : {},
169
199
  '__HST_COLLECT__': isServer,
170
200
  },
171
- cacheDir: isServer ? 'node_modules/.hst-vite-server' : 'node_modules/.hst-vite',
201
+ cacheDir: isServer ? 'node_modules/.pvt-vite-server' : 'node_modules/.pvt-vite',
172
202
  }
173
203
  },
174
204
 
@@ -220,6 +250,7 @@ export async function getViteConfigWithPlugins(isServer: boolean, ctx: Context):
220
250
  <script type="module" src="/@fs/${APP_PATH}/bundle-sandbox${process.env.POVESTE_DEV ? '-dev' : ''}.js"></script>
221
251
  </body>
222
252
  </html>`
253
+ html = await applyHeadTransform(html, ctx.config.head)
223
254
  // Apply Vite HTML transforms. This injects the Vite HMR client, and
224
255
  // also applies HTML transforms from Vite plugins
225
256
  html = await server.transformIndexHtml(req.url, html)
@@ -246,12 +277,14 @@ export async function getViteConfigWithPlugins(isServer: boolean, ctx: Context):
246
277
  <meta name="viewport" content="width=device-width,initial-scale=1">
247
278
  <meta name="description" content="">
248
279
  ${ctx.config.theme?.favicon ? `<link rel="icon" type="${lookupMime(ctx.config.theme.favicon)}" href="${server.config.base}${ctx.config.theme.favicon}"/>` : ''}
280
+ <style>html,body{margin:0;padding:0}</style>
249
281
  </head>
250
282
  <body>
251
283
  <div id="app"></div>
252
284
  <script type="module" src="/@fs/${APP_PATH}/bundle-main${process.env.POVESTE_DEV ? '-dev' : ''}.js"></script>
253
285
  </body>
254
286
  </html>`
287
+ html = await applyHeadTransform(html, ctx.config.head)
255
288
  // Apply Vite HTML transforms. This injects the Vite HMR client, and
256
289
  // also applies HTML transforms from Vite plugins
257
290
  html = await server.transformIndexHtml(req.url, html)
@@ -266,6 +299,21 @@ export async function getViteConfigWithPlugins(isServer: boolean, ctx: Context):
266
299
  })
267
300
 
268
301
  plugins.push(createVirtualFilesPlugin(ctx, isServer))
302
+ plugins.push(globalStylesPlugin({
303
+ files: ctx.config.globalStyles ?? [],
304
+ rootDir: ctx.root,
305
+ }))
306
+
307
+ // Dev-mode isolation. User CSS is wrapped in `@scope (.__poveste-render-story)`
308
+ // so it cannot leak into chrome (popovers, tooltips, dropdowns). The build
309
+ // path emits stronger per-bundle isolation that ALSO keeps the user's
310
+ // teleporting-component styling working inside the sandbox iframe — in dev
311
+ // that case is a known trade-off, see docs/guide/css.md.
312
+ const isolate = ctx.config.isolateStyles !== false
313
+ if (ctx.mode === 'dev' && !isServer) {
314
+ plugins.push(userCssScopePlugin({ enabled: isolate, mode: 'wrap' }))
315
+ plugins.push(chromeCssScopePlugin({ enabled: isolate }))
316
+ }
269
317
 
270
318
  // Replace dev flag
271
319
  const flags = {