juxscript 1.0.46 â 1.0.47
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 +238 -50
- package/bin/cli.js +191 -155
- package/lib/components/docs-data.json +1 -1
- package/machinery/compiler.js +7 -59
- package/machinery/server.js +0 -62
- package/machinery/watcher.js +43 -116
- package/package.json +24 -15
- package/machinery/config.js +0 -94
package/README.md
CHANGED
|
@@ -1,83 +1,271 @@
|
|
|
1
|
-
#
|
|
1
|
+
# The web needs a higher level of abstraction: Meet **JUX**
|
|
2
2
|
|
|
3
|
-
> Say goodbye to markup `</</>>>`. Build reactive UIs with pure JavaScript.
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
```diff
|
|
5
|
+
- IF YOU ARE CURRENTLY USING THIS PACKAGE COOL! But it is still a little fragile.
|
|
6
|
+
- We are working on opening our repo to public and working on our documentation pages.
|
|
7
|
+
+ Stay tuned! For now, you will see our roadmap here!
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
```
|
|
10
|
+
- [X] Router
|
|
11
|
+
- [ ] Cross Page Store.
|
|
12
|
+
- [ ] Distributable Bundle (Static Sites)
|
|
13
|
+
- [ ] Tree Shake/Efficiencies.
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
- [ ] Data
|
|
16
|
+
- [ ] Drivers: File, S3, Database.
|
|
17
|
+
- [ ] const d = jux.data('id',{});
|
|
18
|
+
- [ ] d.driver(file|s3|database)
|
|
19
|
+
- [ ] d.items([] | juxitem)
|
|
20
|
+
- [ ] d.store(callback)
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
- [X] Layouts (100% done.)
|
|
23
|
+
- [ ] *Authoring Layout Pages* - `docs`
|
|
24
|
+
- [ ] *Authoring Application Pages* - `docs`
|
|
25
|
+
- [ ] *Authoring Custom Components* - `docs`
|
|
26
|
+
|
|
27
|
+
- [ ] Render Dependency Tree
|
|
28
|
+
> Idea here is, one element may be a predicate for another. Will need promises. **predicting problems with slow-loading components that depend on containers from other components. May to to separate concerns with container "building" vs. content addition OR use async processes (promises).
|
|
29
|
+
- [X] Reactivity (90% done.)
|
|
30
|
+
- [ ] Client Components (99% of what would be needed.)
|
|
31
|
+
- [X] Charts
|
|
32
|
+
- [X] Poor Intellisense support? Could be this issue. - fixed.
|
|
33
|
+
- [ ] Api Wrapper
|
|
34
|
+
- [X] Params/Active State for Menu/Nav matching - built in.
|
|
35
|
+
- [ ] CDN Bundle (import CDN/'state', 'jux' from cdn.)
|
|
36
|
+
- [ ] Icon
|
|
37
|
+
|
|
38
|
+
- [ ] Quickstart Boilerplates (20% done,notion.jux)
|
|
39
|
+
- [ ] Mobile Nav
|
|
40
|
+
- [ ] `npx jux present notion|default` etc..
|
|
41
|
+
- [ ] Server side components (api and database)
|
|
42
|
+
- [ ] Quick deploy option
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## *JUX* Authoring UI's in pure javascript.
|
|
46
|
+
|
|
47
|
+
> Build beautiful, reactive UI's using only javascript. **No markup required.**
|
|
48
|
+
> A clean authorship layer capable of being taught to non-developers, strong enough to be used by real developers.
|
|
49
|
+
|
|
50
|
+
>> **jux** challenges the idea that markup based systems are the most efficient way to author web applications.
|
|
51
|
+
## AI friendly - **use less tokens!**
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
```diff
|
|
55
|
+
- Markup is expensive!
|
|
56
|
+
Have you ever considered the energy requirements to ship chunks of HTML markup like Vue or React components?
|
|
16
57
|
```
|
|
17
58
|
|
|
18
|
-
|
|
59
|
+
- [ ] Build a code calculator (token calculator)
|
|
60
|
+
> As a user, I want to be efficient with the LLM's. I want my code to be clean. I want to avoid repeating myself. I ultimately, want to write less code. Less code is better code.
|
|
61
|
+
|
|
62
|
+
## Ecosystem friendly (NPM)
|
|
63
|
+
|
|
64
|
+
> Because it is just javascript, .jux files work with any npm package you want to throw at it with standard ESM syntax.
|
|
65
|
+
|
|
66
|
+
## GETTING STARTED
|
|
19
67
|
|
|
20
68
|
```bash
|
|
69
|
+
# New project
|
|
70
|
+
mkdir my-project
|
|
71
|
+
cd my-project
|
|
72
|
+
npm init -y
|
|
21
73
|
npm install juxscript
|
|
74
|
+
|
|
75
|
+
# Initialize (creates jux/ directory)
|
|
22
76
|
npx jux init
|
|
77
|
+
|
|
78
|
+
# Builds jux-dist and serves index.jux
|
|
23
79
|
npx jux serve
|
|
24
80
|
```
|
|
81
|
+
> install
|
|
82
|
+
`npm i juxscript`
|
|
25
83
|
|
|
26
|
-
|
|
84
|
+
>> Your IDE must be setup to read .jux files as .js files. In VS Code, this is do in the `.vscode/settings.json`. Here is an example.
|
|
27
85
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
86
|
+
```json
|
|
87
|
+
//.vscode/settings.json
|
|
88
|
+
{
|
|
89
|
+
"files.associations": {
|
|
90
|
+
"*.jux": "javascript",
|
|
91
|
+
"*.juxt": "javascript"
|
|
92
|
+
},
|
|
93
|
+
"javascript.validate.enable": false,
|
|
94
|
+
"[javascript]": {
|
|
95
|
+
"editor.defaultFormatter": "vscode.typescript-language-features"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
34
99
|
|
|
35
|
-
|
|
100
|
+
> building and serving the `examples` project directory.
|
|
101
|
+
`npm run dev`
|
|
36
102
|
|
|
37
|
-
|
|
38
|
-
|
|
103
|
+
> building only (the `examples` project directory)
|
|
104
|
+
`npm run build:examples`
|
|
39
105
|
|
|
40
|
-
// Create reactive state
|
|
41
|
-
const count = state(0);
|
|
42
106
|
|
|
43
|
-
|
|
44
|
-
jux.button('increment')
|
|
45
|
-
.label('Click me!')
|
|
46
|
-
.bind('click', () => count.value++)
|
|
47
|
-
.render('#app');
|
|
107
|
+
# Authoring in Jux
|
|
48
108
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
109
|
+
1. Build a Layout Page you can reuse.
|
|
110
|
+
Check out the presets, they speed this process up. Just modify them with your goals, such as logo, menu options, nav etc.
|
|
111
|
+
2. Build your Pages
|
|
112
|
+
Just get to work, using the **jux** component library to build most of what you need, filling in any details with *javascript* logically arranged the way you want. Think, authoring SFC but with only the composition layer.
|
|
113
|
+
3. Test it out!
|
|
114
|
+
Jux comes with a watcher system built in, so you can see your edits to *.jux* files live.
|
|
53
115
|
|
|
54
|
-
|
|
116
|
+
# GOAL: Eliminating the Markup System of building UI's.
|
|
117
|
+
> HTML markup is the equivalent of still writing in `C` ro `C++` despite the availability of more functional levels of abstraction like `python`.
|
|
118
|
+
> To remove the requirement for markup, **jux** must address the utility of markup.
|
|
55
119
|
|
|
56
|
-
|
|
57
|
-
- [API Reference](https://juxscript.com/api) _(coming soon)_
|
|
58
|
-
- [Examples](https://github.com/juxscript/examples)
|
|
120
|
+
## Layouts
|
|
59
121
|
|
|
60
|
-
|
|
122
|
+
### Grid System
|
|
123
|
+
- [ ] Laying out JUX files in a rational way.
|
|
124
|
+
> Develop a higher-level layout javascript grammar for building layouts and palettes of style using *ZERO markup*.
|
|
61
125
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
126
|
+
- [ ] A Partial View Strategy
|
|
127
|
+
>>> As a dev I may need to load partial pages/components into other pages. This should avoid noisy wrappers and ideally be just javascript imports of jux objects.
|
|
128
|
+
|
|
129
|
+
### Components
|
|
130
|
+
|
|
131
|
+
We have constructed the most helpful components for building 99% of core business/saas web applications, that present beautifully on the page and are created purely with javascript.
|
|
132
|
+
> https://www.shadcn-vue.com/docs/components
|
|
133
|
+
> Run time slots.. Example, `table.slot(columnName -> object);`
|
|
134
|
+
|
|
135
|
+
# Backend
|
|
136
|
+
|
|
137
|
+
### Routing & Views
|
|
138
|
+
- [X] Subviews architecture
|
|
139
|
+
- [X] Nested routing support
|
|
140
|
+
- [X] View composition
|
|
141
|
+
- [X] Layout system
|
|
142
|
+
- [X] User views
|
|
143
|
+
- [X] `profile.jux` - User
|
|
144
|
+
|
|
145
|
+
### Server Docs/Documentation.
|
|
146
|
+
|
|
147
|
+
- [ ] Should *jux* ship with a server at all? Or merely for demos.
|
|
68
148
|
|
|
69
|
-
|
|
149
|
+
> What is the server even doing? Is it merely a matter of understanding that JUX builder/compilation gives you a mirror of how you setup your project. It can be as flat or deep as you would like.
|
|
70
150
|
|
|
71
|
-
|
|
151
|
+
> *At this point*, we are speaking almost 100% towards UI interfaces.
|
|
152
|
+
> Servers should be 'loosely coupled.'. This allows for a standard.
|
|
153
|
+
>>> In the future, we could introduce an *auto api* feature, similar to *next.js* api heuristic for folders and files.
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
[ **@jesseallenrxtrail -> Discuss.** ]
|
|
158
|
+
|
|
159
|
+
### Chore: Commit to building more in package directory.
|
|
160
|
+
|
|
161
|
+
> We can experiment with our example project, using a simple `express` server, running. What third-party integration do we need?
|
|
162
|
+
- [ ] Chore: Cleanup our `machinery/server.js` to separate concerns and build our actual endpoints we need to build software here.
|
|
163
|
+
|
|
164
|
+
#### Alternative Chore: package this for `npmjs.org`, combine with our existing project, re-author frontend in *jux*.
|
|
165
|
+
|
|
166
|
+
- [ ] Alternative Chore: package this for `npmjs.org` and ship it.
|
|
167
|
+
- [ ] Start a brand new project.
|
|
168
|
+
- [ ] Build the server aspect in `flask` (already done).
|
|
169
|
+
- [ ] Author the frontend in **jux** instead of **vue**.
|
|
170
|
+
|
|
171
|
+
> This may require us to move our 'complilation error module', `lib/components/error-handler.ts` to the front-end for display without a 'server call' at all?
|
|
172
|
+
- [ ] Chore for this? This takes more analysis, discussion.
|
|
173
|
+
|
|
174
|
+
> How and what will we wire for backend and what **jux** components will we support for working with backends.
|
|
175
|
+
- [ X ] SQL Database (query component)
|
|
176
|
+
- [ ] Chore: This is currently supported. Needs revisiting and testing
|
|
177
|
+
> API's
|
|
178
|
+
- [ ] Base payload creater and consumer as a **jux** component. *jux.api*??
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
# Jux Configs
|
|
183
|
+
|
|
184
|
+
> Jux configs.. `jux.config.js` what should this entail?
|
|
185
|
+
Currently it looks like this:
|
|
186
|
+
- [ ] Chore: **WHAT IS ACTUALLY BEING USED AND WHERE?**
|
|
72
187
|
|
|
73
|
-
|
|
188
|
+
```javascript
|
|
189
|
+
export default {
|
|
190
|
+
projectRoot: __dirname,
|
|
191
|
+
distDir: path.join(__dirname, 'dist'),
|
|
192
|
+
build: {
|
|
193
|
+
minify: false
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
database: {
|
|
197
|
+
// Default connection
|
|
198
|
+
default: 'sqlite',
|
|
199
|
+
|
|
200
|
+
// Named connections
|
|
201
|
+
connections: {
|
|
202
|
+
sqlite: {
|
|
203
|
+
driver: 'sqlite',
|
|
204
|
+
database: path.join(__dirname, 'db', 'jux.db')
|
|
205
|
+
},
|
|
206
|
+
... // props to feed drivers for mysql/postgres
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
server: {
|
|
211
|
+
port: process.env.PORT || 3000,
|
|
212
|
+
host: process.env.HOST || 'localhost'
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
**As a user, I will want to control where JUX sends queries.**
|
|
218
|
+
**As a user, I may want to specify a default proxy for backend. I may also want to specify, explicitly, URL's to hit other service endpoints**
|
|
219
|
+
- [ ] Escape hatch (backend API proxy vs default)
|
|
220
|
+
- [ ] As a user, I may want to run my own server for backend requests/proxies.
|
|
221
|
+
- [ ] As a user, I may want to hit multiple endpoints in the same JUX file.
|
|
74
222
|
|
|
75
|
-
|
|
223
|
+
---
|
|
76
224
|
|
|
77
|
-
|
|
225
|
+
# CORE
|
|
78
226
|
|
|
79
|
-
|
|
227
|
+
### STATIC SERVING (your local file system, s3 bucket static sites, any host that allows serving etc...)
|
|
228
|
+
**Should JUX function, as a 100% client side app (STATIC WEBSITES)**
|
|
229
|
+
- [ ] Chore: Discovery... I think the answer is yes. I think what might be missing is bundling.
|
|
230
|
+

|
|
80
231
|
|
|
81
232
|
---
|
|
233
|
+
## REACTIVITY LAYER.
|
|
234
|
+
### Goal for Reactivity
|
|
235
|
+
> We are shooting for better than nasty amounts of javascript, not as sophisticated as vue/react.
|
|
236
|
+
|
|
237
|
+
> As a user, I want changes in state of my objects to be consumable by other objects on the page so they can paint changes and interface with each other seamlessly.
|
|
238
|
+
- [ ] Build a basic `v-model` strategy in pure javascript. Different than jquery, javascript etc.
|
|
239
|
+
- [ ] Build standard eventing systems, listeners and emitters on components where they make sense.
|
|
240
|
+
`
|
|
241
|
+
# ROADMAP
|
|
242
|
+
|
|
243
|
+
### KEY UI PAGES
|
|
244
|
+
- [ ] JWT Authentication (`authJWT`) (demo `examples` only)
|
|
245
|
+
- [ ] Token generation (demo `examples` only)
|
|
246
|
+
- [ ] Token validation middleware (demo `examples` only)
|
|
247
|
+
- [ ] Refresh token logic (demo `examples` only)
|
|
248
|
+
- [ ] *Login system PAGES! Ships with `vendor` assets like lib files/layouts etc.*
|
|
249
|
+
- [ ] Login
|
|
250
|
+
- [ ] Logout
|
|
251
|
+
- [ ] Profile
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
### Additional Features
|
|
255
|
+
- [ ] API documentation
|
|
256
|
+
- [ ] Testing setup
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
### LAYOUT NOTES...
|
|
263
|
+
### Jux Standard Layout Regions
|
|
264
|
+
|
|
265
|
+
- [ ] CHORE : REVISIT! I think maybe we ship with our figma, notion, default etc. But then document how to build and reuse a .jux page as a layout using the `jux.layout` class.
|
|
266
|
+
|
|
267
|
+
### Start with a layout page
|
|
268
|
+
|
|
269
|
+
You can build a simple one, or copy and use one of the Jux presets.
|
|
82
270
|
|
|
83
|
-
|
|
271
|
+
`npx jux preset notion|default|figma|slack|other`
|
package/bin/cli.js
CHANGED
|
@@ -1,5 +1,123 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const command = process.argv[2];
|
|
11
|
+
|
|
12
|
+
// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
13
|
+
// CREATE COMMAND - Runs BEFORE dependencies are installed
|
|
14
|
+
// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
15
|
+
|
|
16
|
+
if (command === 'create') {
|
|
17
|
+
const projectName = process.argv[3] || 'my-jux-app';
|
|
18
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
19
|
+
|
|
20
|
+
console.log(`
|
|
21
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
22
|
+
â â
|
|
23
|
+
â đ¨ Welcome to JUX â
|
|
24
|
+
â â
|
|
25
|
+
â Creating your new JUX project... â
|
|
26
|
+
â â
|
|
27
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
if (fs.existsSync(projectPath)) {
|
|
31
|
+
console.error(`â Directory "${projectName}" already exists.`);
|
|
32
|
+
console.error(` Please choose a different name or remove the existing directory.\n`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const { execSync } = await import('child_process');
|
|
38
|
+
|
|
39
|
+
console.log(`đ Creating directory: ${projectName}`);
|
|
40
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
41
|
+
process.chdir(projectPath);
|
|
42
|
+
|
|
43
|
+
console.log(`đĻ Initializing package.json...`);
|
|
44
|
+
const packageJson = {
|
|
45
|
+
name: projectName,
|
|
46
|
+
version: '0.1.0',
|
|
47
|
+
type: 'module',
|
|
48
|
+
scripts: {
|
|
49
|
+
dev: 'jux serve',
|
|
50
|
+
build: 'jux build'
|
|
51
|
+
},
|
|
52
|
+
dependencies: {
|
|
53
|
+
juxscript: 'latest'
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
|
|
57
|
+
console.log(` â package.json created`);
|
|
58
|
+
|
|
59
|
+
console.log(`\nđĨ Installing juxscript...\n`);
|
|
60
|
+
try {
|
|
61
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
62
|
+
console.log(`\n â Dependencies installed`);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`\n â ī¸ npm install failed, but continuing...`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`\nđ¨ Initializing JUX project structure...`);
|
|
68
|
+
execSync('npx jux init', { stdio: 'inherit' });
|
|
69
|
+
|
|
70
|
+
console.log(`\nđ Creating .gitignore...`);
|
|
71
|
+
fs.writeFileSync('.gitignore', `jux-dist/\nnode_modules/\n.DS_Store\n.env\n*.log\n`);
|
|
72
|
+
console.log(` â .gitignore created`);
|
|
73
|
+
|
|
74
|
+
console.log(`
|
|
75
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
76
|
+
â â
|
|
77
|
+
â â
Project created successfully! â
|
|
78
|
+
â â
|
|
79
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
80
|
+
|
|
81
|
+
đ Resources:
|
|
82
|
+
Documentation: [coming soon]
|
|
83
|
+
GitHub: https://github.com/juxscript/jux
|
|
84
|
+
Examples: https://github.com/juxscript/examples
|
|
85
|
+
|
|
86
|
+
â If you find JUX useful, please star us on GitHub!
|
|
87
|
+
đ Security: Report issues to security@juxscript.com [placeholder]
|
|
88
|
+
|
|
89
|
+
Say goodbye to markup </</>>>.
|
|
90
|
+
Happy javascripting your frontend! đ
|
|
91
|
+
|
|
92
|
+
Next steps:
|
|
93
|
+
cd ${projectName}
|
|
94
|
+
npm run dev
|
|
95
|
+
`);
|
|
96
|
+
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`\nâ Project creation failed:`, err.message);
|
|
99
|
+
|
|
100
|
+
if (fs.existsSync(projectPath)) {
|
|
101
|
+
try {
|
|
102
|
+
process.chdir('..');
|
|
103
|
+
fs.rmSync(projectPath, { recursive: true, force: true });
|
|
104
|
+
console.log(` â Cleaned up failed project directory\n`);
|
|
105
|
+
} catch (cleanupErr) {
|
|
106
|
+
console.error(` â ī¸ Could not clean up directory\n`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
process.exit(0); // â
Exit after create completes
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
117
|
+
// ALL OTHER COMMANDS - Require dependencies to be installed
|
|
118
|
+
// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
119
|
+
|
|
120
|
+
// â
Now import dependencies (only needed for init, build, serve)
|
|
3
121
|
import {
|
|
4
122
|
copyLibToOutput,
|
|
5
123
|
copyProjectAssets,
|
|
@@ -10,16 +128,6 @@ import {
|
|
|
10
128
|
} from '../machinery/compiler.js';
|
|
11
129
|
import { generateDocs } from '../machinery/doc-generator.js';
|
|
12
130
|
import { start } from '../machinery/server.js';
|
|
13
|
-
import path from 'path';
|
|
14
|
-
import fs from 'fs';
|
|
15
|
-
import { fileURLToPath } from 'url';
|
|
16
|
-
import { loadConfig, runBootstrap } from '../machinery/config.js';
|
|
17
|
-
|
|
18
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
-
const __dirname = path.dirname(__filename);
|
|
20
|
-
|
|
21
|
-
// Load configuration first (before PATHS)
|
|
22
|
-
const config = await loadConfig(process.cwd());
|
|
23
131
|
|
|
24
132
|
// CLEAR PATH CONTRACT - CONVENTIONS
|
|
25
133
|
const PATHS = {
|
|
@@ -29,14 +137,20 @@ const PATHS = {
|
|
|
29
137
|
// Where the user's project root is (where they run `npx jux`)
|
|
30
138
|
projectRoot: process.cwd(),
|
|
31
139
|
|
|
32
|
-
// Where user's .jux source files live (
|
|
33
|
-
juxSource
|
|
140
|
+
// Where user's .jux source files live (CONVENTION: ./jux/)
|
|
141
|
+
get juxSource() {
|
|
142
|
+
return path.join(this.projectRoot, 'jux');
|
|
143
|
+
},
|
|
34
144
|
|
|
35
145
|
// Where jux lib files are (components, layouts, etc.)
|
|
36
|
-
juxLib
|
|
146
|
+
get juxLib() {
|
|
147
|
+
return path.join(this.packageRoot, 'lib');
|
|
148
|
+
},
|
|
37
149
|
|
|
38
|
-
// Where frontend build output goes (
|
|
39
|
-
frontendDist
|
|
150
|
+
// Where frontend build output goes (CONVENTION: ./jux-dist/)
|
|
151
|
+
get frontendDist() {
|
|
152
|
+
return path.join(this.projectRoot, 'jux-dist');
|
|
153
|
+
}
|
|
40
154
|
};
|
|
41
155
|
|
|
42
156
|
console.log('đ JUX Paths:');
|
|
@@ -46,10 +160,6 @@ console.log(` Source: ${PATHS.juxSource}`);
|
|
|
46
160
|
console.log(` Output: ${PATHS.frontendDist}`);
|
|
47
161
|
console.log(` Lib: ${PATHS.juxLib}\n`);
|
|
48
162
|
|
|
49
|
-
const command = process.argv[2];
|
|
50
|
-
const watchMode = process.argv.includes('--watch');
|
|
51
|
-
const bundleMode = process.argv.includes('--bundle');
|
|
52
|
-
|
|
53
163
|
/**
|
|
54
164
|
* Recursively find .jux files in a directory
|
|
55
165
|
*/
|
|
@@ -78,9 +188,8 @@ function findJuxFiles(dir, fileList = []) {
|
|
|
78
188
|
* Build the entire JUX project (ALWAYS uses router bundle)
|
|
79
189
|
*
|
|
80
190
|
* @param {boolean} isServe - Whether building for dev server
|
|
81
|
-
* @param {number} wsPort - WebSocket port for hot reload
|
|
82
191
|
*/
|
|
83
|
-
async function buildProject(isServe = false
|
|
192
|
+
async function buildProject(isServe = false) {
|
|
84
193
|
const buildStartTime = performance.now();
|
|
85
194
|
console.log('đ¨ Building JUX frontend...\n');
|
|
86
195
|
|
|
@@ -100,14 +209,14 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
100
209
|
|
|
101
210
|
// Step 1: Generate documentation
|
|
102
211
|
const docsStartTime = performance.now();
|
|
103
|
-
let docsTime = 0;
|
|
212
|
+
let docsTime = 0; // â
Declare with default value
|
|
104
213
|
console.log('đ Generating documentation...');
|
|
105
214
|
try {
|
|
106
215
|
await generateDocs(PATHS.juxLib);
|
|
107
216
|
docsTime = performance.now() - docsStartTime;
|
|
108
217
|
console.log(`â
Documentation generated (${docsTime.toFixed(0)}ms)\n`);
|
|
109
218
|
} catch (error) {
|
|
110
|
-
docsTime = performance.now() - docsStartTime;
|
|
219
|
+
docsTime = performance.now() - docsStartTime; // â
Still calculate time even on error
|
|
111
220
|
console.warn(`â ī¸ Failed to generate docs (${docsTime.toFixed(0)}ms):`, error.message);
|
|
112
221
|
}
|
|
113
222
|
|
|
@@ -123,7 +232,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
123
232
|
const presetsTime = performance.now() - presetsStartTime;
|
|
124
233
|
console.log(`âąī¸ Presets copy time: ${presetsTime.toFixed(0)}ms\n`);
|
|
125
234
|
|
|
126
|
-
// Step 4: Copy project assets
|
|
235
|
+
// Step 4: Copy project assets (CSS, JS, images)
|
|
127
236
|
const assetsStartTime = performance.now();
|
|
128
237
|
await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
|
|
129
238
|
const assetsTime = performance.now() - assetsStartTime;
|
|
@@ -144,6 +253,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
144
253
|
process.exit(1);
|
|
145
254
|
}
|
|
146
255
|
|
|
256
|
+
// â
Bundle and get the generated filename
|
|
147
257
|
const bundleStartTime = performance.now();
|
|
148
258
|
const mainJsFilename = await bundleJuxFilesToRouter(PATHS.juxSource, PATHS.frontendDist, {
|
|
149
259
|
routePrefix: ''
|
|
@@ -173,14 +283,16 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
173
283
|
};
|
|
174
284
|
});
|
|
175
285
|
|
|
286
|
+
// â
Generate unified index.html
|
|
176
287
|
const indexStartTime = performance.now();
|
|
177
|
-
generateIndexHtml(PATHS.frontendDist, routes, mainJsFilename
|
|
288
|
+
generateIndexHtml(PATHS.frontendDist, routes, mainJsFilename);
|
|
178
289
|
const indexTime = performance.now() - indexStartTime;
|
|
179
290
|
|
|
180
291
|
const totalBuildTime = performance.now() - buildStartTime;
|
|
181
292
|
|
|
182
293
|
console.log(`\nâ
Bundled ${projectJuxFiles.length} page(s) â ${PATHS.frontendDist}/${mainJsFilename}\n`);
|
|
183
294
|
|
|
295
|
+
// â
Build summary with timing breakdown
|
|
184
296
|
console.log(`đ Build Summary:`);
|
|
185
297
|
console.log(` ââââââââââââââââââââââââââââ`);
|
|
186
298
|
console.log(` Documentation: ${docsTime.toFixed(0)}ms`);
|
|
@@ -193,6 +305,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
193
305
|
console.log(` ââââââââââââââââââââââââââââ`);
|
|
194
306
|
console.log(` Total build time: ${totalBuildTime.toFixed(0)}ms\n`);
|
|
195
307
|
|
|
308
|
+
// Show usage
|
|
196
309
|
if (!isServe) {
|
|
197
310
|
console.log('đĻ Serve from your backend:');
|
|
198
311
|
console.log(` Express: app.use(express.static('jux-dist'))`);
|
|
@@ -215,102 +328,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
|
|
|
215
328
|
}
|
|
216
329
|
|
|
217
330
|
(async () => {
|
|
218
|
-
if (command === '
|
|
219
|
-
const projectName = process.argv[3] || 'my-jux-app';
|
|
220
|
-
const projectPath = path.join(PATHS.projectRoot, projectName);
|
|
221
|
-
|
|
222
|
-
console.log(`
|
|
223
|
-
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
224
|
-
â â
|
|
225
|
-
â đ¨ Welcome to JUX â
|
|
226
|
-
â â
|
|
227
|
-
â Creating your new JUX project... â
|
|
228
|
-
â â
|
|
229
|
-
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
230
|
-
`);
|
|
231
|
-
|
|
232
|
-
if (fs.existsSync(projectPath)) {
|
|
233
|
-
console.error(`â Directory "${projectName}" already exists.`);
|
|
234
|
-
console.error(` Please choose a different name or remove the existing directory.\n`);
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
const { execSync } = await import('child_process');
|
|
240
|
-
|
|
241
|
-
console.log(`đ Creating directory: ${projectName}`);
|
|
242
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
243
|
-
process.chdir(projectPath);
|
|
244
|
-
|
|
245
|
-
console.log(`đĻ Initializing package.json...`);
|
|
246
|
-
const packageJson = {
|
|
247
|
-
name: projectName,
|
|
248
|
-
version: '0.1.0',
|
|
249
|
-
type: 'module',
|
|
250
|
-
scripts: {
|
|
251
|
-
dev: 'jux serve',
|
|
252
|
-
build: 'jux build'
|
|
253
|
-
},
|
|
254
|
-
dependencies: {
|
|
255
|
-
juxscript: 'latest' // â
Changed from '^1.0.8' to 'latest'
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
|
|
259
|
-
console.log(` â package.json created`);
|
|
260
|
-
|
|
261
|
-
console.log(`\nđĨ Installing juxscript...\n`);
|
|
262
|
-
try {
|
|
263
|
-
execSync('npm install', { stdio: 'inherit' });
|
|
264
|
-
console.log(`\n â Dependencies installed`);
|
|
265
|
-
} catch (err) {
|
|
266
|
-
console.error(`\n â ī¸ npm install failed, but continuing...`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
console.log(`\nđ¨ Initializing JUX project structure...`);
|
|
270
|
-
execSync('npx jux init', { stdio: 'inherit' });
|
|
271
|
-
|
|
272
|
-
console.log(`\nđ Creating .gitignore...`);
|
|
273
|
-
fs.writeFileSync('.gitignore', `jux-dist/\nnode_modules/\n.DS_Store\n.env\n*.log\n`);
|
|
274
|
-
console.log(` â .gitignore created`);
|
|
275
|
-
|
|
276
|
-
console.log(`
|
|
277
|
-
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
278
|
-
â â
|
|
279
|
-
â â
Project created successfully! â
|
|
280
|
-
â â
|
|
281
|
-
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
282
|
-
|
|
283
|
-
đ Resources:
|
|
284
|
-
Documentation: [coming soon]
|
|
285
|
-
GitHub: https://github.com/juxscript/jux
|
|
286
|
-
Examples: https://github.com/juxscript/examples
|
|
287
|
-
|
|
288
|
-
â If you find JUX useful, please star us on GitHub!
|
|
289
|
-
đ Security: Report issues to security@juxscript.com [placeholder]
|
|
290
|
-
|
|
291
|
-
Say goodbye to markup </</>>>.
|
|
292
|
-
Happy javascripting your frontend! đ
|
|
293
|
-
|
|
294
|
-
Next steps, run: cd ${projectName} && npm run dev
|
|
295
|
-
`);
|
|
296
|
-
|
|
297
|
-
} catch (err) {
|
|
298
|
-
console.error(`\nâ Project creation failed:`, err.message);
|
|
299
|
-
|
|
300
|
-
if (fs.existsSync(projectPath)) {
|
|
301
|
-
try {
|
|
302
|
-
process.chdir('..');
|
|
303
|
-
fs.rmSync(projectPath, { recursive: true, force: true });
|
|
304
|
-
console.log(` â Cleaned up failed project directory\n`);
|
|
305
|
-
} catch (cleanupErr) {
|
|
306
|
-
console.error(` â ī¸ Could not clean up directory\n`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
process.exit(1);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
} else if (command === 'init') {
|
|
331
|
+
if (command === 'init') {
|
|
314
332
|
console.log('đ¨ Initializing JUX project...\n');
|
|
315
333
|
|
|
316
334
|
const juxDir = PATHS.juxSource;
|
|
@@ -320,8 +338,10 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
320
338
|
process.exit(1);
|
|
321
339
|
}
|
|
322
340
|
|
|
341
|
+
// Create structure
|
|
323
342
|
fs.mkdirSync(juxDir, { recursive: true });
|
|
324
343
|
|
|
344
|
+
// Copy jux.jux as the starter index.jux (if it exists)
|
|
325
345
|
const juxJuxSrc = path.join(PATHS.packageRoot, 'presets', 'jux.jux');
|
|
326
346
|
const indexJuxDest = path.join(juxDir, 'index.jux');
|
|
327
347
|
|
|
@@ -329,6 +349,7 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
329
349
|
fs.copyFileSync(juxJuxSrc, indexJuxDest);
|
|
330
350
|
console.log('+ Created jux/index.jux from jux.jux template');
|
|
331
351
|
} else {
|
|
352
|
+
// Fallback to hey.jux if jux.jux doesn't exist
|
|
332
353
|
const heyJuxSrc = path.join(PATHS.packageRoot, 'presets', 'hey.jux');
|
|
333
354
|
if (fs.existsSync(heyJuxSrc)) {
|
|
334
355
|
fs.copyFileSync(heyJuxSrc, indexJuxDest);
|
|
@@ -341,6 +362,7 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
341
362
|
}
|
|
342
363
|
}
|
|
343
364
|
|
|
365
|
+
// Copy entire presets folder to jux/presets/ (excluding jux.jux)
|
|
344
366
|
const presetsSrc = path.join(PATHS.packageRoot, 'presets');
|
|
345
367
|
const presetsDest = path.join(juxDir, 'presets');
|
|
346
368
|
|
|
@@ -354,6 +376,7 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
354
376
|
const srcPath = path.join(src, entry.name);
|
|
355
377
|
const destPath = path.join(dest, entry.name);
|
|
356
378
|
|
|
379
|
+
// Skip jux.jux since we already copied it to index.jux
|
|
357
380
|
if (entry.isFile() && entry.name === 'jux.jux') {
|
|
358
381
|
continue;
|
|
359
382
|
}
|
|
@@ -378,51 +401,37 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
378
401
|
}
|
|
379
402
|
}
|
|
380
403
|
|
|
404
|
+
// Create package.json if it doesn't exist
|
|
381
405
|
const pkgPath = path.join(PATHS.projectRoot, 'package.json');
|
|
382
406
|
if (!fs.existsSync(pkgPath)) {
|
|
383
|
-
// â
Get project name from current directory or default
|
|
384
|
-
const projectName = path.basename(PATHS.projectRoot).toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
385
|
-
|
|
386
407
|
const pkgContent = {
|
|
387
|
-
"name":
|
|
388
|
-
"version": "
|
|
408
|
+
"name": "my-jux-project",
|
|
409
|
+
"version": "1.0.0",
|
|
389
410
|
"type": "module",
|
|
390
411
|
"scripts": {
|
|
391
|
-
"
|
|
392
|
-
"
|
|
412
|
+
"build": "jux build",
|
|
413
|
+
"serve": "jux serve"
|
|
393
414
|
},
|
|
394
415
|
"dependencies": {
|
|
395
|
-
"juxscript": "
|
|
416
|
+
"juxscript": "^1.0.8"
|
|
396
417
|
}
|
|
397
418
|
};
|
|
398
419
|
fs.writeFileSync(pkgPath, JSON.stringify(pkgContent, null, 2));
|
|
399
420
|
console.log('+ Created package.json');
|
|
400
421
|
}
|
|
401
422
|
|
|
423
|
+
// Create .gitignore
|
|
402
424
|
const gitignorePath = path.join(PATHS.projectRoot, '.gitignore');
|
|
403
|
-
const gitignoreContent = `jux-dist
|
|
425
|
+
const gitignoreContent = `jux-dist/
|
|
426
|
+
node_modules/
|
|
427
|
+
.DS_Store
|
|
428
|
+
`;
|
|
404
429
|
|
|
405
430
|
if (!fs.existsSync(gitignorePath)) {
|
|
406
431
|
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
407
432
|
console.log('+ Created .gitignore');
|
|
408
433
|
}
|
|
409
434
|
|
|
410
|
-
// â
Create actual juxconfig.js (not just example)
|
|
411
|
-
const configSrc = path.join(PATHS.packageRoot, 'juxconfig.example.js');
|
|
412
|
-
const configDest = path.join(PATHS.projectRoot, 'juxconfig.js');
|
|
413
|
-
|
|
414
|
-
if (fs.existsSync(configSrc) && !fs.existsSync(configDest)) {
|
|
415
|
-
fs.copyFileSync(configSrc, configDest);
|
|
416
|
-
console.log('+ Created juxconfig.js (customize as needed)');
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Also copy example as reference
|
|
420
|
-
const configExampleDest = path.join(PATHS.projectRoot, 'juxconfig.example.js');
|
|
421
|
-
if (fs.existsSync(configSrc) && !fs.existsSync(configExampleDest)) {
|
|
422
|
-
fs.copyFileSync(configSrc, configExampleDest);
|
|
423
|
-
console.log('+ Created juxconfig.example.js (reference)');
|
|
424
|
-
}
|
|
425
|
-
|
|
426
435
|
console.log('\nâ
JUX project initialized!\n');
|
|
427
436
|
console.log('Next steps:');
|
|
428
437
|
console.log(' npm install # Install dependencies');
|
|
@@ -430,15 +439,18 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
430
439
|
console.log('Check out the docs: https://juxscript.com/docs\n');
|
|
431
440
|
|
|
432
441
|
} else if (command === 'build') {
|
|
442
|
+
// â
Always builds router bundle
|
|
433
443
|
await buildProject(false);
|
|
434
444
|
console.log(`â
Build complete: ${PATHS.frontendDist}`);
|
|
435
445
|
|
|
436
446
|
} else if (command === 'serve') {
|
|
437
|
-
|
|
438
|
-
|
|
447
|
+
// â
Always serves router bundle
|
|
448
|
+
await buildProject(true);
|
|
449
|
+
|
|
450
|
+
// Parse port arguments: npx jux serve [httpPort] [wsPort]
|
|
451
|
+
const httpPort = parseInt(process.argv[3]) || 3000;
|
|
452
|
+
const wsPort = parseInt(process.argv[4]) || 3001;
|
|
439
453
|
|
|
440
|
-
await runBootstrap(config.bootstrap);
|
|
441
|
-
await buildProject(true, wsPort);
|
|
442
454
|
await start(httpPort, wsPort);
|
|
443
455
|
|
|
444
456
|
} else {
|
|
@@ -446,15 +458,39 @@ Next steps, run: cd ${projectName} && npm run dev
|
|
|
446
458
|
JUX CLI - A JavaScript UX authorship platform
|
|
447
459
|
|
|
448
460
|
Usage:
|
|
449
|
-
npx jux
|
|
450
|
-
npx jux init Initialize JUX in current directory
|
|
461
|
+
npx jux init Initialize a new JUX project
|
|
451
462
|
npx jux build Build router bundle to ./jux-dist/
|
|
452
463
|
npx jux serve [http] [ws] Start dev server with hot reload
|
|
453
464
|
|
|
465
|
+
Arguments:
|
|
466
|
+
[http] HTTP server port (default: 3000)
|
|
467
|
+
[ws] WebSocket port (default: 3001)
|
|
468
|
+
|
|
469
|
+
Project Structure:
|
|
470
|
+
my-project/
|
|
471
|
+
âââ jux/ # Your .jux source files
|
|
472
|
+
â âââ index.jux # Entry point
|
|
473
|
+
â âââ pages/ # Additional pages
|
|
474
|
+
âââ jux-dist/ # Build output (git-ignore this)
|
|
475
|
+
âââ server/ # Your backend
|
|
476
|
+
âââ package.json
|
|
477
|
+
|
|
478
|
+
Import Style:
|
|
479
|
+
// In your project's .jux files
|
|
480
|
+
import { jux, state } from 'juxscript';
|
|
481
|
+
import 'juxscript/presets/notion.js';
|
|
482
|
+
|
|
483
|
+
Getting Started:
|
|
484
|
+
1. npx jux init # Create project structure
|
|
485
|
+
2. npm install # Install dependencies
|
|
486
|
+
3. npx jux serve # Dev server with hot reload
|
|
487
|
+
4. Serve jux-dist/ from your backend
|
|
488
|
+
|
|
454
489
|
Examples:
|
|
455
|
-
npx jux
|
|
456
|
-
npx jux serve
|
|
457
|
-
npx jux serve 8080
|
|
490
|
+
npx jux build # Build production bundle
|
|
491
|
+
npx jux serve # Dev server (ports 3000/3001)
|
|
492
|
+
npx jux serve 8080 # HTTP on 8080, WS on 3001
|
|
493
|
+
npx jux serve 8080 8081 # HTTP on 8080, WS on 8081
|
|
458
494
|
`);
|
|
459
495
|
}
|
|
460
496
|
})();
|
package/machinery/compiler.js
CHANGED
|
@@ -673,12 +673,16 @@ render();
|
|
|
673
673
|
*
|
|
674
674
|
* @param {string} distDir - Destination directory
|
|
675
675
|
* @param {Array<{path: string, functionName: string}>} routes - Route definitions
|
|
676
|
-
* @param {string} mainJsFilename - The generated main.js filename
|
|
677
|
-
* @param {number} wsPort - WebSocket port for hot reload
|
|
676
|
+
* @param {string} mainJsFilename - The generated main.js filename (e.g., 'main.1234567890.js')
|
|
678
677
|
*/
|
|
679
|
-
export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js'
|
|
678
|
+
export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js') {
|
|
680
679
|
console.log('đ Generating index.html...');
|
|
681
680
|
|
|
681
|
+
// Generate navigation links
|
|
682
|
+
const navLinks = routes
|
|
683
|
+
.map(r => ` <a href="${r.path}">${r.functionName.replace(/_/g, ' ')}</a>`)
|
|
684
|
+
.join(' |\n');
|
|
685
|
+
|
|
682
686
|
const importMapScript = generateImportMapScript();
|
|
683
687
|
|
|
684
688
|
const html = `<!DOCTYPE html>
|
|
@@ -693,62 +697,6 @@ export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js', w
|
|
|
693
697
|
<div id="app"></div>
|
|
694
698
|
${importMapScript}
|
|
695
699
|
<script type="module" src="/${mainJsFilename}"></script>
|
|
696
|
-
|
|
697
|
-
<!-- Hot Reload Script -->
|
|
698
|
-
<script>
|
|
699
|
-
(function() {
|
|
700
|
-
const ws = new WebSocket('ws://' + location.hostname + ':${wsPort}');
|
|
701
|
-
|
|
702
|
-
ws.onopen = () => {
|
|
703
|
-
console.log('đ Hot reload connected');
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
ws.onmessage = (event) => {
|
|
707
|
-
const data = JSON.parse(event.data);
|
|
708
|
-
|
|
709
|
-
if (data.type === 'reload') {
|
|
710
|
-
console.log('đ Hot reload triggered - reloading page...');
|
|
711
|
-
location.reload();
|
|
712
|
-
} else if (data.type === 'css-reload') {
|
|
713
|
-
console.log('đ¨ CSS hot reload:', data.path);
|
|
714
|
-
|
|
715
|
-
// Find all link tags pointing to this CSS file
|
|
716
|
-
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
717
|
-
links.forEach(link => {
|
|
718
|
-
if (link.href.includes(data.path)) {
|
|
719
|
-
const newLink = link.cloneNode();
|
|
720
|
-
newLink.href = data.path + '?t=' + Date.now();
|
|
721
|
-
link.parentNode.insertBefore(newLink, link.nextSibling);
|
|
722
|
-
setTimeout(() => link.remove(), 100);
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
// Also reload any @import in style tags
|
|
727
|
-
const styles = document.querySelectorAll('style');
|
|
728
|
-
styles.forEach(style => {
|
|
729
|
-
if (style.textContent.includes(data.path)) {
|
|
730
|
-
style.textContent = style.textContent.replace(
|
|
731
|
-
new RegExp(data.path + '(\\\\?t=\\\\d+)?', 'g'),
|
|
732
|
-
data.path + '?t=' + Date.now()
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
};
|
|
738
|
-
|
|
739
|
-
ws.onclose = () => {
|
|
740
|
-
console.log('đ Hot reload disconnected');
|
|
741
|
-
// Try to reconnect after 1 second
|
|
742
|
-
setTimeout(() => {
|
|
743
|
-
location.reload();
|
|
744
|
-
}, 1000);
|
|
745
|
-
};
|
|
746
|
-
|
|
747
|
-
ws.onerror = (error) => {
|
|
748
|
-
console.error('đ Hot reload error:', error);
|
|
749
|
-
};
|
|
750
|
-
})();
|
|
751
|
-
</script>
|
|
752
700
|
</body>
|
|
753
701
|
</html>`;
|
|
754
702
|
|
package/machinery/server.js
CHANGED
|
@@ -149,66 +149,4 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './jux-dist') {
|
|
|
149
149
|
|
|
150
150
|
export async function start(httpPort = 3000, wsPort = 3001) {
|
|
151
151
|
return serve(httpPort, wsPort, './jux-dist');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js') {
|
|
155
|
-
const html = `<!DOCTYPE html>
|
|
156
|
-
<html lang="en">
|
|
157
|
-
<head>
|
|
158
|
-
<meta charset="UTF-8">
|
|
159
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
160
|
-
<title>Jux Application</title>
|
|
161
|
-
</head>
|
|
162
|
-
<body data-theme="">
|
|
163
|
-
<div id="app"></div>
|
|
164
|
-
${importMapScript}
|
|
165
|
-
<script type="module" src="/${mainJsFilename}"></script>
|
|
166
|
-
|
|
167
|
-
<!-- Hot Reload Script -->
|
|
168
|
-
<script>
|
|
169
|
-
(function() {
|
|
170
|
-
const ws = new WebSocket('ws://' + location.hostname + ':${wsPort}');
|
|
171
|
-
|
|
172
|
-
ws.onmessage = (event) => {
|
|
173
|
-
const data = JSON.parse(event.data);
|
|
174
|
-
|
|
175
|
-
if (data.type === 'reload') {
|
|
176
|
-
console.log('đ Hot reload triggered');
|
|
177
|
-
location.reload();
|
|
178
|
-
} else if (data.type === 'css-reload') {
|
|
179
|
-
console.log('đ¨ CSS hot reload:', data.path);
|
|
180
|
-
|
|
181
|
-
// Find all link tags pointing to this CSS file
|
|
182
|
-
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
183
|
-
links.forEach(link => {
|
|
184
|
-
if (link.href.includes(data.path)) {
|
|
185
|
-
const newLink = link.cloneNode();
|
|
186
|
-
newLink.href = data.path + '?t=' + Date.now();
|
|
187
|
-
link.parentNode.insertBefore(newLink, link.nextSibling);
|
|
188
|
-
setTimeout(() => link.remove(), 100);
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// Also reload any @import in style tags
|
|
193
|
-
const styles = document.querySelectorAll('style');
|
|
194
|
-
styles.forEach(style => {
|
|
195
|
-
if (style.textContent.includes(data.path)) {
|
|
196
|
-
style.textContent = style.textContent.replace(
|
|
197
|
-
new RegExp(data.path + '(\\?t=\\d+)?', 'g'),
|
|
198
|
-
data.path + '?t=' + Date.now()
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
ws.onclose = () => {
|
|
206
|
-
console.log('đ Hot reload disconnected');
|
|
207
|
-
};
|
|
208
|
-
})();
|
|
209
|
-
</script>
|
|
210
|
-
</body>
|
|
211
|
-
</html>`;
|
|
212
|
-
|
|
213
|
-
return html;
|
|
214
152
|
}
|
package/machinery/watcher.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
copyLibToOutput,
|
|
5
|
+
bundleJuxFilesToRouter,
|
|
6
|
+
generateIndexHtml
|
|
7
|
+
} from './compiler.js';
|
|
4
8
|
|
|
5
9
|
let isRebuilding = false;
|
|
6
10
|
let rebuildQueued = false;
|
|
@@ -115,130 +119,53 @@ async function fullRebuild(juxSource, distDir) {
|
|
|
115
119
|
* Start watching for file changes and rebuild on change
|
|
116
120
|
*/
|
|
117
121
|
export function startWatcher(juxSource, distDir, wsClients) {
|
|
118
|
-
console.log(`đ Watching
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
console.log(`đ Watching: ${juxSource}`);
|
|
123
|
+
|
|
124
|
+
const watcher = fs.watch(juxSource, { recursive: true }, async (eventType, filename) => {
|
|
125
|
+
// Ignore non-.jux files and certain patterns
|
|
126
|
+
if (!filename ||
|
|
127
|
+
!filename.endsWith('.jux') ||
|
|
128
|
+
filename.includes('node_modules') ||
|
|
129
|
+
filename.includes('jux-dist') ||
|
|
130
|
+
filename.startsWith('.')) {
|
|
131
|
+
return;
|
|
126
132
|
}
|
|
127
|
-
debounceTimers.set(key, setTimeout(() => {
|
|
128
|
-
debounceTimers.delete(key);
|
|
129
|
-
fn();
|
|
130
|
-
}, delay));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Recursively watch directories
|
|
134
|
-
function watchRecursive(dir) {
|
|
135
|
-
if (!fs.existsSync(dir)) return;
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
139
|
-
if (!filename) return;
|
|
140
|
-
|
|
141
|
-
const filePath = path.join(dir, filename);
|
|
142
|
-
const ext = path.extname(filename);
|
|
143
|
-
|
|
144
|
-
// Skip certain patterns
|
|
145
|
-
if (filename.includes('node_modules') ||
|
|
146
|
-
filename.includes('jux-dist') ||
|
|
147
|
-
filename.includes('.git') ||
|
|
148
|
-
filename.startsWith('.')) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Debounce to avoid multiple rapid events
|
|
153
|
-
debounce(filePath, async () => {
|
|
154
|
-
// â
Handle CSS files
|
|
155
|
-
if (ext === '.css') {
|
|
156
|
-
console.log(`\nđ¨ CSS changed: ${filename}`);
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
const relativePath = path.relative(juxSource, filePath);
|
|
160
|
-
const destPath = path.join(distDir, relativePath);
|
|
161
|
-
const destDir = path.dirname(destPath);
|
|
162
|
-
|
|
163
|
-
if (!fs.existsSync(destDir)) {
|
|
164
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
fs.copyFileSync(filePath, destPath);
|
|
168
|
-
console.log(` â Copied to: ${path.relative(process.cwd(), destPath)}`);
|
|
169
|
-
|
|
170
|
-
// Notify browser to reload CSS
|
|
171
|
-
wsClients.forEach(client => {
|
|
172
|
-
if (client.readyState === 1) {
|
|
173
|
-
client.send(JSON.stringify({
|
|
174
|
-
type: 'css-reload',
|
|
175
|
-
path: `/${relativePath}`
|
|
176
|
-
}));
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
console.log(` đ Browser CSS reloaded\n`);
|
|
181
|
-
} catch (err) {
|
|
182
|
-
console.error(` â CSS copy failed:`, err.message);
|
|
183
|
-
}
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// â
Handle .jux files
|
|
188
|
-
if (ext === '.jux') {
|
|
189
|
-
console.log(`\nđ File changed: ${filename}`);
|
|
190
|
-
console.log(' đ¨ Rebuilding...');
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
await bundleJuxFilesToRouter(juxSource, distDir, { routePrefix: '' });
|
|
194
|
-
|
|
195
|
-
wsClients.forEach(client => {
|
|
196
|
-
if (client.readyState === 1) {
|
|
197
|
-
client.send(JSON.stringify({ type: 'reload' }));
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
console.log(' â
Rebuild complete');
|
|
202
|
-
console.log(' đ Browser reloaded\n');
|
|
203
|
-
} catch (err) {
|
|
204
|
-
console.error(' â Rebuild failed:', err.message);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
133
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
134
|
+
// Debounce: If already rebuilding, queue another rebuild
|
|
135
|
+
if (isRebuilding) {
|
|
136
|
+
rebuildQueued = true;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
211
139
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const destPath = path.join(distDir, relativePath);
|
|
215
|
-
const destDir = path.dirname(destPath);
|
|
140
|
+
isRebuilding = true;
|
|
141
|
+
console.log(`\nđ File changed: ${filename}`);
|
|
216
142
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
143
|
+
// Rebuild the entire bundle
|
|
144
|
+
const success = await fullRebuild(juxSource, distDir);
|
|
220
145
|
|
|
221
|
-
|
|
222
|
-
console.log(` â Copied to: ${path.relative(process.cwd(), destPath)}`);
|
|
146
|
+
isRebuilding = false;
|
|
223
147
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
});
|
|
148
|
+
// Notify all WebSocket clients to reload
|
|
149
|
+
if (success && wsClients && wsClients.length > 0) {
|
|
150
|
+
console.log(`đ Notifying ${wsClients.length} client(s) to reload`);
|
|
229
151
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
152
|
+
// â
Add small delay to ensure file is fully written
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
wsClients.forEach(client => {
|
|
155
|
+
if (client.readyState === 1) { // OPEN
|
|
156
|
+
client.send(JSON.stringify({ type: 'reload' }));
|
|
234
157
|
}
|
|
235
158
|
});
|
|
236
|
-
});
|
|
237
|
-
} catch (err) {
|
|
238
|
-
console.error(` â ī¸ Failed to watch ${dir}:`, err.message);
|
|
159
|
+
}, 100);
|
|
239
160
|
}
|
|
240
|
-
}
|
|
241
161
|
|
|
242
|
-
|
|
243
|
-
|
|
162
|
+
// Process queued rebuild if needed
|
|
163
|
+
if (rebuildQueued) {
|
|
164
|
+
rebuildQueued = false;
|
|
165
|
+
console.log('đ Processing queued rebuild...');
|
|
166
|
+
setTimeout(() => watcher.emit('change', 'change', filename), 500);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return watcher;
|
|
244
171
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juxscript",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Build reactive UIs with pure JavaScript - no markup needed. Say goodbye to </</>>>",
|
|
3
|
+
"version": "1.0.47",
|
|
5
4
|
"type": "module",
|
|
5
|
+
"description": "A JavaScript UX authorship platform",
|
|
6
6
|
"main": "lib/jux.js",
|
|
7
7
|
"types": "lib/jux.d.ts",
|
|
8
8
|
"access": "public",
|
|
@@ -39,20 +39,29 @@
|
|
|
39
39
|
"jux": "./bin/cli.js"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"reactive",
|
|
42
|
+
"ui",
|
|
43
|
+
"ux",
|
|
45
44
|
"components",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"hot-reload",
|
|
50
|
-
"typescript"
|
|
45
|
+
"framework",
|
|
46
|
+
"reactive",
|
|
47
|
+
"javascript"
|
|
51
48
|
],
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
"
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc",
|
|
51
|
+
"dev": "tsc --watch",
|
|
52
|
+
"prepublishOnly": "npm run build"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"chokidar": "^3.5.3",
|
|
56
|
+
"esbuild": "^0.19.0",
|
|
57
|
+
"express": "^4.18.2",
|
|
58
|
+
"glob": "^13.0.0",
|
|
59
|
+
"ws": "^8.13.0"
|
|
55
60
|
},
|
|
56
|
-
"
|
|
57
|
-
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/express": "^4.17.17",
|
|
63
|
+
"@types/node": "^20.0.0",
|
|
64
|
+
"@types/ws": "^8.5.5",
|
|
65
|
+
"typescript": "^5.0.0"
|
|
66
|
+
}
|
|
58
67
|
}
|
package/machinery/config.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Default JUX configuration
|
|
6
|
-
*/
|
|
7
|
-
export const defaultConfig = {
|
|
8
|
-
// Source directory for .jux files
|
|
9
|
-
sourceDir: 'jux',
|
|
10
|
-
|
|
11
|
-
// Output directory for built files
|
|
12
|
-
distDir: 'jux-dist',
|
|
13
|
-
|
|
14
|
-
// Dev server ports
|
|
15
|
-
ports: {
|
|
16
|
-
http: 3000,
|
|
17
|
-
ws: 3001
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
// Build options
|
|
21
|
-
build: {
|
|
22
|
-
minify: false,
|
|
23
|
-
sourcemap: true
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
// Bootstrap functions (run before app starts)
|
|
27
|
-
bootstrap: []
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Load juxconfig.js from project root
|
|
32
|
-
* @param {string} projectRoot - Project root directory
|
|
33
|
-
* @returns {object} Merged configuration
|
|
34
|
-
*/
|
|
35
|
-
export async function loadConfig(projectRoot) {
|
|
36
|
-
const configPath = path.join(projectRoot, 'juxconfig.js');
|
|
37
|
-
|
|
38
|
-
if (!fs.existsSync(configPath)) {
|
|
39
|
-
console.log('âšī¸ No juxconfig.js found, using defaults');
|
|
40
|
-
return defaultConfig;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
console.log(`đ Loading config from: ${configPath}`);
|
|
45
|
-
|
|
46
|
-
// Dynamic import for ES modules
|
|
47
|
-
const configUrl = `file://${configPath}`;
|
|
48
|
-
const { default: userConfig } = await import(configUrl);
|
|
49
|
-
|
|
50
|
-
// Merge with defaults
|
|
51
|
-
const config = {
|
|
52
|
-
...defaultConfig,
|
|
53
|
-
...userConfig,
|
|
54
|
-
ports: {
|
|
55
|
-
...defaultConfig.ports,
|
|
56
|
-
...(userConfig.ports || {})
|
|
57
|
-
},
|
|
58
|
-
build: {
|
|
59
|
-
...defaultConfig.build,
|
|
60
|
-
...(userConfig.build || {})
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
console.log(` â Config loaded`);
|
|
65
|
-
console.log(` Source: ${config.sourceDir}`);
|
|
66
|
-
console.log(` Output: ${config.distDir}\n`);
|
|
67
|
-
|
|
68
|
-
return config;
|
|
69
|
-
} catch (err) {
|
|
70
|
-
console.warn(`â ī¸ Failed to load juxconfig.js:`, err.message);
|
|
71
|
-
console.warn(` Using default configuration\n`);
|
|
72
|
-
return defaultConfig;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Run bootstrap functions from config
|
|
78
|
-
* @param {Array<Function>} bootstrapFns - Array of bootstrap functions
|
|
79
|
-
*/
|
|
80
|
-
export async function runBootstrap(bootstrapFns) {
|
|
81
|
-
if (!bootstrapFns || bootstrapFns.length === 0) return;
|
|
82
|
-
|
|
83
|
-
console.log(`đ Running ${bootstrapFns.length} bootstrap function(s)...`);
|
|
84
|
-
|
|
85
|
-
for (const fn of bootstrapFns) {
|
|
86
|
-
try {
|
|
87
|
-
await fn();
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.error(` â Bootstrap function failed:`, err.message);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log(` â Bootstrap complete\n`);
|
|
94
|
-
}
|