@sprlab/wccompiler 0.6.4 → 0.7.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/README.md CHANGED
@@ -636,6 +636,63 @@ Component-level `standalone` always takes precedence over the global config. Thi
636
636
 
637
637
  Each standalone component has its own isolated reactive runtime. Signals from component A cannot be observed by effects in component B — they are completely independent. This is by design for distribution scenarios where components must be self-contained. If you need cross-component reactivity (e.g., shared state), use the default shared mode (`standalone: false`).
638
638
 
639
+ ## Framework Integrations
640
+
641
+ WCC components are native custom elements — they work in any framework. Optional integration helpers reduce configuration friction:
642
+
643
+ ### Vue 3 (Vite)
644
+
645
+ ```js
646
+ // vite.config.js
647
+ import { wccVuePlugin } from '@sprlab/wccompiler/integrations/vue'
648
+
649
+ export default defineConfig({
650
+ plugins: [wccVuePlugin()]
651
+ })
652
+
653
+ // Custom prefix:
654
+ // plugins: [wccVuePlugin({ prefix: 'my-' })]
655
+ ```
656
+
657
+ ### React
658
+
659
+ React 19+ supports custom elements natively. For React 18, use the event hook:
660
+
661
+ ```jsx
662
+ import { useWccEvent } from '@sprlab/wccompiler/integrations/react'
663
+
664
+ function App() {
665
+ const ref = useWccEvent('change', (e) => console.log(e.detail))
666
+ return <wcc-counter ref={ref}></wcc-counter>
667
+ }
668
+ ```
669
+
670
+ ### Angular
671
+
672
+ ```ts
673
+ import { WCC_SCHEMAS } from '@sprlab/wccompiler/integrations/angular'
674
+
675
+ // Standalone component (Angular 17+)
676
+ @Component({
677
+ schemas: WCC_SCHEMAS,
678
+ template: `<wcc-counter></wcc-counter>`
679
+ })
680
+
681
+ // Or NgModule approach
682
+ @NgModule({
683
+ schemas: WCC_SCHEMAS,
684
+ })
685
+ ```
686
+
687
+ ### Vanilla
688
+
689
+ No configuration needed:
690
+
691
+ ```html
692
+ <script type="module" src="dist/wcc-counter.js"></script>
693
+ <wcc-counter></wcc-counter>
694
+ ```
695
+
639
696
  ## Editor Support
640
697
 
641
698
  The **wcCompiler (.wcc) Language Support** extension is available on the VS Code Marketplace. It provides syntax highlighting, completions, and diagnostics for `.wcc` files.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Angular schema helper for WCC custom elements.
3
+ * Provides CUSTOM_ELEMENTS_SCHEMA configuration for Angular modules/components.
4
+ *
5
+ * @module @sprlab/wccompiler/integrations/angular
6
+ */
7
+
8
+ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
9
+
10
+ /**
11
+ * Schema array for Angular components/modules that use WCC custom elements.
12
+ * Use in @Component({ schemas: WCC_SCHEMAS }) or @NgModule({ schemas: WCC_SCHEMAS })
13
+ *
14
+ * @type {Array<import('@angular/core').SchemaMetadata>}
15
+ */
16
+ export const WCC_SCHEMAS = [CUSTOM_ELEMENTS_SCHEMA]
17
+
18
+ /**
19
+ * NgModule-compatible class that declares CUSTOM_ELEMENTS_SCHEMA.
20
+ * Import this in your NgModule's imports array.
21
+ *
22
+ * Usage:
23
+ * @NgModule({ imports: [WccModule] })
24
+ * export class AppModule {}
25
+ *
26
+ * Note: For standalone components (Angular 17+), use WCC_SCHEMAS directly:
27
+ * @Component({ schemas: WCC_SCHEMAS })
28
+ */
29
+ export class WccModule {
30
+ static schemas = WCC_SCHEMAS
31
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * React hook for WCC custom element events.
3
+ * Bridges CustomEvent to React's ref-based event system.
4
+ *
5
+ * @module @sprlab/wccompiler/integrations/react
6
+ */
7
+
8
+ import { useRef, useEffect } from 'react'
9
+
10
+ /**
11
+ * Hook that attaches a CustomEvent listener to a DOM element via ref.
12
+ *
13
+ * @param {string} eventName - The event name to listen for
14
+ * @param {(event: CustomEvent) => void} handler - Event handler callback
15
+ * @returns {import('react').RefObject<HTMLElement>}
16
+ */
17
+ export function useWccEvent(eventName, handler) {
18
+ const ref = useRef(null)
19
+ const handlerRef = useRef(handler)
20
+ handlerRef.current = handler
21
+
22
+ useEffect(() => {
23
+ const el = ref.current
24
+ if (!el) return
25
+ const listener = (e) => handlerRef.current(e)
26
+ el.addEventListener(eventName, listener)
27
+ return () => el.removeEventListener(eventName, listener)
28
+ }, [eventName])
29
+
30
+ return ref
31
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Vue Vite plugin for WCC custom elements.
3
+ * Configures isCustomElement to recognize WCC component tags.
4
+ *
5
+ * @module @sprlab/wccompiler/integrations/vue
6
+ */
7
+
8
+ import vue from '@vitejs/plugin-vue'
9
+
10
+ /**
11
+ * @typedef {Object} WccVuePluginOptions
12
+ * @property {string} [prefix='wcc-'] - Tag prefix for custom element detection
13
+ */
14
+
15
+ /**
16
+ * Creates a Vite plugin that configures Vue's template compiler
17
+ * to recognize custom elements with the given prefix.
18
+ *
19
+ * @param {WccVuePluginOptions} [options]
20
+ * @returns {import('vite').Plugin}
21
+ */
22
+ export function wccVuePlugin(options = {}) {
23
+ const prefix = typeof options.prefix === 'string' ? options.prefix : 'wcc-'
24
+ return vue({
25
+ template: {
26
+ compilerOptions: {
27
+ isCustomElement: (tag) => tag.startsWith(prefix)
28
+ }
29
+ }
30
+ })
31
+ }
package/lib/codegen.js CHANGED
@@ -933,9 +933,24 @@ export function generateComponent(parseResult, options = {}) {
933
933
  for (let idx = 0; idx < watchers.length; idx++) {
934
934
  const w = watchers[idx];
935
935
  if (w.kind === 'signal') {
936
- lines.push(` this.__prev_${w.target} = undefined;`);
936
+ // For signal watchers watching a prop, initialize with the prop's default value
937
+ // so that attributeChangedCallback changes before connectedCallback are detected
938
+ if (propNames.has(w.target)) {
939
+ const propDef = propDefs.find(p => p.name === w.target);
940
+ lines.push(` this.__prev_${w.target} = ${propDef ? propDef.default : 'undefined'};`);
941
+ } else {
942
+ lines.push(` this.__prev_${w.target} = undefined;`);
943
+ }
937
944
  } else {
938
- lines.push(` this.__prev_watch${idx} = undefined;`);
945
+ // For getter watchers, check if the getter references a prop (e.g., props.value)
946
+ // Initialize with the prop's default so pre-connection attribute changes are detected
947
+ const propMatch = propsObjectName ? w.target.match(new RegExp(`^${propsObjectName}\\.(\\w+)$`)) : null;
948
+ if (propMatch && propNames.has(propMatch[1])) {
949
+ const propDef = propDefs.find(p => p.name === propMatch[1]);
950
+ lines.push(` this.__prev_watch${idx} = ${propDef ? propDef.default : 'undefined'};`);
951
+ } else {
952
+ lines.push(` this.__prev_watch${idx} = undefined;`);
953
+ }
939
954
  }
940
955
  }
941
956
 
@@ -1170,7 +1185,7 @@ export function generateComponent(parseResult, options = {}) {
1170
1185
  }
1171
1186
  lines.push(' this.__disposers.push(__effect(() => {');
1172
1187
  lines.push(` const ${w.newParam} = ${watchRef};`);
1173
- lines.push(` if (this.__prev_${w.target} !== undefined) {`);
1188
+ lines.push(` if (this.__prev_${w.target} !== undefined && this.__prev_${w.target} !== ${w.newParam}) {`);
1174
1189
  lines.push(` const ${w.oldParam} = this.__prev_${w.target};`);
1175
1190
  lines.push(' __untrack(() => {');
1176
1191
  const bodyLines = body.split('\n');
@@ -1187,7 +1202,7 @@ export function generateComponent(parseResult, options = {}) {
1187
1202
  const prevName = `__prev_watch${idx}`;
1188
1203
  lines.push(' this.__disposers.push(__effect(() => {');
1189
1204
  lines.push(` const ${w.newParam} = ${getterExpr};`);
1190
- lines.push(` if (this.${prevName} !== undefined) {`);
1205
+ lines.push(` if (this.${prevName} !== undefined && this.${prevName} !== ${w.newParam}) {`);
1191
1206
  lines.push(` const ${w.oldParam} = this.${prevName};`);
1192
1207
  lines.push(' __untrack(() => {');
1193
1208
  const bodyLines2 = body.split('\n');
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.6.4",
3
+ "version": "0.7.1",
4
4
  "description": "Zero-runtime compiler that transforms .wcc single-file components into native web components with signals-based reactivity",
5
5
  "type": "module",
6
+ "exports": {
7
+ ".": "./lib/compiler.js",
8
+ "./integrations/vue": "./integrations/vue.js",
9
+ "./integrations/react": "./integrations/react.js",
10
+ "./integrations/angular": "./integrations/angular.js"
11
+ },
6
12
  "bin": {
7
13
  "wcc": "./bin/wcc.js"
8
14
  },
@@ -10,6 +16,7 @@
10
16
  "bin/",
11
17
  "lib/*.js",
12
18
  "!lib/*.test.js",
19
+ "integrations/",
13
20
  "types/",
14
21
  "README.md"
15
22
  ],
@@ -34,6 +41,18 @@
34
41
  "esbuild": "^0.27.0",
35
42
  "linkedom": "^0.18.12"
36
43
  },
44
+ "peerDependencies": {
45
+ "@vitejs/plugin-vue": ">=4.0.0",
46
+ "vue": ">=3.0.0",
47
+ "react": ">=18.0.0",
48
+ "@angular/core": ">=14.0.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@vitejs/plugin-vue": { "optional": true },
52
+ "vue": { "optional": true },
53
+ "react": { "optional": true },
54
+ "@angular/core": { "optional": true }
55
+ },
37
56
  "devDependencies": {
38
57
  "fast-check": "^4.1.1",
39
58
  "jsdom": "^29.1.1",