@sprlab/wccompiler 0.10.7 → 0.10.9
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/bin/wcc.js +112 -1
- package/integrations/react.js +9 -107
- package/package.json +1 -1
package/bin/wcc.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { readdirSync, writeFileSync, mkdirSync, existsSync, watch, copyFileSync } from 'node:fs';
|
|
3
|
+
import { readdirSync, writeFileSync, mkdirSync, existsSync, watch, copyFileSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { resolve, relative, extname, basename, dirname, join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { loadConfig } from '../lib/config.js';
|
|
@@ -61,9 +61,120 @@ async function build(config, cwd) {
|
|
|
61
61
|
const runtimeDest = join(outputDir, 'wcc-runtime.js');
|
|
62
62
|
copyFileSync(runtimeSrc, runtimeDest);
|
|
63
63
|
|
|
64
|
+
// Generate framework stubs (React + Vue) from compiled component metadata
|
|
65
|
+
generateFrameworkStubs(outputDir);
|
|
66
|
+
|
|
64
67
|
return errors;
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Generates framework stub files (React + Vue) from compiled component metadata.
|
|
72
|
+
* Reads static __meta from each compiled .js file and produces:
|
|
73
|
+
* - wcc-react.js + wcc-react.d.ts (importable stubs for React)
|
|
74
|
+
* - wcc-vue.js + wcc-vue.d.ts (importable stubs for Vue)
|
|
75
|
+
*/
|
|
76
|
+
function generateFrameworkStubs(outputDir) {
|
|
77
|
+
|
|
78
|
+
const files = readdirSync(outputDir).filter(f => f.endsWith('.js') && !f.startsWith('__') && f !== 'wcc-runtime.js' && f !== 'wcc-react.js' && f !== 'wcc-vue.js');
|
|
79
|
+
const components = [];
|
|
80
|
+
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
const content = readFileSync(join(outputDir, file), 'utf-8');
|
|
83
|
+
const metaMatch = content.match(/static __meta\s*=\s*\{([^}]+)\}/);
|
|
84
|
+
if (!metaMatch) continue;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const metaStr = '{' + metaMatch[1] + '}';
|
|
88
|
+
const parsed = metaStr
|
|
89
|
+
.replace(/'/g, '"')
|
|
90
|
+
.replace(/(\w+):/g, '"$1":')
|
|
91
|
+
.replace(/,\s*}/g, '}')
|
|
92
|
+
.replace(/,\s*]/g, ']');
|
|
93
|
+
const meta = JSON.parse(parsed);
|
|
94
|
+
if (!meta.tag) continue;
|
|
95
|
+
|
|
96
|
+
const pascalName = meta.tag.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('');
|
|
97
|
+
components.push({ meta, pascalName, file });
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// Skip unparseable
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (components.length === 0) return;
|
|
104
|
+
|
|
105
|
+
// ── React stubs ──
|
|
106
|
+
let reactJs = '// Auto-generated by wcc build — React component stubs\n';
|
|
107
|
+
reactJs += '// Import these in your JSX. The wccReactPlugin transforms them at build time.\n\n';
|
|
108
|
+
|
|
109
|
+
let reactDts = '// Auto-generated by wcc build — React component types\n\n';
|
|
110
|
+
|
|
111
|
+
for (const comp of components) {
|
|
112
|
+
const slots = comp.meta.slots || [];
|
|
113
|
+
const slotProps = slots.filter(s => s).map(s => {
|
|
114
|
+
const pascal = s[0].toUpperCase() + s.slice(1);
|
|
115
|
+
return `${pascal}: '${s}'`;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// JS stub
|
|
119
|
+
reactJs += `export const ${comp.pascalName} = Object.assign('${comp.meta.tag}', { __tag: '${comp.meta.tag}'`;
|
|
120
|
+
for (const s of slots) {
|
|
121
|
+
if (!s) continue;
|
|
122
|
+
const pascal = s[0].toUpperCase() + s.slice(1);
|
|
123
|
+
reactJs += `, ${pascal}: '${s}'`;
|
|
124
|
+
}
|
|
125
|
+
reactJs += ` });\n`;
|
|
126
|
+
|
|
127
|
+
// TypeScript declaration
|
|
128
|
+
const slotTypes = slots.filter(s => s).map(s => {
|
|
129
|
+
const pascal = s[0].toUpperCase() + s.slice(1);
|
|
130
|
+
return ` ${pascal}: string;`;
|
|
131
|
+
}).join('\n');
|
|
132
|
+
|
|
133
|
+
reactDts += `export declare const ${comp.pascalName}: string & {\n __tag: '${comp.meta.tag}';\n${slotTypes}\n};\n\n`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
writeFileSync(join(outputDir, 'wcc-react.js'), reactJs);
|
|
137
|
+
writeFileSync(join(outputDir, 'wcc-react.d.ts'), reactDts);
|
|
138
|
+
|
|
139
|
+
// ── Vue stubs ──
|
|
140
|
+
let vueJs = '// Auto-generated by wcc build — Vue component stubs\n';
|
|
141
|
+
vueJs += '// Import these in your Vue SFC for type safety and IDE support.\n\n';
|
|
142
|
+
|
|
143
|
+
let vueDts = '// Auto-generated by wcc build — Vue component types\n\n';
|
|
144
|
+
|
|
145
|
+
for (const comp of components) {
|
|
146
|
+
const props = comp.meta.props || [];
|
|
147
|
+
const events = comp.meta.events || [];
|
|
148
|
+
const models = comp.meta.models || [];
|
|
149
|
+
const slots = comp.meta.slots || [];
|
|
150
|
+
|
|
151
|
+
// JS stub (just the tag name — Vue uses kebab-case directly)
|
|
152
|
+
vueJs += `export const ${comp.pascalName} = '${comp.meta.tag}';\n`;
|
|
153
|
+
|
|
154
|
+
// TypeScript declaration with props/events/slots info
|
|
155
|
+
const propTypes = props.map(p => {
|
|
156
|
+
const type = typeof p.default === 'number' || /^\d/.test(p.default) ? 'number'
|
|
157
|
+
: p.default === 'true' || p.default === 'false' ? 'boolean' : 'string';
|
|
158
|
+
return ` ${p.name}?: ${type};`;
|
|
159
|
+
}).join('\n');
|
|
160
|
+
|
|
161
|
+
const eventTypes = events.map(e => ` '${e}': CustomEvent;`).join('\n');
|
|
162
|
+
const modelTypes = models.map(m => ` '${m}': any;`).join('\n');
|
|
163
|
+
const slotTypeEntries = slots.filter(s => s).map(s => ` '${s}': any;`).join('\n');
|
|
164
|
+
|
|
165
|
+
vueDts += `export declare const ${comp.pascalName}: '${comp.meta.tag}';\n`;
|
|
166
|
+
vueDts += `/** Component: ${comp.meta.tag} */\n`;
|
|
167
|
+
vueDts += `export interface ${comp.pascalName}Props {\n${propTypes}\n}\n`;
|
|
168
|
+
if (events.length) vueDts += `export interface ${comp.pascalName}Events {\n${eventTypes}\n}\n`;
|
|
169
|
+
if (models.length) vueDts += `export interface ${comp.pascalName}Models {\n${modelTypes}\n}\n`;
|
|
170
|
+
if (slots.filter(s => s).length) vueDts += `export interface ${comp.pascalName}Slots {\n${slotTypeEntries}\n}\n`;
|
|
171
|
+
vueDts += '\n';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
writeFileSync(join(outputDir, 'wcc-vue.js'), vueJs);
|
|
175
|
+
writeFileSync(join(outputDir, 'wcc-vue.d.ts'), vueDts);
|
|
176
|
+
}
|
|
177
|
+
|
|
67
178
|
function discoverFiles(dir) {
|
|
68
179
|
const results = [];
|
|
69
180
|
const entries = readdirSync(dir, { withFileTypes: true, recursive: true });
|
package/integrations/react.js
CHANGED
|
@@ -841,117 +841,19 @@ export function wccReactPlugin(options = {}) {
|
|
|
841
841
|
|
|
842
842
|
|
|
843
843
|
/**
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
844
|
+
* @deprecated Use the CLI-generated stubs instead (dist/wcc-react.js).
|
|
845
|
+
* The `wcc build` command now auto-generates importable stubs with types.
|
|
846
|
+
* This virtual module plugin is kept for backward compatibility but will be removed.
|
|
847
847
|
*
|
|
848
|
-
*
|
|
849
|
-
* import {
|
|
850
|
-
*
|
|
851
|
-
* The plugin scans the output directory for compiled .js files, reads their
|
|
852
|
-
* `static __meta` declarations, and generates wrapper code using createWccWrapper.
|
|
853
|
-
*
|
|
854
|
-
* @param {Object} [options]
|
|
855
|
-
* @param {string} [options.moduleId='@wcc/react'] - Virtual module ID for imports
|
|
856
|
-
* @param {string} [options.componentsDir='./dist'] - Directory containing compiled WCC .js files
|
|
857
|
-
* @param {string} [options.prefix='wcc-'] - Tag prefix filter
|
|
858
|
-
* @returns {import('vite').Plugin}
|
|
859
|
-
*
|
|
860
|
-
* @example vite.config.js
|
|
861
|
-
* ```js
|
|
862
|
-
* import { wccReactPlugin, wccReactComponents } from '@sprlab/wccompiler/integrations/react'
|
|
863
|
-
* export default {
|
|
864
|
-
* plugins: [
|
|
865
|
-
* wccReactPlugin(),
|
|
866
|
-
* wccReactComponents({ componentsDir: './src/wcc' })
|
|
867
|
-
* ]
|
|
868
|
-
* }
|
|
869
|
-
* ```
|
|
870
|
-
*
|
|
871
|
-
* @example Component.jsx
|
|
872
|
-
* ```jsx
|
|
873
|
-
* import { WccCounter, WccCard } from '@wcc/react'
|
|
874
|
-
*
|
|
875
|
-
* <WccCounter count={count} onCountChange={setCount} />
|
|
876
|
-
* <WccCard><div slot="header">Title</div></WccCard>
|
|
877
|
-
* ```
|
|
848
|
+
* Migration:
|
|
849
|
+
* Before: import { WccCard } from '@wcc/react' (virtual module)
|
|
850
|
+
* After: import { WccCard } from './dist/wcc-react' (real file, tree-shakeable)
|
|
878
851
|
*/
|
|
879
852
|
export function wccReactComponents(options = {}) {
|
|
880
|
-
const {
|
|
881
|
-
moduleId = '@wcc/react',
|
|
882
|
-
componentsDir = './dist',
|
|
883
|
-
prefix = 'wcc-'
|
|
884
|
-
} = options
|
|
885
|
-
|
|
886
|
-
const resolvedId = '\0' + moduleId
|
|
887
|
-
|
|
888
853
|
return {
|
|
889
|
-
name: 'vite-plugin-wcc-react-components',
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
return null
|
|
893
|
-
},
|
|
894
|
-
async load(id) {
|
|
895
|
-
if (id !== resolvedId) return null
|
|
896
|
-
|
|
897
|
-
// Scan componentsDir for .js files and extract __meta
|
|
898
|
-
const fs = await import('fs')
|
|
899
|
-
const path = await import('path')
|
|
900
|
-
|
|
901
|
-
const dir = path.default.resolve(componentsDir)
|
|
902
|
-
if (!fs.default.existsSync(dir)) {
|
|
903
|
-
this.warn(`[wcc-react-components] Directory not found: ${dir}`)
|
|
904
|
-
return 'export {}'
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
const files = fs.default.readdirSync(dir).filter(f => f.endsWith('.js'))
|
|
908
|
-
const components = []
|
|
909
|
-
|
|
910
|
-
for (const file of files) {
|
|
911
|
-
const content = fs.default.readFileSync(path.default.join(dir, file), 'utf-8')
|
|
912
|
-
const metaMatch = content.match(/static __meta\s*=\s*(\{[^}]+\})/)
|
|
913
|
-
if (!metaMatch) continue
|
|
914
|
-
|
|
915
|
-
try {
|
|
916
|
-
// Parse the meta object (it's a JS object literal, evaluate safely)
|
|
917
|
-
const metaStr = metaMatch[1]
|
|
918
|
-
.replace(/'/g, '"')
|
|
919
|
-
.replace(/(\w+):/g, '"$1":')
|
|
920
|
-
.replace(/,\s*}/g, '}')
|
|
921
|
-
.replace(/,\s*]/g, ']')
|
|
922
|
-
const meta = JSON.parse(metaStr)
|
|
923
|
-
|
|
924
|
-
if (!meta.tag || !meta.tag.startsWith(prefix)) continue
|
|
925
|
-
|
|
926
|
-
const pascalName = meta.tag.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('')
|
|
927
|
-
components.push({ meta, pascalName, file })
|
|
928
|
-
} catch (e) {
|
|
929
|
-
// Skip files with unparseable meta
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
if (components.length === 0) {
|
|
934
|
-
return 'export {}'
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// Generate the virtual module code
|
|
938
|
-
let code = `import { createWccWrapper } from '@sprlab/wccompiler/adapters/react';\n`
|
|
939
|
-
|
|
940
|
-
// Import each component file to ensure registration
|
|
941
|
-
for (const comp of components) {
|
|
942
|
-
code += `import '${path.default.resolve(dir, comp.file)}';\n`
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
code += '\n'
|
|
946
|
-
|
|
947
|
-
// Generate wrapper exports
|
|
948
|
-
for (const comp of components) {
|
|
949
|
-
const eventsArr = JSON.stringify(comp.meta.events || [])
|
|
950
|
-
const modelsArr = JSON.stringify(comp.meta.models || [])
|
|
951
|
-
code += `export const ${comp.pascalName} = createWccWrapper('${comp.meta.tag}', { events: ${eventsArr}, models: ${modelsArr} });\n`
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
return code
|
|
854
|
+
name: 'vite-plugin-wcc-react-components-deprecated',
|
|
855
|
+
buildStart() {
|
|
856
|
+
this.warn('[wcc] wccReactComponents() is deprecated. Use the CLI-generated stubs from dist/wcc-react.js instead.')
|
|
955
857
|
}
|
|
956
858
|
}
|
|
957
859
|
}
|
package/package.json
CHANGED