Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(types): 根据各个小程序官方文档,同步组件类型声明 #12441

Merged
merged 21 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
46df76f
refactor(types): 根据各个小程序官方文档,同步组件类型声明
robinv8 Sep 6, 2022
19802a4
chore: add third-party dependencies
robinv8 Sep 6, 2022
afc0d94
feat: 实现组件类型声明同步工具
robinv8 Sep 6, 2022
67747b0
refactor(types): 根据各个小程序官方文档,同步组件类型声明
robinv8 Sep 7, 2022
d2d4f4b
refactor(types): 添加泛型
robinv8 Sep 7, 2022
8b77b51
refactor: 完善类型同步程序
robinv8 Sep 7, 2022
e3359ff
chore: update devDependencies
robinv8 Sep 7, 2022
b9ab5f0
refactor(types): 根据各个小程序官方文档,同步组件类型声明
robinv8 Sep 8, 2022
9623e42
chore: update dependency
robinv8 Sep 8, 2022
09f6e7e
Merge branch 'next' into dev-types
ZakaryCode Sep 9, 2022
388fe18
refactor(types): 恢复注释
robinv8 Sep 9, 2022
7c5d905
fix(types): 修复类型同步异常的问题
robinv8 Sep 9, 2022
a473c86
refactor: 调整组件类型同步程序细节
robinv8 Sep 9, 2022
377e018
refactor(types): 优化组件类型同步程序
robinv8 Sep 12, 2022
4f083eb
fix: rn 兼容
zhiqingchen Sep 13, 2022
a26b926
refactor(types): 同步组件类型声明
robinv8 Sep 13, 2022
9980bad
chore: update dependency
robinv8 Sep 13, 2022
386685d
fix(types): 修复组件类型声明描述信息错误的问题
robinv8 Sep 13, 2022
b7c56fb
refactor(types): update LivePlayer.d.ts
robinv8 Sep 13, 2022
ac433d6
refactor: 同步组件类型声明程序优化
robinv8 Sep 13, 2022
b701416
Merge branch 'next' into dev-types
ZakaryCode Sep 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export interface RadioState {
export interface RadioProps extends _RadioProps {
style?: StyleProp<ViewStyle>|any;
/* rn独有的,在taro里面看不到,得翻源码才能看到 **/
onChange?: (evt: EventOnChange) => void;
// onChange?: (evt: EventOnChange) => void;
}
2 changes: 2 additions & 0 deletions packages/taro-components-rn/src/components/Video/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ global._taroVideoMap = {}

interface Props extends VideoProps {
onLoad: () => void;
// 兼容旧版本,可传入 style 对象
style?: any;
}

class _Video extends Component<Props, any> {
Expand Down
6 changes: 5 additions & 1 deletion packages/taro-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"dependencies": {
"@stencil/core": "~2.13.0",
"@stencil/sass": "1.5.2",
"@tarojs/taro": "workspace:*",
"@tarojs/router": "workspace:*",
"@tarojs/taro": "workspace:*",
"better-scroll": "^1.14.1",
"classnames": "^2.2.5",
"hls.js": "^1.1.5",
Expand All @@ -52,9 +52,13 @@
"weui": "^1.1.2"
},
"devDependencies": {
"@types/node": "^14.14.31",
"css-loader": "3.4.2",
"humps": "^2.0.1",
"jquery": "^3.4.1",
"karmatic": "^2.1.0",
"lodash": "^4.17.21",
"miniapp-types": "1.1.6",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"simulant": "^0.2.2",
Expand Down
241 changes: 241 additions & 0 deletions packages/taro-components/src/utils/json-schema-to-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import generator from '@babel/generator'
import * as parser from '@babel/parser'
import traverse from '@babel/traverse'
import * as t from '@babel/types'
import * as fs from 'fs'
import * as humps from 'humps'
import { flattenDeep, isEmpty, toArray, xorWith } from 'lodash'
import * as path from 'path'
import { format as prettify } from 'prettier'

const MINI_APP_TYPES = ['weapp', 'alipay', 'swan', 'tt', 'qq', 'jd'] as const

const OMIT_PROPS = ['generic:simple-component']
class GenerateTypes {
jsonSchemas: any = {}
componentName
constructor (componentName) {
this.componentName = componentName

MINI_APP_TYPES.forEach((type) => {
try {
const json = require(`miniapp-types/dist/schema/${type}/${
componentName === 'AD' ? 'ad' : humps.decamelize(componentName, { separator: '-' })
}.json`)

if (!json) {
return
}
if (!this.jsonSchemas[componentName]) {
this.jsonSchemas[componentName] = {}
}
this.jsonSchemas[componentName][type] = json
} catch (error) {
// console.log(error)
if (!this.jsonSchemas[componentName]) {
this.jsonSchemas[componentName] = {}
}
}
})
}

// 获取不存在的属性
getMissingProps (props: { [key in typeof MINI_APP_TYPES[number]]?: string[] }) {
const obj = {}
const jsonSchema = this.jsonSchemas[this.componentName]
if (!jsonSchema) {
return obj
}
Object.keys(this.jsonSchemas[this.componentName]).forEach((key) => {
const filteredList = xorWith(props[key], Object.keys(this.jsonSchemas[this.componentName][key].properties))
if (filteredList.length > 0) {
obj[key] = filteredList.map((item) =>
item.match(/^bind/) ? humps.camelize(item.replace(/^bind/, 'on')) : item
)
}
})

return obj
}

// 转换不存在的属性,便于添加到已有的类型声明中
convertProps (props) {
const array = [...new Set(flattenDeep(toArray(props)))]
const reverseProps = {}
array.forEach((prop) => {
reverseProps[prop] = Object.keys(props).filter((key) => props[key].includes(prop))
})
return reverseProps
}

updateComment (ast) {
const componentName = this.componentName
const jsonSchemas = this.jsonSchemas[this.componentName]
const existProps: { [key in typeof MINI_APP_TYPES[number]]?: string[] } = {}

traverse(ast, {
TSInterfaceDeclaration (astPath) {
if (astPath.node.id.name !== `${componentName}Props`) {
return
}
astPath.traverse({
TSPropertySignature (astPath) {
const { name } = astPath.node.key as any
if (!name) {
return
}
const supportedPlatforms: string[] = []

const covertedName = name.match(/^on/)
? name.replace(/^on/, 'bind')
: humps.decamelize(name, { separator: '-' })
MINI_APP_TYPES.forEach((type) => {
if (jsonSchemas[type]?.properties[name]) {
if (isEmpty(existProps[type])) {
existProps[type] = [name]
}
existProps[type]?.push(name)
supportedPlatforms.push(type)
}

if (name !== covertedName && jsonSchemas[type]?.properties[covertedName]) {
if (isEmpty(existProps[type])) {
existProps[type] = [covertedName]
}
existProps[type]?.push(covertedName)
supportedPlatforms.push(type)
}
})
if (isEmpty(astPath.node.leadingComments) || !astPath.node.leadingComments?.[0]?.value) {
return
}
const value = astPath.node.leadingComments?.[0]?.value

// 保留原有 h5 类型
if (value.toLowerCase().indexOf('h5') > -1) {
supportedPlatforms.push('h5')
}

// 保留原有 rn 类型
if (value.toLowerCase().indexOf('rn') > -1) {
supportedPlatforms.push('rn')
}
if (isEmpty(supportedPlatforms)) {
astPath.remove()
} else {
astPath.node.leadingComments[0].value = value.replace(
/@supported .*?\n/,
`@supported ${supportedPlatforms.join(', ')}\n`
)
if (value.match(/@deprecated/)) {
astPath.node.leadingComments[0].value = value.replace(/\* @deprecated.*?\n/, '')
}
}
},
})
},
})
return {
existProps,
ast,
}
}

// 添加不存在的属性
addProps (ast, props) {
const componentName = this.componentName
const jsonSchemas = this.jsonSchemas[this.componentName]
traverse(ast, {
TSInterfaceDeclaration (astPath) {
if (astPath.node.id.name !== `${componentName}Props`) {
return
}
const addedProps: string[] = []
astPath.traverse({
TSInterfaceBody (astPath) {
Object.keys(props).forEach((prop) => {
if (OMIT_PROPS.includes(prop)) {
return
}
const node = t.cloneNode(astPath.node.body[0]) as t.TSPropertySignature
node.key = t.identifier(humps.camelize(prop, { separator: '-' }))
const platform = props[prop][0]
const json = jsonSchemas[platform]
const propSchema = json.properties[prop] || json.properties[prop.replace(/^on/, 'bind')]
const { type, tsType, enum: enumArray } = propSchema
let value
if (type === 'string') {
if (!enumArray) {
value = t.tsTypeReference(t.identifier(type))
} else {
value = t.tsUnionType(enumArray.map((item) => t.tsLiteralType(t.stringLiteral(item))))
}
} else if (type instanceof Array) {
value = t.tsTypeReference(t.identifier(type.join('|')))
} else if (tsType === '() => void') {
value = t.tsTypeReference(t.identifier('CommonEventFunction'))
} else {
value = t.tsTypeReference(t.identifier('string'))
}
node.typeAnnotation = t.tsTypeAnnotation(value)
node.optional = !json.required?.[prop] || !json.required?.[prop.replace(/^on/, 'bind')]

if (node.leadingComments) {
let commentValue = `* ${propSchema.description?.replace(/\n/g, '\n * ')} \n`
commentValue += `* @supported ${props[prop].join(', ')}\n`
const defaultValue = propSchema.defaultValue
if (defaultValue) {
if (defaultValue instanceof Array) {
commentValue += `* @default ${propSchema.defaultValue.join(',')}\n`
} else {
commentValue += `* @default ${propSchema.defaultValue}\n`
}
}
node.leadingComments[0].value = commentValue
}
astPath.node.body.push(node)
addedProps.push(prop)
})
},
})
},
})
}

formatJSDoc (ast) {
traverse(ast, {
enter (astPath) {
if (astPath.node.trailingComments) {
astPath.node.trailingComments = []
}
},
})
}

exec () {
const filePath = path.join(process.cwd(), 'types', `${this.componentName}.d.ts`)
const codeStr = fs.readFileSync(filePath, 'utf8')
const ast = parser.parse(codeStr, {
sourceType: 'module',
strictMode: false,
plugins: ['typescript'],
})
const { existProps } = this.updateComment(ast)
const missingProps = this.getMissingProps(existProps)
const props = this.convertProps(missingProps)
this.addProps(ast, props)
this.formatJSDoc(ast)
const result = generator(ast)
const code = prettify(result.code, { parser: 'typescript', singleQuote: true, semi: false })
fs.writeFileSync(filePath, code)
}
}
const typesFiles: string[] = fs.readdirSync(path.join(process.cwd(), 'types'))

typesFiles.forEach((fileName) => {
const componentName = fileName.replace(/\.d\.ts$/, '')
const generateTypes = new GenerateTypes(componentName)
generateTypes.exec()
})

export default GenerateTypes
Loading