v1.ts 7.7 KB
Newer Older
fxy060608's avatar
fxy060608 已提交
1 2 3
import type { Plugin } from 'vite'
import path from 'path'
import {
fxy060608's avatar
fxy060608 已提交
4
  isInHBuilderX,
fxy060608's avatar
fxy060608 已提交
5 6 7 8 9 10
  normalizePath,
  parseVueRequest,
  requireResolve,
} from '@dcloudio/uni-cli-shared'
import {
  ClassDeclaration,
fxy060608's avatar
fxy060608 已提交
11 12
  ClassExpression,
  Expression,
fxy060608's avatar
fxy060608 已提交
13
  FunctionDeclaration,
fxy060608's avatar
fxy060608 已提交
14 15
  FunctionExpression,
  Identifier,
fxy060608's avatar
fxy060608 已提交
16
  Module,
fxy060608's avatar
fxy060608 已提交
17
  TsFunctionType,
fxy060608's avatar
fxy060608 已提交
18
  TsInterfaceDeclaration,
fxy060608's avatar
fxy060608 已提交
19
  TsType,
fxy060608's avatar
fxy060608 已提交
20
  TsTypeAliasDeclaration,
fxy060608's avatar
fxy060608 已提交
21
  TsTypeAnnotation,
fxy060608's avatar
fxy060608 已提交
22
  VariableDeclaration,
fxy060608's avatar
fxy060608 已提交
23
} from '../../types/types'
fxy060608's avatar
fxy060608 已提交
24
import { compile, parsePackage } from '../utils/compiler'
fxy060608's avatar
fxy060608 已提交
25

fxy060608's avatar
fxy060608 已提交
26
export function uniUtsV1Plugin(): Plugin {
fxy060608's avatar
fxy060608 已提交
27
  let isFirst = true
fxy060608's avatar
fxy060608 已提交
28 29 30 31
  return {
    name: 'uni:uts-v1',
    apply: 'build',
    enforce: 'pre',
fxy060608's avatar
fxy060608 已提交
32 33 34 35 36 37 38 39
    resolveId(id, importer) {
      if (isUtsModuleRoot(id)) {
        return requireResolve(
          id,
          (importer && path.dirname(importer)) || process.env.UNI_INPUT_DIR
        )
      }
    },
fxy060608's avatar
fxy060608 已提交
40
    async transform(code, id, opts) {
fxy060608's avatar
fxy060608 已提交
41 42 43
      if (opts && opts.ssr) {
        return
      }
fxy060608's avatar
fxy060608 已提交
44
      const { filename } = parseVueRequest(id)
fxy060608's avatar
fxy060608 已提交
45 46 47
      if (path.extname(filename) !== '.uts') {
        return
      }
fxy060608's avatar
fxy060608 已提交
48 49
      const pkg = parsePackage(filename)
      if (!pkg) {
fxy060608's avatar
fxy060608 已提交
50 51
        return
      }
fxy060608's avatar
fxy060608 已提交
52
      // 懒加载 uts 编译器
fxy060608's avatar
fxy060608 已提交
53 54
      // eslint-disable-next-line no-restricted-globals
      const { parse } = require('@dcloudio/uts')
fxy060608's avatar
fxy060608 已提交
55
      const ast = await parse(code, { noColor: isInHBuilderX() })
fxy060608's avatar
fxy060608 已提交
56 57 58
      code = `
import { initUtsProxyClass, initUtsProxyFunction } from '@dcloudio/uni-app'
const pkg = '${pkg}'
fxy060608's avatar
fxy060608 已提交
59
const cls = 'IndexKt'
fxy060608's avatar
fxy060608 已提交
60 61
${genProxyCode(ast)}
`
fxy060608's avatar
fxy060608 已提交
62 63 64 65 66 67 68 69 70 71 72 73 74
      const dexFile = await compile(id)
      if (!isFirst && dexFile) {
        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(dexFile)
        process.env.UNI_APP_CHANGED_DEX_FILES = JSON.stringify([
          ...new Set(files),
        ])
      }
fxy060608's avatar
fxy060608 已提交
75
      return code
fxy060608's avatar
fxy060608 已提交
76
    },
fxy060608's avatar
fxy060608 已提交
77 78 79
    buildEnd() {
      isFirst = false
    },
fxy060608's avatar
fxy060608 已提交
80 81 82
  }
}

fxy060608's avatar
fxy060608 已提交
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
// 仅限 uni_modules/test-plugin 格式
function isUtsModuleRoot(id: string) {
  const parts = normalizePath(id).split('/')
  if (parts[parts.length - 2] === 'uni_modules') {
    return true
  }
  return false
}

function genProxyFunctionCode(
  method: string,
  async: boolean,
  isDefault: boolean = false
) {
  if (isDefault) {
fxy060608's avatar
fxy060608 已提交
98
    return `export default initUtsProxyFunction(${async}, { package: pkg, class: cls, name: '${method}'})`
fxy060608's avatar
fxy060608 已提交
99
  }
fxy060608's avatar
fxy060608 已提交
100
  return `export const ${method} = initUtsProxyFunction(${async}, { package: pkg, class: cls, name: '${method}'})`
fxy060608's avatar
fxy060608 已提交
101 102 103 104
}

function genProxyClassCode(
  cls: string,
fxy060608's avatar
fxy060608 已提交
105 106 107 108 109 110
  options: {
    methods: Record<string, any>
    staticMethods: Record<string, any>
    props: string[]
    staticProps: string[]
  },
fxy060608's avatar
fxy060608 已提交
111 112 113
  isDefault: boolean = false
) {
  if (isDefault) {
fxy060608's avatar
fxy060608 已提交
114 115
    return `export default initUtsProxyClass({ package: pkg, class: '${cls}', ...${JSON.stringify(
      options
fxy060608's avatar
fxy060608 已提交
116 117
    )} })`
  }
fxy060608's avatar
fxy060608 已提交
118 119
  return `export const ${cls} = initUtsProxyClass({ package: pkg, class: '${cls}', ...${JSON.stringify(
    options
fxy060608's avatar
fxy060608 已提交
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
  )} })`
}

function genTsTypeAliasDeclarationCode(decl: TsTypeAliasDeclaration) {
  if (isFunctionType(decl.typeAnnotation)) {
    return genProxyFunctionCode(
      decl.id.value,
      isReturnPromise(decl.typeAnnotation.typeAnnotation)
    )
  }
}
function genTsInterfaceDeclarationCode(
  decl: TsInterfaceDeclaration,
  isDefault: boolean = false
) {
  const cls = decl.id.value
  const methods: Record<string, { async?: boolean }> = {}
  decl.body.body.forEach((item) => {
    if (item.type === 'TsMethodSignature') {
      if (item.key.type === 'Identifier') {
        methods[item.key.value] = {
          async: isReturnPromise(item.typeAnn),
        }
fxy060608's avatar
fxy060608 已提交
143
      }
fxy060608's avatar
fxy060608 已提交
144 145
    }
  })
fxy060608's avatar
fxy060608 已提交
146 147 148 149 150
  return genProxyClassCode(
    cls,
    { methods, staticMethods: {}, props: [], staticProps: [] },
    isDefault
  )
fxy060608's avatar
fxy060608 已提交
151 152 153
}

function genFunctionDeclarationCode(
fxy060608's avatar
fxy060608 已提交
154
  decl: FunctionDeclaration | FunctionExpression,
fxy060608's avatar
fxy060608 已提交
155 156 157
  isDefault: boolean = false
) {
  return genProxyFunctionCode(
fxy060608's avatar
fxy060608 已提交
158
    decl.identifier!.value,
fxy060608's avatar
fxy060608 已提交
159 160 161 162 163 164
    decl.async || isReturnPromise(decl.returnType),
    isDefault
  )
}

function genClassDeclarationCode(
fxy060608's avatar
fxy060608 已提交
165
  decl: ClassDeclaration | ClassExpression,
fxy060608's avatar
fxy060608 已提交
166 167
  isDefault: boolean = false
) {
fxy060608's avatar
fxy060608 已提交
168
  const cls = decl.identifier!.value
fxy060608's avatar
fxy060608 已提交
169
  const methods: Record<string, { async?: boolean }> = {}
fxy060608's avatar
fxy060608 已提交
170 171 172
  const staticMethods: Record<string, { async?: boolean }> = {}
  const props: string[] = []
  const staticProps: string[] = []
fxy060608's avatar
fxy060608 已提交
173 174 175
  decl.body.forEach((item) => {
    if (item.type === 'ClassMethod') {
      if (item.key.type === 'Identifier') {
fxy060608's avatar
fxy060608 已提交
176 177
        const name = item.key.value
        const value = {
fxy060608's avatar
fxy060608 已提交
178 179 180
          async:
            item.function.async || isReturnPromise(item.function.returnType),
        }
fxy060608's avatar
fxy060608 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193
        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 已提交
194
      }
fxy060608's avatar
fxy060608 已提交
195 196
    }
  })
fxy060608's avatar
fxy060608 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
  return genProxyClassCode(
    cls,
    { methods, staticMethods, props, staticProps },
    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 已提交
217
  // 目前仅支持 const 的 boolean,number,string
fxy060608's avatar
fxy060608 已提交
218 219
  const lits = ['BooleanLiteral', 'NumericLiteral', 'StringLiteral']
  if (
fxy060608's avatar
fxy060608 已提交
220
    decl.kind === 'const' &&
fxy060608's avatar
fxy060608 已提交
221 222 223 224 225 226 227 228 229 230 231 232 233 234
    !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 已提交
235
    return `export ${decl.kind} ${decl.declarations
fxy060608's avatar
fxy060608 已提交
236 237 238
      .map((d) => `${(d.id as Identifier).value} = ${genInitCode(d.init!)}`)
      .join(', ')}`
  }
fxy060608's avatar
fxy060608 已提交
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
}

function genProxyCode({ body }: Module) {
  const codes: string[] = []
  body.forEach((item) => {
    let code: string | undefined
    if (item.type === 'ExportDeclaration') {
      const decl = item.declaration
      switch (decl.type) {
        case 'FunctionDeclaration':
          code = genFunctionDeclarationCode(decl, false)
          break
        case 'ClassDeclaration':
          code = genClassDeclarationCode(decl, false)
          break
        case 'TsTypeAliasDeclaration':
          code = genTsTypeAliasDeclarationCode(decl)
          break
        case 'TsInterfaceDeclaration':
          code = genTsInterfaceDeclarationCode(decl, false)
          break
fxy060608's avatar
fxy060608 已提交
260 261 262
        case 'VariableDeclaration':
          code = genVariableDeclarationCode(decl)
          break
fxy060608's avatar
fxy060608 已提交
263
      }
fxy060608's avatar
fxy060608 已提交
264
    } else if (item.type === 'ExportDefaultDeclaration') {
fxy060608's avatar
fxy060608 已提交
265 266 267 268 269 270 271 272 273 274 275 276
      const decl = item.decl
      if (decl.type === 'TsInterfaceDeclaration') {
        code = genTsInterfaceDeclarationCode(decl, true)
      } else if (decl.type === 'ClassExpression') {
        if (decl.identifier) {
          // export default class test{}
          code = genClassDeclarationCode(decl, false)
        }
      } else if (decl.type === 'FunctionExpression') {
        if (decl.identifier) {
          code = genFunctionDeclarationCode(decl, false)
        }
fxy060608's avatar
fxy060608 已提交
277
      }
fxy060608's avatar
fxy060608 已提交
278
    }
fxy060608's avatar
fxy060608 已提交
279 280 281
    if (code) {
      codes.push(code)
    }
fxy060608's avatar
fxy060608 已提交
282
  })
fxy060608's avatar
fxy060608 已提交
283
  return codes.join(`\n`)
fxy060608's avatar
fxy060608 已提交
284 285
}

fxy060608's avatar
fxy060608 已提交
286 287 288 289
function isFunctionType(type: TsType): type is TsFunctionType {
  return type.type === 'TsFunctionType'
}

fxy060608's avatar
fxy060608 已提交
290 291 292 293 294
function isReturnPromise(anno?: TsTypeAnnotation) {
  if (!anno) {
    return false
  }
  const { typeAnnotation } = anno
fxy060608's avatar
fxy060608 已提交
295 296 297 298 299 300
  return (
    typeAnnotation.type === 'TsTypeReference' &&
    typeAnnotation.typeName.type === 'Identifier' &&
    typeAnnotation.typeName.value === 'Promise'
  )
}