orga-build 0.3.2 → 0.5.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=build.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.test.d.ts","sourceRoot":"","sources":["build.test.js"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { test, describe, before, after } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import fs from 'node:fs/promises'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { build } from '../build.js'
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
+ const fixtureDir = path.join(__dirname, 'fixtures')
10
+ const outDir = path.join(__dirname, '.test-output')
11
+
12
+ describe('orga-build', () => {
13
+ before(async () => {
14
+ await fs.mkdir(fixtureDir, { recursive: true })
15
+ // Create minimal fixture
16
+ await fs.writeFile(
17
+ path.join(fixtureDir, 'index.org'),
18
+ `#+title: Test Page
19
+
20
+ * Hello World
21
+
22
+ This is a test page.
23
+ `
24
+ )
25
+ })
26
+
27
+ after(async () => {
28
+ await fs.rm(outDir, { recursive: true, force: true })
29
+ await fs.rm(fixtureDir, { recursive: true, force: true })
30
+ })
31
+
32
+ test('builds org files to HTML', async () => {
33
+ await build({
34
+ root: fixtureDir,
35
+ outDir: outDir,
36
+ containerClass: [],
37
+ vitePlugins: [],
38
+ preBuild: [],
39
+ postBuild: []
40
+ })
41
+
42
+ // Check output exists
43
+ const indexPath = path.join(outDir, 'index.html')
44
+ const indexExists = await fs
45
+ .access(indexPath)
46
+ .then(() => true)
47
+ .catch(() => false)
48
+ assert.ok(indexExists, 'index.html should exist')
49
+
50
+ // Check content
51
+ const html = await fs.readFile(indexPath, 'utf-8')
52
+ assert.ok(html.includes('<title>Test Page</title>'), 'should have title')
53
+ assert.ok(html.includes('Hello World'), 'should have heading content')
54
+ })
55
+
56
+ test('generates assets directory', async () => {
57
+ const assetsDir = path.join(outDir, 'assets')
58
+ const assetsExists = await fs
59
+ .access(assetsDir)
60
+ .then(() => true)
61
+ .catch(() => false)
62
+ assert.ok(assetsExists, 'assets directory should exist')
63
+ })
64
+ })
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["build.js"],"names":[],"mappings":"AAmBA;;GAEG;AACH,qEAFW,OAAO,aAAa,EAAE,MAAM,iBA8JtC;AArKD;;;;EAIC"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["build.js"],"names":[],"mappings":"AAqBA;;GAEG;AACH,qEAFW,OAAO,aAAa,EAAE,MAAM,iBAoJtC;AA9JD;;;;EAIC"}
package/lib/build.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import path from 'node:path'
2
2
  import { createRequire } from 'node:module'
3
- import { build as viteBuild } from 'vite'
3
+ import { createBuilder } from 'vite'
4
4
  import { setupOrga } from './orga.js'
5
5
  import react from '@vitejs/plugin-react'
6
6
  import { fileURLToPath, pathToFileURL } from 'node:url'
7
7
  import { copy, emptyDir, ensureDir } from './fs.js'
8
8
  import { pluginFactory } from './vite.js'
9
9
  import fs from 'fs/promises'
10
- import assert from 'node:assert'
11
10
 
12
11
  const require = createRequire(import.meta.url)
13
12
 
@@ -17,6 +16,9 @@ export const alias = {
17
16
  wouter: path.dirname(require.resolve('wouter'))
18
17
  }
19
18
 
19
+ const ssrEntry = fileURLToPath(new URL('./ssr.jsx', import.meta.url))
20
+ const clientEntry = fileURLToPath(new URL('./client.jsx', import.meta.url))
21
+
20
22
  /**
21
23
  * @param {import('./config.js').Config} config
22
24
  */
@@ -26,7 +28,6 @@ export async function build({
26
28
  containerClass,
27
29
  vitePlugins = []
28
30
  }) {
29
- /* --- prepare folders, out, ssr, client --- */
30
31
  await emptyDir(outDir)
31
32
  const ssrOutDir = path.join(outDir, '.ssr')
32
33
  const clientOutDir = path.join(outDir, '.client')
@@ -38,80 +39,71 @@ export async function build({
38
39
  ...vitePlugins
39
40
  ]
40
41
 
41
- /* --- build ssr bundle: server.mjs --- */
42
- console.log('preparing ssr bundle...')
43
- await viteBuild({
42
+ // Shared config with environment-specific build settings
43
+ const builder = await createBuilder({
44
44
  root,
45
45
  plugins,
46
- build: {
47
- ssr: true,
48
- cssCodeSplit: false,
49
- emptyOutDir: true,
50
- rollupOptions: {
51
- input: fileURLToPath(new URL('./ssr.jsx', import.meta.url)),
52
- output: {
53
- entryFileNames: '[name].mjs',
54
- chunkFileNames: '[name]-[hash].mjs'
46
+ resolve: { alias },
47
+ ssr: { noExternal: true },
48
+ environments: {
49
+ ssr: {
50
+ build: {
51
+ ssr: true,
52
+ outDir: ssrOutDir,
53
+ cssCodeSplit: false,
54
+ emptyOutDir: true,
55
+ minify: false,
56
+ rollupOptions: {
57
+ input: ssrEntry,
58
+ output: {
59
+ entryFileNames: '[name].mjs',
60
+ chunkFileNames: '[name]-[hash].mjs'
61
+ }
62
+ }
55
63
  }
56
64
  },
57
- outDir: ssrOutDir,
58
- minify: false
59
- },
60
- ssr: {
61
- noExternal: true
62
- },
63
- resolve: {
64
- alias: alias
65
+ client: {
66
+ build: {
67
+ outDir: clientOutDir,
68
+ cssCodeSplit: false,
69
+ emptyOutDir: true,
70
+ assetsDir: 'assets',
71
+ rollupOptions: {
72
+ input: clientEntry,
73
+ preserveEntrySignatures: 'allow-extension'
74
+ }
75
+ }
76
+ }
65
77
  }
66
78
  })
67
79
 
68
- /* --- import ssr bundle entry output, to get all the data and render function --- */
80
+ // Build SSR first to get render function and pages
81
+ console.log('preparing ssr bundle...')
82
+ await builder.build(builder.environments.ssr)
69
83
 
70
84
  const { render, pages } = await import(
71
85
  pathToFileURL(path.join(ssrOutDir, 'ssr.mjs')).toString()
72
86
  )
73
87
 
74
- /* --- build client bundle: client.mjs --- */
75
- const _clientResult = await viteBuild({
76
- root,
77
- plugins,
78
- build: {
79
- cssCodeSplit: false,
80
- emptyOutDir: true,
81
- rollupOptions: {
82
- input: fileURLToPath(new URL('./client.jsx', import.meta.url)),
83
- preserveEntrySignatures: 'allow-extension'
84
- },
85
- assetsDir: 'assets',
86
- outDir: clientOutDir
87
- },
88
- ssr: {
89
- noExternal: true
90
- },
91
- resolve: {
92
- alias: alias
93
- }
94
- })
88
+ // Build client bundle
89
+ const _clientResult = await builder.build(builder.environments.client)
95
90
 
96
- /** @type {import('vite').Rollup.RollupOutput} */
97
- let clientResult
98
- if (Array.isArray(_clientResult)) {
99
- if (_clientResult.length !== 1)
100
- throw new Error(`expect viteBuild to have only one BuildResult`)
101
- clientResult = _clientResult[0]
102
- } else {
103
- assert('output' in _clientResult)
104
- clientResult = _clientResult
105
- }
91
+ // Normalize build result to single RollupOutput
92
+ const clientOutput = Array.isArray(_clientResult)
93
+ ? _clientResult[0].output
94
+ : 'output' in _clientResult
95
+ ? _clientResult.output
96
+ : null
97
+ if (!clientOutput) throw new Error('Unexpected client build result')
106
98
 
107
99
  /* --- get from client bundle result: entry chunk, css chunks --- */
108
- const entryChunk = clientResult.output.filter((c) => {
109
- return c.type === 'chunk' && c.isEntry
110
- })[0]
100
+ const entryChunk = clientOutput.find(
101
+ (/** @type {any} */ c) => c.type === 'chunk' && c.isEntry
102
+ )
111
103
 
112
- const cssChunks = clientResult.output.filter((c) => {
113
- return c.type === 'asset' && c.fileName.endsWith('.css')
114
- })
104
+ const cssChunks = clientOutput.filter(
105
+ (/** @type {any} */ c) => c.type === 'asset' && c.fileName.endsWith('.css')
106
+ )
115
107
 
116
108
  /* --- get html template, inject entry js and css --- */
117
109
  const template = await fs.readFile(
@@ -157,11 +149,11 @@ export async function build({
157
149
  `
158
150
  )
159
151
  const css = cssChunks
160
- .map((c) => `<link rel="stylesheet" href="/${c.fileName}">`)
152
+ .map((/** @type {any} */ c) => `<link rel="stylesheet" href="/${c.fileName}">`)
161
153
  .join('\n')
162
154
  html = html.replace(
163
155
  '<script type="module" src="/@orga-build/main.js"></script>',
164
- `<script type="module" src="/${entryChunk.fileName}"></script>`
156
+ `<script type="module" src="/${entryChunk?.fileName}"></script>`
165
157
  )
166
158
 
167
159
  html = html.replace('</head>', `${css}</head>`)
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["serve.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,8BAHW,OAAO,aAAa,EAAE,MAAM,SAC5B,MAAM,iBA2ChB"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["serve.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,8BAHW,OAAO,aAAa,EAAE,MAAM,SAC5B,MAAM,iBA4ChB"}
package/lib/serve.js CHANGED
@@ -45,10 +45,11 @@ export async function serve(config, port = 3000) {
45
45
  const html = template
46
46
  res.status(200).setHeader('Content-Type', 'text/html').end(html)
47
47
  } catch (/** @type{any} */ e) {
48
- vite.ssrFixStacktrace(e)
49
48
  next(e)
50
49
  }
51
50
  })
52
51
 
53
- app.listen(port)
52
+ app.listen(port, () => {
53
+ console.log(` Server running at http://localhost:${port}/`)
54
+ })
54
55
  }
package/lib/vite.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["vite.js"],"names":[],"mappings":"AASA;;;;GAIG;AACH,uCAHG;IAAwB,GAAG,EAAnB,MAAM;CACd,GAAU,OAAO,MAAM,EAAE,MAAM,CA6JjC"}
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["vite.js"],"names":[],"mappings":"AASA;;;;GAIG;AACH,uCAHG;IAAwB,GAAG,EAAnB,MAAM;CACd,GAAU,OAAO,MAAM,EAAE,MAAM,CA2JjC"}
package/lib/vite.js CHANGED
@@ -19,6 +19,11 @@ export function pluginFactory({ dir }) {
19
19
  name: 'vite-plugin-orga-pages',
20
20
  enforce: 'pre',
21
21
  config: (config, env) => ({
22
+ future: {
23
+ removePluginHookSsrArgument: 'warn',
24
+ removePluginHookHandleHotUpdate: 'warn',
25
+ removeSsrLoadModule: 'warn'
26
+ },
22
27
  optimizeDeps: {
23
28
  include: [
24
29
  'react',
@@ -31,25 +36,18 @@ export function pluginFactory({ dir }) {
31
36
  }
32
37
  }),
33
38
 
34
- configureServer(server) {
35
- const { watcher, moduleGraph, ws } = server
36
-
37
- // Invalidate content module on file changes
38
- watcher.on('change', (filePath) => {
39
- const module = moduleGraph.getModuleById(contentModuleIdResolved)
40
- if (module) {
41
- moduleGraph.invalidateModule(module)
42
- // Send HMR update to client
43
- ws.send({
44
- type: 'full-reload',
45
- path: '*'
46
- })
47
- }
48
- })
39
+ hotUpdate() {
40
+ // Invalidate content module when content files change
41
+ const module = this.environment.moduleGraph.getModuleById(
42
+ contentModuleIdResolved
43
+ )
44
+ if (module) {
45
+ this.environment.moduleGraph.invalidateModule(module)
46
+ // Full reload for now; can optimize to HMR later
47
+ this.environment.hot.send({ type: 'full-reload', path: '*' })
48
+ }
49
49
  },
50
50
 
51
- buildStart() {},
52
-
53
51
  async resolveId(id, importer) {
54
52
  if (id === appEntryId) {
55
53
  return appEntryId
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "orga-build",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "A simple tool that builds org-mode files into a website",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=20.19.0"
8
+ },
6
9
  "bin": {
7
10
  "orga-build": "./cli.js"
8
11
  },
@@ -40,14 +43,14 @@
40
43
  "directory": "packages/orga-build"
41
44
  },
42
45
  "dependencies": {
43
- "@vitejs/plugin-react": "^4.4.0",
46
+ "@vitejs/plugin-react": "^5.1.4",
44
47
  "express": "^5.1.0",
45
48
  "globby": "^14.1.0",
46
49
  "react": "^19.0.0",
47
50
  "react-dom": "^19.0.0",
48
51
  "rehype-katex": "^7.0.1",
49
52
  "unist-util-visit-parents": "^6.0.1",
50
- "vite": "6.3.2",
53
+ "vite": "^7.3.1",
51
54
  "wouter": "^3.7.0",
52
55
  "@orgajs/rollup": "1.3.3"
53
56
  },
@@ -60,6 +63,7 @@
60
63
  "orga": "4.6.0"
61
64
  },
62
65
  "scripts": {
63
- "clean": "fd . -e d.ts -e d.ts.map -I -x rm {}"
66
+ "clean": "fd . -e d.ts -e d.ts.map -I -x rm {}",
67
+ "test": "node --test --no-warnings \"lib/__tests__/*.test.js\""
64
68
  }
65
69
  }