@ape-egg/vibe 1.0.3 → 1.1.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/README.md +228 -23
  3. package/compiler/bin/vibe-compile.js +109 -0
  4. package/compiler/native/.gitkeep +0 -0
  5. package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
  6. package/compiler/src/Cargo.lock +1885 -0
  7. package/compiler/src/Cargo.toml +29 -0
  8. package/compiler/src/compiler/compile.rs +1209 -0
  9. package/compiler/src/compiler/mod.rs +5 -0
  10. package/compiler/src/config.rs +184 -0
  11. package/compiler/src/main.rs +284 -0
  12. package/compiler/src/parser/element.rs +96 -0
  13. package/compiler/src/parser/html.rs +339 -0
  14. package/compiler/src/parser/mod.rs +8 -0
  15. package/index.js +2 -233
  16. package/package.json +26 -3
  17. package/{affected.js → runtime/affected.js} +66 -14
  18. package/runtime/cleanup.js +59 -0
  19. package/runtime/component.js +116 -0
  20. package/{conditionals.js → runtime/conditionals.js} +27 -23
  21. package/{constants.js → runtime/constants.js} +23 -3
  22. package/runtime/debug.js +91 -0
  23. package/{hydrate.js → runtime/hydrate.js} +58 -20
  24. package/runtime/index.js +614 -0
  25. package/{iterate.js → runtime/iterate.js} +53 -45
  26. package/{iteration-utils.js → runtime/iteration-utils.js} +11 -1
  27. package/{parse.js → runtime/parse.js} +37 -7
  28. package/runtime/state.js +52 -0
  29. package/{utils.js → runtime/utils.js} +13 -0
  30. package/llms.txt +0 -279
  31. package/state.js +0 -26
  32. /package/{_vibe-compiled-iteration-batch.js → runtime/_vibe-compiled-iteration-batch.js} +0 -0
  33. /package/{link.js → runtime/manifest.js} +0 -0
  34. /package/{vibe.css → runtime/vibe.css} +0 -0
@@ -0,0 +1,339 @@
1
+ use std::fs;
2
+ use std::path::Path;
3
+ use std::collections::{HashMap, HashSet};
4
+ use thiserror::Error;
5
+
6
+ use super::element::{Element, ElementCache};
7
+
8
+ #[derive(Error, Debug)]
9
+ pub enum ParseError {
10
+ #[error("Failed to read file {path}: {source}")]
11
+ ReadError {
12
+ path: String,
13
+ #[source]
14
+ source: std::io::Error,
15
+ },
16
+ #[error("Element not found: {0}")]
17
+ #[allow(dead_code)]
18
+ ElementNotFound(String),
19
+ }
20
+
21
+ pub struct HtmlParser {
22
+ components_dir: std::path::PathBuf,
23
+ cache: ElementCache,
24
+ }
25
+
26
+ impl HtmlParser {
27
+ pub fn new(components_dir: std::path::PathBuf) -> Self {
28
+ Self {
29
+ components_dir,
30
+ cache: ElementCache::new(),
31
+ }
32
+ }
33
+
34
+ /// Load all elements from the elements directory
35
+ pub fn load_elements(&mut self) -> Result<(), ParseError> {
36
+ if !self.components_dir.exists() {
37
+ return Ok(()); // No elements directory is fine
38
+ }
39
+
40
+ self.load_elements_recursive(&self.components_dir.clone())
41
+ }
42
+
43
+ fn load_elements_recursive(&mut self, dir: &Path) -> Result<(), ParseError> {
44
+ let entries = fs::read_dir(dir).map_err(|e| ParseError::ReadError {
45
+ path: dir.display().to_string(),
46
+ source: e,
47
+ })?;
48
+
49
+ for entry in entries.flatten() {
50
+ let path = entry.path();
51
+
52
+ if path.is_dir() {
53
+ self.load_elements_recursive(&path)?;
54
+ } else if path.extension().map_or(false, |ext| ext == "html") {
55
+ let tag_name = path
56
+ .file_stem()
57
+ .and_then(|s| s.to_str())
58
+ .unwrap_or("")
59
+ .to_string();
60
+
61
+ let content = fs::read_to_string(&path).map_err(|e| ParseError::ReadError {
62
+ path: path.display().to_string(),
63
+ source: e,
64
+ })?;
65
+
66
+ let element = Element::new(tag_name.clone(), path.clone(), content);
67
+ self.cache.insert(tag_name, element);
68
+ }
69
+ }
70
+
71
+ Ok(())
72
+ }
73
+
74
+ /// Get an element by tag name
75
+ pub fn _get_element(&self, tag_name: &str) -> Option<&Element> {
76
+ self.cache.get(tag_name)
77
+ }
78
+
79
+ /// Get all loaded elements
80
+ #[allow(dead_code)]
81
+ pub fn elements(&self) -> &ElementCache {
82
+ &self.cache
83
+ }
84
+
85
+ /// Process HTML content: transform custom tags to <component>, optionally inline, apply accessibility
86
+ pub fn process_html(
87
+ &self,
88
+ content: &str,
89
+ accessibility: bool,
90
+ exclude_tags: &[String],
91
+ elements_as_is: bool,
92
+ components_dir: &str,
93
+ ) -> String {
94
+ self.process_html_with_cache(content, accessibility, exclude_tags, elements_as_is, components_dir, &HashMap::new())
95
+ }
96
+
97
+ pub fn process_html_with_cache(
98
+ &self,
99
+ content: &str,
100
+ accessibility: bool,
101
+ exclude_tags: &[String],
102
+ elements_as_is: bool,
103
+ components_dir: &str,
104
+ external_cache: &HashMap<String, String>,
105
+ ) -> String {
106
+ let mut result = content.to_string();
107
+
108
+ // Step 1: Transform custom tags matching element files to <component> tags
109
+ result = self.transform_custom_tags_to_component(&result, components_dir);
110
+
111
+ // Step 2: If elements_as_is is false, recursively inline all <component> elements
112
+ if !elements_as_is {
113
+ result = self.inline_component_elements(&result, external_cache);
114
+ }
115
+
116
+ // Step 3: Accessibility transform if requested
117
+ if accessibility {
118
+ result = transform_custom_tags_to_divs(&result, exclude_tags);
119
+ }
120
+
121
+ result
122
+ }
123
+
124
+ /// Transform custom tags that match element files to <component src="..."> tags
125
+ fn transform_custom_tags_to_component(&self, content: &str, components_dir: &str) -> String {
126
+ let mut result = content.to_string();
127
+
128
+ // Find all tags that match loaded elements
129
+ for tag_name in self.cache.keys() {
130
+ // Match opening and closing tags with any attributes and children
131
+ let tag_pattern = format!(r"<{}(\s[^>]*)?>", regex::escape(tag_name));
132
+ let tag_re = regex::Regex::new(&tag_pattern).unwrap();
133
+ let closing_pattern = format!(r"</{}>", regex::escape(tag_name));
134
+
135
+ // Find all occurrences and transform them
136
+ let mut matches: Vec<(usize, usize, String, Option<String>)> = Vec::new();
137
+
138
+ // Find opening tags
139
+ for cap in tag_re.find_iter(&result) {
140
+ let start = cap.start();
141
+ let tag_with_attrs = cap.as_str();
142
+
143
+ // Extract attributes (everything between tag name and >)
144
+ let attrs = if tag_with_attrs.ends_with('>') {
145
+ let inner = &tag_with_attrs[tag_name.len() + 1..tag_with_attrs.len() - 1];
146
+ if inner.trim().is_empty() {
147
+ None
148
+ } else {
149
+ Some(inner.to_string())
150
+ }
151
+ } else {
152
+ None
153
+ };
154
+
155
+ // Find corresponding closing tag
156
+ if let Some(closing_pos) = result[cap.end()..].find(&closing_pattern) {
157
+ let closing_start = cap.end() + closing_pos;
158
+ let closing_end = closing_start + closing_pattern.len();
159
+ let children = result[cap.end()..closing_start].to_string();
160
+
161
+ matches.push((start, closing_end, children, attrs));
162
+ }
163
+ }
164
+
165
+ // Replace from end to start to maintain indices
166
+ matches.reverse();
167
+ for (start, end, children, attrs) in matches {
168
+ let attrs_str = attrs.map(|a| format!(" {}", a)).unwrap_or_default();
169
+ let replacement = if children.trim().is_empty() {
170
+ format!("<component src=\"/{}/{}.html\"{}>", components_dir, tag_name, attrs_str)
171
+ } else {
172
+ format!("<component src=\"/{}/{}.html\"{}>{}</component>", components_dir, tag_name, attrs_str, children)
173
+ };
174
+ result.replace_range(start..end, &replacement);
175
+ }
176
+ }
177
+
178
+ result
179
+ }
180
+
181
+ /// Recursively inline all <component> elements with their HTML content
182
+ fn inline_component_elements(&self, content: &str, external_cache: &HashMap<String, String>) -> String {
183
+ let mut result = content.to_string();
184
+ let mut changed = true;
185
+ let mut iterations = 0;
186
+ const MAX_ITERATIONS: usize = 100; // Prevent infinite loops
187
+
188
+ while changed && iterations < MAX_ITERATIONS {
189
+ changed = false;
190
+ iterations += 1;
191
+
192
+ // Match <component src="..." attrs...>children</component>
193
+ // Handles internal (/components/file.html) and external (http://... or https://...)
194
+ // Use (?s) flag to make . match newlines
195
+ let component_re = regex::Regex::new(
196
+ r#"(?s)<component\s+src="([^"]+)"([^>]*)>(.*?)</component>"#
197
+ ).unwrap();
198
+
199
+ let matches: Vec<_> = component_re.captures_iter(&result).map(|cap| {
200
+ let full_match = cap.get(0).unwrap();
201
+ let src = cap.get(1).unwrap().as_str();
202
+ let attrs_str = cap.get(2).map(|m| m.as_str().to_string()).unwrap_or_default();
203
+ let slot_content = cap.get(3).map(|m| m.as_str().to_string());
204
+
205
+ // Parse attributes into a map (prop_name -> prop_value)
206
+ let props = Self::parse_props(&attrs_str);
207
+
208
+ (full_match.start(), full_match.end(), src.to_string(), props, slot_content)
209
+ }).collect();
210
+
211
+ if !matches.is_empty() {
212
+ changed = true;
213
+ }
214
+
215
+ // Replace from end to start
216
+ for (start, end, src, props, slot_content) in matches.iter().rev() {
217
+ // Check if it's an external URL
218
+ let replacement_content = if src.starts_with("http://") || src.starts_with("https://") {
219
+ // External component - get from cache
220
+ external_cache.get(src).cloned()
221
+ } else {
222
+ // Internal component - get element name from filename (e.g., "card.html" -> "card")
223
+ let element_name = src
224
+ .trim_start_matches("./")
225
+ .trim_start_matches('/')
226
+ .split('/')
227
+ .last()
228
+ .unwrap_or(src)
229
+ .trim_end_matches(".html");
230
+
231
+ self.cache.get(element_name).map(|e| e.content.clone())
232
+ };
233
+
234
+ if let Some(mut replacement) = replacement_content {
235
+ // Replace props: for each prop like headline="@[pageTitle]",
236
+ // replace @[headline] in content with @[pageTitle]
237
+ for (prop_name, prop_value) in props {
238
+ let prop_binding = format!("@[{}]", prop_name);
239
+ replacement = replacement.replace(&prop_binding, prop_value);
240
+ }
241
+
242
+ // Replace <slot></slot> with slot content if present
243
+ if let Some(ref slot) = slot_content {
244
+ if !slot.trim().is_empty() {
245
+ replacement = replacement.replace("<slot></slot>", slot);
246
+ replacement = replacement.replace("<slot/>", slot);
247
+ replacement = replacement.replace("<slot />", slot);
248
+ }
249
+ }
250
+
251
+ result.replace_range(*start..*end, &replacement);
252
+ }
253
+ // If not found in cache or internal elements, leave as-is
254
+ }
255
+ }
256
+
257
+ result
258
+ }
259
+
260
+ /// Parse component props from attributes string
261
+ /// Example: ` headline="@[pageTitle]" theme="dark"` -> {"headline": "@[pageTitle]", "theme": "dark"}
262
+ fn parse_props(attrs_str: &str) -> HashMap<String, String> {
263
+ let mut props = HashMap::new();
264
+
265
+ // Match attribute="value" pairs
266
+ let attr_re = regex::Regex::new(r#"(\w+)="([^"]*)""#).unwrap();
267
+
268
+ for cap in attr_re.captures_iter(attrs_str) {
269
+ if let (Some(name), Some(value)) = (cap.get(1), cap.get(2)) {
270
+ props.insert(name.as_str().to_string(), value.as_str().to_string());
271
+ }
272
+ }
273
+
274
+ props
275
+ }
276
+ }
277
+
278
+ /// Transform custom HTML elements to divs with classes
279
+ fn transform_custom_tags_to_divs(content: &str, exclude_tags: &[String]) -> String {
280
+ let mut result = content.to_string();
281
+
282
+ // Standard HTML5 elements (should not be transformed)
283
+ let standard_tags: HashSet<&str> = [
284
+ "a", "abbr", "address", "area", "article", "aside", "audio",
285
+ "b", "base", "bdi", "bdo", "blockquote", "body", "br", "button",
286
+ "canvas", "caption", "cite", "code", "col", "colgroup",
287
+ "data", "datalist", "dd", "del", "details", "dfn", "dialog", "div", "dl", "dt",
288
+ "em", "embed",
289
+ "fieldset", "figcaption", "figure", "footer", "form",
290
+ "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html",
291
+ "i", "iframe", "img", "input", "ins",
292
+ "kbd",
293
+ "label", "legend", "li", "link",
294
+ "main", "map", "mark", "menu", "meta", "meter",
295
+ "nav", "noscript",
296
+ "object", "ol", "optgroup", "option", "output",
297
+ "p", "param", "picture", "pre", "progress",
298
+ "q",
299
+ "rp", "rt", "ruby",
300
+ "s", "samp", "script", "search", "section", "select", "slot", "small", "source", "span", "strong", "style", "sub", "summary", "sup", "svg",
301
+ "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track",
302
+ "u", "ul",
303
+ "var", "video",
304
+ "wbr",
305
+ ].into_iter().collect();
306
+
307
+ // Find all custom tags and transform them
308
+ let tag_pattern = regex::Regex::new(r"<([a-z][a-z0-9-]*)([^>]*)>").unwrap();
309
+ let _closing_pattern = regex::Regex::new(r"</([a-z][a-z0-9-]*)>").unwrap();
310
+
311
+ // Collect unique custom tags first
312
+ let mut custom_tags: Vec<String> = Vec::new();
313
+ for cap in tag_pattern.captures_iter(&result.clone()) {
314
+ if let Some(m) = cap.get(1) {
315
+ let tag = m.as_str().to_string();
316
+ if !standard_tags.contains(tag.as_str())
317
+ && !exclude_tags.contains(&tag)
318
+ && !custom_tags.contains(&tag)
319
+ {
320
+ custom_tags.push(tag);
321
+ }
322
+ }
323
+ }
324
+
325
+ // Transform each custom tag
326
+ for tag in custom_tags {
327
+ // Opening tag: <custom-tag attrs> -> <div class="custom-tag" attrs>
328
+ let open_re = regex::Regex::new(&format!(r"<{}([^>]*)>", regex::escape(&tag))).unwrap();
329
+ result = open_re
330
+ .replace_all(&result, format!("<div class=\"{}\"$1>", tag).as_str())
331
+ .to_string();
332
+
333
+ // Closing tag: </custom-tag> -> </div>
334
+ let close_re = regex::Regex::new(&format!(r"</{}>", regex::escape(&tag))).unwrap();
335
+ result = close_re.replace_all(&result, "</div>").to_string();
336
+ }
337
+
338
+ result
339
+ }
@@ -0,0 +1,8 @@
1
+ pub mod html;
2
+ mod element;
3
+
4
+ pub use html::HtmlParser;
5
+ #[allow(unused_imports)]
6
+ pub use html::ParseError;
7
+ #[allow(unused_imports)]
8
+ pub use element::Element;
package/index.js CHANGED
@@ -1,233 +1,2 @@
1
- import state from './state.js';
2
- import parse from './parse.js';
3
- import link from './link.js';
4
- import hydrate, { setPreviousState } from './hydrate.js';
5
- import affected from './affected.js';
6
- import { deepMerge, hash } from './utils.js';
7
- import { renderAllIterations, setRenderAllConditionals } from './iterate.js';
8
- import { renderAllConditionals } from './conditionals.js';
9
- import { NON_REACTIVE_ELEMENTS } from './constants.js';
10
-
11
- // Wire up cross-module dependency after all modules are loaded
12
- setRenderAllConditionals(renderAllConditionals);
13
-
14
- // Check if node itself or any ancestor is non-reactive or dehydrated
15
- const isNonReactiveOrInside = (node) => {
16
- let current = node;
17
- while (current && current !== document.body) {
18
- if (NON_REACTIVE_ELEMENTS.includes(current.nodeName)) {
19
- return true;
20
- }
21
- if (current.hasAttribute?.('dehydrate')) {
22
- return true;
23
- }
24
- current = current.parentElement;
25
- }
26
- return false;
27
- };
28
-
29
- // Navigate tree using dot notation (handles .children at each level)
30
- const navigateTree = (tree, path) => {
31
- if (!path) return tree;
32
- return path.split('.').reduce((node, key) => node?.children?.[key], tree);
33
- };
34
-
35
- // Get or create a node in the tree at the given path
36
- const ensureNode = (tree, path) => {
37
- const keys = path.split('.');
38
- return keys.reduce((node, key) => {
39
- if (!node.children[key]) {
40
- node.children[key] = { children: {} };
41
- }
42
- return node.children[key];
43
- }, tree);
44
- };
45
-
46
- const main = (s, attrName = 'vibe') => {
47
- // Find element(s) with the specified attribute
48
- const elements = document.querySelectorAll(`[${attrName}]`);
49
-
50
- if (elements.length === 0) {
51
- console.info(`[vibe] No element found with attribute "${attrName}". Falling back to body.`);
52
- } else if (elements.length > 1) {
53
- console.info(
54
- `[vibe] Multiple elements (${elements.length}) found with attribute "${attrName}". Hydrating the first one.`,
55
- );
56
- }
57
-
58
- const rootElement = elements[0] || document.body;
59
- let parsedTree = parse(rootElement);
60
- let linkList = link(parsedTree);
61
-
62
- // Store previous state for comparison
63
- let previousState = {};
64
-
65
- // Lifecycle hooks that users can subscribe to
66
- const hooks = {
67
- afterUpdate: [],
68
- afterDomMutation: [],
69
- };
70
-
71
- // Observer reference - set after observer is created
72
- let observer = null;
73
-
74
- const $ = state(s, (newState) => {
75
- const mergedState = deepMerge($, newState);
76
- const affectedElements = affected(parsedTree, previousState, mergedState);
77
-
78
- if (observer) observer.disconnect();
79
- hydrate(affectedElements, mergedState, linkList);
80
- if (observer) {
81
- observer.observe(rootElement, {
82
- attributes: false,
83
- characterData: false,
84
- childList: true,
85
- subtree: true,
86
- });
87
- }
88
-
89
- const prev = structuredClone(previousState);
90
- previousState = { ...$, ...newState };
91
- hooks.afterUpdate.forEach((callback) => callback(structuredClone({ ...$ }), prev));
92
- });
93
-
94
- // Add hook subscription method (non-enumerable so it won't be spread/cloned with state)
95
- Object.defineProperty($, 'on', {
96
- value: (event, callback) => {
97
- if (hooks[event]) {
98
- hooks[event].push(callback);
99
- }
100
- return () => (hooks[event] = hooks[event].filter((cb) => cb !== callback));
101
- },
102
- enumerable: false,
103
- });
104
-
105
- // Initial hydration
106
- const affectedElements = affected(parsedTree, $, $);
107
- hydrate(affectedElements, $, linkList);
108
-
109
- // Render all iterations and conditionals after initial hydration
110
- setPreviousState($);
111
- previousState = { ...$ };
112
- renderAllIterations(parsedTree, $, linkList);
113
- renderAllConditionals(parsedTree, $, linkList);
114
-
115
- observer = new MutationObserver((mutations) => {
116
- // Early exit if no mutations to process (common case)
117
- if (mutations.length === 0) return;
118
-
119
- let hadChanges = false;
120
- let parsedParents = null; // Lazy init - only create Set when needed
121
-
122
- mutations.forEach(({ addedNodes, removedNodes, target }) => {
123
- // Process removed nodes first (cleanup before additions)
124
- removedNodes.forEach((node) => {
125
- const entry = Object.entries(linkList).find(([_, element]) => element === node);
126
-
127
- // Skip nodes that aren't tracked (e.g., iteration-generated nodes or nodes outside reactive scope)
128
- if (!entry) return;
129
-
130
- const [dotAnnotation] = entry;
131
- delete linkList[dotAnnotation];
132
-
133
- const dotPath = dotAnnotation.split('.');
134
- const name = dotPath.pop();
135
- const parentDotAnnotation = dotPath.join('.');
136
-
137
- const picked = navigateTree(parsedTree, parentDotAnnotation);
138
-
139
- // If we can't navigate to the parent, skip
140
- if (!picked || !picked.element) return;
141
-
142
- // Update parent's parsed HTML (only once per parent)
143
- if (!parsedParents) parsedParents = new Set();
144
- if (!parsedParents.has(picked)) {
145
- const { parsed } = parse(picked.element);
146
- picked.parsed = parsed;
147
- parsedParents.add(picked);
148
- }
149
-
150
- // Remove the node from parent's children
151
- delete picked.children[name];
152
-
153
- hadChanges = true;
154
- });
155
-
156
- addedNodes.forEach((node) => {
157
- // Skip if node itself or any ancestor is non-reactive
158
- if (isNonReactiveOrInside(node)) {
159
- return;
160
- }
161
-
162
- const entry = Object.entries(linkList).find(([_, element]) => element === target);
163
-
164
- // If parent isn't tracked, this node is outside the reactive scope
165
- if (!entry) return;
166
-
167
- const [dotAnnotation] = entry;
168
- const picked = navigateTree(parsedTree, dotAnnotation);
169
-
170
- // If we can't navigate to the parent in the tree, skip
171
- if (!picked) return;
172
-
173
- // If parent has no element reference, re-parse from the actual DOM element
174
- if (!picked.element) {
175
- picked.element = target;
176
- }
177
-
178
- // Parse the newly added node
179
- const name = `${node.nodeName.toLowerCase()}_${hash()}`;
180
- const parsedNode = parse(node);
181
-
182
- // Update parent's parsed HTML (only once per parent)
183
- if (!parsedParents) parsedParents = new Set();
184
- if (!parsedParents.has(picked)) {
185
- const { parsed } = parse(picked.element);
186
- picked.parsed = parsed;
187
- parsedParents.add(picked);
188
- }
189
-
190
- // Add the parsed node to parent's children
191
- picked.children[name] = parsedNode;
192
-
193
- linkList[`${dotAnnotation}.${name}`] = node;
194
-
195
- // Only hydrate the newly added node and its descendants, not the entire tree
196
- // Use empty object as "previous state" so all bindings in new node are considered affected
197
- const affectedElements = affected(parsedNode, {}, $);
198
-
199
- hydrate(affectedElements, $, linkList);
200
-
201
- // Process iterations and conditionals in the newly added node
202
- renderAllIterations(parsedNode, $, linkList);
203
- renderAllConditionals(parsedNode, $, linkList);
204
-
205
- hadChanges = true;
206
- });
207
- });
208
-
209
- // Fire hooks once after all mutations are processed (not per-node)
210
- if (hadChanges) {
211
- hooks.afterDomMutation.forEach((callback) => callback());
212
- }
213
- });
214
-
215
- observer.observe(rootElement, {
216
- attributes: false,
217
- characterData: false,
218
- childList: true,
219
- subtree: true,
220
- attributeOldValue: false,
221
- characterDataOldValue: false,
222
- });
223
-
224
- // Force reflow - ensures layout is applied before transitions re-enable
225
- rootElement.offsetHeight;
226
-
227
- // Remove the vibe attribute to reveal content and enable transitions
228
- rootElement.removeAttribute(attrName);
229
-
230
- return $;
231
- };
232
-
233
- export default main;
1
+ // Re-export from runtime for backwards compatibility
2
+ export { default } from './runtime/index.js';
package/package.json CHANGED
@@ -1,10 +1,32 @@
1
1
  {
2
2
  "name": "@ape-egg/vibe",
3
- "version": "1.0.3",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
- "description": "Runtime-first reactivity",
5
+ "description": "Runtime-first reactivity with optional compiler",
6
6
  "main": "index.js",
7
7
  "homepage": "https://vibe.korte.kim",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./runtime": "./runtime/index.js",
11
+ "./compiler": "./compiler/bin/vibe-compile.js"
12
+ },
13
+ "bin": {
14
+ "vibe": "./compiler/bin/vibe-compile.js"
15
+ },
16
+ "files": [
17
+ "index.js",
18
+ "runtime/",
19
+ "compiler/bin/",
20
+ "compiler/native/",
21
+ "compiler/src/Cargo.lock",
22
+ "compiler/src/Cargo.toml",
23
+ "compiler/src/compiler/",
24
+ "compiler/src/config.rs",
25
+ "compiler/src/main.rs",
26
+ "compiler/src/parser/",
27
+ "README.md",
28
+ "CHANGELOG.md"
29
+ ],
8
30
  "keywords": [
9
31
  "reactive",
10
32
  "framework",
@@ -12,7 +34,8 @@
12
34
  "ui",
13
35
  "mutation-observer",
14
36
  "proxy",
15
- "minimalistic"
37
+ "minimalistic",
38
+ "compiler"
16
39
  ],
17
40
  "scripts": {
18
41
  "test": "echo \"Error: no test specified\" && exit 1"