honertia 0.1.1 → 0.1.2
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 +288 -20
- package/dist/helpers.d.ts +17 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +30 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,20 +9,13 @@
|
|
|
9
9
|
[](https://workers.cloudflare.com/)
|
|
10
10
|
[](https://effect.website/)
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Overview
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
An Inertia.js-style adapter for Hono with Effect.js integration. Inertia keeps a server-driven app but behaves like an SPA: link clicks and form posts are intercepted, a fetch/XHR request returns a JSON page object (component + props), and the client swaps the page without a full reload. Honertia layers Laravel-style route patterns and Effect actions on top of that so handlers stay clean, readable, and composable.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
## Raison d'être
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- **Runtime**: Node.js 18+ or Bun 1.0+
|
|
21
|
-
- **Peer Dependencies**:
|
|
22
|
-
- `hono` >= 4.0.0
|
|
23
|
-
- `better-auth` >= 1.0.0
|
|
24
|
-
- **Dependencies**:
|
|
25
|
-
- `effect` >= 3.12.0
|
|
18
|
+
I've found myself wanting to use Cloudflare Workers for everything, but having come from a Laravel background nothing quite matched the DX and simplicity of Laravel x Inertia.js. When building Laravel projects I would always use Loris Leiva's laravel-actions package among other opinionated architecture decisions such as Vite, Tailwind, Bun, React etc., all of which have or will be incorporated into this project. With Cloudflare Workers the obvious choice is Hono and so we've adapted the Inertia.js protocol to run on workers+hono to mimic a Laravel-style app. Ever since learning of Effect.ts I've known that I wanted to use it for something bigger, and so we've utilised it here. Ultimately this is a workers+hono+vite+bun+laravel+inertia+effect+betterauth+planetscale mashup.
|
|
26
19
|
|
|
27
20
|
## Installation
|
|
28
21
|
|
|
@@ -36,8 +29,9 @@ bun add honertia
|
|
|
36
29
|
// src/index.ts
|
|
37
30
|
import { Hono } from 'hono'
|
|
38
31
|
import { logger } from 'hono/logger'
|
|
39
|
-
import { setupHonertia, createTemplate, registerErrorHandlers, vite } from 'honertia'
|
|
32
|
+
import { setupHonertia, createTemplate, createVersion, registerErrorHandlers, vite } from 'honertia'
|
|
40
33
|
import { Context, Layer } from 'effect'
|
|
34
|
+
import manifest from '../dist/manifest.json'
|
|
41
35
|
|
|
42
36
|
import { createDb } from './db'
|
|
43
37
|
import type { Env } from './types'
|
|
@@ -45,13 +39,16 @@ import { createAuth } from './lib/auth'
|
|
|
45
39
|
import { registerRoutes } from './routes'
|
|
46
40
|
|
|
47
41
|
const app = new Hono<Env>()
|
|
42
|
+
const assetVersion = createVersion(manifest)
|
|
43
|
+
const entry = manifest['src/main.tsx']
|
|
44
|
+
const assetPath = (path: string) => `/${path}`
|
|
48
45
|
|
|
49
46
|
class BindingsService extends Context.Tag('app/Bindings')<
|
|
50
47
|
BindingsService,
|
|
51
48
|
{ KV: KVNamespace }
|
|
52
49
|
>() {}
|
|
53
50
|
|
|
54
|
-
//
|
|
51
|
+
// Request-scoped setup: put db/auth on c.var so Honertia/Effect can read them.
|
|
55
52
|
app.use('*', async (c, next) => {
|
|
56
53
|
c.set('db', createDb(c.env.DATABASE_URL))
|
|
57
54
|
c.set('auth', createAuth({
|
|
@@ -62,24 +59,28 @@ app.use('*', async (c, next) => {
|
|
|
62
59
|
await next()
|
|
63
60
|
})
|
|
64
61
|
|
|
65
|
-
// Honertia
|
|
62
|
+
// Honertia bundles the core middleware + auth loading + Effect runtime setup.
|
|
66
63
|
app.use('*', setupHonertia<Env, BindingsService>({
|
|
67
64
|
honertia: {
|
|
68
|
-
|
|
65
|
+
// Use your asset manifest hash so Inertia reloads on deploy.
|
|
66
|
+
version: assetVersion,
|
|
69
67
|
render: createTemplate((ctx) => {
|
|
70
68
|
const isProd = ctx.env.ENVIRONMENT === 'production'
|
|
71
69
|
return {
|
|
72
|
-
title: '
|
|
73
|
-
scripts: isProd ? [
|
|
70
|
+
title: 'My Web App',
|
|
71
|
+
scripts: isProd ? [assetPath(entry.file)] : [vite.script()],
|
|
72
|
+
styles: isProd ? (entry.css ?? []).map(assetPath) : [],
|
|
74
73
|
head: isProd ? '' : vite.hmrHead(),
|
|
75
74
|
}
|
|
76
75
|
}),
|
|
77
76
|
},
|
|
78
77
|
effect: {
|
|
78
|
+
// Expose Cloudflare bindings to Effect handlers via a service layer.
|
|
79
79
|
services: (c) => Layer.succeed(BindingsService, {
|
|
80
80
|
KV: c.env.MY_KV,
|
|
81
81
|
}),
|
|
82
82
|
},
|
|
83
|
+
// Optional: extra Hono middleware in the same chain.
|
|
83
84
|
middleware: [
|
|
84
85
|
logger(),
|
|
85
86
|
// register additional middleware here...
|
|
@@ -101,16 +102,18 @@ import { effectAuthRoutes, RequireAuthLayer } from 'honertia/auth'
|
|
|
101
102
|
import { showDashboard, listProjects, createProject, showProject, deleteProject } from './actions'
|
|
102
103
|
|
|
103
104
|
export function registerRoutes(app: Hono<Env>) {
|
|
104
|
-
// Auth routes (login, register, logout, API handler)
|
|
105
|
+
// Auth routes (login, register, logout, API handler) wired to better-auth.
|
|
106
|
+
// CORS for /api/auth/* can be enabled via the `cors` option (see below).
|
|
105
107
|
effectAuthRoutes(app, {
|
|
106
108
|
loginComponent: 'Auth/Login',
|
|
107
109
|
registerComponent: 'Auth/Register',
|
|
108
110
|
})
|
|
109
111
|
|
|
110
|
-
//
|
|
112
|
+
// Effect routes give you typed, DI-friendly handlers (no direct Hono ctx).
|
|
111
113
|
effectRoutes(app)
|
|
112
114
|
.provide(RequireAuthLayer)
|
|
113
115
|
.group((route) => {
|
|
116
|
+
// Grouped routes share layers and path prefixes.
|
|
114
117
|
route.get('/', showDashboard) // GET example.com
|
|
115
118
|
|
|
116
119
|
route.prefix('/projects').group((route) => {
|
|
@@ -123,6 +126,163 @@ export function registerRoutes(app: Hono<Env>) {
|
|
|
123
126
|
}
|
|
124
127
|
```
|
|
125
128
|
|
|
129
|
+
### Environment Variables
|
|
130
|
+
|
|
131
|
+
Honertia reads these from `c.env` (Cloudflare Workers bindings):
|
|
132
|
+
|
|
133
|
+
```toml
|
|
134
|
+
# wrangler.toml
|
|
135
|
+
ENVIRONMENT = "production"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
If you prefer `wrangler.jsonc`, the same binding looks like:
|
|
139
|
+
|
|
140
|
+
```jsonc
|
|
141
|
+
{
|
|
142
|
+
"vars": {
|
|
143
|
+
"ENVIRONMENT": "production"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Set secrets like `DATABASE_URL` and `BETTER_AUTH_SECRET` via Wrangler (not in source control):
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
wrangler secret put DATABASE_URL
|
|
152
|
+
wrangler secret put BETTER_AUTH_SECRET
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Client Setup (React + Inertia)
|
|
156
|
+
|
|
157
|
+
Honertia uses the standard Inertia React client. You'll need a client entry
|
|
158
|
+
point and a Vite build that emits a manifest (for `createVersion`).
|
|
159
|
+
|
|
160
|
+
Install client dependencies:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
bun add react react-dom @inertiajs/react
|
|
164
|
+
bun add -d @vitejs/plugin-react tailwindcss @tailwindcss/vite
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Create a Vite config that enables Tailwind v4, sets up an alias used in the
|
|
168
|
+
examples, and emits `dist/manifest.json`:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// vite.config.ts
|
|
172
|
+
import { defineConfig } from 'vite'
|
|
173
|
+
import react from '@vitejs/plugin-react'
|
|
174
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
175
|
+
import path from 'path'
|
|
176
|
+
|
|
177
|
+
export default defineConfig({
|
|
178
|
+
plugins: [tailwindcss(), react()],
|
|
179
|
+
build: {
|
|
180
|
+
outDir: 'dist',
|
|
181
|
+
// Use an explicit filename so imports match build output.
|
|
182
|
+
manifest: 'manifest.json',
|
|
183
|
+
emptyOutDir: true,
|
|
184
|
+
},
|
|
185
|
+
resolve: {
|
|
186
|
+
alias: {
|
|
187
|
+
'~': path.resolve(__dirname, 'src'),
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Create a Tailwind CSS entry file:
|
|
194
|
+
|
|
195
|
+
```css
|
|
196
|
+
/* src/styles.css */
|
|
197
|
+
@import "tailwindcss";
|
|
198
|
+
|
|
199
|
+
@layer base {
|
|
200
|
+
body {
|
|
201
|
+
margin: 0;
|
|
202
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
203
|
+
background-color: #f8fafc;
|
|
204
|
+
color: #0f172a;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Set up the Inertia client entry point (default path matches `vite.script()`):
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
// src/main.tsx
|
|
213
|
+
import './styles.css'
|
|
214
|
+
|
|
215
|
+
import { createInertiaApp } from '@inertiajs/react'
|
|
216
|
+
import { createRoot } from 'react-dom/client'
|
|
217
|
+
|
|
218
|
+
const pages = import.meta.glob('./pages/**/*.tsx')
|
|
219
|
+
|
|
220
|
+
createInertiaApp({
|
|
221
|
+
resolve: (name) => {
|
|
222
|
+
const page = pages[`./pages/${name}.tsx`]
|
|
223
|
+
if (!page) {
|
|
224
|
+
throw new Error(`Page not found: ${name}`)
|
|
225
|
+
}
|
|
226
|
+
return page()
|
|
227
|
+
},
|
|
228
|
+
setup({ el, App, props }) {
|
|
229
|
+
createRoot(el).render(<App {...props} />)
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The `resolve` function maps `render('Projects/Index')` to
|
|
235
|
+
`src/pages/Projects/Index.tsx`.
|
|
236
|
+
|
|
237
|
+
Optional: add a `tailwind.config.ts` only if you need theme extensions or
|
|
238
|
+
custom content globs.
|
|
239
|
+
|
|
240
|
+
### Build & Deploy Notes
|
|
241
|
+
|
|
242
|
+
The server imports `dist/manifest.json`, so it must exist at build time. In
|
|
243
|
+
production, read scripts and styles from the manifest (Tailwind's CSS is listed
|
|
244
|
+
under your entry's `css` array). When deploying with Wrangler, build the client
|
|
245
|
+
assets first:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# build client assets before deploying the worker
|
|
249
|
+
bun run build:client
|
|
250
|
+
wrangler deploy
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Optional dev convenience: if you want to run the worker without building the
|
|
254
|
+
client, you can keep a stub `dist/manifest.json` (ignored by git) and replace it
|
|
255
|
+
once you run `vite build`.
|
|
256
|
+
|
|
257
|
+
### Recommended File Structure
|
|
258
|
+
|
|
259
|
+
Here's a minimal project layout that matches the Quick Start:
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
.
|
|
263
|
+
├── src/
|
|
264
|
+
│ ├── index.ts # Hono app setup (setupHonertia)
|
|
265
|
+
│ ├── routes.ts # effectRoutes / effectAuthRoutes
|
|
266
|
+
│ ├── main.tsx # Inertia + React client entry
|
|
267
|
+
│ ├── styles.css # Tailwind CSS entry
|
|
268
|
+
│ ├── actions/
|
|
269
|
+
│ │ └── projects/
|
|
270
|
+
│ │ └── list.ts # listProjects action
|
|
271
|
+
│ ├── pages/
|
|
272
|
+
│ │ └── Projects/
|
|
273
|
+
│ │ └── Index.tsx # render('Projects/Index')
|
|
274
|
+
│ ├── db.ts
|
|
275
|
+
│ ├── lib/
|
|
276
|
+
│ │ └── auth.ts
|
|
277
|
+
│ └── types.ts
|
|
278
|
+
├── dist/
|
|
279
|
+
│ └── manifest.json # generated by Vite build
|
|
280
|
+
├── vite.config.ts
|
|
281
|
+
├── wrangler.toml # or wrangler.jsonc
|
|
282
|
+
├── package.json
|
|
283
|
+
└── tsconfig.json
|
|
284
|
+
```
|
|
285
|
+
|
|
126
286
|
### Example Action
|
|
127
287
|
|
|
128
288
|
Here's the `listProjects` action referenced above:
|
|
@@ -157,10 +317,92 @@ export const listProjects = Effect.gen(function* () {
|
|
|
157
317
|
const db = yield* DatabaseService
|
|
158
318
|
const user = yield* AuthUserService
|
|
159
319
|
const props = yield* fetchProjects(db as Database, user)
|
|
160
|
-
return yield* render('
|
|
320
|
+
return yield* render('Projects/Index', props)
|
|
161
321
|
})
|
|
162
322
|
```
|
|
163
323
|
|
|
324
|
+
The component name `Projects/Index` maps to a file on disk. A common
|
|
325
|
+
Vite + React layout is:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
src/pages/Projects/Index.tsx
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
That means the folders mirror the component path, and `Index.tsx` is the file
|
|
332
|
+
that exports the page component. In the example below, `Link` comes from
|
|
333
|
+
`@inertiajs/react` because it performs Inertia client-side visits (preserving
|
|
334
|
+
page state and avoiding full reloads), whereas a plain `<a>` would do a full
|
|
335
|
+
navigation.
|
|
336
|
+
|
|
337
|
+
```tsx
|
|
338
|
+
// src/pages/Projects/Index.tsx
|
|
339
|
+
/**
|
|
340
|
+
* Projects Index Page
|
|
341
|
+
*/
|
|
342
|
+
|
|
343
|
+
import { Link } from '@inertiajs/react'
|
|
344
|
+
import Layout from '~/components/Layout'
|
|
345
|
+
import type { PageProps, Project } from '~/types'
|
|
346
|
+
|
|
347
|
+
interface Props {
|
|
348
|
+
projects: Project[]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export default function ProjectsIndex({ projects }: PageProps<Props>) {
|
|
352
|
+
return (
|
|
353
|
+
<Layout>
|
|
354
|
+
<div className="flex justify-between items-center mb-6">
|
|
355
|
+
<h1 className="text-2xl font-bold text-gray-900">Projects</h1>
|
|
356
|
+
<Link
|
|
357
|
+
href="/projects/create"
|
|
358
|
+
className="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700"
|
|
359
|
+
>
|
|
360
|
+
New Project
|
|
361
|
+
</Link>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div className="bg-white rounded-lg shadow">
|
|
365
|
+
{projects.length === 0 ? (
|
|
366
|
+
<div className="p-6 text-center text-gray-500">
|
|
367
|
+
No projects yet.{' '}
|
|
368
|
+
<Link href="/projects/create" className="text-indigo-600 hover:underline">
|
|
369
|
+
Create your first project
|
|
370
|
+
</Link>
|
|
371
|
+
</div>
|
|
372
|
+
) : (
|
|
373
|
+
<ul className="divide-y divide-gray-200">
|
|
374
|
+
{projects.map((project) => (
|
|
375
|
+
<li key={project.id}>
|
|
376
|
+
<Link
|
|
377
|
+
href={`/projects/${project.id}`}
|
|
378
|
+
className="block px-6 py-4 hover:bg-gray-50"
|
|
379
|
+
>
|
|
380
|
+
<div className="flex justify-between items-start">
|
|
381
|
+
<div>
|
|
382
|
+
<h3 className="text-sm font-medium text-gray-900">
|
|
383
|
+
{project.name}
|
|
384
|
+
</h3>
|
|
385
|
+
{project.description && (
|
|
386
|
+
<p className="text-sm text-gray-500 mt-1">
|
|
387
|
+
{project.description}
|
|
388
|
+
</p>
|
|
389
|
+
)}
|
|
390
|
+
</div>
|
|
391
|
+
<span className="text-sm text-gray-400">
|
|
392
|
+
{new Date(project.createdAt).toLocaleDateString()}
|
|
393
|
+
</span>
|
|
394
|
+
</div>
|
|
395
|
+
</Link>
|
|
396
|
+
</li>
|
|
397
|
+
))}
|
|
398
|
+
</ul>
|
|
399
|
+
)}
|
|
400
|
+
</div>
|
|
401
|
+
</Layout>
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
164
406
|
### Vite Helpers
|
|
165
407
|
|
|
166
408
|
The `vite` helper provides dev/prod asset management:
|
|
@@ -172,6 +414,15 @@ vite.script() // 'http://localhost:5173/src/main.tsx'
|
|
|
172
414
|
vite.hmrHead() // HMR preamble script tags for React Fast Refresh
|
|
173
415
|
```
|
|
174
416
|
|
|
417
|
+
## Requirements
|
|
418
|
+
|
|
419
|
+
- **Runtime**: Node.js 18+ or Bun 1.0+
|
|
420
|
+
- **Peer Dependencies**:
|
|
421
|
+
- `hono` >= 4.0.0
|
|
422
|
+
- `better-auth` >= 1.0.0
|
|
423
|
+
- **Dependencies**:
|
|
424
|
+
- `effect` >= 3.12.0
|
|
425
|
+
|
|
175
426
|
## Core Concepts
|
|
176
427
|
|
|
177
428
|
### Effect-Based Handlers
|
|
@@ -567,6 +818,23 @@ effectAuthRoutes(app, {
|
|
|
567
818
|
})
|
|
568
819
|
```
|
|
569
820
|
|
|
821
|
+
To enable CORS for the auth API handler (`/api/auth/*`), pass a `cors` config.
|
|
822
|
+
By default, no CORS headers are added (recommended when your UI and API share the same origin).
|
|
823
|
+
Use this when your frontend is on a different origin (local dev, separate domain, mobile app, etc.).
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
826
|
+
effectAuthRoutes(app, {
|
|
827
|
+
apiPath: '/api/auth',
|
|
828
|
+
cors: {
|
|
829
|
+
origin: ['http://localhost:5173', 'http://localhost:3000'],
|
|
830
|
+
credentials: true,
|
|
831
|
+
},
|
|
832
|
+
})
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
This sets the appropriate `Access-Control-*` headers and handles `OPTIONS` preflight for the auth API routes.
|
|
836
|
+
Always keep the `origin` list tight; avoid `'*'` for auth endpoints, especially with `credentials: true`.
|
|
837
|
+
|
|
570
838
|
## Action Factories
|
|
571
839
|
|
|
572
840
|
For common patterns, use action factories:
|
package/dist/helpers.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ import type { PageObject } from './types.js';
|
|
|
6
6
|
export interface PageProps {
|
|
7
7
|
errors?: Record<string, string>;
|
|
8
8
|
}
|
|
9
|
+
export interface AssetManifestEntry {
|
|
10
|
+
file?: string;
|
|
11
|
+
css?: string[];
|
|
12
|
+
assets?: string[];
|
|
13
|
+
}
|
|
14
|
+
export type AssetManifest = Record<string, string | AssetManifestEntry>;
|
|
9
15
|
export interface TemplateOptions {
|
|
10
16
|
title?: string;
|
|
11
17
|
scripts?: string[];
|
|
@@ -26,16 +32,23 @@ export interface TemplateOptions {
|
|
|
26
32
|
*
|
|
27
33
|
* @example Dynamic config based on environment
|
|
28
34
|
* ```ts
|
|
35
|
+
* const entry = manifest['src/main.tsx']
|
|
36
|
+
* const assetPath = (path: string) => `/${path}`
|
|
37
|
+
*
|
|
29
38
|
* createTemplate((ctx) => ({
|
|
30
39
|
* title: 'App',
|
|
31
40
|
* scripts: ctx.env.ENVIRONMENT === 'production'
|
|
32
|
-
* ? [
|
|
33
|
-
* : [
|
|
41
|
+
* ? [assetPath(entry.file)]
|
|
42
|
+
* : [vite.script()],
|
|
43
|
+
* styles: ctx.env.ENVIRONMENT === 'production'
|
|
44
|
+
* ? (entry.css ?? []).map(assetPath)
|
|
45
|
+
* : [],
|
|
46
|
+
* head: ctx.env.ENVIRONMENT === 'production' ? '' : vite.hmrHead(),
|
|
34
47
|
* }))
|
|
35
48
|
* ```
|
|
36
49
|
*/
|
|
37
50
|
export declare function createTemplate(options: TemplateOptions | ((ctx: Context) => TemplateOptions)): (page: PageObject, ctx?: Context) => string;
|
|
38
|
-
export declare function createVersion(manifest:
|
|
51
|
+
export declare function createVersion(manifest: AssetManifest): string;
|
|
39
52
|
/**
|
|
40
53
|
* Vite development configuration helpers.
|
|
41
54
|
*/
|
|
@@ -57,7 +70,7 @@ export declare const vite: {
|
|
|
57
70
|
* @param port - Vite dev server port (default: 5173)
|
|
58
71
|
* @example
|
|
59
72
|
* ```ts
|
|
60
|
-
* scripts: isProd ? ['/
|
|
73
|
+
* scripts: isProd ? [manifest['src/main.tsx'].file] : [vite.script()]
|
|
61
74
|
* ```
|
|
62
75
|
*/
|
|
63
76
|
script(entry?: string, port?: number): string;
|
package/dist/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAAC,CAAA;AAEvE,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,eAAe,CAAC,GAC7D,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,MAAM,CA6C7C;AAWD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CA8B7D;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;IACf;;;;;;;;OAQG;4BACmB,MAAM;IAa5B;;;;;;;;;OASG;2CAC2C,MAAM;CAGrD,CAAA"}
|
package/dist/helpers.js
CHANGED
|
@@ -14,11 +14,18 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @example Dynamic config based on environment
|
|
16
16
|
* ```ts
|
|
17
|
+
* const entry = manifest['src/main.tsx']
|
|
18
|
+
* const assetPath = (path: string) => `/${path}`
|
|
19
|
+
*
|
|
17
20
|
* createTemplate((ctx) => ({
|
|
18
21
|
* title: 'App',
|
|
19
22
|
* scripts: ctx.env.ENVIRONMENT === 'production'
|
|
20
|
-
* ? [
|
|
21
|
-
* : [
|
|
23
|
+
* ? [assetPath(entry.file)]
|
|
24
|
+
* : [vite.script()],
|
|
25
|
+
* styles: ctx.env.ENVIRONMENT === 'production'
|
|
26
|
+
* ? (entry.css ?? []).map(assetPath)
|
|
27
|
+
* : [],
|
|
28
|
+
* head: ctx.env.ENVIRONMENT === 'production' ? '' : vite.hmrHead(),
|
|
22
29
|
* }))
|
|
23
30
|
* ```
|
|
24
31
|
*/
|
|
@@ -66,7 +73,26 @@ function escapeHtml(str) {
|
|
|
66
73
|
.replace(/'/g, ''');
|
|
67
74
|
}
|
|
68
75
|
export function createVersion(manifest) {
|
|
69
|
-
const
|
|
76
|
+
const assetFiles = [];
|
|
77
|
+
for (const value of Object.values(manifest)) {
|
|
78
|
+
if (typeof value === 'string') {
|
|
79
|
+
assetFiles.push(value);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!value || typeof value !== 'object') {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value.file === 'string') {
|
|
86
|
+
assetFiles.push(value.file);
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(value.css)) {
|
|
89
|
+
assetFiles.push(...value.css);
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(value.assets)) {
|
|
92
|
+
assetFiles.push(...value.assets);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const combined = assetFiles.sort().join('');
|
|
70
96
|
let hash = 0;
|
|
71
97
|
for (let i = 0; i < combined.length; i++) {
|
|
72
98
|
const char = combined.charCodeAt(i);
|
|
@@ -107,7 +133,7 @@ export const vite = {
|
|
|
107
133
|
* @param port - Vite dev server port (default: 5173)
|
|
108
134
|
* @example
|
|
109
135
|
* ```ts
|
|
110
|
-
* scripts: isProd ? ['/
|
|
136
|
+
* scripts: isProd ? [manifest['src/main.tsx'].file] : [vite.script()]
|
|
111
137
|
* ```
|
|
112
138
|
*/
|
|
113
139
|
script(entry = '/src/main.tsx', port = 5173) {
|