orga-build 0.9.0 → 0.10.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 (66) hide show
  1. package/README.org +282 -33
  2. package/cli.js +4 -4
  3. package/lib/__tests__/build.test.js +441 -22
  4. package/lib/__tests__/dev.test.d.ts +2 -0
  5. package/lib/__tests__/dev.test.d.ts.map +1 -0
  6. package/lib/__tests__/dev.test.js +295 -0
  7. package/lib/__tests__/fixtures.d.ts +8 -0
  8. package/lib/__tests__/fixtures.d.ts.map +1 -0
  9. package/lib/__tests__/fixtures.js +61 -0
  10. package/lib/app.jsx +23 -50
  11. package/lib/build.d.ts +4 -1
  12. package/lib/build.d.ts.map +1 -1
  13. package/lib/build.js +10 -175
  14. package/lib/config.d.ts +5 -3
  15. package/lib/config.d.ts.map +1 -1
  16. package/lib/config.js +8 -16
  17. package/lib/content.d.ts +6 -0
  18. package/lib/dev-ssr.d.ts +14 -0
  19. package/lib/dev-ssr.d.ts.map +1 -0
  20. package/lib/dev-ssr.js +137 -0
  21. package/lib/endpoint.d.ts +5 -0
  22. package/lib/endpoint.d.ts.map +1 -1
  23. package/lib/endpoint.js +1 -0
  24. package/lib/files.d.ts +8 -3
  25. package/lib/files.d.ts.map +1 -1
  26. package/lib/files.js +104 -20
  27. package/lib/fs.d.ts +0 -5
  28. package/lib/fs.d.ts.map +1 -1
  29. package/lib/fs.js +0 -18
  30. package/lib/html.d.ts +37 -0
  31. package/lib/html.d.ts.map +1 -0
  32. package/lib/html.js +101 -0
  33. package/lib/index.html +0 -1
  34. package/lib/island-client.d.ts +5 -0
  35. package/lib/island-client.d.ts.map +1 -0
  36. package/lib/island-client.js +41 -0
  37. package/lib/island.d.ts +18 -0
  38. package/lib/island.d.ts.map +1 -0
  39. package/lib/island.js +146 -0
  40. package/lib/island.jsx +108 -0
  41. package/lib/orga.d.ts +1 -1
  42. package/lib/orga.d.ts.map +1 -1
  43. package/lib/orga.js +54 -16
  44. package/lib/plugin.d.ts +20 -32
  45. package/lib/plugin.d.ts.map +1 -1
  46. package/lib/plugin.js +128 -178
  47. package/lib/prerender.d.ts +11 -0
  48. package/lib/prerender.d.ts.map +1 -0
  49. package/lib/prerender.js +184 -0
  50. package/lib/serve.d.ts.map +1 -1
  51. package/lib/serve.js +5 -23
  52. package/lib/ssr.jsx +9 -14
  53. package/lib/util.d.ts +0 -22
  54. package/lib/util.d.ts.map +1 -1
  55. package/lib/util.js +0 -62
  56. package/lib/vite.d.ts +5 -6
  57. package/lib/vite.d.ts.map +1 -1
  58. package/lib/vite.js +74 -23
  59. package/package.json +5 -8
  60. package/lib/components.d.ts +0 -2
  61. package/lib/components.d.ts.map +0 -1
  62. package/lib/components.js +0 -1
  63. package/lib/csr.jsx +0 -11
  64. package/lib/watch.d.ts +0 -10
  65. package/lib/watch.d.ts.map +0 -1
  66. package/lib/watch.js +0 -53
package/lib/index.html CHANGED
@@ -7,6 +7,5 @@
7
7
  </head>
8
8
  <body>
9
9
  <div id="root"></div>
10
- <script type="module" src="/@orga-build/main.js"></script>
11
10
  </body>
12
11
  </html>
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @param {Element} el
3
+ */
4
+ declare function hydrate(el: Element): Promise<void>;
5
+ //# sourceMappingURL=island-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"island-client.d.ts","sourceRoot":"","sources":["island-client.js"],"names":[],"mappings":"AAqBA;;GAEG;AACH,6BAFW,OAAO,iBAkBjB"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Hydrates every `<orga-island>` on the page: imports the module the server
3
+ * referenced and mounts React on the server-rendered markup. React itself is
4
+ * only loaded once an island hydrates, so `client="visible"` islands below the
5
+ * fold cost nothing until scrolled into view.
6
+ */
7
+ for (const el of Array.from(document.querySelectorAll('orga-island'))) {
8
+ // The island has `display: contents` and no box of its own, so visibility
9
+ // is watched on its children; a text-only island has none and hydrates now.
10
+ if (el.getAttribute('client') !== 'visible' || !el.children.length) {
11
+ hydrate(el)
12
+ continue
13
+ }
14
+ const observer = new IntersectionObserver((entries) => {
15
+ if (!entries.some((entry) => entry.isIntersecting)) return
16
+ observer.disconnect()
17
+ hydrate(el)
18
+ })
19
+ for (const child of Array.from(el.children)) observer.observe(child)
20
+ }
21
+
22
+ /**
23
+ * @param {Element} el
24
+ */
25
+ async function hydrate(el) {
26
+ const [{ createElement }, { hydrateRoot }, mod] = await Promise.all([
27
+ import('react'),
28
+ import('react-dom/client'),
29
+ import(
30
+ /* @vite-ignore */
31
+ new URL(/** @type {string} */ (el.getAttribute('src')), document.baseURI)
32
+ .href
33
+ )
34
+ ])
35
+ const Component = mod[/** @type {string} */ (el.getAttribute('export'))]
36
+ const props = JSON.parse(el.getAttribute('props') || '{}')
37
+ hydrateRoot(el, createElement(Component, props), {
38
+ // Same prefix the server rendered this island with, so `useId` matches.
39
+ identifierPrefix: el.getAttribute('prefix') || ''
40
+ })
41
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Islands: a module that starts with React's `'use client'` directive runs in
3
+ * the browser. In the `ssr` environment each component it exports (named in
4
+ * PascalCase, or the default export) is wrapped so it renders inside an
5
+ * `<orga-island>` element (see `island.jsx`); hooks and other exports pass
6
+ * through. In the browser the client runtime imports the real module and
7
+ * hydrates it there. Pages without islands ship no JavaScript.
8
+ *
9
+ * Islands are discovered while the `ssr` environment is transformed, so the
10
+ * `client` environment must be built after it: its `buildStart` emits a chunk
11
+ * per island plus the runtime, and the client manifest maps them to hashed
12
+ * files for prerendering.
13
+ *
14
+ * @returns {import('vite').Plugin}
15
+ */
16
+ export function islandPlugin(): import("vite").Plugin;
17
+ export const clientRuntime: string;
18
+ //# sourceMappingURL=island.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"island.d.ts","sourceRoot":"","sources":["island.js"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;GAcG;AACH,gCAFa,OAAO,MAAM,EAAE,MAAM,CAoFjC;AAvGD,mCAEC"}
package/lib/island.js ADDED
@@ -0,0 +1,146 @@
1
+ import path from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { normalizePath } from 'vite'
4
+
5
+ const serverEntry = fileURLToPath(new URL('./island.jsx', import.meta.url))
6
+ export const clientRuntime = fileURLToPath(
7
+ new URL('./island-client.js', import.meta.url)
8
+ )
9
+ const implQuery = '?orga-island'
10
+ const directive = /^(?:\s|\/\/.*|\/\*[\s\S]*?\*\/)*(['"])use client\1/
11
+
12
+ /**
13
+ * Islands: a module that starts with React's `'use client'` directive runs in
14
+ * the browser. In the `ssr` environment each component it exports (named in
15
+ * PascalCase, or the default export) is wrapped so it renders inside an
16
+ * `<orga-island>` element (see `island.jsx`); hooks and other exports pass
17
+ * through. In the browser the client runtime imports the real module and
18
+ * hydrates it there. Pages without islands ship no JavaScript.
19
+ *
20
+ * Islands are discovered while the `ssr` environment is transformed, so the
21
+ * `client` environment must be built after it: its `buildStart` emits a chunk
22
+ * per island plus the runtime, and the client manifest maps them to hashed
23
+ * files for prerendering.
24
+ *
25
+ * @returns {import('vite').Plugin}
26
+ */
27
+ export function islandPlugin() {
28
+ /** @type {Set<string>} */
29
+ const islands = new Set()
30
+ /** @type {string} */
31
+ let root
32
+ let ssrBuilt = false
33
+ let clientBuiltAfterSsr = false
34
+ /** @type {Set<string>} */
35
+ const ssrAssets = new Set()
36
+
37
+ return {
38
+ name: 'orga-build:islands',
39
+
40
+ api: {
41
+ /** Whether the client build predates island discovery and lacks them. */
42
+ get clientIsStale() {
43
+ return islands.size > 0 && !clientBuiltAfterSsr
44
+ },
45
+ /** Files (relative to the SSR outDir) the SSR build emitted as assets. */
46
+ ssrAssets
47
+ },
48
+
49
+ configResolved(config) {
50
+ root = config.root
51
+ },
52
+
53
+ transform: {
54
+ filter: { code: 'use client' },
55
+ handler(code, id) {
56
+ if (this.environment.name !== 'ssr' || id.includes(implQuery)) return
57
+ if (!directive.test(code)) return
58
+ const src = normalizePath(path.relative(root, id))
59
+ const ast = this.parse(code)
60
+ if (ast.body.some((node) => node.type === 'ExportAllDeclaration')) {
61
+ this.error(`'use client' modules can't use \`export *\``)
62
+ }
63
+ const names = exportNames(ast)
64
+ if (names.some(isComponentName)) islands.add(id)
65
+ const exports = names.map((name) => {
66
+ const binding =
67
+ name === 'default' ? 'export default' : `export const ${name} =`
68
+ const key = JSON.stringify(name)
69
+ return isComponentName(name)
70
+ ? `${binding} island(impl[${key}], ${JSON.stringify(src)}, ${key})`
71
+ : `${binding} impl[${key}]`
72
+ })
73
+ return [
74
+ `import * as impl from ${JSON.stringify(id + implQuery)}`,
75
+ `import { island } from ${JSON.stringify(serverEntry)}`,
76
+ ...exports
77
+ ].join('\n')
78
+ }
79
+ },
80
+
81
+ buildEnd() {
82
+ if (this.environment.name === 'ssr') ssrBuilt = true
83
+ },
84
+
85
+ // Pages live only in the SSR graph, so their images and CSS are emitted
86
+ // there (see `emitAssets`); remember which files, for prerendering to
87
+ // copy into the site. Source maps stay behind. `writeBundle` sees the
88
+ // final bundle, after Vite's CSS plugin has added its file.
89
+ writeBundle(_options, bundle) {
90
+ if (this.environment.name !== 'ssr') return
91
+ ssrAssets.clear()
92
+ for (const output of Object.values(bundle)) {
93
+ if (output.type === 'asset' && !output.fileName.endsWith('.map')) {
94
+ ssrAssets.add(output.fileName)
95
+ }
96
+ }
97
+ },
98
+
99
+ buildStart() {
100
+ if (this.environment.name !== 'client') return
101
+ clientBuiltAfterSsr = ssrBuilt
102
+ if (this.environment.mode !== 'build' || !islands.size) return
103
+ for (const id of [...islands, clientRuntime]) {
104
+ // Vite drops entry exports in app builds; the runtime needs them.
105
+ this.emitFile({ type: 'chunk', id, preserveSignature: 'strict' })
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Components are PascalCase by convention, as React Fast Refresh assumes.
113
+ *
114
+ * @param {string} name
115
+ */
116
+ function isComponentName(name) {
117
+ return name === 'default' || /^[A-Z]/.test(name)
118
+ }
119
+
120
+ /**
121
+ * Names of a module's exports. Destructured exports are skipped.
122
+ *
123
+ * @param {ReturnType<import('vite').Rollup.PluginContext['parse']>} ast
124
+ */
125
+ function exportNames(ast) {
126
+ /** @type {string[]} */
127
+ const names = []
128
+ for (const node of ast.body) {
129
+ if (node.type === 'ExportDefaultDeclaration') names.push('default')
130
+ if (node.type !== 'ExportNamedDeclaration') continue
131
+ const declaration = node.declaration
132
+ if (declaration && 'declarations' in declaration) {
133
+ for (const { id } of declaration.declarations) {
134
+ if (id.type === 'Identifier') names.push(id.name)
135
+ }
136
+ } else if (declaration?.id && 'name' in declaration.id) {
137
+ names.push(declaration.id.name)
138
+ }
139
+ for (const { exported } of node.specifiers) {
140
+ names.push(
141
+ exported.type === 'Identifier' ? exported.name : exported.value
142
+ )
143
+ }
144
+ }
145
+ return names
146
+ }
package/lib/island.jsx ADDED
@@ -0,0 +1,108 @@
1
+ import { createContext, useContext } from 'react'
2
+ import { renderToString } from 'react-dom/server'
3
+
4
+ /** Set while rendering inside an island: nested islands render inline. */
5
+ const InsideIsland = createContext(false)
6
+
7
+ /** @type {(src: string) => string} */
8
+ let resolveUrl = (src) => src
9
+ let islandCount = 0
10
+
11
+ /**
12
+ * Start rendering a page: set how an island's source path (relative to the
13
+ * Vite root) becomes the URL the browser imports it from, and restart the
14
+ * per-page island numbering.
15
+ *
16
+ * @param {(src: string) => string} fn
17
+ */
18
+ export function beginPage(fn) {
19
+ resolveUrl = fn
20
+ islandCount = 0
21
+ }
22
+
23
+ /**
24
+ * Server-side wrapper for a `'use client'` export. Components render inside an
25
+ * `<orga-island>` that tells the client runtime what to import and hydrate;
26
+ * other exports pass through unchanged.
27
+ *
28
+ * Each island is rendered as its own React tree with its own `identifierPrefix`,
29
+ * matching how the browser hydrates it, so `useId` values line up.
30
+ *
31
+ * Props are serialized as JSON, so functions and `children` can't cross to the
32
+ * browser. The `client` prop picks when to hydrate: `load` (default) or
33
+ * `visible`.
34
+ *
35
+ * @template T
36
+ * @param {T} Component
37
+ * @param {string} src - Source path relative to the Vite root
38
+ * @param {string} name - Export name
39
+ * @returns {T}
40
+ */
41
+ export function island(Component, src, name) {
42
+ if (isType(Component, 'react.lazy')) {
43
+ throw new Error(
44
+ `<${name}> from ${src}: a lazy() component can't be an island because it can't be prerendered, use lazy() inside the island instead`
45
+ )
46
+ }
47
+ if (!isComponent(Component)) return Component
48
+ const Island = ({ client = 'load', ...props }) => {
49
+ // An island inside another island is already client code.
50
+ if (useContext(InsideIsland)) return <Component {...props} />
51
+ /** @param {string} key */
52
+ const reject = (key) => {
53
+ throw new Error(
54
+ `<${name}> from ${src} is an island: prop "${key}" can't be sent to the browser, pass JSON-serializable props only`
55
+ )
56
+ }
57
+ if ('children' in props) reject('children')
58
+ const json = JSON.stringify(props, (key, value) =>
59
+ typeof value === 'function' ? reject(key) : value
60
+ )
61
+ const prefix = `island-${islandCount++}-`
62
+ // Render from the serialized props: the browser sees exactly these.
63
+ const html = renderToString(
64
+ <InsideIsland value={true}>
65
+ <Component {...JSON.parse(json)} />
66
+ </InsideIsland>,
67
+ { identifierPrefix: prefix }
68
+ )
69
+ return (
70
+ <orga-island
71
+ src={resolveUrl(src)}
72
+ export={name}
73
+ props={json}
74
+ prefix={prefix}
75
+ client={client}
76
+ style={{ display: 'contents' }}
77
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: markup React just rendered for this island
78
+ dangerouslySetInnerHTML={{ __html: html }}
79
+ />
80
+ )
81
+ }
82
+ return /** @type {T} */ (/** @type {unknown} */ (Island))
83
+ }
84
+
85
+ /**
86
+ * @param {unknown} value
87
+ * @param {string} type - A `$$typeof` symbol description, e.g. `react.memo`
88
+ */
89
+ function isType(value, type) {
90
+ return (
91
+ typeof value === 'object' &&
92
+ value !== null &&
93
+ /** @type {any} */ (value).$$typeof === Symbol.for(type)
94
+ )
95
+ }
96
+
97
+ /**
98
+ * `memo()` and `forwardRef()` components are objects, not functions.
99
+ *
100
+ * @param {unknown} value
101
+ */
102
+ function isComponent(value) {
103
+ return (
104
+ typeof value === 'function' ||
105
+ isType(value, 'react.memo') ||
106
+ isType(value, 'react.forward_ref')
107
+ )
108
+ }
package/lib/orga.d.ts CHANGED
@@ -8,5 +8,5 @@ export function setupOrga({ containerClass, root, rehypePlugins }: {
8
8
  containerClass: string | string[];
9
9
  root: string;
10
10
  rehypePlugins?: import("unified").PluggableList | undefined;
11
- }): import("@orgajs/rollup").Plugin;
11
+ }): (import("vite").Plugin<any> | import("@orgajs/rollup").Plugin)[];
12
12
  //# sourceMappingURL=orga.d.ts.map
package/lib/orga.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"orga.d.ts","sourceRoot":"","sources":["orga.js"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,mEAJG;IAAiC,cAAc,EAAvC,MAAM,GAAC,MAAM,EAAE;IACC,IAAI,EAApB,MAAM;IACoC,aAAa;CACjE,mCAUA"}
1
+ {"version":3,"file":"orga.d.ts","sourceRoot":"","sources":["orga.js"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,mEAJG;IAAiC,cAAc,EAAvC,MAAM,GAAC,MAAM,EAAE;IACC,IAAI,EAApB,MAAM;IACoC,aAAa;CACjE,oEAsBA"}
package/lib/orga.js CHANGED
@@ -13,14 +13,26 @@ import { getSlugFromContentFilePath } from './files.js'
13
13
  * @param {import('unified').PluggableList} [options.rehypePlugins] - Extra rehype plugins appended to defaults
14
14
  */
15
15
  export function setupOrga({ containerClass, root, rehypePlugins = [] }) {
16
- return _orga({
17
- rehypePlugins: [
18
- [rehypeWrap, { className: containerClass }],
19
- [rewriteOrgFileLinks, { root }],
20
- mediaAssets,
21
- ...rehypePlugins
22
- ]
23
- })
16
+ /** Vite's `base`, set once the config resolves. */
17
+ const site = { base: '/' }
18
+ /** @type {import('vite').Plugin} */
19
+ const base = {
20
+ name: 'orga-build:base',
21
+ configResolved(config) {
22
+ site.base = config.base
23
+ }
24
+ }
25
+ return [
26
+ base,
27
+ _orga({
28
+ rehypePlugins: [
29
+ [rehypeWrap, { className: containerClass }],
30
+ [rewriteOrgFileLinks, { root, site }],
31
+ mediaAssets,
32
+ ...rehypePlugins
33
+ ]
34
+ })
35
+ ]
24
36
  }
25
37
 
26
38
  // --- plugins ---
@@ -33,16 +45,19 @@ function mediaAssets() {
33
45
  /** @type {Record<string, string>} */
34
46
  const imports = {}
35
47
  visitParents(tree, [{ tagName: 'img' }, { tagName: 'video' }], (node) => {
36
- node.type = 'jsx'
37
48
  const { src, ...rest } = node.properties
38
49
  if (typeof src !== 'string') return
39
- if (src.startsWith('http')) return
50
+ // A scheme (`https:`, `data:`) or `//host` means it isn't a local file.
51
+ if (/^([a-z][a-z\d+.-]*:|\/\/)/i.test(src)) return
52
+ node.type = 'jsx'
40
53
  const tagName = node.tagName
41
54
  if (!imports[src]) imports[src] = `asset_${genId()}`
42
55
  const name = imports[src]
43
56
  const attrs = Object.entries(rest)
44
57
  .filter(([, v]) => v !== undefined && v !== false)
45
- .map(([k, v]) => (v === true ? k : `${k}='${v}'`))
58
+ .map(([k, v]) =>
59
+ v === true ? k : `${k}={${JSON.stringify(String(v))}}`
60
+ )
46
61
  .join(' ')
47
62
  node.value = `<${tagName} src={${name}}${attrs ? ` ${attrs}` : ''}/>`
48
63
  })
@@ -50,7 +65,7 @@ function mediaAssets() {
50
65
  for (const [src, name] of Object.entries(imports)) {
51
66
  tree.children.unshift({
52
67
  type: 'jsx',
53
- value: `import ${name} from '${src}'`,
68
+ value: `import ${name} from ${JSON.stringify(src)}`,
54
69
  children: []
55
70
  })
56
71
  }
@@ -89,10 +104,13 @@ function rehypeWrap({ className = [] }) {
89
104
  }
90
105
 
91
106
  /**
107
+ * Point links to `.org` files at their pages, keeping any `#fragment`.
108
+ *
92
109
  * @param {Object} options
93
110
  * @param {string} options.root
111
+ * @param {{ base: string }} options.site
94
112
  */
95
- function rewriteOrgFileLinks({ root }) {
113
+ function rewriteOrgFileLinks({ root, site }) {
96
114
  /**
97
115
  * @param {any} tree
98
116
  * @param {import('vfile').VFile} [file]
@@ -104,16 +122,36 @@ function rewriteOrgFileLinks({ root }) {
104
122
  visitParents(tree, { tagName: 'a' }, (node) => {
105
123
  const href = node?.properties?.href
106
124
  if (typeof href !== 'string') return
107
- if (!href.endsWith('.org')) return
125
+ if (/^[a-z][a-z\d+.-]*:/i.test(href)) return
126
+ const hashIndex = href.indexOf('#')
127
+ const target = hashIndex === -1 ? href : href.slice(0, hashIndex)
128
+ const hash = hashIndex === -1 ? '' : href.slice(hashIndex)
129
+ if (!target.endsWith('.org')) return
108
130
 
109
131
  const targetSlug = resolveOrgHrefToContentSlug({
110
132
  root,
111
133
  filePath,
112
- href
134
+ href: target
113
135
  })
114
136
  if (!targetSlug) return
115
- node.properties.href = targetSlug
137
+ node.properties.href = pageUrl(targetSlug) + hash
116
138
  })
139
+
140
+ /**
141
+ * URL of the page at `slug`. With a relative `base`, it is relative to
142
+ * this page's directory, as pages are written to `<slug>/index.html`.
143
+ * @param {string} slug
144
+ */
145
+ function pageUrl(slug) {
146
+ const { base } = site
147
+ if ((base === './' || base === '') && filePath) {
148
+ const depth = getSlugFromContentFilePath(path.relative(root, filePath))
149
+ .split('/')
150
+ .filter(Boolean).length
151
+ return (depth ? '../'.repeat(depth) : './') + slug.slice(1)
152
+ }
153
+ return base.replace(/\/$/, '') + slug
154
+ }
117
155
  }
118
156
  }
119
157
 
package/lib/plugin.d.ts CHANGED
@@ -1,60 +1,44 @@
1
1
  /**
2
2
  * @typedef {Object} OrgaBuildPluginOptions
3
3
  * @property {string} root - Root directory for content files
4
- * @property {string | undefined} [outDir] - Output directory (excluded from file discovery)
4
+ * @property {string | undefined} [outDir] - Output directory (default: Vite's `build.outDir`)
5
5
  * @property {string|string[]} [containerClass] - CSS class(es) to wrap rendered content
6
- * @property {string[]} [styles] - Global stylesheet URLs to import/inject
6
+ * @property {string[]} [styles] - Global stylesheet URLs to link from the HTML shell
7
7
  * @property {import('unified').PluggableList} [rehypePlugins] - Extra rehype plugins appended to orga-build defaults
8
8
  * @property {string[]} [exclude] - Glob patterns for files to exclude from content scanning
9
+ * @property {string | undefined} [site] - Absolute URL the site is served from, e.g. `https://example.com`
9
10
  */
10
11
  /**
11
- * Creates the canonical orga-build plugin preset.
12
- * This is the single composition path used by both dev and build.
12
+ * Creates the orga-build plugin preset: everything needed for `vite` (dev SSR)
13
+ * and `vite build` (static site) to work, used by the CLI and usable directly
14
+ * in a `vite.config.js`.
13
15
  *
14
16
  * @param {OrgaBuildPluginOptions} options
15
17
  * @returns {import('vite').PluginOption[]}
16
18
  */
17
- export function orgaBuildPlugin({ root, outDir, containerClass, styles, rehypePlugins, exclude }: OrgaBuildPluginOptions): import("vite").PluginOption[];
19
+ export function orgaBuildPlugin({ root, outDir, containerClass, styles, rehypePlugins, exclude, site }: OrgaBuildPluginOptions): import("vite").PluginOption[];
18
20
  /**
19
21
  * Creates the full Vite config options for orga-build.
20
- * Includes plugins, resolve aliases, and other shared config.
21
22
  *
22
- * @param {OrgaBuildPluginOptions & { outDir?: string, vitePlugins?: import('vite').PluginOption[], includeFallbackHtml?: boolean, projectRoot?: string }} options
23
- * @returns {{ plugins: import('vite').PluginOption[], resolve: { alias: typeof alias } }}
23
+ * @param {OrgaBuildPluginOptions & { vitePlugins?: import('vite').PluginOption[] }} options
24
+ * @returns {{ plugins: import('vite').PluginOption[] }}
24
25
  */
25
- export function createOrgaBuildConfig({ root, outDir, containerClass, styles, rehypePlugins, vitePlugins, includeFallbackHtml, projectRoot, exclude }: OrgaBuildPluginOptions & {
26
- outDir?: string;
26
+ export function createOrgaBuildConfig({ vitePlugins, ...options }: OrgaBuildPluginOptions & {
27
27
  vitePlugins?: import("vite").PluginOption[];
28
- includeFallbackHtml?: boolean;
29
- projectRoot?: string;
30
28
  }): {
31
29
  plugins: import("vite").PluginOption[];
32
- resolve: {
33
- alias: typeof alias;
34
- };
35
30
  };
36
31
  /**
37
- * Creates an HTML serving plugin that handles index.html for dev mode.
38
- *
39
- * This plugin performs per-request SSR in dev mode (matching Astro/SvelteKit behaviour):
40
- * - SSR-renders each page on every request using Vite's server module runner
41
- * - Injects rendered content and page metadata (%orga.*% placeholders) into the template
42
- * - Falls back to the shell HTML for unknown routes (client-side router handles 404)
43
- * - Only handles GET/HEAD requests that accept HTML
44
- * - Does not intercept asset requests
45
- *
46
- * @param {string} projectRoot - Project root directory (where orga.config.js lives)
47
- * @param {string[]} [styles]
48
- * @returns {import('vite').Plugin}
32
+ * Prefix of emitted asset paths in server-rendered markup, replaced per page
33
+ * while prerendering (see `prerender.js`).
49
34
  */
50
- export function htmlFallbackPlugin(projectRoot: string, styles?: string[]): import("vite").Plugin;
35
+ export const assetUrlMarker: "/@orga-build/asset/";
51
36
  /**
52
- * Alias map for React and wouter to ensure consistent resolution
37
+ * Alias map for React to ensure a single copy is bundled
53
38
  */
54
39
  export const alias: {
55
40
  react: string;
56
41
  'react-dom': string;
57
- wouter: string;
58
42
  };
59
43
  export type OrgaBuildPluginOptions = {
60
44
  /**
@@ -62,7 +46,7 @@ export type OrgaBuildPluginOptions = {
62
46
  */
63
47
  root: string;
64
48
  /**
65
- * - Output directory (excluded from file discovery)
49
+ * - Output directory (default: Vite's `build.outDir`)
66
50
  */
67
51
  outDir?: string | undefined;
68
52
  /**
@@ -70,7 +54,7 @@ export type OrgaBuildPluginOptions = {
70
54
  */
71
55
  containerClass?: string | string[];
72
56
  /**
73
- * - Global stylesheet URLs to import/inject
57
+ * - Global stylesheet URLs to link from the HTML shell
74
58
  */
75
59
  styles?: string[];
76
60
  /**
@@ -81,5 +65,9 @@ export type OrgaBuildPluginOptions = {
81
65
  * - Glob patterns for files to exclude from content scanning
82
66
  */
83
67
  exclude?: string[];
68
+ /**
69
+ * - Absolute URL the site is served from, e.g. `https://example.com`
70
+ */
71
+ site?: string | undefined;
84
72
  };
85
73
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["plugin.js"],"names":[],"mappings":"AAwBA;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,kGAHW,sBAAsB,GACpB,OAAO,MAAM,EAAE,YAAY,EAAE,CAezC;AAED;;;;;;GAMG;AACH,uJAHW,sBAAsB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5I;IAAE,OAAO,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,KAAK,CAAA;KAAE,CAAA;CAAE,CAiCxF;AAiBD;;;;;;;;;;;;;GAaG;AACH,gDAJW,MAAM,WACN,MAAM,EAAE,GACN,OAAO,MAAM,EAAE,MAAM,CAsHjC;AAlOD;;GAEG;AACH;;;;EAIC;;;;;UAIa,MAAM;;;;aACN,MAAM,GAAG,SAAS;;;;qBAClB,MAAM,GAAC,MAAM,EAAE;;;;aACf,MAAM,EAAE;;;;oBACR,OAAO,SAAS,EAAE,aAAa;;;;cAC/B,MAAM,EAAE"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["plugin.js"],"names":[],"mappings":"AA2BA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH,wGAHW,sBAAsB,GACpB,OAAO,MAAM,EAAE,YAAY,EAAE,CAyBzC;AAkBD;;;;;GAKG;AACH,mEAHW,sBAAsB,GAAG;IAAE,WAAW,CAAC,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,CAAA;CAAE,GACtE;IAAE,OAAO,EAAE,OAAO,MAAM,EAAE,YAAY,EAAE,CAAA;CAAE,CAMtD;AArFD;;;GAGG;AACH,6BAA8B,qBAAqB,CAAA;AAGnD;;GAEG;AACH;;;EAGC;;;;;UAIa,MAAM;;;;aACN,MAAM,GAAG,SAAS;;;;qBAClB,MAAM,GAAC,MAAM,EAAE;;;;aACf,MAAM,EAAE;;;;oBACR,OAAO,SAAS,EAAE,aAAa;;;;cAC/B,MAAM,EAAE;;;;WACR,MAAM,GAAG,SAAS"}