brustjs 0.1.21-alpha → 0.1.22-alpha
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 +12 -1
- package/example/pokedex/components/AddToTeamButton.tsx +55 -64
- package/example/pokedex/lib/loaders.ts +13 -3
- package/example/pokedex/lib/types.ts +6 -3
- package/example/pokedex/pages/DetailPage.tsx +1 -2
- package/package.json +9 -8
- package/runtime/cli/build.ts +41 -0
- package/runtime/cli/native-routes-emit.ts +28 -4
- package/runtime/index.js +52 -52
- package/runtime/index.ts +24 -1
- package/runtime/islands/importmap.ts +6 -0
- package/runtime/native/build.ts +147 -0
- package/runtime/native/index.ts +3 -0
- package/runtime/native/runtime.ts +324 -0
- package/runtime/store/signal.ts +36 -18
package/README.md
CHANGED
|
@@ -87,6 +87,17 @@ brustjs new <name> # scaffold a project (partial — see Status)
|
|
|
87
87
|
`renderToString` runs once per key, then serves a frozen pair from Rust.
|
|
88
88
|
- **`native: true` routes** — JSX compiled to a jinja template at build time and
|
|
89
89
|
rendered Rust-side (`minijinja`), skipping React on the server entirely.
|
|
90
|
+
- **Native interactivity without islands** — Alpine.js-style `x-*` DOM directives
|
|
91
|
+
(`x-data`/`x-text`/`x-show`/`x-bind-*`/`x-on-*`/`x-for`) on a `native` page,
|
|
92
|
+
bound to the store by a small react-free runtime. Logic lives in a co-located
|
|
93
|
+
`export const behavior` (single-file component); each component's JS is a
|
|
94
|
+
separate chunk loaded **on demand** — a page never downloads a component it
|
|
95
|
+
doesn't render.
|
|
96
|
+
- **Isomorphic store** — `brustjs/store`: `signal`/`computed`/`effect` +
|
|
97
|
+
`defineStore(name, factory)`. One `window` singleton per name on the client (so
|
|
98
|
+
separate island/directive chunks share state), a per-request `AsyncLocalStorage`
|
|
99
|
+
instance on the server. `useStore` adapter for React islands; a native directive
|
|
100
|
+
button and a React island reactively share the same store.
|
|
90
101
|
- **Typed actions** — `defineActions().get/post/put/patch/delete/head(path, ctx => R, { body, query })`
|
|
91
102
|
on the server; `client<typeof actions>()` is an Eden-Treaty-style proxy that
|
|
92
103
|
infers the whole API from the server types (no codegen) and returns
|
|
@@ -119,7 +130,7 @@ bun test tests/integration.test.ts # integration (real server)
|
|
|
119
130
|
```
|
|
120
131
|
crates/brust/ Rust: accept loop, worker pool, napi exports, SAB
|
|
121
132
|
crates/jsx-rust-compiler/ JSX → jinja compiler for native: true routes
|
|
122
|
-
runtime/ Bun-side: routing, render, actions, CLI
|
|
133
|
+
runtime/ Bun-side: routing, render, actions, store, native directives, CLI
|
|
123
134
|
example/ pokedex native-first demo
|
|
124
135
|
bench/ · docs/ · architecture.md
|
|
125
136
|
```
|
|
@@ -1,96 +1,87 @@
|
|
|
1
|
-
//
|
|
1
|
+
// NATIVE INTERACTIVE COMPONENT (Spec B dogfood) — the "Add to team / In your
|
|
2
|
+
// team" toggle on the detail page. Formerly a React island; now a single-file
|
|
3
|
+
// native directive component: a co-located `export const behavior` (client
|
|
4
|
+
// logic, react-free) + a JSX `default` export (the native template the compiler
|
|
5
|
+
// lowers to minijinja). The build bundles ONLY `behavior` into _directives.js;
|
|
6
|
+
// the JSX default is tree-shaken out so react never leaks into the client bundle.
|
|
2
7
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
+
// The behavior is react-free: `signal`/`computed` from brustjs/store (the window
|
|
9
|
+
// singleton on the client), `client` from brustjs/client (the treaty action
|
|
10
|
+
// client — also react-free), and the shared teamStore. NO react imports.
|
|
11
|
+
import { client } from 'brustjs/client'
|
|
12
|
+
import { computed, signal } from 'brustjs/store'
|
|
8
13
|
import type { Actions } from '../actions'
|
|
9
14
|
import type { AddToTeamProps } from '../lib/types'
|
|
10
15
|
import { teamStore } from '../stores/team'
|
|
11
16
|
|
|
12
17
|
const api = client<Actions>()
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
// behavior → client bundle, registered as "addToTeamButton" (camelCase filename).
|
|
20
|
+
// `props` is the JSON parsed out of the element's x-props attribute (precomputed
|
|
21
|
+
// by the loader as a JSON string — native templates can't call JSON.stringify).
|
|
22
|
+
export const behavior = ({ props }: { props: AddToTeamProps }) => {
|
|
15
23
|
// Shared store (GAP S4): writing teamStore.members here is observed by the
|
|
16
|
-
// TeamBuilder island — they resolve the same window singleton.
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (r.data) teamStore.members.set(r.data.team)
|
|
26
|
-
})
|
|
27
|
-
}, [])
|
|
24
|
+
// TeamBuilder island — they resolve the same window singleton. A native
|
|
25
|
+
// x-on-click mutation is therefore seen reactively by a React island.
|
|
26
|
+
const busy = signal(false)
|
|
27
|
+
const inTeam = computed(() => (teamStore.members() ?? []).some((m) => m.id === props.id))
|
|
28
|
+
const label = computed(() =>
|
|
29
|
+
inTeam() ? '✓ In your team' : disabled() ? 'Team Full' : '+ Add to team',
|
|
30
|
+
)
|
|
31
|
+
const btnClass = computed(() => `aa-btn aa-btn--full${inTeam() ? ' aa-btn--secondary' : ''}`)
|
|
32
|
+
const disabled = computed(() => busy() || ((teamStore.members()?.length ?? 0) >= 6 && !inTeam()))
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
async function init() {
|
|
35
|
+
const r = await api.team.get()
|
|
36
|
+
if (r.data) teamStore.members.set(r.data.team)
|
|
37
|
+
}
|
|
30
38
|
|
|
31
39
|
async function toggle() {
|
|
32
|
-
|
|
40
|
+
busy.set(true)
|
|
33
41
|
try {
|
|
34
|
-
if (inTeam) {
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
// dispatch returns 411 on non-GET/HEAD without one. See GAPS S12.
|
|
38
|
-
const { data } = await api.team({ id: p.id }).delete({})
|
|
42
|
+
if (inTeam()) {
|
|
43
|
+
// Bodyless DELETE is OK now (GAPS S12 fixed) — no more `.delete({})`.
|
|
44
|
+
const { data } = await api.team({ id: props.id }).delete()
|
|
39
45
|
if (data) teamStore.members.set(data.team)
|
|
40
46
|
} else {
|
|
41
47
|
const { data } = await api.team.post({
|
|
42
|
-
id:
|
|
43
|
-
name:
|
|
44
|
-
displayName:
|
|
45
|
-
num:
|
|
46
|
-
types:
|
|
47
|
-
artwork:
|
|
48
|
+
id: props.id,
|
|
49
|
+
name: props.name,
|
|
50
|
+
displayName: props.displayName,
|
|
51
|
+
num: props.num,
|
|
52
|
+
types: props.types,
|
|
53
|
+
artwork: props.artwork,
|
|
48
54
|
})
|
|
49
|
-
if (data?.full) {
|
|
50
|
-
setToast('ทีมเต็มแล้ว · สูงสุด 6 ตัว')
|
|
51
|
-
setTimeout(() => setToast(null), 2200)
|
|
52
|
-
} else if (data) {
|
|
55
|
+
if (data && !data?.full) {
|
|
53
56
|
teamStore.members.set(data.team)
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
} finally {
|
|
57
|
-
|
|
60
|
+
busy.set(false)
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
return { init, label, btnClass, toggle, disabled }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// default → jinja (server). The x-* directives are static string attributes the
|
|
68
|
+
// native compiler passes straight through; the directive runtime binds them to
|
|
69
|
+
// the behavior instance on the client. `data` is the loader-precomputed JSON
|
|
70
|
+
// string, emitted by the compiler as x-props="{{ (data) | e }}" (XSS-safe).
|
|
71
|
+
export default function AddToTeamButton({ data }: { data: string }) {
|
|
61
72
|
return (
|
|
62
|
-
<div style={{ position: 'relative' }}>
|
|
73
|
+
<div x-data="addToTeamButton" x-props={data} style={{ position: 'relative' }}>
|
|
63
74
|
<button
|
|
64
75
|
type="button"
|
|
65
|
-
|
|
76
|
+
x-text="label"
|
|
77
|
+
x-bind-class="btnClass"
|
|
78
|
+
x-bind-disabled="disabled"
|
|
79
|
+
x-on-click="toggle"
|
|
80
|
+
className="aa-btn aa-btn--full"
|
|
66
81
|
style={{ width: '100%' }}
|
|
67
|
-
onClick={toggle}
|
|
68
|
-
disabled={busy}
|
|
69
82
|
>
|
|
70
|
-
|
|
83
|
+
+ Add to team
|
|
71
84
|
</button>
|
|
72
|
-
{toast && (
|
|
73
|
-
<div
|
|
74
|
-
style={{
|
|
75
|
-
position: 'absolute',
|
|
76
|
-
top: 'calc(100% + 8px)',
|
|
77
|
-
left: 0,
|
|
78
|
-
right: 0,
|
|
79
|
-
zIndex: 50,
|
|
80
|
-
padding: '8px 12px',
|
|
81
|
-
borderRadius: 'var(--radius-md)',
|
|
82
|
-
background: 'var(--danger-50)',
|
|
83
|
-
color: 'var(--danger-700)',
|
|
84
|
-
border: '1px solid rgba(212,28,89,0.25)',
|
|
85
|
-
fontSize: 'var(--text-xs)',
|
|
86
|
-
fontWeight: 600,
|
|
87
|
-
textAlign: 'center',
|
|
88
|
-
boxShadow: 'var(--shadow-md)',
|
|
89
|
-
}}
|
|
90
|
-
>
|
|
91
|
-
{toast}
|
|
92
|
-
</div>
|
|
93
|
-
)}
|
|
94
85
|
</div>
|
|
95
86
|
)
|
|
96
87
|
}
|
|
@@ -163,14 +163,17 @@ export async function detailLoader({ params }: LoaderCtx): Promise<DetailData |
|
|
|
163
163
|
hasAbilities: abilities.length > 0,
|
|
164
164
|
evolution,
|
|
165
165
|
hasEvolution,
|
|
166
|
-
|
|
166
|
+
// Native templates can't call JSON.stringify, so precompute the x-props JSON
|
|
167
|
+
// here. The compiler emits it as x-props="{{ (addProps) | e }}" (XSS-safe);
|
|
168
|
+
// the directive runtime JSON.parses it back into the behavior's `props`.
|
|
169
|
+
addProps: JSON.stringify({
|
|
167
170
|
id: p.id,
|
|
168
171
|
name: p.name,
|
|
169
172
|
displayName: cap(p.name),
|
|
170
173
|
num: pad(p.id),
|
|
171
174
|
types: p.types,
|
|
172
175
|
artwork: p.artwork,
|
|
173
|
-
},
|
|
176
|
+
}),
|
|
174
177
|
teamProps: { teamInitial: teamStore.list() },
|
|
175
178
|
}
|
|
176
179
|
}
|
|
@@ -197,7 +200,14 @@ function emptyDetail(name: string): DetailData {
|
|
|
197
200
|
hasAbilities: false,
|
|
198
201
|
evolution: [],
|
|
199
202
|
hasEvolution: false,
|
|
200
|
-
addProps: {
|
|
203
|
+
addProps: JSON.stringify({
|
|
204
|
+
id: 0,
|
|
205
|
+
name,
|
|
206
|
+
displayName: cap(name),
|
|
207
|
+
num: '',
|
|
208
|
+
types: [],
|
|
209
|
+
artwork: '',
|
|
210
|
+
}),
|
|
201
211
|
teamProps: { teamInitial: teamStore.list() },
|
|
202
212
|
}
|
|
203
213
|
}
|
|
@@ -95,12 +95,15 @@ export interface DetailData {
|
|
|
95
95
|
hasAbilities: boolean
|
|
96
96
|
evolution: EvolutionStageVM[]
|
|
97
97
|
hasEvolution: boolean
|
|
98
|
-
//
|
|
99
|
-
addProps
|
|
98
|
+
// native interactive props: a single string path each (native props can't be
|
|
99
|
+
// object literals). addProps is the loader-precomputed JSON string handed to
|
|
100
|
+
// <AddToTeamButton data={addProps} /> → x-props (Spec B native directives).
|
|
101
|
+
addProps: string
|
|
100
102
|
teamProps: { teamInitial: TeamMember[] }
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
/**
|
|
105
|
+
/** Shape of the AddToTeamButton native behavior's `props` (JSON-parsed from
|
|
106
|
+
* x-props). Matches the action body fields so toggle() can post it directly. */
|
|
104
107
|
export interface AddToTeamProps {
|
|
105
108
|
id: number
|
|
106
109
|
name: string
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
// `{s.showLevel && <Level/>}` separators — no more loader-computed hide-classes.
|
|
10
10
|
// The <title> is dynamic via `<BrustPage title={pageTitle}>` (S8) and inline
|
|
11
11
|
// styles use `style={{…}}` objects (S1).
|
|
12
|
-
import { Island } from 'brustjs'
|
|
13
12
|
import AddToTeamButton from '../components/AddToTeamButton'
|
|
14
13
|
import PageLayout from '../components/PageLayout'
|
|
15
14
|
import type { DetailData } from '../lib/types'
|
|
@@ -71,7 +70,7 @@ export default function DetailPage({
|
|
|
71
70
|
</span>
|
|
72
71
|
))}
|
|
73
72
|
</div>
|
|
74
|
-
<
|
|
73
|
+
<AddToTeamButton native data={addProps} />
|
|
75
74
|
</div>
|
|
76
75
|
|
|
77
76
|
<div className="dex-detail-right">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brustjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22-alpha",
|
|
4
4
|
"description": "Bun + Rust SSR framework — React on the server, Rust everywhere else (napi cdylib + per-worker SharedArrayBuffer).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"typescript": "^6.0.3"
|
|
41
41
|
},
|
|
42
42
|
"optionalDependencies": {
|
|
43
|
-
"brustjs-darwin-x64": "0.1.
|
|
44
|
-
"brustjs-darwin-arm64": "0.1.
|
|
45
|
-
"brustjs-linux-x64-gnu": "0.1.
|
|
46
|
-
"brustjs-linux-arm64-gnu": "0.1.
|
|
47
|
-
"brustjs-linux-x64-musl": "0.1.
|
|
48
|
-
"brustjs-linux-arm64-musl": "0.1.
|
|
43
|
+
"brustjs-darwin-x64": "0.1.22-alpha",
|
|
44
|
+
"brustjs-darwin-arm64": "0.1.22-alpha",
|
|
45
|
+
"brustjs-linux-x64-gnu": "0.1.22-alpha",
|
|
46
|
+
"brustjs-linux-arm64-gnu": "0.1.22-alpha",
|
|
47
|
+
"brustjs-linux-x64-musl": "0.1.22-alpha",
|
|
48
|
+
"brustjs-linux-arm64-musl": "0.1.22-alpha"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "^19.2.6",
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
"./routes": "./runtime/routes.ts",
|
|
69
69
|
"./client": "./runtime/client/index.ts",
|
|
70
70
|
"./create": "./runtime/create.ts",
|
|
71
|
-
"./store": "./runtime/store/index.ts"
|
|
71
|
+
"./store": "./runtime/store/index.ts",
|
|
72
|
+
"./native": "./runtime/native/index.ts"
|
|
72
73
|
},
|
|
73
74
|
"files": [
|
|
74
75
|
"runtime",
|
package/runtime/cli/build.ts
CHANGED
|
@@ -248,6 +248,47 @@ export async function runBuild(args: string[]): Promise<void> {
|
|
|
248
248
|
console.log('[brust build] islands: skipped (no <Island> usage)')
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// 3.5. Build the directive runtime bundle (if any native interactive component —
|
|
252
|
+
// a file with `export const behavior` — is reachable from the routes graph).
|
|
253
|
+
// MUST run AFTER buildIslands: buildIslands does `rm -rf outDir/islands`, so
|
|
254
|
+
// running this first would wipe _directives.js. This block creates the islands
|
|
255
|
+
// dir itself (the islands block is skipped when there are no <Island> usages).
|
|
256
|
+
{
|
|
257
|
+
const { scanDirectiveComponents, buildDirectives } = await import('../native/build.ts')
|
|
258
|
+
let directiveComponents = new Map<string, string>()
|
|
259
|
+
if (existsSync(routesFile)) {
|
|
260
|
+
try {
|
|
261
|
+
directiveComponents = scanDirectiveComponents(routesFile)
|
|
262
|
+
} catch (err) {
|
|
263
|
+
// e.g. two files derive the same directive register name — surface a clean
|
|
264
|
+
// message instead of an unformatted stack out of `brust build`.
|
|
265
|
+
console.error(`[brust build] directives: ${(err as Error).message}`)
|
|
266
|
+
process.exit(1)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (directiveComponents.size > 0) {
|
|
270
|
+
const islandsOutDir = path.join(outDir, 'islands')
|
|
271
|
+
const result = await buildDirectives(directiveComponents, { outDir: islandsOutDir })
|
|
272
|
+
console.log(
|
|
273
|
+
`[brust build] directives: runtime + ${result.count} component chunk(s) → ${islandsOutDir}`,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
// Mirror every directive file (_directives.js + each <name>.directive.js) into
|
|
277
|
+
// cwd/.brust/islands for the source runtime (the islands block's whole-dir mirror
|
|
278
|
+
// ran before these existed, so copy them explicitly). Create the dir in case the
|
|
279
|
+
// islands block was skipped.
|
|
280
|
+
const localIslandsDir = path.join(process.cwd(), '.brust', 'islands')
|
|
281
|
+
if (path.resolve(localIslandsDir) !== path.resolve(islandsOutDir)) {
|
|
282
|
+
await mkdir(localIslandsDir, { recursive: true })
|
|
283
|
+
for (const f of result.files) {
|
|
284
|
+
await cp(path.join(islandsOutDir, f), path.join(localIslandsDir, f))
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
console.log('[brust build] directives: skipped (no export-const-behavior components)')
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
251
292
|
// 4. MCP manifest (if routes.tsx exists).
|
|
252
293
|
let loadedRoutes: any[] | undefined
|
|
253
294
|
if (existsSync(routesFile)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { dirname, relative, resolve } from 'node:path'
|
|
3
3
|
import { buildDevClientTag } from '../dev/client.ts'
|
|
4
|
-
import { ISLANDS_IMPORTMAP_AND_BOOTSTRAP } from '../islands/importmap.ts'
|
|
4
|
+
import { DIRECTIVES_BOOTSTRAP, ISLANDS_IMPORTMAP_AND_BOOTSTRAP } from '../islands/importmap.ts'
|
|
5
5
|
|
|
6
6
|
/** Gather transitive component sources starting from a page source file.
|
|
7
7
|
*
|
|
@@ -104,6 +104,20 @@ function injectDevClientIntoTemplate(template: string): string {
|
|
|
104
104
|
return template + tag
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/** Bake the directive runtime loader into a native template iff it uses any
|
|
108
|
+
* x-data directive. Idempotent. Wrapped in {% raw %} for symmetry with the islands
|
|
109
|
+
* bootstrap bake (the tag has no {{ }} but the wrap is harmless + consistent). */
|
|
110
|
+
export function bakeDirectivesIfUsed(template: string, force = false): string {
|
|
111
|
+
// `force` (app has ≥1 directive component) bakes on EVERY native page so the
|
|
112
|
+
// runtime is live to catch SPA-nav swaps into a directive page. Otherwise
|
|
113
|
+
// attribute-anchored (`x-data=`) so a literal "x-data" in text/content can't
|
|
114
|
+
// trigger a stray <script> that would 404 (no bundle built for that route).
|
|
115
|
+
if (!force && !/x-data=/.test(template)) return template
|
|
116
|
+
const baked = `{% raw %}${DIRECTIVES_BOOTSTRAP}{% endraw %}`
|
|
117
|
+
if (template.includes(baked)) return template
|
|
118
|
+
return template + baked
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
/** Sub-project J — build pass that turns user's `pages/<Name>.tsx` files into
|
|
108
122
|
* `.brust/jinja/<Name>.jinja` templates. Invoked from `brust build` and
|
|
109
123
|
* `brust dev` after the user's routes are flattened.
|
|
@@ -327,6 +341,17 @@ export async function emitNativeTemplates(opts: NativeRouteEmitOpts): Promise<vo
|
|
|
327
341
|
const importMap =
|
|
328
342
|
nativeRoutes.length > 0 ? scanImports(opts.entryFile) : new Map<string, string>()
|
|
329
343
|
|
|
344
|
+
// App-wide directive presence: if ANY native interactive component exists, the
|
|
345
|
+
// directive runtime (`_directives.js`) must load on EVERY native page — not just
|
|
346
|
+
// pages whose own template uses x-data. SPA nav (owned by the islands bootstrap)
|
|
347
|
+
// swaps <main> but does NOT execute <script> tags in the swapped HTML, so the
|
|
348
|
+
// runtime must already be live on the page you navigate FROM for its
|
|
349
|
+
// MutationObserver to mount the incoming x-data. Dynamic import = call-time
|
|
350
|
+
// (avoids a module-eval cycle with native/build.ts → scanImports here).
|
|
351
|
+
const hasDirectives =
|
|
352
|
+
nativeRoutes.length > 0 &&
|
|
353
|
+
(await import('../native/build.ts')).scanDirectiveComponents(opts.entryFile).size > 0
|
|
354
|
+
|
|
330
355
|
const built: string[] = []
|
|
331
356
|
for (const r of nativeRoutes) {
|
|
332
357
|
const name = r.nativeTemplate!
|
|
@@ -357,10 +382,9 @@ export async function emitNativeTemplates(opts: NativeRouteEmitOpts): Promise<vo
|
|
|
357
382
|
// Dev-only: native routes don't pass through the React renderer's dev-client
|
|
358
383
|
// injection, so splice the /_brust/dev WS script in here. reEmitJinja() runs
|
|
359
384
|
// this on every hot reload, so the script is always present in dev.
|
|
385
|
+
const withDirectives = bakeDirectivesIfUsed(compiled.template, hasDirectives)
|
|
360
386
|
const template =
|
|
361
|
-
process.env.BRUST_DEV === '1'
|
|
362
|
-
? injectDevClientIntoTemplate(compiled.template)
|
|
363
|
-
: compiled.template
|
|
387
|
+
process.env.BRUST_DEV === '1' ? injectDevClientIntoTemplate(withDirectives) : withDirectives
|
|
364
388
|
writeFileSync(outPath, template)
|
|
365
389
|
built.push(name)
|
|
366
390
|
|