orga-build 0.8.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.
- package/README.org +296 -41
- package/cli.js +4 -4
- package/lib/__tests__/build.test.js +444 -23
- package/lib/__tests__/dev.test.d.ts +2 -0
- package/lib/__tests__/dev.test.d.ts.map +1 -0
- package/lib/__tests__/dev.test.js +295 -0
- package/lib/__tests__/fixtures.d.ts +8 -0
- package/lib/__tests__/fixtures.d.ts.map +1 -0
- package/lib/__tests__/fixtures.js +61 -0
- package/lib/app.jsx +23 -50
- package/lib/build.d.ts +4 -1
- package/lib/build.d.ts.map +1 -1
- package/lib/build.js +12 -176
- package/lib/config.d.ts +9 -3
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +14 -18
- package/lib/content.d.ts +6 -0
- package/lib/dev-ssr.d.ts +14 -0
- package/lib/dev-ssr.d.ts.map +1 -0
- package/lib/dev-ssr.js +137 -0
- package/lib/endpoint.d.ts +5 -0
- package/lib/endpoint.d.ts.map +1 -1
- package/lib/endpoint.js +1 -0
- package/lib/files.d.ts +10 -3
- package/lib/files.d.ts.map +1 -1
- package/lib/files.js +111 -23
- package/lib/fs.d.ts +0 -5
- package/lib/fs.d.ts.map +1 -1
- package/lib/fs.js +0 -18
- package/lib/html.d.ts +37 -0
- package/lib/html.d.ts.map +1 -0
- package/lib/html.js +101 -0
- package/lib/index.html +0 -1
- package/lib/island-client.d.ts +5 -0
- package/lib/island-client.d.ts.map +1 -0
- package/lib/island-client.js +41 -0
- package/lib/island.d.ts +18 -0
- package/lib/island.d.ts.map +1 -0
- package/lib/island.js +146 -0
- package/lib/island.jsx +108 -0
- package/lib/orga.d.ts +1 -1
- package/lib/orga.d.ts.map +1 -1
- package/lib/orga.js +54 -16
- package/lib/plugin.d.ts +25 -32
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +130 -170
- package/lib/prerender.d.ts +11 -0
- package/lib/prerender.d.ts.map +1 -0
- package/lib/prerender.js +184 -0
- package/lib/serve.d.ts.map +1 -1
- package/lib/serve.js +5 -23
- package/lib/ssr.jsx +9 -14
- package/lib/util.d.ts +0 -22
- package/lib/util.d.ts.map +1 -1
- package/lib/util.js +0 -62
- package/lib/vite.d.ts +7 -6
- package/lib/vite.d.ts.map +1 -1
- package/lib/vite.js +75 -23
- package/package.json +5 -8
- package/lib/components.d.ts +0 -2
- package/lib/components.d.ts.map +0 -1
- package/lib/components.js +0 -1
- package/lib/csr.jsx +0 -11
- package/lib/watch.d.ts +0 -10
- package/lib/watch.d.ts.map +0 -1
- package/lib/watch.js +0 -53
|
@@ -3,12 +3,33 @@ import fs from 'node:fs/promises'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { after, before, describe, test } from 'node:test'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { createBuilder } from 'vite'
|
|
6
7
|
import { build } from '../build.js'
|
|
8
|
+
import { renderPageHtml } from '../html.js'
|
|
9
|
+
import { orgaBuildPlugin } from '../plugin.js'
|
|
10
|
+
import { writeIslandFixture } from './fixtures.js'
|
|
7
11
|
|
|
8
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
13
|
const fixtureDir = path.join(__dirname, 'fixtures')
|
|
10
14
|
const outDir = path.join(__dirname, '.test-output')
|
|
11
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Build `dir` with plain Vite and only `orgaBuildPlugin`, as a
|
|
18
|
+
* `vite.config.js` would, without the orga-build CLI.
|
|
19
|
+
* @param {string} dir
|
|
20
|
+
* @param {import('vite').InlineConfig} [config]
|
|
21
|
+
*/
|
|
22
|
+
async function viteBuild(dir, config = {}) {
|
|
23
|
+
const builder = await createBuilder({
|
|
24
|
+
root: dir,
|
|
25
|
+
configFile: false,
|
|
26
|
+
logLevel: 'silent',
|
|
27
|
+
plugins: orgaBuildPlugin({ root: dir }),
|
|
28
|
+
...config
|
|
29
|
+
})
|
|
30
|
+
await builder.buildApp()
|
|
31
|
+
}
|
|
32
|
+
|
|
12
33
|
function markCodeBlocks() {
|
|
13
34
|
/**
|
|
14
35
|
* @param {any} tree
|
|
@@ -41,6 +62,8 @@ Here's [[file:./docs/index.org][index page]].
|
|
|
41
62
|
|
|
42
63
|
Here's [[file:more.org][another page]].
|
|
43
64
|
|
|
65
|
+
Jump to [[*Hello World][the top]], [[file:more.org::*Some Heading][a heading]] or [[file:./docs/index.org::#cid][an anchor]].
|
|
66
|
+
|
|
44
67
|
Here's [[mailto:hi@unclex.net][send me an email]].
|
|
45
68
|
`
|
|
46
69
|
)
|
|
@@ -78,9 +101,7 @@ export function GET() {
|
|
|
78
101
|
root: fixtureDir,
|
|
79
102
|
outDir: outDir,
|
|
80
103
|
containerClass: [],
|
|
81
|
-
vitePlugins: []
|
|
82
|
-
preBuild: [],
|
|
83
|
-
postBuild: []
|
|
104
|
+
vitePlugins: []
|
|
84
105
|
})
|
|
85
106
|
|
|
86
107
|
// Check output exists
|
|
@@ -100,30 +121,98 @@ export function GET() {
|
|
|
100
121
|
'should rewrite docs/index.org to /docs'
|
|
101
122
|
)
|
|
102
123
|
assert.ok(html.includes('href="/more"'), 'should rewrite more.org to /more')
|
|
124
|
+
assert.ok(html.includes('<h1 id="hello-world">'), 'should id headings')
|
|
125
|
+
assert.ok(html.includes('href="#hello-world"'), 'should link in page')
|
|
126
|
+
assert.ok(
|
|
127
|
+
html.includes('href="/more#some-heading"'),
|
|
128
|
+
'should keep heading search as fragment'
|
|
129
|
+
)
|
|
130
|
+
assert.ok(
|
|
131
|
+
html.includes('href="/docs#cid"'),
|
|
132
|
+
'should keep custom-id search as fragment'
|
|
133
|
+
)
|
|
103
134
|
assert.ok(
|
|
104
135
|
html.includes('href="mailto:hi@unclex.net"'),
|
|
105
136
|
'should keep mailto protocol in href'
|
|
106
137
|
)
|
|
107
138
|
})
|
|
108
139
|
|
|
109
|
-
test('
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
test('ships no JavaScript without islands', async () => {
|
|
141
|
+
const html = await fs.readFile(path.join(outDir, 'index.html'), 'utf-8')
|
|
142
|
+
assert.ok(!html.includes('<script'), html)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test("hydrates 'use client' components as islands", async () => {
|
|
146
|
+
const dir = path.join(__dirname, 'fixtures-islands')
|
|
147
|
+
try {
|
|
148
|
+
await writeIslandFixture(dir)
|
|
149
|
+
await viteBuild(dir)
|
|
150
|
+
|
|
151
|
+
const html = await fs.readFile(
|
|
152
|
+
path.join(dir, 'dist', 'index.html'),
|
|
153
|
+
'utf-8'
|
|
154
|
+
)
|
|
155
|
+
const island = html.match(/<orga-island ([^>]+)>/)?.[1] ?? ''
|
|
156
|
+
const islandSrc = island.match(/src="([^"]+)"/)?.[1] ?? ''
|
|
157
|
+
assert.match(islandSrc, /^\/assets\/.+\.js$/)
|
|
158
|
+
assert.match(island, /export="Counter"/)
|
|
159
|
+
assert.match(island, /props="{"start":1}"/)
|
|
160
|
+
assert.match(island, /client="load"/)
|
|
161
|
+
assert.match(island, /prefix="island-0-"/)
|
|
162
|
+
assert.match(
|
|
163
|
+
html,
|
|
164
|
+
/<button id="[^"]*island-0-[^"]*">1<\/button>/,
|
|
165
|
+
'island is prerendered with its own useId prefix'
|
|
166
|
+
)
|
|
167
|
+
const chunk = await fs.readFile(
|
|
168
|
+
path.join(dir, 'dist', islandSrc),
|
|
169
|
+
'utf-8'
|
|
170
|
+
)
|
|
171
|
+
assert.match(chunk, /\bCounter\b/, 'island chunk keeps its exports')
|
|
172
|
+
|
|
173
|
+
const script =
|
|
174
|
+
html.match(/<script type="module" src="([^"]*island[^"]*\.js)"/)?.[1] ??
|
|
175
|
+
''
|
|
176
|
+
assert.match(script, /^\/assets\//, html)
|
|
177
|
+
await fs.access(path.join(dir, 'dist', script))
|
|
178
|
+
|
|
179
|
+
const nested = await fs.readFile(
|
|
180
|
+
path.join(dir, 'dist', 'docs', 'index.html'),
|
|
181
|
+
'utf-8'
|
|
182
|
+
)
|
|
183
|
+
assert.match(nested, /<orga-island [^>]*client="visible"/)
|
|
184
|
+
assert.match(
|
|
185
|
+
nested,
|
|
186
|
+
/<orga-island [^>]*export="MemoCounter" props="{"start":3,"meta":{"children":\["a"\]}}"/,
|
|
187
|
+
'nested `children` keys are data; undefined is dropped'
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
const plain = await fs.readFile(
|
|
191
|
+
path.join(dir, 'dist', 'plain', 'index.html'),
|
|
192
|
+
'utf-8'
|
|
193
|
+
)
|
|
194
|
+
assert.ok(!plain.includes('<script'), 'pages without islands stay static')
|
|
195
|
+
|
|
196
|
+
const page = await fs.readFile(
|
|
197
|
+
path.join(dir, 'dist', 'page', 'index.html'),
|
|
198
|
+
'utf-8'
|
|
199
|
+
)
|
|
200
|
+
assert.match(page, /<title>Page<\/title>/)
|
|
201
|
+
assert.match(page, /<orga-island [^>]*export="default" props="{}"/)
|
|
202
|
+
} finally {
|
|
203
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
204
|
+
}
|
|
116
205
|
})
|
|
117
206
|
|
|
118
207
|
test('processes configured global styles through vite and injects built css', async () => {
|
|
208
|
+
const styleUrl =
|
|
209
|
+
'/' + path.relative(process.cwd(), path.join(fixtureDir, 'style.css'))
|
|
119
210
|
await build({
|
|
120
211
|
root: fixtureDir,
|
|
121
212
|
outDir: outDir,
|
|
122
213
|
containerClass: [],
|
|
123
|
-
styles: [
|
|
124
|
-
vitePlugins: []
|
|
125
|
-
preBuild: [],
|
|
126
|
-
postBuild: []
|
|
214
|
+
styles: [styleUrl],
|
|
215
|
+
vitePlugins: []
|
|
127
216
|
})
|
|
128
217
|
|
|
129
218
|
const html = await fs.readFile(path.join(outDir, 'index.html'), 'utf-8')
|
|
@@ -164,9 +253,7 @@ This page verifies custom rehype plugins.`
|
|
|
164
253
|
outDir: outDirRehype,
|
|
165
254
|
containerClass: [],
|
|
166
255
|
rehypePlugins: [markCodeBlocks],
|
|
167
|
-
vitePlugins: []
|
|
168
|
-
preBuild: [],
|
|
169
|
-
postBuild: []
|
|
256
|
+
vitePlugins: []
|
|
170
257
|
})
|
|
171
258
|
|
|
172
259
|
const html = await fs.readFile(
|
|
@@ -188,9 +275,7 @@ This page verifies custom rehype plugins.`
|
|
|
188
275
|
root: fixtureDir,
|
|
189
276
|
outDir: outDir,
|
|
190
277
|
containerClass: [],
|
|
191
|
-
vitePlugins: []
|
|
192
|
-
preBuild: [],
|
|
193
|
-
postBuild: []
|
|
278
|
+
vitePlugins: []
|
|
194
279
|
})
|
|
195
280
|
|
|
196
281
|
const rss = await fs.readFile(path.join(outDir, 'rss.xml'), 'utf-8')
|
|
@@ -217,9 +302,7 @@ This page verifies custom rehype plugins.`
|
|
|
217
302
|
root: fixtureDirConflict,
|
|
218
303
|
outDir: outDirConflict,
|
|
219
304
|
containerClass: [],
|
|
220
|
-
vitePlugins: []
|
|
221
|
-
preBuild: [],
|
|
222
|
-
postBuild: []
|
|
305
|
+
vitePlugins: []
|
|
223
306
|
}),
|
|
224
307
|
/Route conflict detected/
|
|
225
308
|
)
|
|
@@ -228,4 +311,342 @@ This page verifies custom rehype plugins.`
|
|
|
228
311
|
await fs.rm(fixtureDirConflict, { recursive: true, force: true })
|
|
229
312
|
}
|
|
230
313
|
})
|
|
314
|
+
|
|
315
|
+
test("excludes Vite's output directory from content discovery", async () => {
|
|
316
|
+
const dir = path.join(__dirname, 'fixtures-vite-outdir')
|
|
317
|
+
// Dotted chunk names would make bundles look like endpoint routes.
|
|
318
|
+
const buildOnce = () =>
|
|
319
|
+
viteBuild(dir, {
|
|
320
|
+
build: {
|
|
321
|
+
rolldownOptions: {
|
|
322
|
+
output: { entryFileNames: 'assets/[name].[hash].js' }
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
|
+
try {
|
|
327
|
+
await fs.mkdir(dir, { recursive: true })
|
|
328
|
+
await fs.writeFile(path.join(dir, 'index.org'), '#+title: Home\n\nHome')
|
|
329
|
+
|
|
330
|
+
await buildOnce()
|
|
331
|
+
// The second build sees the first build's output inside the root.
|
|
332
|
+
await buildOnce()
|
|
333
|
+
|
|
334
|
+
const html = await fs.readFile(
|
|
335
|
+
path.join(dir, 'dist', 'index.html'),
|
|
336
|
+
'utf-8'
|
|
337
|
+
)
|
|
338
|
+
assert.ok(html.includes('<title>Home</title>'))
|
|
339
|
+
} finally {
|
|
340
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
test('leaves draft pages out of the build', async () => {
|
|
345
|
+
const dir = path.join(__dirname, 'fixtures-drafts')
|
|
346
|
+
try {
|
|
347
|
+
await fs.mkdir(dir, { recursive: true })
|
|
348
|
+
await fs.writeFile(path.join(dir, 'index.org'), 'Home')
|
|
349
|
+
await fs.writeFile(path.join(dir, 'draft.org'), '#+draft: t\n\nWIP')
|
|
350
|
+
await fs.writeFile(
|
|
351
|
+
path.join(dir, 'published.org'),
|
|
352
|
+
'#+draft: false\n\nDone'
|
|
353
|
+
)
|
|
354
|
+
await fs.writeFile(
|
|
355
|
+
path.join(dir, 'pages.json.ts'),
|
|
356
|
+
`import { getPages } from 'orga-build:content'
|
|
357
|
+
export function GET() {
|
|
358
|
+
return Response.json(getPages().map((page) => page.slug).sort())
|
|
359
|
+
}
|
|
360
|
+
`
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
await viteBuild(dir)
|
|
364
|
+
|
|
365
|
+
const out = path.join(dir, 'dist')
|
|
366
|
+
const slugs = JSON.parse(
|
|
367
|
+
await fs.readFile(path.join(out, 'pages.json'), 'utf-8')
|
|
368
|
+
)
|
|
369
|
+
assert.deepEqual(slugs, ['/', '/published'])
|
|
370
|
+
await fs.access(path.join(out, 'published', 'index.html'))
|
|
371
|
+
await assert.rejects(fs.access(path.join(out, 'draft', 'index.html')))
|
|
372
|
+
} finally {
|
|
373
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
test('reads TSX page metadata and exposes site', async () => {
|
|
378
|
+
const dir = path.join(__dirname, 'fixtures-metadata')
|
|
379
|
+
try {
|
|
380
|
+
await fs.mkdir(dir, { recursive: true })
|
|
381
|
+
await fs.writeFile(path.join(dir, 'post.org'), '#+title: Org post\n')
|
|
382
|
+
await fs.writeFile(
|
|
383
|
+
path.join(dir, 'page.tsx'),
|
|
384
|
+
`import { site } from 'orga-build:content'
|
|
385
|
+
export const title = 'TSX page'
|
|
386
|
+
export const tags = ['a', 'b'] as const
|
|
387
|
+
export const order = -1
|
|
388
|
+
export const render = () => title
|
|
389
|
+
export default function Page() {
|
|
390
|
+
return <a href={site + '/page'}>link</a>
|
|
391
|
+
}
|
|
392
|
+
`
|
|
393
|
+
)
|
|
394
|
+
await fs.writeFile(
|
|
395
|
+
path.join(dir, 'draft.jsx'),
|
|
396
|
+
'export const draft = true\nexport default () => <p />\n'
|
|
397
|
+
)
|
|
398
|
+
await fs.writeFile(
|
|
399
|
+
path.join(dir, 'pages.json.ts'),
|
|
400
|
+
`import { getPages, site } from 'orga-build:content'
|
|
401
|
+
export function GET(ctx) {
|
|
402
|
+
return Response.json({
|
|
403
|
+
site,
|
|
404
|
+
ctxSite: ctx.site,
|
|
405
|
+
url: ctx.url.href,
|
|
406
|
+
pages: getPages()
|
|
407
|
+
.map(({ slug, data }) => ({ slug, data }))
|
|
408
|
+
.sort((a, b) => a.slug.localeCompare(b.slug))
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
`
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
await viteBuild(dir, {
|
|
415
|
+
plugins: orgaBuildPlugin({
|
|
416
|
+
root: dir,
|
|
417
|
+
site: 'https://example.com/blog/'
|
|
418
|
+
})
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
const out = path.join(dir, 'dist')
|
|
422
|
+
const json = JSON.parse(
|
|
423
|
+
await fs.readFile(path.join(out, 'pages.json'), 'utf-8')
|
|
424
|
+
)
|
|
425
|
+
assert.deepEqual(json, {
|
|
426
|
+
site: 'https://example.com/blog',
|
|
427
|
+
ctxSite: 'https://example.com/blog',
|
|
428
|
+
url: 'https://example.com/blog/pages.json',
|
|
429
|
+
pages: [
|
|
430
|
+
{
|
|
431
|
+
slug: '/page',
|
|
432
|
+
data: { title: 'TSX page', tags: ['a', 'b'], order: -1 }
|
|
433
|
+
},
|
|
434
|
+
{ slug: '/post', data: { title: 'Org post' } }
|
|
435
|
+
]
|
|
436
|
+
})
|
|
437
|
+
const html = await fs.readFile(
|
|
438
|
+
path.join(out, 'page', 'index.html'),
|
|
439
|
+
'utf-8'
|
|
440
|
+
)
|
|
441
|
+
assert.ok(html.includes('href="https://example.com/blog/page"'), html)
|
|
442
|
+
for (const site of ['example.com', 'localhost:3000']) {
|
|
443
|
+
assert.throws(
|
|
444
|
+
() => orgaBuildPlugin({ root: dir, site }),
|
|
445
|
+
/"site" must be an absolute http\(s\) URL/
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
} finally {
|
|
449
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
450
|
+
}
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
test('prerenders after a configured builder.buildApp', async () => {
|
|
454
|
+
const dir = path.join(__dirname, 'fixtures-vite-builder')
|
|
455
|
+
try {
|
|
456
|
+
await writeIslandFixture(dir)
|
|
457
|
+
|
|
458
|
+
await viteBuild(dir, {
|
|
459
|
+
builder: {
|
|
460
|
+
// Builds every environment itself, client first (as e.g. the
|
|
461
|
+
// Cloudflare plugin does): that client build predates island
|
|
462
|
+
// discovery and must be redone.
|
|
463
|
+
async buildApp(builder) {
|
|
464
|
+
await builder.build(builder.environments.client)
|
|
465
|
+
await builder.build(builder.environments.ssr)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
const html = await fs.readFile(
|
|
471
|
+
path.join(dir, 'dist', 'index.html'),
|
|
472
|
+
'utf-8'
|
|
473
|
+
)
|
|
474
|
+
assert.ok(html.includes('<title>Home</title>'), 'page should survive')
|
|
475
|
+
const islandSrc = html.match(/<orga-island [^>]*src="([^"]+)"/)?.[1]
|
|
476
|
+
assert.ok(islandSrc, 'island is rendered')
|
|
477
|
+
await fs.access(path.join(dir, 'dist', islandSrc))
|
|
478
|
+
} finally {
|
|
479
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
test('rejects island props that cannot reach the browser', async () => {
|
|
484
|
+
const dir = path.join(__dirname, 'fixtures-island-props')
|
|
485
|
+
try {
|
|
486
|
+
await writeIslandFixture(dir)
|
|
487
|
+
await fs.writeFile(
|
|
488
|
+
path.join(dir, 'index.org'),
|
|
489
|
+
'#+jsx: <Counter start={1} config={{ onClick: () => {} }} />\n'
|
|
490
|
+
)
|
|
491
|
+
await assert.rejects(() => viteBuild(dir), /prop "onClick" can't be sent/)
|
|
492
|
+
} finally {
|
|
493
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
test('emits images and CSS imported by pages', async () => {
|
|
498
|
+
const dir = path.join(__dirname, 'fixtures-page-assets')
|
|
499
|
+
try {
|
|
500
|
+
await fs.mkdir(dir, { recursive: true })
|
|
501
|
+
// Larger than Vite's inline limit, so it is emitted as a file.
|
|
502
|
+
await fs.writeFile(path.join(dir, 'pic.png'), Buffer.alloc(8192, 1))
|
|
503
|
+
await fs.writeFile(path.join(dir, "it's.png"), Buffer.alloc(8192, 2))
|
|
504
|
+
await fs.writeFile(
|
|
505
|
+
path.join(dir, 'index.org'),
|
|
506
|
+
"[[./pic.png]]\n\n[[./it's.png]]\n"
|
|
507
|
+
)
|
|
508
|
+
await fs.writeFile(path.join(dir, 'page.css'), '.page { color: red }\n')
|
|
509
|
+
await fs.writeFile(
|
|
510
|
+
path.join(dir, 'page.tsx'),
|
|
511
|
+
`import './page.css'
|
|
512
|
+
export default function Page() {
|
|
513
|
+
return <p className="page">hi</p>
|
|
514
|
+
}
|
|
515
|
+
`
|
|
516
|
+
)
|
|
517
|
+
await fs.writeFile(
|
|
518
|
+
path.join(dir, 'feed.json.ts'),
|
|
519
|
+
`import pic from './pic.png'
|
|
520
|
+
export function GET() {
|
|
521
|
+
return Response.json({ pic })
|
|
522
|
+
}
|
|
523
|
+
`
|
|
524
|
+
)
|
|
525
|
+
await viteBuild(dir, { build: { sourcemap: true } })
|
|
526
|
+
// The SSR bundle and its source map are not part of the site.
|
|
527
|
+
for (const file of ['ssr.mjs', 'ssr.mjs.map']) {
|
|
528
|
+
await assert.rejects(fs.access(path.join(dir, 'dist', file)), file)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const html = await fs.readFile(
|
|
532
|
+
path.join(dir, 'dist', 'index.html'),
|
|
533
|
+
'utf-8'
|
|
534
|
+
)
|
|
535
|
+
const img = html.match(/<img src="\/(assets\/pic-[^"]+\.png)"/)?.[1] ?? ''
|
|
536
|
+
assert.ok(img, html)
|
|
537
|
+
await fs.access(path.join(dir, 'dist', img))
|
|
538
|
+
assert.equal(html.match(/<img src="\/assets\//g)?.length, 2, html)
|
|
539
|
+
|
|
540
|
+
const feed = JSON.parse(
|
|
541
|
+
await fs.readFile(path.join(dir, 'dist', 'feed.json'), 'utf-8')
|
|
542
|
+
)
|
|
543
|
+
assert.equal(feed.pic, `/${img}`, 'endpoints get real asset URLs too')
|
|
544
|
+
|
|
545
|
+
const page = await fs.readFile(
|
|
546
|
+
path.join(dir, 'dist', 'page', 'index.html'),
|
|
547
|
+
'utf-8'
|
|
548
|
+
)
|
|
549
|
+
const css =
|
|
550
|
+
page.match(
|
|
551
|
+
/<link rel="stylesheet" href="\/(assets\/[^"]+\.css)"/
|
|
552
|
+
)?.[1] ?? ''
|
|
553
|
+
assert.ok(css, page)
|
|
554
|
+
const built = await fs.readFile(path.join(dir, 'dist', css), 'utf-8')
|
|
555
|
+
assert.ok(built.includes('.page'), built)
|
|
556
|
+
} finally {
|
|
557
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
558
|
+
}
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
test('leaves external image URLs alone', async () => {
|
|
562
|
+
const dir = path.join(__dirname, 'fixtures-external-assets')
|
|
563
|
+
const srcs = ['https://example.com/a.png', '//cdn.example.com/b.png']
|
|
564
|
+
try {
|
|
565
|
+
await fs.mkdir(dir, { recursive: true })
|
|
566
|
+
await fs.writeFile(
|
|
567
|
+
path.join(dir, 'index.org'),
|
|
568
|
+
srcs.map((src) => `[[${src}]]`).join('\n\n') + '\n'
|
|
569
|
+
)
|
|
570
|
+
await viteBuild(dir)
|
|
571
|
+
const html = await fs.readFile(
|
|
572
|
+
path.join(dir, 'dist', 'index.html'),
|
|
573
|
+
'utf-8'
|
|
574
|
+
)
|
|
575
|
+
for (const src of srcs) {
|
|
576
|
+
assert.ok(html.includes(`<img src="${src}"`), `${src}: ${html}`)
|
|
577
|
+
}
|
|
578
|
+
} finally {
|
|
579
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
580
|
+
}
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
test('injects head tags for a page that renders nothing', () => {
|
|
584
|
+
const html = renderPageHtml(
|
|
585
|
+
'<html><head></head><body><div id="root"></div></body></html>',
|
|
586
|
+
{ content: '', page: {}, styles: ['/s.css'] }
|
|
587
|
+
)
|
|
588
|
+
assert.match(html, /<link rel="stylesheet" href="\/s.css"><\/head>/)
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
test('rebases relative asset URLs for nested pages', async () => {
|
|
592
|
+
const dir = path.join(__dirname, 'fixtures-vite-base')
|
|
593
|
+
try {
|
|
594
|
+
await writeIslandFixture(dir)
|
|
595
|
+
|
|
596
|
+
await viteBuild(dir, { base: './' })
|
|
597
|
+
|
|
598
|
+
for (const [page, prefix] of [
|
|
599
|
+
['', './'],
|
|
600
|
+
['docs', '../']
|
|
601
|
+
]) {
|
|
602
|
+
const pageDir = path.join(dir, 'dist', page)
|
|
603
|
+
const html = await fs.readFile(
|
|
604
|
+
path.join(pageDir, 'index.html'),
|
|
605
|
+
'utf-8'
|
|
606
|
+
)
|
|
607
|
+
for (const pattern of [
|
|
608
|
+
/<script type="module"[^>]* src="([^"]+)"/,
|
|
609
|
+
/<orga-island [^>]*src="([^"]+)"/,
|
|
610
|
+
...(page ? [/<img src="([^"]+)"/] : [])
|
|
611
|
+
]) {
|
|
612
|
+
const src = html.match(pattern)?.[1] ?? ''
|
|
613
|
+
assert.ok(
|
|
614
|
+
src.startsWith(`${prefix}assets/`),
|
|
615
|
+
`${page || '/'}: ${src}`
|
|
616
|
+
)
|
|
617
|
+
await fs.access(path.join(pageDir, src))
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
} finally {
|
|
621
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
622
|
+
}
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
test('resolves org links under base', async () => {
|
|
626
|
+
const dir = path.join(__dirname, 'fixtures-link-base')
|
|
627
|
+
try {
|
|
628
|
+
await fs.mkdir(path.join(dir, 'docs'), { recursive: true })
|
|
629
|
+
await fs.writeFile(
|
|
630
|
+
path.join(dir, 'index.org'),
|
|
631
|
+
'[[file:docs/a.org::*Part One][a]]\n'
|
|
632
|
+
)
|
|
633
|
+
await fs.writeFile(
|
|
634
|
+
path.join(dir, 'docs', 'a.org'),
|
|
635
|
+
'* Part One\n[[file:../index.org][home]]\n'
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
for (const [base, home, a] of [
|
|
639
|
+
['/blog/', '/blog/', '/blog/docs/a#part-one'],
|
|
640
|
+
['./', '../../', './docs/a#part-one']
|
|
641
|
+
]) {
|
|
642
|
+
await viteBuild(dir, { base })
|
|
643
|
+
const read = (/** @type {string} */ page) =>
|
|
644
|
+
fs.readFile(path.join(dir, 'dist', page, 'index.html'), 'utf-8')
|
|
645
|
+
assert.ok((await read('')).includes(`href="${a}"`), base)
|
|
646
|
+
assert.ok((await read('docs/a')).includes(`href="${home}"`), base)
|
|
647
|
+
}
|
|
648
|
+
} finally {
|
|
649
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
650
|
+
}
|
|
651
|
+
})
|
|
231
652
|
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.test.d.ts","sourceRoot":"","sources":["dev.test.js"],"names":[],"mappings":""}
|