Skip to content

Commit 58578bd

Browse files
committed
feat(ui): support for "web-types"
1 parent d77922f commit 58578bd

File tree

5 files changed

+293
-30
lines changed

5 files changed

+293
-30
lines changed

packages/ui/build/build.api.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ const topSections = {
9797
modifiers: (val) => parseObjectWithPascalCaseProps(val, 'modifiers'),
9898
},
9999
},
100+
101+
util: {
102+
rootProps: [], // computed after this declaration
103+
rootValidations: {
104+
meta: (val) => Object(val) === val || "'meta' must be an Object",
105+
addedIn: parseAddedIn,
106+
quasarConfOptions: (val) => parseObjectWithPascalCaseProps(val, 'quasarConfOptions'),
107+
props: (val) => parseObjectWithPascalCaseProps(val, 'props'),
108+
methods: (val) => parseObjectWithPascalCaseProps(val, 'methods'),
109+
},
110+
},
100111
}
101112
Object.keys(topSections).forEach((section) => {
102113
topSections[section].rootProps = Object.keys(topSections[section].rootValidations)
@@ -1638,7 +1649,12 @@ function resetRuntimeImports() {
16381649
delete globalThis.__QUASAR_SSR_CLIENT__
16391650
}
16401651

1641-
export async function generate({ compact = false } = {}) {
1652+
export async function generate({ compact = false } = {}): Promise<{
1653+
components: any[]
1654+
directives: any[]
1655+
plugins: any[]
1656+
utils: any[]
1657+
}> {
16421658
const encodeFn = compact === true ? JSON.stringify : (json) => JSON.stringify(json, null, 2)
16431659

16441660
prepareRuntimeImports()
@@ -1687,11 +1703,12 @@ export async function generate({ compact = false } = {}) {
16871703
}
16881704
}
16891705

1690-
function run() {
1691-
generate()
1692-
.then(() => {
1706+
async function run() {
1707+
return generate()
1708+
.then((api) => {
16931709
console.log('build.api: done')
16941710
console.log()
1711+
return api
16951712
})
16961713
.catch((error) => {
16971714
console.error(error)
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
import { version, resolveToRoot, logError, writeFile, kebabCase } from './build.utils.js'
2+
3+
const resolve = (file: string): string => resolveToRoot('dist/web-types', file)
4+
5+
const alternateUrl = 'https://qcalendar.netlify.app'
6+
7+
interface Api {
8+
components: Component[]
9+
directives: Directive[]
10+
}
11+
12+
interface Component {
13+
api: {
14+
events?: Record<string, EventApi>
15+
props?: Record<string, PropApi>
16+
scopedSlots?: Record<string, SlotApi>
17+
slots?: Record<string, SlotApi>
18+
meta: Meta
19+
}
20+
name: string
21+
}
22+
23+
interface Directive {
24+
name: string
25+
api: {
26+
modifiers?: Record<string, ModifierApi>
27+
value: ValueApi
28+
meta: Meta
29+
}
30+
}
31+
32+
interface EventApi {
33+
params?: Record<string, ParamApi>
34+
desc: string
35+
examples?: string[]
36+
}
37+
38+
interface PropApi {
39+
type: string
40+
values?: string[]
41+
category?: string
42+
required?: boolean
43+
default?: any
44+
desc: string
45+
examples?: string[]
46+
}
47+
48+
interface SlotApi {
49+
scope?: Record<string, ScopeApi>
50+
desc: string
51+
examples?: string[]
52+
}
53+
54+
interface ModifierApi {
55+
desc: string
56+
examples?: string[]
57+
}
58+
59+
interface ValueApi {
60+
type: string
61+
}
62+
63+
interface ParamApi {
64+
type: string
65+
desc: string
66+
examples?: string[]
67+
}
68+
69+
interface ScopeApi {
70+
type: string
71+
desc: string
72+
examples?: string[]
73+
}
74+
75+
interface Meta {
76+
docsUrl?: string
77+
}
78+
79+
function resolveType({ type, values }: { type: string; values?: string[] }): string {
80+
if (Array.isArray(type)) {
81+
return type.map((type) => resolveType({ type })).join('|')
82+
}
83+
if (type === 'String' && values) {
84+
return values.map((v) => (v === null ? 'null' : `'${v}'`)).join('|')
85+
}
86+
if (['Any', 'String', 'Boolean', 'Number', 'Object'].includes(type)) {
87+
return type.toLowerCase()
88+
}
89+
if (type === 'Array') {
90+
return 'any[]'
91+
}
92+
return type
93+
}
94+
95+
function getDescription(api: { desc: string; examples?: string[] }): string {
96+
return api.examples ? `${api.desc}\n\nExamples:\n${api.examples.join('\n')}` : api.desc
97+
}
98+
99+
export function generate({ api, compact = false }: { api: Api; compact?: boolean }): void {
100+
const encodeFn = compact === true ? JSON.stringify : (json: any) => JSON.stringify(json, null, 2)
101+
102+
try {
103+
const webtypes = encodeFn({
104+
$schema: '',
105+
framework: 'vue',
106+
name: 'qcalendar',
107+
version,
108+
contributions: {
109+
html: {
110+
'types-syntax': 'typescript',
111+
112+
tags: api.components.map(({ api: { events, props, scopedSlots, slots, meta }, name }) => {
113+
const slotTypes: any[] = []
114+
if (slots) {
115+
Object.entries(slots).forEach(([name, slotApi]) => {
116+
slotTypes.push({
117+
name,
118+
description: getDescription(slotApi),
119+
'doc-url': meta.docsUrl || alternateUrl,
120+
})
121+
})
122+
}
123+
124+
if (scopedSlots) {
125+
Object.entries(scopedSlots).forEach(([name, slotApi]) => {
126+
slotTypes.push({
127+
name,
128+
'vue-properties':
129+
slotApi.scope &&
130+
Object.entries(slotApi.scope).map(([name, api]) => ({
131+
name,
132+
type: resolveType(api),
133+
description: getDescription(api),
134+
'doc-url': meta.docsUrl || alternateUrl,
135+
})),
136+
description: getDescription(slotApi),
137+
'doc-url': meta.docsUrl || alternateUrl,
138+
})
139+
})
140+
}
141+
142+
const result: any = {
143+
name,
144+
source: {
145+
module: 'qcalendar',
146+
symbol: name,
147+
},
148+
attributes:
149+
props &&
150+
Object.entries(props).map(([name, propApi]) => {
151+
const result: any = {
152+
name,
153+
value: {
154+
kind: 'expression',
155+
type: resolveType(propApi),
156+
},
157+
description: getDescription(propApi),
158+
'doc-url': meta.docsUrl || alternateUrl,
159+
}
160+
if (propApi.required) {
161+
result.required = true
162+
}
163+
if (propApi.default) {
164+
result.default = JSON.stringify(propApi.default)
165+
}
166+
if (propApi.type === 'Boolean') {
167+
// Deprecated but used for compatibility with WebStorm 2019.2.
168+
result.type = 'boolean'
169+
}
170+
return result
171+
}),
172+
events:
173+
events &&
174+
Object.entries(events).map(([name, eventApi]) => ({
175+
name,
176+
arguments:
177+
eventApi.params &&
178+
Object.entries(eventApi.params).map(([paramName, paramApi]) => ({
179+
name: paramName,
180+
type: resolveType(paramApi),
181+
description: getDescription(paramApi),
182+
'doc-url': meta.docsUrl || alternateUrl,
183+
})),
184+
description: getDescription(eventApi),
185+
'doc-url': meta.docsUrl || alternateUrl,
186+
})),
187+
slots: slotTypes,
188+
description: `${name} - QCalendar component`,
189+
'doc-url': meta.docsUrl || alternateUrl,
190+
}
191+
if (
192+
props &&
193+
props.value &&
194+
((events && events.input) || props.value.category === 'model')
195+
) {
196+
result['vue-model'] = {
197+
prop: 'value',
198+
event: 'input',
199+
}
200+
}
201+
Object.entries(result).forEach(([key, v]) => {
202+
if (!v) {
203+
delete result[key]
204+
}
205+
})
206+
207+
return result
208+
}),
209+
210+
attributes: api.directives.map(({ name, api: { modifiers, value, meta } }) => {
211+
const valueType = value.type
212+
const result: any = {
213+
name: 'v-' + kebabCase(name),
214+
source: {
215+
module: 'qcalendar',
216+
symbol: name,
217+
},
218+
required: false, // Directive is never required
219+
description: `${name} - QCalendar directive`,
220+
'doc-url': meta.docsUrl || alternateUrl,
221+
}
222+
if (modifiers) {
223+
result['vue-modifiers'] = Object.entries(modifiers).map(([name, api]) => ({
224+
name,
225+
description: getDescription(api),
226+
'doc-url': meta.docsUrl || alternateUrl,
227+
}))
228+
}
229+
if (valueType !== 'Boolean') {
230+
result.value = {
231+
kind: 'expression',
232+
type: resolveType(value),
233+
}
234+
}
235+
return result
236+
}),
237+
},
238+
},
239+
})
240+
241+
writeFile(resolve('web-types.json'), webtypes)
242+
} catch (err) {
243+
logError('build.web-types.ts: something went wrong...')
244+
console.log()
245+
console.error(err)
246+
console.log()
247+
process.exit(1)
248+
}
249+
}

packages/ui/build/index.ts

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,28 @@ async function copyTypesToDist() {
3232
}
3333

3434
// Dynamic imports to maintain order
35-
import('./script.app-ext').then(({ syncAppExt }) => {
36-
syncAppExt()
37-
38-
import('./script.clean').then(() => {
39-
console.log(
40-
` 📦 Building ${green('v' + version)}...${parallel ? blue(' [multi-threaded]') : ''}\n`,
41-
)
42-
43-
createFolder('dist')
44-
createFolder('dist/transforms')
45-
createFolder('dist/types')
46-
createFolder('dist/api')
47-
48-
copyTypesToDist().then(() => {
49-
import('./script.version').then(() => {
50-
import('./build.api').then(() => {
51-
import('./script.javascript').then(() => {
52-
import('./script.css').then(() => {
53-
buildApi()
54-
})
55-
})
56-
})
57-
})
58-
})
59-
})
35+
import('./script.app-ext').then(async ({ syncAppExt }) => {
36+
await syncAppExt()
6037
})
6138

39+
await import('./script.clean.js')
40+
41+
console.log(
42+
` 📦 Building QCalendar ${green('v' + version)}...${parallel ? blue(' [multi-threaded]') : ''}\n`,
43+
)
44+
45+
createFolder('dist')
46+
createFolder('dist/transforms')
47+
createFolder('dist/types')
48+
createFolder('dist/api')
49+
createFolder('dist/web-types')
50+
51+
await copyTypesToDist()
52+
await import('./script.version')
53+
const api = await import('./build.api.js').then(({ generate }) => generate({ compact: true }))
54+
import('./build.web-types.js').then(({ generate }) => generate({ api, compact: true }))
55+
import('./script.javascript')
56+
import('./script.css')
57+
6258
// runJob(join(__dirname, './script.javascript'))
6359
// runJob(join(__dirname, './script.css'))

packages/ui/build/script.app-ext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function updateDependency(
4646
return false
4747
}
4848

49-
export function syncAppExt(syncVersion = true) {
49+
export async function syncAppExt(syncVersion = true): Promise<void> {
5050
const appExtDir = resolvePath('app-extension')
5151
const uiDir = resolvePath('ui')
5252

packages/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"main": "dist/index.cjs.js",
1313
"typings": "dist/types/index.d.ts",
1414
"types": "dist/calendar.d.ts",
15+
"web-types": "dist/web-types/web-types.json",
1516
"files": [
1617
"dist",
1718
"src"

0 commit comments

Comments
 (0)