@heshammesalam/heshjs 0.2.0 → 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.
- package/README.md +17 -12
- package/cli.js +31 -0
- package/dist/architecture/diagnostics.d.ts +12 -0
- package/dist/architecture/diagnostics.js +8 -0
- package/dist/architecture/diagnostics.js.map +1 -0
- package/dist/architecture/html.d.ts +18 -0
- package/dist/architecture/html.js +150 -0
- package/dist/architecture/html.js.map +1 -0
- package/dist/architecture/index.d.ts +42 -0
- package/dist/architecture/index.js +428 -0
- package/dist/architecture/index.js.map +1 -0
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/index.js.map +1 -1
- package/dist/destinations/index.d.ts +63 -0
- package/dist/destinations/index.js +186 -0
- package/dist/destinations/index.js.map +1 -0
- package/dist/dom/index.d.ts +2 -0
- package/dist/dom/index.js +5 -1
- package/dist/dom/index.js.map +1 -1
- package/dist/domains/index.d.ts +24 -0
- package/dist/domains/index.js +110 -0
- package/dist/domains/index.js.map +1 -0
- package/dist/heshjs/index.d.ts +2 -0
- package/dist/heshjs/index.js +1 -0
- package/dist/heshjs/index.js.map +1 -1
- package/dist/hmvc/index.d.ts +2 -0
- package/dist/hmvc/index.js +11 -0
- package/dist/hmvc/index.js.map +1 -1
- package/dist/parser/index.d.ts +15 -1
- package/dist/parser/index.js +73 -3
- package/dist/parser/index.js.map +1 -1
- package/dist/presentation/index.d.ts +12 -0
- package/dist/presentation/index.js +39 -0
- package/dist/presentation/index.js.map +1 -0
- package/dist/units/index.d.ts +13 -0
- package/dist/units/index.js +16 -0
- package/dist/units/index.js.map +1 -0
- package/dist/vite/index.d.ts +4 -1
- package/dist/vite/index.js +36 -2
- package/dist/vite/index.js.map +1 -1
- package/docs/contracts.md +67 -0
- package/docs/domain-graph.md +205 -0
- package/docs/grammar.md +4 -0
- package/docs/units.md +71 -0
- package/package.json +10 -4
- package/scaffold/assets/hesh-icon.png +0 -0
- package/scaffold/create.js +78 -0
- package/scaffold/template.js +188 -0
- package/src/architecture/diagnostics.ts +11 -0
- package/src/architecture/html.ts +101 -0
- package/src/architecture/index.ts +291 -0
- package/src/compiler/index.ts +1 -0
- package/src/destinations/index.ts +147 -0
- package/src/dom/index.ts +6 -1
- package/src/domains/index.ts +97 -0
- package/src/heshjs/index.ts +2 -0
- package/src/hmvc/index.ts +7 -0
- package/src/parser/index.ts +55 -4
- package/src/presentation/index.ts +33 -0
- package/src/units/index.ts +23 -0
- package/src/vite/index.ts +24 -2
package/dist/vite/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,MAAM,UAAU,IAAI;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Plugin } from 'vite';\nimport { compile } from '../compiler/index.js';\nimport { createArchitectureCompiler, writeProject, formatDiagnostic, type ProjectOptions } from '../architecture/index.js';\nimport { resolve } from 'node:path';\nexport function hesh(options: { architecture?: ProjectOptions } = {}): Plugin {\n let production = false;\n const architecture = createArchitectureCompiler();\n let project: ProjectOptions | undefined;\n let watched: string[] = [];\n let warn: ((message: string) => void) | undefined;\n const check = () => {\n if (!project) return;\n const result = architecture.check(project);\n if (result.diagnostics.some(item => item.severity === 'error')) throw new Error(result.diagnostics.map(formatDiagnostic).join('\\n'));\n for (const item of result.diagnostics) if (item.severity === 'warning') warn?.(formatDiagnostic(item));\n writeProject(result); watched = result.files;\n };\n return {\n name: 'heshjs', enforce: 'pre',\n config() { return { appType: 'spa' }; },\n configResolved(config) {\n production = config.command === 'build';\n warn = message => config.logger.warn(message);\n if (options.architecture) { project = { ...options.architecture, root: resolve(config.root, options.architecture.root) }; check(); }\n },\n buildStart() { for (const file of watched) this.addWatchFile(file); },\n transform(source, id) {\n if (project && /(?:^|\\/)server(?:\\/|$)|\\.server\\.[cm]?[jt]s$/.test(id.replace(/\\\\/g, '/'))) throw new Error(`HESH-SEC-001 Server-only module cannot enter the browser bundle: ${id}`);\n if (!id.endsWith('.hesh')) return;\n const result = compile(source, { filename: id, development: !production });\n return { code: result.code, map: null };\n },\n handleHotUpdate(context) {\n if (project && (watched.includes(context.file.replace(/\\\\/g, '/').toLowerCase()) || watched.includes(context.file.replace(/\\\\/g, '/')))) {\n check(); context.server.ws.send({ type: 'full-reload' }); return [];\n }\n if (context.file.endsWith('.hesh')) { context.server.ws.send({ type: 'full-reload' }); return []; }\n }\n };\n}\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Architecture declaration grammar (experimental)
|
|
2
|
+
|
|
3
|
+
These declarations extend the existing Hesh lexer/parser. A file may contain
|
|
4
|
+
views or architecture declarations; project compilation resolves architecture.
|
|
5
|
+
Imports in architecture files reference other .hesh descriptor files explicitly.
|
|
6
|
+
|
|
7
|
+
```ebnf
|
|
8
|
+
domain = "domain", Identifier, "{",
|
|
9
|
+
"implementation", ":", String, ";",
|
|
10
|
+
[ "owns", "{", { Identifier, ";" }, "}" ],
|
|
11
|
+
"exposes", "{", { Path, ";" }, "}",
|
|
12
|
+
[ "consumes", "{", { Path, ";" }, "}" ],
|
|
13
|
+
[ "access", "{", { Path, ":", String, ";" }, "}" ], "}" ;
|
|
14
|
+
presentation = "presentation", Identifier, "{",
|
|
15
|
+
"html", ":", String, ";", "logic", ":", String, ";",
|
|
16
|
+
[ "styles", ":", String, ";" ],
|
|
17
|
+
"bindings", "{", { Identifier, ";" }, "}",
|
|
18
|
+
"actions", "{", { Identifier, ";" }, "}",
|
|
19
|
+
[ "parts", "{", { Identifier, ";" }, "}" ], "}" ;
|
|
20
|
+
destination = "destination", Identifier, "{",
|
|
21
|
+
"url", ":", String, ";",
|
|
22
|
+
"input", "{", { Identifier, ":", ("string" | "number" | "boolean"), ";" }, "}",
|
|
23
|
+
"presentation", ":", Identifier, ";", [ "access", ":", String, ";" ], "}" ;
|
|
24
|
+
application = "application", Identifier, "{",
|
|
25
|
+
"domains", "{", { Identifier, ";" }, "}",
|
|
26
|
+
"destinations", "{", { Identifier, ";" }, "}", "}" ;
|
|
27
|
+
Path = Identifier, { ".", Identifier } ;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Fields/blocks can appear in any order, once each. Unknown/duplicate fields are
|
|
31
|
+
errors. Strings use ordinary JS string escaping. Identifiers cannot be prototype
|
|
32
|
+
mutation names. Locations are retained for diagnostics. Semicolons are required.
|
|
33
|
+
|
|
34
|
+
`implementation` and `logic` have form `./module.js#exportName` (or .ts/.mjs).
|
|
35
|
+
A domain factory takes a dependencies object and returns its actual API. Every
|
|
36
|
+
exposed dotted operation must exist and be callable according to the TS checker;
|
|
37
|
+
every consumed path is Domain.operation and must be publicly exposed. An access
|
|
38
|
+
entry must name an exposed operation. `owns` is ownership metadata, not a generated
|
|
39
|
+
entity/validation/database implementation. The runtime passes only declared
|
|
40
|
+
consumed operations and exposes only the validated public facade.
|
|
41
|
+
|
|
42
|
+
A presentation factory receives `{ domains, input, location, signal }` and returns
|
|
43
|
+
`{ bindings, actions }`. bindings values may be signals/readers/ordinary values;
|
|
44
|
+
actions are callable. Both declared names and nested template paths are checked
|
|
45
|
+
against its inferred JS/TS return shape. HTML text `{product.name}` is escaped via
|
|
46
|
+
text nodes. `h-bind="product.name"` binds textContent (cannot have child content),
|
|
47
|
+
`h-action="reserve"` listens to click (submit on a form), `h-value="quantity"`
|
|
48
|
+
binds a writable signal to an input/textarea/select, and
|
|
49
|
+
`h-destination="ProductDetails" h-input-id="product.id"` binds a native anchor to
|
|
50
|
+
a validated destination. Other normal HTML attributes/semantics are preserved.
|
|
51
|
+
No arbitrary JS expressions, raw HTML, inline event attributes, script elements,
|
|
52
|
+
h-each, h-key or unknown h-* directives are accepted in this first milestone.
|
|
53
|
+
Use normal CSS files; template scoping and keyed template lists remain deferred.
|
|
54
|
+
|
|
55
|
+
Standalone presentations also compile to reusable HTML screens. `h-outlet` on an
|
|
56
|
+
empty container creates the persistent shell's destination outlet (not permitted
|
|
57
|
+
inside destination screens). `h-part="Badge"` mounts a declared nested Hesh Unit
|
|
58
|
+
from the factory's `parts.Badge`. Parts inherit the parent context and disposal
|
|
59
|
+
scope. A container may have only one of h-bind, h-outlet or h-part and no children.
|
|
60
|
+
|
|
61
|
+
Destination URL params must exactly match input names. Values are encoded/decoded
|
|
62
|
+
once; number inputs must be finite and booleans serialize as true/false. Duplicate
|
|
63
|
+
or structurally conflicting URL destinations are errors. Generated declarations
|
|
64
|
+
provide TS input types and JS IntelliSense. Runtime validation also rejects missing,
|
|
65
|
+
extra or invalid fields in JavaScript. Destination policy names are client metadata;
|
|
66
|
+
they cannot enforce server-side authorization. A policy without an authorize
|
|
67
|
+
callback is denied rather than silently accepted.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Domain Graph first vertical slice (0.3)
|
|
2
|
+
|
|
3
|
+
This opt-in architecture builds on the existing Hesh compiler, shallow reactivity,
|
|
4
|
+
DOM renderer, router and HMVC. Existing components and `createRouter` remain valid.
|
|
5
|
+
This is the first architecture milestone, not an enterprise-ready framework.
|
|
6
|
+
These APIs require 0.3 or later. Install `@heshammesalam/heshjs@0.3.0` in an
|
|
7
|
+
existing Vite application and enable the architecture option described below.
|
|
8
|
+
The npm creator's default template remains HMVC; it does not scaffold domains.
|
|
9
|
+
The following commands run the reference examples from the framework repository:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install
|
|
13
|
+
npm run check:architecture
|
|
14
|
+
npm run dev:domains # JS Identity/Inventory/Finance demo, localhost:5180
|
|
15
|
+
npm run dev:domains:ts # strict TS alternative, localhost:5181
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The JS demo uses mock identity, an abortable timer-backed mock product API and a
|
|
19
|
+
mock tax service. No HTTP server, credentials, payments or real authorization are
|
|
20
|
+
implemented. Browser refresh restores destination/path input, not in-memory demo
|
|
21
|
+
login state. Journeys are deliberately not simulated.
|
|
22
|
+
|
|
23
|
+
## Descriptors and real implementations
|
|
24
|
+
|
|
25
|
+
```hesh
|
|
26
|
+
domain Inventory {
|
|
27
|
+
implementation: "./inventory.js#createInventory";
|
|
28
|
+
owns { Product; }
|
|
29
|
+
exposes { products.get; stock.reserve; }
|
|
30
|
+
consumes { Identity.session; }
|
|
31
|
+
access { stock.reserve: "inventory.reserve"; }
|
|
32
|
+
}
|
|
33
|
+
presentation ProductScreen {
|
|
34
|
+
html: "./ui/product.html";
|
|
35
|
+
logic: "./ui/product.js#createProduct";
|
|
36
|
+
styles: "./ui/product.css";
|
|
37
|
+
bindings { title; }
|
|
38
|
+
actions { reserve; }
|
|
39
|
+
}
|
|
40
|
+
destination ProductDetails {
|
|
41
|
+
url: "/products/:id";
|
|
42
|
+
input { id: string; }
|
|
43
|
+
presentation: ProductScreen;
|
|
44
|
+
access: "inventory.read";
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// inventory.js: private implementation, no imports from another domain's files.
|
|
50
|
+
export function createInventory(dependencies) {
|
|
51
|
+
return {
|
|
52
|
+
products: { get: id => ({ id, title: 'Mock product' }) },
|
|
53
|
+
stock: { reserve: id => dependencies.Identity.session() + ': ' + id }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`owns` is metadata, not a generated entity/database/validation system. Exposes are
|
|
59
|
+
checked against the real factory return type. Consumers receive only the listed
|
|
60
|
+
public operations, not the whole implementation object. Factories execute once
|
|
61
|
+
per application domain graph in dependency order. Cycles and missing contracts
|
|
62
|
+
are errors. Dependencies and public facades are frozen; disposed graphs reject
|
|
63
|
+
further operation calls. Factories may use `onCleanup` for owned resources.
|
|
64
|
+
|
|
65
|
+
The application descriptor imports domain files explicitly and selects names:
|
|
66
|
+
|
|
67
|
+
```hesh
|
|
68
|
+
import "./domains/identity/domain.hesh";
|
|
69
|
+
import "./domains/inventory/domain.hesh";
|
|
70
|
+
application Shop {
|
|
71
|
+
domains { Identity; Inventory; }
|
|
72
|
+
destinations { ProductDetails; }
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Every selected destination/presentation belongs to one selected domain directory.
|
|
77
|
+
Each domain has its own directory boundary. Shared code can be imported by domains;
|
|
78
|
+
shared code cannot re-export a private domain implementation as an escape hatch.
|
|
79
|
+
|
|
80
|
+
## Standard HTML, CSS and explicit bindings
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<section aria-labelledby="title">
|
|
84
|
+
<h1 id="title">{title}</h1>
|
|
85
|
+
<button h-action="reserve">Reserve</button>
|
|
86
|
+
</section>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import { signal } from '@heshammesalam/heshjs';
|
|
91
|
+
export function createProduct({ domains, input, location, signal: cancellation }) {
|
|
92
|
+
const title = signal(domains.Inventory.products.get(input.id).title);
|
|
93
|
+
return {
|
|
94
|
+
bindings: { title },
|
|
95
|
+
actions: { reserve() { domains.Inventory.stock.reserve(input.id); } }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Bindings are explicit declared data paths, not arbitrary evaluated JS strings.
|
|
101
|
+
`{product.name}` and `h-bind="product.name"` use text nodes. `\{`/`\}` produce
|
|
102
|
+
literal braces. A whole attribute binding like `disabled="{disabled}"` preserves
|
|
103
|
+
boolean values; mixed attribute text is a string. Standard native HTML/custom tags
|
|
104
|
+
are preserved without Hesh element aliases. Browser forms, labels, ARIA and native
|
|
105
|
+
validation remain available. `h-action` means click, or submit on a form (preventing
|
|
106
|
+
full submission). Events are passed to actions and async errors use onError.
|
|
107
|
+
`h-value="email"` is two-way binding to a root writable signal on text controls.
|
|
108
|
+
|
|
109
|
+
`h-destination="ProductDetails" h-input-id="productId"` on a native anchor builds
|
|
110
|
+
a real href from the destination contract. Input attributes follow HTML's case
|
|
111
|
+
insensitivity; destination input names must therefore be unique ignoring case.
|
|
112
|
+
Missing/extra/wrongly typed inputs, missing bindings/actions and unsafe directives
|
|
113
|
+
produce located diagnostics. Dynamic any-typed data emits warnings when the shape
|
|
114
|
+
cannot be proven. JS receives inferred checks/JSDoc where present and runtime
|
|
115
|
+
input validation; this does not make arbitrary JavaScript fully type-safe.
|
|
116
|
+
|
|
117
|
+
HTML is parsed with parse5 at build time and emitted as DOM instructions. No
|
|
118
|
+
innerHTML/eval/Function constructor is used. Script/embedding/foreign/template
|
|
119
|
+
elements, inline event attributes and unknown h-* directives are rejected. SVG,
|
|
120
|
+
keyed h-each, raw HTML and scoped CSS are not implemented in this slice. Standard
|
|
121
|
+
CSS is imported unchanged through Vite, including variables, modern selectors,
|
|
122
|
+
media/container queries, animations and global themes. CSS is global in this release.
|
|
123
|
+
|
|
124
|
+
## Generated typed destinations
|
|
125
|
+
|
|
126
|
+
Configure Vite using `hesh({ architecture: { root: 'src' } })`. For a tsconfig
|
|
127
|
+
outside src add `tsconfig: '../tsconfig.json'`. The plugin validates before build
|
|
128
|
+
and generates `src/.hesh-generated/app.js`, app.d.ts and compiled presentation
|
|
129
|
+
modules. Do not hand-edit generated files. Invalid checks block build; last valid
|
|
130
|
+
files are not deleted, so bypassing the check pipeline can use stale output.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
import { createApplication } from './.hesh-generated/app.js';
|
|
134
|
+
const app = createApplication({
|
|
135
|
+
authorize: policy => policy === 'inventory.read', // CLIENT ONLY
|
|
136
|
+
onError: error => console.error(error),
|
|
137
|
+
onDiagnostic: event => console.debug(event.type, event.destination, event.reason)
|
|
138
|
+
});
|
|
139
|
+
const dispose = app.start(document.querySelector('#app'));
|
|
140
|
+
app.destinations.ProductDetails.open({ id: '15' });
|
|
141
|
+
app.destinations.ProductDetails.replace({ id: '20' }, { query: { tab: 'stock' } });
|
|
142
|
+
await app.destinations.ProductDetails.prefetch({ id: '30' });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Generated declarations give each destination its exact required input fields and
|
|
146
|
+
primitive types. Empty-input destinations use `Record<string, never>`. Runtime
|
|
147
|
+
validation also rejects unknown/missing fields, non-finite numbers, empty strings
|
|
148
|
+
and URL dot segments. Inputs are encoded once and decoded once; URL parameter
|
|
149
|
+
names currently use the existing router's ASCII letter/digit/underscore rules.
|
|
150
|
+
Overlapping destination patterns are rejected so an input cannot silently open a
|
|
151
|
+
different static destination. This restriction applies only to the new contracts;
|
|
152
|
+
the existing router retains its static-priority behavior.
|
|
153
|
+
|
|
154
|
+
Page is the only supported presentation mode. Destinations load their own compiled
|
|
155
|
+
presentation and logic resources lazily. Prefetch deduplicates those code loads;
|
|
156
|
+
it does not fetch business data or authorize operations. Navigation disposes the
|
|
157
|
+
previous presentation, aborts its signal and prevents a late loader from mounting
|
|
158
|
+
stale UI. Dynamic module downloads themselves cannot be canceled. Factories are
|
|
159
|
+
synchronous and start their own asynchronous work using the supplied signal.
|
|
160
|
+
Guard against late results when using non-abortable services. Query/hash changes
|
|
161
|
+
create a fresh presentation instance. Successful mounting focuses a heading or
|
|
162
|
+
`data-hesh-focus`; scroll restoration and modal focus trapping are deferred.
|
|
163
|
+
|
|
164
|
+
`app.status.get()` is reactive loading/ready/error state. Optional diagnostic
|
|
165
|
+
events identify loading, ready, blocked, errors, cancellation and disposal. They
|
|
166
|
+
do not infer business intent or explain arbitrary reactive dependency chains.
|
|
167
|
+
Client policies default to deny when declared without an authorize callback;
|
|
168
|
+
server-side enforcement is still necessary for every protected operation.
|
|
169
|
+
|
|
170
|
+
## Architecture validation and graph tooling
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
hesh check ./src --write
|
|
174
|
+
hesh check ./src --tsconfig ../tsconfig.json --write
|
|
175
|
+
hesh graph ./src
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
From the repository use `node packages/heshjs/cli.js ...`. Root means the directory
|
|
179
|
+
containing app.hesh, typically src. The TS resolver handles aliases, relative paths,
|
|
180
|
+
type imports and re-exports. Canonical paths account for filesystem aliases and
|
|
181
|
+
symlinks. Physical cross-domain imports of implementations are private, even if
|
|
182
|
+
the target JS module exports a function; use declared injection instead. Node and
|
|
183
|
+
server-only modules are blocked in client code. Computed imports in application
|
|
184
|
+
source are rejected because their target cannot be validated. This is build-time
|
|
185
|
+
architecture enforcement for configured projects, not a sandbox against malicious
|
|
186
|
+
application code, reflection or uninspected third-party packages.
|
|
187
|
+
|
|
188
|
+
Diagnostics include code, severity, path, offset/line/column, explanation and a
|
|
189
|
+
suggestion. HTML locations identify their source node/attribute; entity decoding
|
|
190
|
+
can make the exact inner text offset approximate. Graph JSON contains domain
|
|
191
|
+
dependencies, destination ownership/input schemas and compiled navigation links.
|
|
192
|
+
Descriptor ASTs are reused when unchanged and output writes are content-based.
|
|
193
|
+
Semantic analysis currently rechecks the whole graph; precise dependency-aware
|
|
194
|
+
typecheck invalidation and full HTML/Hesh source maps are deferred. The Vite workflow
|
|
195
|
+
uses full reload rather than state-preserving HMR.
|
|
196
|
+
|
|
197
|
+
## Deliberate limits
|
|
198
|
+
|
|
199
|
+
Journeys, modal/panel/nested destination modes, redirects, scroll restoration,
|
|
200
|
+
file routing, generic async resources/caching, deep collections, scoped CSS,
|
|
201
|
+
server authorization, SSR/hydration, advanced scaffolding commands and a language
|
|
202
|
+
server are not implemented. Existing HMVC nested routes remain usable separately.
|
|
203
|
+
Production hosts still need SPA fallback to index.html for direct URLs. No
|
|
204
|
+
performance superiority, globally unique architecture or enterprise-readiness
|
|
205
|
+
claim is made. See architecture/domain-graph-audit.md for migration order.
|
package/docs/grammar.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Milestone 1 language (v0.1)
|
|
2
2
|
|
|
3
|
+
This remains the legacy component grammar. The opt-in architecture descriptor
|
|
4
|
+
extension is specified separately in [contracts.md](contracts.md); it does not
|
|
5
|
+
replace existing .hesh views or add template directives to the brace UI syntax.
|
|
6
|
+
|
|
3
7
|
The grammar is deliberately smaller than the product roadmap. Source is UTF-8.
|
|
4
8
|
Whitespace and JavaScript comments separate tokens. Identifiers follow JavaScript
|
|
5
9
|
identifier rules. Strings and numbers follow JavaScript lexical rules.
|
package/docs/units.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Unified HeshJS 0.5
|
|
2
|
+
|
|
3
|
+
Use Node 22.12 or later:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx @heshammesalam/heshjs@latest create
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Choose your folder and JavaScript or TypeScript. JavaScript is the default.
|
|
10
|
+
For automation: `npx @heshammesalam/heshjs@latest create my-site --ts`.
|
|
11
|
+
Run `cd my-site`, `npm install`, then `npm run dev`.
|
|
12
|
+
|
|
13
|
+
Only `@heshammesalam/heshjs` is needed for Hesh runtime/compiler/CLI features.
|
|
14
|
+
Vite is the build tool; TypeScript is included in typed project development dependencies.
|
|
15
|
+
The former `create-heshjs` package is a legacy entry point. Do not use `npm create`:
|
|
16
|
+
that convention resolves a separate creator package. Use the unified npx command.
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
src/
|
|
20
|
+
app.hesh
|
|
21
|
+
hesh-units/
|
|
22
|
+
identity/
|
|
23
|
+
unit.hesh
|
|
24
|
+
state.js # .ts in typed projects
|
|
25
|
+
actions.js
|
|
26
|
+
parts/login/
|
|
27
|
+
state.js
|
|
28
|
+
actions.js
|
|
29
|
+
screen.html
|
|
30
|
+
workspace/
|
|
31
|
+
unit.hesh
|
|
32
|
+
state.js
|
|
33
|
+
actions.js
|
|
34
|
+
parts/home/...
|
|
35
|
+
parts/about/...
|
|
36
|
+
parts/counter/parts/detail/...
|
|
37
|
+
foundation/shell/
|
|
38
|
+
unit.hesh
|
|
39
|
+
unit.js
|
|
40
|
+
state.js
|
|
41
|
+
actions.js
|
|
42
|
+
screen.html
|
|
43
|
+
parts/badge/
|
|
44
|
+
unit.js
|
|
45
|
+
state.js
|
|
46
|
+
actions.js
|
|
47
|
+
screen.html
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Hesh Units preserve hierarchical MVC ownership with Hesh-specific names. State
|
|
51
|
+
corresponds to the model, actions to the controller, and screen to the view.
|
|
52
|
+
`defineUnit` composes these using the same scope/parent/lifetime machinery as
|
|
53
|
+
`defineModule`. `h-part="Badge"` mounts an explicitly declared child unit and
|
|
54
|
+
disposes its state effects, DOM and listeners with its parent. Instances have
|
|
55
|
+
independent state. `h-outlet` mounts the SPA destination outlet inside a persistent
|
|
56
|
+
shell; it is not permitted inside asynchronous destination screens.
|
|
57
|
+
|
|
58
|
+
Every starter UI screen is ordinary HTML, including shell and badge. `.hesh`
|
|
59
|
+
files declare Domain Graph contracts; generated `.hesh-generated` JS and type
|
|
60
|
+
declarations are compiler output. Existing brace views and `/hmvc` APIs remain
|
|
61
|
+
available for compatibility. Domain Graph controls business dependencies and
|
|
62
|
+
typed destinations; HTML units control hierarchy and ownership.
|
|
63
|
+
|
|
64
|
+
Use `npm run check` for architecture checks (plus strict tsc in typed projects),
|
|
65
|
+
`npm run graph` for dependencies, and `npm run build` for production. The supplied
|
|
66
|
+
logo is bundled locally and used as favicon. The session is an in-memory demo;
|
|
67
|
+
production authorization needs server enforcement. Configure the production host
|
|
68
|
+
to serve index.html for app URLs while serving real static assets normally.
|
|
69
|
+
|
|
70
|
+
Unsupported features remain unsupported: SSR, Journeys, deep reactivity and
|
|
71
|
+
keyed HTML template lists. Native HTML bindings accept data paths, not arbitrary JS.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heshammesalam/heshjs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Independent reactive UI framework with HTML screens, hierarchical Hesh Units, Domain Graph and an integrated project CLI",
|
|
5
5
|
"author": "Hesham",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|
|
@@ -16,11 +16,17 @@
|
|
|
16
16
|
"./vite": { "types": "./dist/vite/index.d.ts", "import": "./dist/vite/index.js" },
|
|
17
17
|
"./router": { "types": "./dist/router/index.d.ts", "import": "./dist/router/index.js" },
|
|
18
18
|
"./hmvc": { "types": "./dist/hmvc/index.d.ts", "import": "./dist/hmvc/index.js" },
|
|
19
|
+
"./units": { "types": "./dist/units/index.d.ts", "import": "./dist/units/index.js" },
|
|
20
|
+
"./domains": { "types": "./dist/domains/index.d.ts", "import": "./dist/domains/index.js" },
|
|
21
|
+
"./destinations": { "types": "./dist/destinations/index.d.ts", "import": "./dist/destinations/index.js" },
|
|
22
|
+
"./presentation": { "types": "./dist/presentation/index.d.ts", "import": "./dist/presentation/index.js" },
|
|
23
|
+
"./architecture": { "types": "./dist/architecture/index.d.ts", "import": "./dist/architecture/index.js" },
|
|
19
24
|
"./package.json": "./package.json"
|
|
20
25
|
},
|
|
21
|
-
"files": ["dist", "src", "docs", "README.md"],
|
|
26
|
+
"files": ["dist", "src", "docs", "README.md", "cli.js", "scaffold"],
|
|
27
|
+
"bin": { "hesh": "./cli.js" },
|
|
22
28
|
"engines": { "node": ">=22.12.0" },
|
|
23
|
-
"dependencies": { "typescript": "~5.9.3" },
|
|
29
|
+
"dependencies": { "typescript": "~5.9.3", "parse5": "8.1.0" },
|
|
24
30
|
"peerDependencies": { "vite": "^7.2.2" },
|
|
25
31
|
"peerDependenciesMeta": { "vite": { "optional": true } },
|
|
26
32
|
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }
|
|
Binary file
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, readdir, writeFile, readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve, basename, join, dirname } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { stdin, stdout } from 'node:process';
|
|
6
|
+
import { applicationFiles } from './template.js';
|
|
7
|
+
|
|
8
|
+
export async function createProject(args = process.argv.slice(2)) {
|
|
9
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
10
|
+
console.log('Usage: hesh create [directory] [--ts | --js]\nOmit directory/language to choose interactively.');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const flags = args.filter(arg => arg.startsWith('--'));
|
|
14
|
+
if (flags.some(flag => !['--ts', '--js'].includes(flag)) || (flags.includes('--ts') && flags.includes('--js'))) throw new Error('Choose --ts or --js. Use --help for usage.');
|
|
15
|
+
const directories = args.filter(arg => !arg.startsWith('--'));
|
|
16
|
+
if (directories.length > 1) throw new Error('Provide at most one project directory. Use --help for usage.');
|
|
17
|
+
let terminal, lines, interrupted = false;
|
|
18
|
+
const ask = async (question, validate) => {
|
|
19
|
+
if (!terminal) {
|
|
20
|
+
terminal = createInterface({ input: stdin, output: stdout, terminal: Boolean(stdin.isTTY && stdout.isTTY) });
|
|
21
|
+
lines = terminal[Symbol.asyncIterator]();
|
|
22
|
+
terminal.on('SIGINT', () => { interrupted = true; terminal.close(); });
|
|
23
|
+
}
|
|
24
|
+
while (true) {
|
|
25
|
+
stdout.write(question);
|
|
26
|
+
const answer = await lines.next();
|
|
27
|
+
if (answer.done) {
|
|
28
|
+
const error = new Error(interrupted ? 'Project creation canceled.' : 'Input ended. Provide a directory and --js or --ts for non-interactive use.');
|
|
29
|
+
error.exitCode = interrupted ? 130 : 1;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
const value = validate(answer.value.trim());
|
|
33
|
+
if (value !== undefined) return value;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
let directory, typed, target;
|
|
37
|
+
try {
|
|
38
|
+
console.log('\nHeshJS — Create your application\n');
|
|
39
|
+
directory = directories[0] ?? await ask('Project folder: ', answer => {
|
|
40
|
+
if (answer && !answer.includes('\0')) return answer;
|
|
41
|
+
console.log('Enter the folder name or path you want to create.');
|
|
42
|
+
});
|
|
43
|
+
target = resolve(directory);
|
|
44
|
+
let existing;
|
|
45
|
+
try { existing = await readdir(target); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
46
|
+
if (existing?.length) throw new Error(`Directory is not empty: ${target}`);
|
|
47
|
+
typed = flags.includes('--ts') ? true : flags.includes('--js') ? false : await ask('Choose language:\n 1. JavaScript\n 2. TypeScript\nLanguage [1]: ', answer => {
|
|
48
|
+
const choice = answer.toLowerCase();
|
|
49
|
+
if (['', '1', 'js', 'javascript'].includes(choice)) return false;
|
|
50
|
+
if (['2', 'ts', 'typescript'].includes(choice)) return true;
|
|
51
|
+
console.log('Enter 1 for JavaScript or 2 for TypeScript.');
|
|
52
|
+
});
|
|
53
|
+
} finally { terminal?.close(); }
|
|
54
|
+
|
|
55
|
+
// Read the bundled asset before creating any project files.
|
|
56
|
+
const icon = await readFile(new URL('./assets/hesh-icon.png', import.meta.url));
|
|
57
|
+
const name = basename(target).toLowerCase().replace(/[^a-z0-9-]/g, '-') || 'hesh-app';
|
|
58
|
+
const files = {
|
|
59
|
+
'package.json': JSON.stringify({ name, private: true, version: '0.0.0', type: 'module', scripts: { dev: 'vite', check: 'hesh check src --write' + (typed ? ' && tsc --noEmit' : ''), graph: 'hesh graph src', build: 'hesh check src --write && ' + (typed ? 'tsc --noEmit && ' : '') + 'vite build', preview: 'vite preview' }, dependencies: { '@heshammesalam/heshjs': '0.5.0' }, devDependencies: { vite: '^7.2.2', ...(typed ? { typescript: '~5.9.3' } : {}) } }, null, 2) + '\n',
|
|
60
|
+
'.gitignore': 'node_modules/\ndist/\n',
|
|
61
|
+
'index.html': `<!doctype html>\n<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" type="image/png" href="/brand/hesh-icon.png"><link rel="apple-touch-icon" href="/brand/hesh-icon.png"><title>HeshJS</title></head><body><div id="app"></div><script type="module" src="/src/main.${typed ? 'ts' : 'js'}"></script></body></html>\n`,
|
|
62
|
+
'vite.config.js': "import { hesh } from '@heshammesalam/heshjs/vite';\nexport default { plugins: [hesh({ architecture: { root: 'src'" + (typed ? ", tsconfig: '../tsconfig.json'" : '') + " } })] };\n",
|
|
63
|
+
'src/style.css': ':root { font: 17px/1.6 system-ui; background: #f3f6fb; color: #17243a; } #app { max-width: 900px; margin: 5vh auto; padding: 32px; background: white; border-radius: 16px; } .hesh-brand { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; } .hesh-brand img { display: block; width: 80px; height: 80px; object-fit: contain; flex: 0 0 80px; } .hesh-brand h1 { margin: 0; font-size: clamp(1.3rem, 4vw, 2rem); } nav { display: flex; flex-wrap: wrap; gap: 20px; } button,input { font: inherit; margin-right: 12px; padding: 8px 16px; } button { cursor: pointer; } button:focus-visible, a:focus-visible, input:focus-visible { outline: 3px solid #2563eb; } main { padding: 20px 0; } footer { border-top: 1px solid #dbe3ef; padding-top: 16px; } .architecture-note { padding: 12px; background: #f0f4ff; border-radius: 8px; } @media(max-width:600px) { #app { margin: 0; padding: 20px; border-radius: 0; } }\n',
|
|
64
|
+
'public/brand/hesh-icon.png': icon,
|
|
65
|
+
...applicationFiles(typed)
|
|
66
|
+
};
|
|
67
|
+
if (typed) {
|
|
68
|
+
files['tsconfig.json'] = JSON.stringify({ compilerOptions: { target: 'ES2022', module: 'ESNext', moduleResolution: 'Bundler', strict: true, noEmit: true, allowImportingTsExtensions: true, skipLibCheck: true, types: ['vite/client'] }, include: ['src'] }, null, 2) + '\n';
|
|
69
|
+
files['src/hesh.d.ts'] = "declare module '*.hesh' {\n import type { Component } from '@heshammesalam/heshjs';\n const component: Component;\n export default component;\n}\n";
|
|
70
|
+
}
|
|
71
|
+
await mkdir(target, { recursive: true });
|
|
72
|
+
for (const [path, content] of Object.entries(files)) {
|
|
73
|
+
await mkdir(dirname(join(target, path)), { recursive: true });
|
|
74
|
+
await writeFile(join(target, path), content, { flag: 'wx' });
|
|
75
|
+
}
|
|
76
|
+
console.log(`Created ${typed ? 'TypeScript' : 'JavaScript'} HeshJS project in ${target}\n\nNext:\n cd ${JSON.stringify(directory)}\n npm install\n npm run dev`);
|
|
77
|
+
}
|
|
78
|
+
|