@hashrock/ono 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/README.md +35 -320
- package/package.json +14 -13
- package/src/barrels.js +230 -0
- package/src/browser/compiler.js +71 -193
- package/src/browser/unocss.js +5 -1
- package/src/builder.js +110 -232
- package/src/bundler.js +215 -64
- package/src/cli.js +22 -191
- package/src/commands/build.js +141 -0
- package/src/commands/dev.js +54 -0
- package/src/constants.js +74 -0
- package/src/jsx-runtime.js +15 -5
- package/src/parser.js +555 -0
- package/src/renderer.js +29 -48
- package/src/server.js +88 -74
- package/src/transformer.js +1 -18
- package/src/unocss.js +20 -24
- package/src/utils.js +55 -0
- package/src/watcher.js +98 -125
- package/src/content.js +0 -272
- package/src/resolver.js +0 -137
package/README.md
CHANGED
|
@@ -1,341 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hashrock/ono
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ミニマリストなSSGフレームワーク。JSXとTypeScriptのJSXトランスフォーマーを活用。
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
Ono is designed to be a minimal alternative to Astro, leveraging TypeScript's built-in JSX transformation capabilities. The core philosophy is:
|
|
8
|
-
|
|
9
|
-
- **Minimal Dependencies**: Uses TypeScript's standard `tsx` transform feature, avoiding complex build toolchains
|
|
10
|
-
- **High Portability**: Designed to be lightweight enough to run in Web Workers or even entirely in the browser
|
|
11
|
-
- **Simple Architecture**: A minimal subset of static site generation features, focusing on what matters most
|
|
12
|
-
- **Future Vision**: Enable browser-based REPL experiences and client-side static site generation
|
|
13
|
-
|
|
14
|
-
Unlike heavyweight frameworks, Ono embraces simplicity. It's perfect for developers who want the power of JSX and component-based development without the complexity of a full framework.
|
|
15
|
-
|
|
16
|
-
## Features
|
|
17
|
-
|
|
18
|
-
- Minimal JSX runtime for building static HTML sites
|
|
19
|
-
- Built-in bundler for JSX files
|
|
20
|
-
- CLI tool for building JSX to HTML
|
|
21
|
-
- Development server with live reload
|
|
22
|
-
- File watching for automatic rebuilds
|
|
23
|
-
- Support for components and props
|
|
24
|
-
- Integrated UnoCSS for atomic CSS generation
|
|
25
|
-
- Pages directory support for multi-page sites
|
|
26
|
-
- Content collections with Markdown support
|
|
27
|
-
- Dynamic routes with `[slug].jsx` pattern
|
|
28
|
-
- Uses TypeScript's JSX transform
|
|
29
|
-
|
|
30
|
-
## Installation
|
|
31
|
-
|
|
32
|
-
### Global Installation
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npm install -g @hashrock/ono
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Local Installation
|
|
5
|
+
## インストール
|
|
39
6
|
|
|
40
7
|
```bash
|
|
41
8
|
npm install @hashrock/ono
|
|
42
9
|
```
|
|
43
10
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
**Try it online**: [Ono REPL](https://hashrock.github.io/ono/) - Worker-powered JSX playground!
|
|
47
|
-
|
|
48
|
-
Or create a simple JSX file and build it:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
# Create a JSX file
|
|
52
|
-
echo '/** @jsxImportSource @hashrock/ono */
|
|
53
|
-
export default function App() {
|
|
54
|
-
return (
|
|
55
|
-
<html>
|
|
56
|
-
<head><title>Hello Ono</title></head>
|
|
57
|
-
<body>
|
|
58
|
-
<h1>Hello, Ono!</h1>
|
|
59
|
-
</body>
|
|
60
|
-
</html>
|
|
61
|
-
);
|
|
62
|
-
}' > index.jsx
|
|
63
|
-
|
|
64
|
-
# Build it
|
|
65
|
-
npx @hashrock/ono build index.jsx
|
|
66
|
-
|
|
67
|
-
# Or start a dev server
|
|
68
|
-
npx @hashrock/ono dev index.jsx
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Usage
|
|
72
|
-
|
|
73
|
-
### CLI Commands
|
|
74
|
-
|
|
75
|
-
**Build** - Build JSX files to static HTML:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
ono build # Build all pages in pages/ directory
|
|
79
|
-
ono build pages # Build all pages in pages/ directory
|
|
80
|
-
ono build example/index.jsx # Build a single file
|
|
81
|
-
ono build --output dist # Specify output directory
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
This will:
|
|
85
|
-
- Build your JSX file(s) to HTML
|
|
86
|
-
- Copy files from `public/` directory
|
|
87
|
-
- Generate UnoCSS automatically
|
|
88
|
-
|
|
89
|
-
**Dev Server** - Start a development server with live reload:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
ono dev # Start dev server for pages/ directory
|
|
93
|
-
ono dev pages # Start dev server for pages/ directory
|
|
94
|
-
ono dev example/index.jsx # Start dev server for a single file
|
|
95
|
-
ono dev --port 8080 # Start dev server on custom port
|
|
96
|
-
ono dev --output build # Use custom output directory
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
This will:
|
|
100
|
-
- Build your JSX file(s) to HTML
|
|
101
|
-
- Start an HTTP server (default: http://localhost:3000)
|
|
102
|
-
- Watch for changes and rebuild automatically
|
|
103
|
-
- Live reload the browser when files change
|
|
104
|
-
- Generate UnoCSS automatically
|
|
105
|
-
|
|
106
|
-
**Help** - Show usage information:
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
ono --help
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### JSX Example
|
|
113
|
-
|
|
114
|
-
Create a JSX file with components:
|
|
11
|
+
## 使い方
|
|
115
12
|
|
|
116
13
|
```jsx
|
|
117
|
-
// example/index.jsx
|
|
118
14
|
export default function App() {
|
|
119
|
-
return
|
|
120
|
-
<html>
|
|
121
|
-
<head>
|
|
122
|
-
<title>My Site</title>
|
|
123
|
-
</head>
|
|
124
|
-
<body>
|
|
125
|
-
<Header title="Welcome" />
|
|
126
|
-
<main>
|
|
127
|
-
<p>Hello, Ono!</p>
|
|
128
|
-
</main>
|
|
129
|
-
</body>
|
|
130
|
-
</html>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function Header({ title }) {
|
|
135
|
-
return (
|
|
136
|
-
<header>
|
|
137
|
-
<h1>{title}</h1>
|
|
138
|
-
</header>
|
|
139
|
-
);
|
|
15
|
+
return <h1>Hello, Ono!</h1>;
|
|
140
16
|
}
|
|
141
17
|
```
|
|
142
18
|
|
|
143
|
-
Build it:
|
|
144
|
-
|
|
145
19
|
```bash
|
|
146
|
-
ono build
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
This generates `dist/index.html`:
|
|
150
|
-
|
|
151
|
-
```html
|
|
152
|
-
<!DOCTYPE html>
|
|
153
|
-
<html>
|
|
154
|
-
<head>
|
|
155
|
-
<title>My Site</title>
|
|
156
|
-
</head>
|
|
157
|
-
<body>
|
|
158
|
-
<header>
|
|
159
|
-
<h1>Welcome</h1>
|
|
160
|
-
</header>
|
|
161
|
-
<main>
|
|
162
|
-
<p>Hello, Ono!</p>
|
|
163
|
-
</main>
|
|
164
|
-
</body>
|
|
165
|
-
</html>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### UnoCSS Integration
|
|
169
|
-
|
|
170
|
-
Ono automatically integrates with UnoCSS. Simply use utility classes in your JSX:
|
|
171
|
-
|
|
172
|
-
```jsx
|
|
173
|
-
export default function App() {
|
|
174
|
-
return (
|
|
175
|
-
<div class="max-w-800px mx-auto p-8">
|
|
176
|
-
<h1 class="text-3xl font-bold text-blue-600">
|
|
177
|
-
Welcome to Ono
|
|
178
|
-
</h1>
|
|
179
|
-
<p class="mt-4 text-gray-700">
|
|
180
|
-
UnoCSS utilities are automatically generated!
|
|
181
|
-
</p>
|
|
182
|
-
</div>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
20
|
+
npx ono build index.jsx
|
|
21
|
+
npx ono dev index.jsx
|
|
185
22
|
```
|
|
186
23
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
### Custom UnoCSS Configuration
|
|
24
|
+
## 機能
|
|
190
25
|
|
|
191
|
-
|
|
26
|
+
- JSXから静的HTMLへの変換
|
|
27
|
+
- ライブリロード付き開発サーバー
|
|
28
|
+
- UnoCSS統合
|
|
29
|
+
- コンテンツコレクション(Markdown)
|
|
30
|
+
- 動的ルート(`[slug].jsx`)
|
|
192
31
|
|
|
193
|
-
|
|
194
|
-
import { presetUno } from "unocss";
|
|
195
|
-
|
|
196
|
-
export default {
|
|
197
|
-
presets: [presetUno()],
|
|
198
|
-
theme: {
|
|
199
|
-
colors: {
|
|
200
|
-
primary: "#0070f3",
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
shortcuts: {
|
|
204
|
-
"btn": "px-4 py-2 rounded bg-primary text-white",
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
```
|
|
32
|
+
## 制限事項
|
|
208
33
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
### TypeScript/JSX Setup
|
|
212
|
-
|
|
213
|
-
Add to your `tsconfig.json`:
|
|
214
|
-
|
|
215
|
-
```json
|
|
216
|
-
{
|
|
217
|
-
"compilerOptions": {
|
|
218
|
-
"jsx": "react-jsx",
|
|
219
|
-
"jsxImportSource": "@hashrock/ono"
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Or use JSDoc comments in your `.jsx` files:
|
|
34
|
+
- **React Fragmentは非対応**: `<>...</>` や `<React.Fragment>` はサポートされていません。代わりに配列や親要素でラップしてください。
|
|
225
35
|
|
|
226
36
|
```jsx
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
```bash
|
|
249
|
-
npm run test:snapshot
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Run all tests (unit + snapshot):
|
|
253
|
-
|
|
254
|
-
```bash
|
|
255
|
-
npm run test:all
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Update snapshots after intentional changes:
|
|
259
|
-
|
|
260
|
-
```bash
|
|
261
|
-
npm run test:snapshot:update
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Watch Mode
|
|
265
|
-
|
|
266
|
-
```bash
|
|
267
|
-
npm run test:watch
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### Testing Strategy
|
|
271
|
-
|
|
272
|
-
This project uses a comprehensive testing approach:
|
|
273
|
-
|
|
274
|
-
- **Unit Tests**: Traditional assertion-based tests for core functionality
|
|
275
|
-
- **Snapshot Tests**: Capture and verify output consistency for HTML rendering, JSX transformation, and CLI commands
|
|
276
|
-
- **Integration Tests**: End-to-end testing of build processes and content collections
|
|
277
|
-
|
|
278
|
-
For more details about snapshot testing, see [SNAPSHOT_TESTING.md](./SNAPSHOT_TESTING.md).
|
|
279
|
-
|
|
280
|
-
### Continuous Integration
|
|
281
|
-
|
|
282
|
-
This project uses GitHub Actions for automated testing. The CI pipeline:
|
|
283
|
-
|
|
284
|
-
- Tests on Node.js 22.x with Ubuntu
|
|
285
|
-
- Runs comprehensive unit and snapshot tests
|
|
286
|
-
- Validates CLI commands and build processes
|
|
287
|
-
- Ensures output consistency across environments
|
|
288
|
-
|
|
289
|
-
See `.github/workflows/ci.yml` for the full configuration
|
|
290
|
-
|
|
291
|
-
### Link for Local Development
|
|
292
|
-
|
|
293
|
-
```bash
|
|
294
|
-
npm link
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
This allows you to use the `ono` command globally while developing.
|
|
298
|
-
|
|
299
|
-
## API
|
|
300
|
-
|
|
301
|
-
### JSX Runtime
|
|
302
|
-
|
|
303
|
-
```javascript
|
|
304
|
-
import { createElement } from '@hashrock/ono/jsx-runtime';
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
The JSX runtime automatically handles JSX transformation when using TypeScript or Babel.
|
|
308
|
-
|
|
309
|
-
### Renderer
|
|
310
|
-
|
|
311
|
-
```javascript
|
|
312
|
-
import { renderToString } from '@hashrock/ono';
|
|
313
|
-
|
|
314
|
-
const html = renderToString(<div>Hello</div>);
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Bundler
|
|
318
|
-
|
|
319
|
-
```javascript
|
|
320
|
-
import { bundle } from '@hashrock/ono';
|
|
321
|
-
|
|
322
|
-
const code = await bundle('./path/to/file.jsx');
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
## Pages Mode
|
|
326
|
-
|
|
327
|
-
Ono supports building multi-page sites by placing JSX files in a `pages/` directory:
|
|
328
|
-
|
|
329
|
-
```
|
|
330
|
-
pages/
|
|
331
|
-
├── index.jsx → dist/index.html
|
|
332
|
-
├── about.jsx → dist/about.html
|
|
333
|
-
└── blog/
|
|
334
|
-
└── first-post.jsx → dist/blog/first-post.html
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
Each file should export a default function that returns a complete HTML document. Use the Layout component pattern for shared structure across pages.
|
|
338
|
-
|
|
339
|
-
## License
|
|
340
|
-
|
|
341
|
-
MIT
|
|
37
|
+
// NG: React Fragmentは使用不可
|
|
38
|
+
<>
|
|
39
|
+
<div>Item 1</div>
|
|
40
|
+
<div>Item 2</div>
|
|
41
|
+
</>
|
|
42
|
+
|
|
43
|
+
// OK: 配列を使用
|
|
44
|
+
[
|
|
45
|
+
<div>Item 1</div>,
|
|
46
|
+
<div>Item 2</div>
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
// OK: 親要素でラップ
|
|
50
|
+
<div>
|
|
51
|
+
<div>Item 1</div>
|
|
52
|
+
<div>Item 2</div>
|
|
53
|
+
</div>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
詳細なドキュメントは[ルートのREADME](../../README.md)を参照してください。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hashrock/ono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Minimalist SSG framework with JSX, powered by TypeScript's JSX transformer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/jsx-runtime.js",
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
"./jsx-runtime": "./src/jsx-runtime.js",
|
|
10
10
|
"./renderer": "./src/renderer.js",
|
|
11
11
|
"./transformer": "./src/transformer.js",
|
|
12
|
+
"./parser": "./src/parser.js",
|
|
12
13
|
"./bundler": "./src/bundler.js",
|
|
13
|
-
"./
|
|
14
|
+
"./barrels": "./src/barrels.js",
|
|
14
15
|
"./browser/compiler": "./src/browser/compiler.js",
|
|
15
16
|
"./browser/unocss": "./src/browser/unocss.js"
|
|
16
17
|
},
|
|
@@ -23,11 +24,10 @@
|
|
|
23
24
|
"LICENSE"
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
|
-
"
|
|
27
|
-
"test
|
|
28
|
-
"test:
|
|
29
|
-
"test:
|
|
30
|
-
"test:all": "npm run test && npm run test:snapshot"
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "node --test test/*.test.js",
|
|
29
|
+
"test:watch": "node --test --watch test/*.test.js",
|
|
30
|
+
"test:update": "node --test --test-update-snapshots test/*.test.js"
|
|
31
31
|
},
|
|
32
32
|
"keywords": [
|
|
33
33
|
"jsx",
|
|
@@ -49,14 +49,15 @@
|
|
|
49
49
|
},
|
|
50
50
|
"homepage": "https://github.com/hashrock/ono#readme",
|
|
51
51
|
"engines": {
|
|
52
|
-
"node": ">=
|
|
52
|
+
"node": ">=22.3.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
+
"@unocss/core": "^66.5.4",
|
|
55
56
|
"@unocss/preset-uno": "^66.5.4",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
57
|
+
"@unocss/reset": "^66.5.4",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^26.1.1"
|
|
61
62
|
}
|
|
62
63
|
}
|
package/src/barrels.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrels - Auto-generated barrel files with type inference
|
|
3
|
+
*/
|
|
4
|
+
import { readdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { join, basename, relative } from "node:path";
|
|
6
|
+
import { importJSXModule } from "./builder.js";
|
|
7
|
+
import { isJSXFile } from "./utils.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Convert kebab-case or snake_case to camelCase
|
|
11
|
+
* @param {string} str - String to convert
|
|
12
|
+
* @returns {string} camelCase string
|
|
13
|
+
*/
|
|
14
|
+
function toCamelCase(str) {
|
|
15
|
+
return str.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Infer TypeScript type from a JavaScript value
|
|
20
|
+
* @param {unknown} value
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function inferType(value) {
|
|
24
|
+
if (value === null) return "null";
|
|
25
|
+
if (value === undefined) return "undefined";
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
if (value.length === 0) return "unknown[]";
|
|
28
|
+
// Infer from first element
|
|
29
|
+
return `${inferType(value[0])}[]`;
|
|
30
|
+
}
|
|
31
|
+
if (value instanceof Date) return "Date";
|
|
32
|
+
if (typeof value === "object") {
|
|
33
|
+
// For nested objects, use Record type
|
|
34
|
+
return "Record<string, unknown>";
|
|
35
|
+
}
|
|
36
|
+
return typeof value; // 'string' | 'number' | 'boolean'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate Meta type definition from collected metas
|
|
41
|
+
* @param {Array<Record<string, unknown> | null>} metas
|
|
42
|
+
*/
|
|
43
|
+
function generateMetaType(metas) {
|
|
44
|
+
if (metas.length === 0) return "export type Meta = Record<string, never>;";
|
|
45
|
+
|
|
46
|
+
// Collect all keys and their occurrence count
|
|
47
|
+
/** @type {Record<string, number>} */
|
|
48
|
+
const keyCounts = {};
|
|
49
|
+
/** @type {Record<string, string>} */
|
|
50
|
+
const keyTypes = {};
|
|
51
|
+
|
|
52
|
+
for (const meta of metas) {
|
|
53
|
+
if (!meta) continue;
|
|
54
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
55
|
+
keyCounts[key] = (keyCounts[key] || 0) + 1;
|
|
56
|
+
// Store the first non-undefined value's type
|
|
57
|
+
if (!keyTypes[key] && value !== undefined) {
|
|
58
|
+
keyTypes[key] = inferType(value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Generate type fields
|
|
64
|
+
const fields = Object.entries(keyCounts)
|
|
65
|
+
.map(([key, count]) => {
|
|
66
|
+
const type = keyTypes[key] || "unknown";
|
|
67
|
+
const optional = count < metas.length;
|
|
68
|
+
return ` ${key}${optional ? "?" : ""}: ${type};`;
|
|
69
|
+
})
|
|
70
|
+
.join("\n");
|
|
71
|
+
|
|
72
|
+
return `export type Meta = {\n${fields}\n};`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get all entry files from a barrel directory
|
|
77
|
+
* @param {string} barrelDir - Directory to scan for barrel entries
|
|
78
|
+
* @returns {Promise<Array<{id: string, file: string, path: string}>>}
|
|
79
|
+
*/
|
|
80
|
+
async function getBarrelEntries(barrelDir) {
|
|
81
|
+
const entries = [];
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const files = await readdir(barrelDir, { withFileTypes: true });
|
|
85
|
+
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
if (file.isFile() && isJSXFile(file.name)) {
|
|
88
|
+
const ext = file.name.endsWith(".tsx") ? ".tsx" : ".jsx";
|
|
89
|
+
const id = basename(file.name, ext);
|
|
90
|
+
entries.push({
|
|
91
|
+
id,
|
|
92
|
+
file: file.name,
|
|
93
|
+
path: join(barrelDir, file.name),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error.code !== "ENOENT") throw error;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return entries.sort((a, b) => a.id.localeCompare(b.id));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Load meta from an entry file by compiling and evaluating it
|
|
106
|
+
* @param {string} entryPath - Path to the entry file
|
|
107
|
+
* @returns {Promise<Record<string, unknown> | null>} Meta object or null
|
|
108
|
+
*/
|
|
109
|
+
async function loadMeta(entryPath) {
|
|
110
|
+
try {
|
|
111
|
+
const module = await importJSXModule(entryPath);
|
|
112
|
+
return module.meta || null;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.warn(`Warning: Could not load meta from ${entryPath}:`, error.message);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Generate a barrel file for a directory
|
|
121
|
+
* @param {string} barrelDir
|
|
122
|
+
* @param {{ silent?: boolean }} [options]
|
|
123
|
+
*/
|
|
124
|
+
export async function generateBarrel(barrelDir, options = {}) {
|
|
125
|
+
const { silent = false } = options;
|
|
126
|
+
|
|
127
|
+
const entries = await getBarrelEntries(barrelDir);
|
|
128
|
+
|
|
129
|
+
if (entries.length === 0) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Load all metas
|
|
134
|
+
const metas = await Promise.all(
|
|
135
|
+
entries.map((entry) => loadMeta(entry.path))
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Generate type definition
|
|
139
|
+
const metaType = generateMetaType(metas);
|
|
140
|
+
|
|
141
|
+
// Generate imports with camelCase identifiers.
|
|
142
|
+
// Import first, then export the local bindings — a plain
|
|
143
|
+
// `export ... from` would not bring the names into scope for `posts`.
|
|
144
|
+
const imports = entries
|
|
145
|
+
.map((entry, idx) => {
|
|
146
|
+
const camelId = toCamelCase(entry.id);
|
|
147
|
+
const from = `'./${basename(barrelDir)}/${entry.file}'`;
|
|
148
|
+
if (metas[idx] !== null) {
|
|
149
|
+
return `import ${camelId}, { meta as ${camelId}Meta } from ${from};\nexport { ${camelId}, ${camelId}Meta };`;
|
|
150
|
+
}
|
|
151
|
+
return `import ${camelId} from ${from};\nexport { ${camelId} };`;
|
|
152
|
+
})
|
|
153
|
+
.join("\n");
|
|
154
|
+
|
|
155
|
+
// Generate entries array (keep original IDs for URL paths)
|
|
156
|
+
const entriesArray = `export const entries = [${entries.map((e) => `'${e.id}'`).join(", ")}] as const;`;
|
|
157
|
+
|
|
158
|
+
// Generate ID to component mapping
|
|
159
|
+
const mapping = entries
|
|
160
|
+
.map((entry, idx) => {
|
|
161
|
+
const camelId = toCamelCase(entry.id);
|
|
162
|
+
const hasExportedMeta = metas[idx] !== null;
|
|
163
|
+
if (hasExportedMeta) {
|
|
164
|
+
return ` '${entry.id}': { component: ${camelId}, meta: ${camelId}Meta }`;
|
|
165
|
+
}
|
|
166
|
+
return ` '${entry.id}': { component: ${camelId}, meta: null }`;
|
|
167
|
+
})
|
|
168
|
+
.join(",\n");
|
|
169
|
+
const mappingExport = `export const posts = {\n${mapping}\n};`;
|
|
170
|
+
const entryIdType = "export type EntryId = typeof entries[number];";
|
|
171
|
+
|
|
172
|
+
// Generate barrel content
|
|
173
|
+
const barrelContent = `// Auto-generated barrel file - DO NOT EDIT
|
|
174
|
+
// Generated by Ono SSG
|
|
175
|
+
|
|
176
|
+
${metaType}
|
|
177
|
+
|
|
178
|
+
${imports}
|
|
179
|
+
|
|
180
|
+
${entriesArray}
|
|
181
|
+
${entryIdType}
|
|
182
|
+
|
|
183
|
+
${mappingExport}
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
// Write barrel file
|
|
187
|
+
const barrelPath = `${barrelDir}.ts`;
|
|
188
|
+
await writeFile(barrelPath, barrelContent);
|
|
189
|
+
|
|
190
|
+
if (!silent) {
|
|
191
|
+
console.log(` Generated: ${relative(process.cwd(), barrelPath)}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return barrelPath;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Generate all barrel files in a directory
|
|
199
|
+
* @param {string} barrelsRoot
|
|
200
|
+
* @param {{ silent?: boolean }} [options]
|
|
201
|
+
*/
|
|
202
|
+
export async function generateBarrels(barrelsRoot, options = {}) {
|
|
203
|
+
const { silent = false } = options;
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const entries = await readdir(barrelsRoot, { withFileTypes: true });
|
|
207
|
+
const results = [];
|
|
208
|
+
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
if (entry.isDirectory()) {
|
|
211
|
+
const barrelDir = join(barrelsRoot, entry.name);
|
|
212
|
+
const result = await generateBarrel(barrelDir, { silent: true });
|
|
213
|
+
if (result) {
|
|
214
|
+
results.push(result);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!silent && results.length > 0) {
|
|
220
|
+
console.log(`Generated ${results.length} barrel file(s)`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return results;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (error.code === "ENOENT") {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|