v1.ts 8.6 KB
Newer Older
fxy060608's avatar
fxy060608 已提交
1 2
import type { Plugin } from 'vite'
import path from 'path'
fxy060608's avatar
fxy060608 已提交
3
import { isInHBuilderX, parseVueRequest } from '@dcloudio/uni-cli-shared'
fxy060608's avatar
fxy060608 已提交
4
import {
fxy060608's avatar
fxy060608 已提交
5
  BindingIdentifier,
fxy060608's avatar
fxy060608 已提交
6
  ClassDeclaration,
fxy060608's avatar
fxy060608 已提交
7 8
  ClassExpression,
  Expression,
fxy060608's avatar
fxy060608 已提交
9
  FunctionDeclaration,
fxy060608's avatar
fxy060608 已提交
10 11
  FunctionExpression,
  Identifier,
fxy060608's avatar
fxy060608 已提交
12
  Module,
fxy060608's avatar
fxy060608 已提交
13
  Param,
fxy060608's avatar
fxy060608 已提交
14
  TsTypeAnnotation,
fxy060608's avatar
fxy060608 已提交
15
  VariableDeclaration,
fxy060608's avatar
fxy060608 已提交
16
} from '../../types/types'
fxy060608's avatar
fxy060608 已提交
17
import { getCompiler } from '../utils/compiler'
fxy060608's avatar
fxy060608 已提交
18

fxy060608's avatar
fxy060608 已提交
19
export function uniUtsV1Plugin(): Plugin {
fxy060608's avatar
fxy060608 已提交
20
  let isFirst = true
fxy060608's avatar
fxy060608 已提交
21 22 23 24
  return {
    name: 'uni:uts-v1',
    apply: 'build',
    enforce: 'pre',
fxy060608's avatar
fxy060608 已提交
25
    async transform(code, id, opts) {
fxy060608's avatar
fxy060608 已提交
26 27 28
      if (opts && opts.ssr) {
        return
      }
fxy060608's avatar
fxy060608 已提交
29 30 31 32 33
      // 目前仅支持 app-android|app-ios
      if (
        process.env.UNI_UTS_PLATFORM !== 'app-android' &&
        process.env.UNI_UTS_PLATFORM !== 'app-ios'
      ) {
fxy060608's avatar
fxy060608 已提交
34 35
        return
      }
fxy060608's avatar
fxy060608 已提交
36
      const { filename } = parseVueRequest(id)
fxy060608's avatar
fxy060608 已提交
37 38 39
      if (path.extname(filename) !== '.uts') {
        return
      }
fxy060608's avatar
fxy060608 已提交
40 41 42 43
      const { compile, parsePackage, createResolveTypeReferenceName } =
        getCompiler(
          process.env.UNI_UTS_PLATFORM === 'app-ios' ? 'swift' : 'kotlin'
        )
fxy060608's avatar
fxy060608 已提交
44
      const pkg = parsePackage(filename)
fxy060608's avatar
fxy060608 已提交
45
      if (!pkg.class) {
fxy060608's avatar
fxy060608 已提交
46 47
        return
      }
fxy060608's avatar
fxy060608 已提交
48
      // 懒加载 uts 编译器
fxy060608's avatar
fxy060608 已提交
49 50
      // eslint-disable-next-line no-restricted-globals
      const { parse } = require('@dcloudio/uts')
fxy060608's avatar
fxy060608 已提交
51
      const ast = await parse(code, { noColor: isInHBuilderX() })
fxy060608's avatar
fxy060608 已提交
52 53
      code = `
import { initUtsProxyClass, initUtsProxyFunction } from '@dcloudio/uni-app'
fxy060608's avatar
fxy060608 已提交
54 55
const pkg = '${pkg.package}'
const cls = '${pkg.class}'
fxy060608's avatar
fxy060608 已提交
56
${genProxyCode(ast, createResolveTypeReferenceName(pkg.namespace, ast))}
fxy060608's avatar
fxy060608 已提交
57
`
fxy060608's avatar
fxy060608 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70
      const res = await compile(id)
      if (process.env.UNI_UTS_PLATFORM === 'app-android') {
        if (!isFirst && res) {
          const files = []
          if (process.env.UNI_APP_CHANGED_DEX_FILES) {
            try {
              files.push(...JSON.parse(process.env.UNI_APP_CHANGED_DEX_FILES))
            } catch (e) {}
          }
          files.push(res)
          process.env.UNI_APP_CHANGED_DEX_FILES = JSON.stringify([
            ...new Set(files),
          ])
fxy060608's avatar
fxy060608 已提交
71 72
        }
      }
fxy060608's avatar
fxy060608 已提交
73
      return code
fxy060608's avatar
fxy060608 已提交
74
    },
fxy060608's avatar
fxy060608 已提交
75 76 77
    buildEnd() {
      isFirst = false
    },
fxy060608's avatar
fxy060608 已提交
78 79 80
  }
}

fxy060608's avatar
fxy060608 已提交
81 82 83
function genProxyFunctionCode(
  method: string,
  async: boolean,
fxy060608's avatar
fxy060608 已提交
84
  params: Parameter[],
fxy060608's avatar
fxy060608 已提交
85 86 87
  isDefault: boolean = false
) {
  if (isDefault) {
fxy060608's avatar
fxy060608 已提交
88 89 90
    return `export default initUtsProxyFunction(${async}, { package: pkg, class: cls, name: '${method}', params: ${JSON.stringify(
      params
    )}})`
fxy060608's avatar
fxy060608 已提交
91
  }
fxy060608's avatar
fxy060608 已提交
92 93 94
  return `export const ${method} = initUtsProxyFunction(${async}, { package: pkg, class: cls, name: '${method}', params: ${JSON.stringify(
    params
  )}})`
fxy060608's avatar
fxy060608 已提交
95 96 97 98
}

function genProxyClassCode(
  cls: string,
fxy060608's avatar
fxy060608 已提交
99
  options: {
fxy060608's avatar
fxy060608 已提交
100
    constructor: { params: Parameter[] }
fxy060608's avatar
fxy060608 已提交
101 102 103 104 105
    methods: Record<string, any>
    staticMethods: Record<string, any>
    props: string[]
    staticProps: string[]
  },
fxy060608's avatar
fxy060608 已提交
106 107 108
  isDefault: boolean = false
) {
  if (isDefault) {
fxy060608's avatar
fxy060608 已提交
109 110
    return `export default initUtsProxyClass({ package: pkg, class: '${cls}', ...${JSON.stringify(
      options
fxy060608's avatar
fxy060608 已提交
111 112
    )} })`
  }
fxy060608's avatar
fxy060608 已提交
113 114
  return `export const ${cls} = initUtsProxyClass({ package: pkg, class: '${cls}', ...${JSON.stringify(
    options
fxy060608's avatar
fxy060608 已提交
115 116 117
  )} })`
}

fxy060608's avatar
fxy060608 已提交
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
interface Parameter {
  name: string
  type: string
}

type ResolveTypeReferenceName = (name: string) => string

function resolveIdentifierType(
  ident: BindingIdentifier,
  resolveTypeReferenceName: ResolveTypeReferenceName
) {
  if (ident.typeAnnotation) {
    const { typeAnnotation } = ident.typeAnnotation
    if (typeAnnotation.type === 'TsKeywordType') {
      return typeAnnotation.kind
    } else if (
      typeAnnotation.type === 'TsTypeReference' &&
      typeAnnotation.typeName.type === 'Identifier'
    ) {
      return resolveTypeReferenceName(typeAnnotation.typeName.value)
    }
fxy060608's avatar
fxy060608 已提交
139
  }
fxy060608's avatar
fxy060608 已提交
140
  return ''
fxy060608's avatar
fxy060608 已提交
141
}
fxy060608's avatar
fxy060608 已提交
142 143 144 145

function resolveFunctionParams(
  params: Param[],
  resolveTypeReferenceName: ResolveTypeReferenceName
fxy060608's avatar
fxy060608 已提交
146
) {
fxy060608's avatar
fxy060608 已提交
147 148 149 150 151 152 153 154 155 156 157 158
  const result: Parameter[] = []
  params.forEach(({ pat }) => {
    if (pat.type === 'Identifier') {
      result.push({
        name: pat.value,
        type: resolveIdentifierType(
          pat as BindingIdentifier,
          resolveTypeReferenceName
        ),
      })
    } else {
      result.push({ name: '', type: '' })
fxy060608's avatar
fxy060608 已提交
159 160
    }
  })
fxy060608's avatar
fxy060608 已提交
161
  return result
fxy060608's avatar
fxy060608 已提交
162 163 164
}

function genFunctionDeclarationCode(
fxy060608's avatar
fxy060608 已提交
165
  decl: FunctionDeclaration | FunctionExpression,
fxy060608's avatar
fxy060608 已提交
166
  resolveTypeReferenceName: ResolveTypeReferenceName,
fxy060608's avatar
fxy060608 已提交
167 168 169
  isDefault: boolean = false
) {
  return genProxyFunctionCode(
fxy060608's avatar
fxy060608 已提交
170
    decl.identifier!.value,
fxy060608's avatar
fxy060608 已提交
171
    decl.async || isReturnPromise(decl.returnType),
fxy060608's avatar
fxy060608 已提交
172
    resolveFunctionParams(decl.params, resolveTypeReferenceName),
fxy060608's avatar
fxy060608 已提交
173 174 175 176 177
    isDefault
  )
}

function genClassDeclarationCode(
fxy060608's avatar
fxy060608 已提交
178
  decl: ClassDeclaration | ClassExpression,
fxy060608's avatar
fxy060608 已提交
179
  resolveTypeReferenceName: ResolveTypeReferenceName,
fxy060608's avatar
fxy060608 已提交
180 181
  isDefault: boolean = false
) {
fxy060608's avatar
fxy060608 已提交
182
  const cls = decl.identifier!.value
fxy060608's avatar
fxy060608 已提交
183 184 185 186 187 188
  const constructor: { params: Parameter[] } = { params: [] }
  const methods: Record<string, { async?: boolean; params: Parameter[] }> = {}
  const staticMethods: Record<
    string,
    { async?: boolean; params: Parameter[] }
  > = {}
fxy060608's avatar
fxy060608 已提交
189 190
  const props: string[] = []
  const staticProps: string[] = []
fxy060608's avatar
fxy060608 已提交
191
  decl.body.forEach((item) => {
fxy060608's avatar
fxy060608 已提交
192 193 194 195 196 197
    if (item.type === 'Constructor') {
      constructor.params = resolveFunctionParams(
        item.params as Param[],
        resolveTypeReferenceName
      )
    } else if (item.type === 'ClassMethod') {
fxy060608's avatar
fxy060608 已提交
198
      if (item.key.type === 'Identifier') {
fxy060608's avatar
fxy060608 已提交
199 200
        const name = item.key.value
        const value = {
fxy060608's avatar
fxy060608 已提交
201 202
          async:
            item.function.async || isReturnPromise(item.function.returnType),
fxy060608's avatar
fxy060608 已提交
203 204 205 206
          params: resolveFunctionParams(
            item.function.params,
            resolveTypeReferenceName
          ),
fxy060608's avatar
fxy060608 已提交
207
        }
fxy060608's avatar
fxy060608 已提交
208 209 210 211 212 213 214 215 216 217 218 219 220
        if (item.isStatic) {
          staticMethods[name] = value
        } else {
          methods[name] = value
        }
      }
    } else if (item.type === 'ClassProperty') {
      if (item.key.type === 'Identifier') {
        if (item.isStatic) {
          staticProps.push(item.key.value)
        } else {
          props.push(item.key.value)
        }
fxy060608's avatar
fxy060608 已提交
221
      }
fxy060608's avatar
fxy060608 已提交
222 223
    }
  })
fxy060608's avatar
fxy060608 已提交
224 225
  return genProxyClassCode(
    cls,
fxy060608's avatar
fxy060608 已提交
226
    { constructor, methods, staticMethods, props, staticProps },
fxy060608's avatar
fxy060608 已提交
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
    isDefault
  )
}

function genInitCode(expr: Expression) {
  switch (expr.type) {
    case 'BooleanLiteral':
      return expr.value + ''
    case 'NumericLiteral':
      return expr.value + ''
    case 'StringLiteral':
      return expr.value
  }
  return ''
}

function genVariableDeclarationCode(decl: VariableDeclaration) {
fxy060608's avatar
fxy060608 已提交
244
  // 目前仅支持 const 的 boolean,number,string
fxy060608's avatar
fxy060608 已提交
245 246
  const lits = ['BooleanLiteral', 'NumericLiteral', 'StringLiteral']
  if (
fxy060608's avatar
fxy060608 已提交
247
    decl.kind === 'const' &&
fxy060608's avatar
fxy060608 已提交
248 249 250 251 252 253 254 255 256 257 258 259 260 261
    !decl.declarations.find((d) => {
      if (d.id.type !== 'Identifier') {
        return true
      }
      if (!d.init) {
        return true
      }
      const type = d.init.type
      if (!lits.includes(type)) {
        return true
      }
      return false
    })
  ) {
fxy060608's avatar
fxy060608 已提交
262
    return `export ${decl.kind} ${decl.declarations
fxy060608's avatar
fxy060608 已提交
263 264 265
      .map((d) => `${(d.id as Identifier).value} = ${genInitCode(d.init!)}`)
      .join(', ')}`
  }
fxy060608's avatar
fxy060608 已提交
266 267
}

fxy060608's avatar
fxy060608 已提交
268 269 270 271
function genProxyCode(
  { body }: Module,
  resolveTypeReferenceName: ResolveTypeReferenceName
) {
fxy060608's avatar
fxy060608 已提交
272
  const codes: string[] = []
fxy060608's avatar
fxy060608 已提交
273

fxy060608's avatar
fxy060608 已提交
274 275 276 277 278 279
  body.forEach((item) => {
    let code: string | undefined
    if (item.type === 'ExportDeclaration') {
      const decl = item.declaration
      switch (decl.type) {
        case 'FunctionDeclaration':
fxy060608's avatar
fxy060608 已提交
280 281 282 283 284
          code = genFunctionDeclarationCode(
            decl,
            resolveTypeReferenceName,
            false
          )
fxy060608's avatar
fxy060608 已提交
285 286
          break
        case 'ClassDeclaration':
fxy060608's avatar
fxy060608 已提交
287
          code = genClassDeclarationCode(decl, resolveTypeReferenceName, false)
fxy060608's avatar
fxy060608 已提交
288
          break
fxy060608's avatar
fxy060608 已提交
289 290 291
        case 'VariableDeclaration':
          code = genVariableDeclarationCode(decl)
          break
fxy060608's avatar
fxy060608 已提交
292
      }
fxy060608's avatar
fxy060608 已提交
293
    } else if (item.type === 'ExportDefaultDeclaration') {
fxy060608's avatar
fxy060608 已提交
294
      const decl = item.decl
fxy060608's avatar
fxy060608 已提交
295
      if (decl.type === 'ClassExpression') {
fxy060608's avatar
fxy060608 已提交
296 297
        if (decl.identifier) {
          // export default class test{}
fxy060608's avatar
fxy060608 已提交
298
          code = genClassDeclarationCode(decl, resolveTypeReferenceName, false)
fxy060608's avatar
fxy060608 已提交
299 300 301
        }
      } else if (decl.type === 'FunctionExpression') {
        if (decl.identifier) {
fxy060608's avatar
fxy060608 已提交
302 303 304 305 306
          code = genFunctionDeclarationCode(
            decl,
            resolveTypeReferenceName,
            true
          )
fxy060608's avatar
fxy060608 已提交
307
        }
fxy060608's avatar
fxy060608 已提交
308
      }
fxy060608's avatar
fxy060608 已提交
309
    }
fxy060608's avatar
fxy060608 已提交
310 311 312
    if (code) {
      codes.push(code)
    }
fxy060608's avatar
fxy060608 已提交
313
  })
fxy060608's avatar
fxy060608 已提交
314
  return codes.join(`\n`)
fxy060608's avatar
fxy060608 已提交
315 316
}

fxy060608's avatar
fxy060608 已提交
317 318 319 320 321
function isReturnPromise(anno?: TsTypeAnnotation) {
  if (!anno) {
    return false
  }
  const { typeAnnotation } = anno
fxy060608's avatar
fxy060608 已提交
322 323 324 325 326 327
  return (
    typeAnnotation.type === 'TsTypeReference' &&
    typeAnnotation.typeName.type === 'Identifier' &&
    typeAnnotation.typeName.value === 'Promise'
  )
}