@sprlab/wccompiler 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
@@ -904,10 +904,51 @@ export function generateComponent(parseResult, options = {}) {
904
904
  lines.push('');
905
905
  }
906
906
 
907
- // Constructor
907
+ // Constructor — reactive state only (no DOM manipulation per Custom Elements spec)
908
908
  lines.push(' constructor() {');
909
909
  lines.push(' super();');
910
910
 
911
+ // Prop signal initialization (BEFORE user signals)
912
+ for (const p of propDefs) {
913
+ lines.push(` this._s_${p.name} = __signal(${p.default});`);
914
+ }
915
+
916
+ // Signal initialization
917
+ for (const s of signals) {
918
+ lines.push(` this._${s.name} = __signal(${s.value});`);
919
+ }
920
+
921
+ // Constant initialization
922
+ for (const c of constantVars) {
923
+ lines.push(` this._const_${c.name} = ${c.value};`);
924
+ }
925
+
926
+ // Computed initialization
927
+ for (const c of computeds) {
928
+ const body = transformExpr(c.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames);
929
+ lines.push(` this._c_${c.name} = __computed(() => ${body});`);
930
+ }
931
+
932
+ // Watcher prev-value initialization
933
+ for (let idx = 0; idx < watchers.length; idx++) {
934
+ const w = watchers[idx];
935
+ if (w.kind === 'signal') {
936
+ lines.push(` this.__prev_${w.target} = undefined;`);
937
+ } else {
938
+ lines.push(` this.__prev_watch${idx} = undefined;`);
939
+ }
940
+ }
941
+
942
+ lines.push(' }');
943
+ lines.push('');
944
+
945
+ // connectedCallback (idempotent — safe for re-mount)
946
+ lines.push(' connectedCallback() {');
947
+ lines.push(' if (this.__connected) return;');
948
+ lines.push(' this.__connected = true;');
949
+
950
+ // ── DOM SETUP (moved from constructor for Custom Elements spec compliance) ──
951
+
911
952
  // Slot resolution: read childNodes BEFORE clearing innerHTML (when slots are present)
912
953
  if (slots.length > 0) {
913
954
  lines.push(' const __slotMap = {};');
@@ -973,37 +1014,6 @@ export function generateComponent(parseResult, options = {}) {
973
1014
  }
974
1015
  }
975
1016
 
976
- // Prop signal initialization (BEFORE user signals)
977
- for (const p of propDefs) {
978
- lines.push(` this._s_${p.name} = __signal(${p.default});`);
979
- }
980
-
981
- // Signal initialization
982
- for (const s of signals) {
983
- lines.push(` this._${s.name} = __signal(${s.value});`);
984
- }
985
-
986
- // Constant initialization
987
- for (const c of constantVars) {
988
- lines.push(` this._const_${c.name} = ${c.value};`);
989
- }
990
-
991
- // Computed initialization
992
- for (const c of computeds) {
993
- const body = transformExpr(c.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames);
994
- lines.push(` this._c_${c.name} = __computed(() => ${body});`);
995
- }
996
-
997
- // Watcher prev-value initialization
998
- for (let idx = 0; idx < watchers.length; idx++) {
999
- const w = watchers[idx];
1000
- if (w.kind === 'signal') {
1001
- lines.push(` this.__prev_${w.target} = undefined;`);
1002
- } else {
1003
- lines.push(` this.__prev_watch${idx} = undefined;`);
1004
- }
1005
- }
1006
-
1007
1017
  // ── if: template creation, anchor reference, state init ──
1008
1018
  for (const ifBlock of ifBlocks) {
1009
1019
  const vn = ifBlock.varName;
@@ -1055,13 +1065,7 @@ export function generateComponent(parseResult, options = {}) {
1055
1065
  }
1056
1066
  }
1057
1067
 
1058
- lines.push(' }');
1059
- lines.push('');
1060
-
1061
- // connectedCallback (idempotent — safe for re-mount)
1062
- lines.push(' connectedCallback() {');
1063
- lines.push(' if (this.__connected) return;');
1064
- lines.push(' this.__connected = true;');
1068
+ // ── EFFECTS AND LISTENERS ──
1065
1069
  lines.push(' this.__ac = new AbortController();');
1066
1070
  lines.push(' this.__disposers = [];');
1067
1071
  lines.push('');
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
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",