@zenithbuild/bundler 1.3.1
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/CLAUDE.md +111 -0
- package/Cargo.lock +4244 -0
- package/Cargo.toml +65 -0
- package/README.md +135 -0
- package/build.rs +5 -0
- package/dev-server.js +117 -0
- package/index.js +18 -0
- package/package.json +14 -0
- package/src/bundler.rs +81 -0
- package/src/css.rs +266 -0
- package/src/html.rs +147 -0
- package/src/lib.rs +116 -0
- package/src/plugin.rs +332 -0
- package/src/store.rs +44 -0
- package/tests/fixtures/hello.zen +19 -0
- package/tests/integration.rs +53 -0
- package/tsconfig.json +29 -0
package/Cargo.toml
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "zenith-bundler"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Rolldown Plugin for the Zenith Framework - Zero-Cost Abstraction Bundler"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
|
|
8
|
+
[lib]
|
|
9
|
+
crate-type = ["cdylib", "rlib"]
|
|
10
|
+
|
|
11
|
+
[features]
|
|
12
|
+
napi = []
|
|
13
|
+
|
|
14
|
+
[dependencies]
|
|
15
|
+
# CSS Optimization & Pruning
|
|
16
|
+
lightningcss = "1.0.0-alpha.70"
|
|
17
|
+
|
|
18
|
+
# Serialization
|
|
19
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
20
|
+
serde_json = "1.0"
|
|
21
|
+
|
|
22
|
+
# Zenith Compiler (local crate)
|
|
23
|
+
compiler-native = { path = "../zenith-compiler/native/compiler-native", default-features = false }
|
|
24
|
+
|
|
25
|
+
# Node.js FFI (for TypeScript interface)
|
|
26
|
+
napi = { version = "2.16.0", features = ["async", "serde-json"] }
|
|
27
|
+
napi-derive = "2.16.0"
|
|
28
|
+
|
|
29
|
+
# Async runtime
|
|
30
|
+
tokio = { version = "1.0", features = ["fs", "macros", "rt-multi-thread"] }
|
|
31
|
+
|
|
32
|
+
# Concurrent collections for CSS buffering
|
|
33
|
+
dashmap = "5.5"
|
|
34
|
+
|
|
35
|
+
# ArcStr for Rolldown compatibility
|
|
36
|
+
arcstr = "1.2"
|
|
37
|
+
|
|
38
|
+
# Error handling
|
|
39
|
+
anyhow = "1.0"
|
|
40
|
+
|
|
41
|
+
# Regex for CSS class extraction
|
|
42
|
+
regex = "1.10"
|
|
43
|
+
|
|
44
|
+
# Rolldown from git (latest compatible version)
|
|
45
|
+
# Note: Using git source because crates.io version 0.1.0 has broken oxc_resolver compat
|
|
46
|
+
[dependencies.rolldown]
|
|
47
|
+
git = "https://github.com/rolldown/rolldown"
|
|
48
|
+
branch = "main"
|
|
49
|
+
default-features = false
|
|
50
|
+
|
|
51
|
+
[dependencies.rolldown_plugin]
|
|
52
|
+
git = "https://github.com/rolldown/rolldown"
|
|
53
|
+
branch = "main"
|
|
54
|
+
default-features = false
|
|
55
|
+
|
|
56
|
+
[dependencies.rolldown_common]
|
|
57
|
+
git = "https://github.com/rolldown/rolldown"
|
|
58
|
+
branch = "main"
|
|
59
|
+
default-features = false
|
|
60
|
+
|
|
61
|
+
[build-dependencies]
|
|
62
|
+
napi-build = "2.1.0"
|
|
63
|
+
|
|
64
|
+
[dev-dependencies]
|
|
65
|
+
pretty_assertions = "1.4"
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Zenith Bundler
|
|
2
|
+
|
|
3
|
+
Zero-Cost Abstraction Bundler for the Zenith Framework.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Zenith Bundler provides capability-based runtime chunking, CSS pruning, and deferred hydration for optimal production builds. Instead of shipping a monolithic runtime, it selectively includes only the capabilities used by each page.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Compiler (ZenIR) → Manifest → Bundler → Optimized Output
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Runtime Slices
|
|
16
|
+
|
|
17
|
+
| Slice | Size | When Included |
|
|
18
|
+
|-------|------|---------------|
|
|
19
|
+
| **Core** | ~2KB | Always required |
|
|
20
|
+
| **Reactivity** | ~8KB | If `{value}` expressions or state used |
|
|
21
|
+
| **Hydration** | ~5KB | If page is interactive |
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Rust crate
|
|
27
|
+
cargo add zenith-bundler
|
|
28
|
+
|
|
29
|
+
# TypeScript package
|
|
30
|
+
bun add @zenithbuild/bundler
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Rust
|
|
36
|
+
|
|
37
|
+
```rust
|
|
38
|
+
use zenith_bundler::{bundle, analyze_manifest, ZenManifest};
|
|
39
|
+
|
|
40
|
+
let manifest = ZenManifest::new("src/pages/index.zen".to_string());
|
|
41
|
+
let analysis = analyze_manifest(&manifest);
|
|
42
|
+
|
|
43
|
+
println!("Required slices: {:?}", analysis.required_slices);
|
|
44
|
+
println!("Is static: {}", analysis.is_static);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### TypeScript
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { bundle, generateRuntime } from '@zenithbuild/bundler'
|
|
51
|
+
|
|
52
|
+
// Full production bundle
|
|
53
|
+
const result = bundle(manifest, {
|
|
54
|
+
minifyJs: true,
|
|
55
|
+
minifyCss: true,
|
|
56
|
+
basePath: '/assets/'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Dev server (HMR)
|
|
60
|
+
const { code, slices } = generateRuntime(manifest, true)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API
|
|
64
|
+
|
|
65
|
+
### `bundle(manifest, options?)`
|
|
66
|
+
|
|
67
|
+
Generates complete HTML/JS/CSS output.
|
|
68
|
+
|
|
69
|
+
**Options:**
|
|
70
|
+
- `minifyJs` - Minify JavaScript (default: true)
|
|
71
|
+
- `minifyCss` - Minify CSS (default: true)
|
|
72
|
+
- `inlineCriticalCss` - Inline critical CSS (default: true)
|
|
73
|
+
- `sourceMaps` - Generate source maps (default: false)
|
|
74
|
+
- `devMode` - Skip optimizations (default: false)
|
|
75
|
+
- `basePath` - Asset base path (default: "/")
|
|
76
|
+
- `lazyLoad` - Lazy load non-critical chunks (default: true)
|
|
77
|
+
- `maxChunkSize` - Max chunk size in bytes (default: 50000)
|
|
78
|
+
|
|
79
|
+
### `generateRuntime(manifest, devMode?)`
|
|
80
|
+
|
|
81
|
+
Generates only the runtime code (for HMR/dev server).
|
|
82
|
+
|
|
83
|
+
### `analyzeManifest(manifest)`
|
|
84
|
+
|
|
85
|
+
Analyzes a manifest and returns required slices.
|
|
86
|
+
|
|
87
|
+
## Bundle Size Budgets
|
|
88
|
+
|
|
89
|
+
| Page Type | Budget |
|
|
90
|
+
|-----------|--------|
|
|
91
|
+
| Static | < 5KB |
|
|
92
|
+
| Interactive | < 20KB |
|
|
93
|
+
| Complex | < 50KB |
|
|
94
|
+
|
|
95
|
+
Run size gate: `bun run js/scripts/size-gate.ts`
|
|
96
|
+
|
|
97
|
+
## Testing
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Rust tests
|
|
101
|
+
cargo test
|
|
102
|
+
|
|
103
|
+
# TypeScript tests
|
|
104
|
+
cd js && bun test
|
|
105
|
+
|
|
106
|
+
# Size gate
|
|
107
|
+
cd js && bun run scripts/size-gate.ts
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Project Structure
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
zenith-bundler/
|
|
114
|
+
├── src/
|
|
115
|
+
│ ├── lib.rs # Main exports
|
|
116
|
+
│ ├── analysis.rs # Manifest analysis
|
|
117
|
+
│ ├── chunking/ # Chunk computation
|
|
118
|
+
│ ├── codegen/ # Runtime generation
|
|
119
|
+
│ ├── css/ # CSS optimization
|
|
120
|
+
│ └── manifest/ # Types & capabilities
|
|
121
|
+
├── tests/
|
|
122
|
+
│ └── integration.rs # Integration tests
|
|
123
|
+
└── js/
|
|
124
|
+
├── src/
|
|
125
|
+
│ ├── index.ts # TypeScript API
|
|
126
|
+
│ ├── types.ts # TypeScript types
|
|
127
|
+
│ └── index.test.ts
|
|
128
|
+
└── scripts/
|
|
129
|
+
└── size-gate.ts # Bundle size CI gate
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
|
135
|
+
# zenith-bundler
|
package/build.rs
ADDED
package/dev-server.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { ZenithDevController } = require('./index.node'); // Assuming built target via napi
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const chokidar = require('chokidar');
|
|
5
|
+
const { WebSocketServer } = require('ws');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
|
|
8
|
+
const app = express();
|
|
9
|
+
const port = 3000;
|
|
10
|
+
|
|
11
|
+
// Initialize Controller (Points to current project root)
|
|
12
|
+
// Default to parent directory (assuming zenith-bundler is inside project or sibling)
|
|
13
|
+
// Adjust this based on where you run the server.
|
|
14
|
+
const start = async (projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, '..')) => {
|
|
15
|
+
console.log(`[DevServer] Starting Zenith for: ${projectRoot}`);
|
|
16
|
+
|
|
17
|
+
let controller;
|
|
18
|
+
try {
|
|
19
|
+
controller = new ZenithDevController(projectRoot);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error("Failed to initialize ZenithDevController:", e);
|
|
22
|
+
console.error("Ensure you have built the native module: 'napi build --platform'");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Create HTTP and WS Server
|
|
27
|
+
const server = http.createServer(app);
|
|
28
|
+
const wss = new WebSocketServer({ server });
|
|
29
|
+
|
|
30
|
+
wss.on('connection', (ws) => {
|
|
31
|
+
// console.log('[HMR] Client connected');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function notifyHMR() {
|
|
35
|
+
// console.log('[HMR] Broadcasting update signal...');
|
|
36
|
+
wss.clients.forEach((client) => {
|
|
37
|
+
if (client.readyState === 1) { // OPEN
|
|
38
|
+
client.send(JSON.stringify({ type: 'update' }));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Watcher Integration (Tactical Fix)
|
|
44
|
+
const watchPath = path.join(projectRoot, 'src/**/*.zen');
|
|
45
|
+
console.log(`[Watcher] Watching ${watchPath}`);
|
|
46
|
+
const watcher = chokidar.watch(watchPath, {
|
|
47
|
+
ignoreInitial: true
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
watcher.on('change', async (filePath) => {
|
|
51
|
+
// console.log(`[Watcher] File changed: ${filePath}`);
|
|
52
|
+
try {
|
|
53
|
+
// 1. Tell Rust to re-compile (updates the AssetStore in RAM)
|
|
54
|
+
console.time('Rebuild');
|
|
55
|
+
await controller.rebuild();
|
|
56
|
+
console.timeEnd('Rebuild');
|
|
57
|
+
|
|
58
|
+
// 2. Tell the Browser to fetch the new assets
|
|
59
|
+
notifyHMR();
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error("Rebuild failed:", e);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Serve Assets
|
|
66
|
+
app.use(async (req, res, next) => {
|
|
67
|
+
// 1. Try to serve from memory store
|
|
68
|
+
const asset = controller.getAsset(req.path);
|
|
69
|
+
|
|
70
|
+
if (asset) {
|
|
71
|
+
if (req.path.endsWith('.js')) {
|
|
72
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
73
|
+
} else if (req.path.endsWith('.css')) {
|
|
74
|
+
res.setHeader('Content-Type', 'text/css');
|
|
75
|
+
}
|
|
76
|
+
return res.send(asset);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2. SPA / HTML Fallback
|
|
80
|
+
if (req.path === '/' || !path.extname(req.path)) {
|
|
81
|
+
res.send(`
|
|
82
|
+
<!DOCTYPE html>
|
|
83
|
+
<html>
|
|
84
|
+
<head>
|
|
85
|
+
<title>Zenith Dev</title>
|
|
86
|
+
<script type="module" src="/index.js"></script>
|
|
87
|
+
<link rel="stylesheet" href="/zenith.css">
|
|
88
|
+
<script>
|
|
89
|
+
// HMR Client
|
|
90
|
+
const ws = new WebSocket('ws://' + location.host);
|
|
91
|
+
ws.onmessage = (event) => {
|
|
92
|
+
const msg = JSON.parse(event.data);
|
|
93
|
+
if (msg.type === 'update') {
|
|
94
|
+
console.log('[HMR] Update received. Reloading...');
|
|
95
|
+
location.reload();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
ws.onopen = () => console.log("[Zenith] HMR Connected");
|
|
99
|
+
</script>
|
|
100
|
+
</head>
|
|
101
|
+
<body>
|
|
102
|
+
<div id="app"></div>
|
|
103
|
+
</body>
|
|
104
|
+
</html>
|
|
105
|
+
`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
next();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
server.listen(port, () => {
|
|
113
|
+
console.log(`Zenith Dev Server running at http://localhost:${port}`);
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
module.exports = { start };
|
package/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const devServer = require('./dev-server.js');
|
|
2
|
+
|
|
3
|
+
function createZenithBundler() {
|
|
4
|
+
return {
|
|
5
|
+
async dev(options) {
|
|
6
|
+
console.log("Starting Zenith Bundler (Dev)...");
|
|
7
|
+
// Assuming options has root or fallback
|
|
8
|
+
const root = options?.root || process.cwd();
|
|
9
|
+
await devServer.start(root);
|
|
10
|
+
},
|
|
11
|
+
async build(options) {
|
|
12
|
+
console.log("Zenith Bundler Build: Not yet implemented in Phase 6.");
|
|
13
|
+
// Placeholder for production build integration
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { createZenithBundler };
|
package/package.json
ADDED
package/src/bundler.rs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//! Zenith Bundler Configuration
|
|
2
|
+
//!
|
|
3
|
+
//! Provides helper functions to create a pre-configured Rolldown bundler
|
|
4
|
+
//! with Zenith's required settings (capability splitting, plugin injection, etc).
|
|
5
|
+
|
|
6
|
+
use rolldown::{Bundler, BundlerBuilder, BundlerOptions, InputItem};
|
|
7
|
+
use std::sync::Arc;
|
|
8
|
+
|
|
9
|
+
use crate::plugin::ZenithPlugin;
|
|
10
|
+
|
|
11
|
+
/// Create a configured Rolldown bundler for a Zenith project
|
|
12
|
+
pub fn create_zenith_bundler(entry_point: &str, components_dir: Option<&str>) -> Bundler {
|
|
13
|
+
// 1. Initialize the Zenith Plugin
|
|
14
|
+
let mut plugin = ZenithPlugin::new(entry_point);
|
|
15
|
+
if let Some(dir) = components_dir {
|
|
16
|
+
plugin = plugin.with_components_dir(dir);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Configure Bundler Options
|
|
20
|
+
// Note: manual_chunks is currently experimental in Rolldown Rust API,
|
|
21
|
+
// we'll need to verify the exact API surface.
|
|
22
|
+
// For now, we rely on the plugin's dynamic imports to drive chunking naturally,
|
|
23
|
+
// and we can enhance this with explicit manual_chunks if the API allows.
|
|
24
|
+
|
|
25
|
+
let options = BundlerOptions {
|
|
26
|
+
input: Some(vec![InputItem {
|
|
27
|
+
name: Some("index".into()),
|
|
28
|
+
import: "virtual:zenith-entry".into(), // Start with our Hydration Controller
|
|
29
|
+
}]),
|
|
30
|
+
// Enable code splitting
|
|
31
|
+
format: Some(rolldown_common::OutputFormat::Esm),
|
|
32
|
+
// Ensure we target browser environment
|
|
33
|
+
platform: Some(rolldown_common::Platform::Browser),
|
|
34
|
+
// Capability-Based Chunking:
|
|
35
|
+
// GSAP should be handled as a separate chunk (capability: "anim").
|
|
36
|
+
// We rely on dynamic imports (import('gsap')) in the user's code to automatically split it.
|
|
37
|
+
// Explicit manual_chunks can be added here if stricter control is needed.
|
|
38
|
+
// Capability-based chunking configuration will go here once verified
|
|
39
|
+
..Default::default()
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let builder = BundlerBuilder::default()
|
|
43
|
+
.with_options(options)
|
|
44
|
+
.with_plugins(vec![Arc::new(plugin)]);
|
|
45
|
+
|
|
46
|
+
builder.build().expect("Failed to build bundler")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Create a configured Rolldown bundler for Dev Mode (Watch + HMR + InMemory)
|
|
50
|
+
pub fn create_dev_bundler(
|
|
51
|
+
entry_point: &str,
|
|
52
|
+
components_dir: Option<&str>,
|
|
53
|
+
store: std::sync::Arc<crate::store::AssetStore>,
|
|
54
|
+
) -> Bundler {
|
|
55
|
+
// 1. Initialize the Zenith Plugin with Store and Dev Mode
|
|
56
|
+
let mut plugin = ZenithPlugin::new(entry_point)
|
|
57
|
+
.with_store(store)
|
|
58
|
+
.with_dev_mode(true);
|
|
59
|
+
|
|
60
|
+
if let Some(dir) = components_dir {
|
|
61
|
+
plugin = plugin.with_components_dir(dir);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Configure Bundler Options (Dev Optimized)
|
|
65
|
+
let options = BundlerOptions {
|
|
66
|
+
input: Some(vec![InputItem {
|
|
67
|
+
name: Some("index".into()),
|
|
68
|
+
import: "virtual:zenith-entry".into(),
|
|
69
|
+
}]),
|
|
70
|
+
format: Some(rolldown_common::OutputFormat::Esm),
|
|
71
|
+
platform: Some(rolldown_common::Platform::Browser),
|
|
72
|
+
sourcemap: Some(rolldown_common::SourceMapType::File), // Enable sourcemaps for dev
|
|
73
|
+
..Default::default()
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
let builder = BundlerBuilder::default()
|
|
77
|
+
.with_options(options)
|
|
78
|
+
.with_plugins(vec![Arc::new(plugin)]);
|
|
79
|
+
|
|
80
|
+
builder.build().expect("Failed to build dev bundler")
|
|
81
|
+
}
|