uni_modules.ts 21.2 KB
Newer Older
1
// 重要:此文件编译后的js,需同步至 vue2 编译器中 uni-cli-shared/lib/uts/uni_modules.js
fxy060608's avatar
fxy060608 已提交
2 3
import path from 'path'
import fs from 'fs-extra'
fxy060608's avatar
fxy060608 已提交
4
import { sync } from 'fast-glob'
fxy060608's avatar
fxy060608 已提交
5
import type { UTSTargetLanguage } from './uts'
fxy060608's avatar
fxy060608 已提交
6
import { normalizePath } from './utils'
fxy060608's avatar
fxy060608 已提交
7
import { genUTSComponentPublicInstanceIdent } from './easycom'
fxy060608's avatar
fxy060608 已提交
8
import { M } from './messages'
fxy060608's avatar
fxy060608 已提交
9

fxy060608's avatar
fxy060608 已提交
10
export type DefineOptions = {
fxy060608's avatar
fxy060608 已提交
11 12 13 14 15 16 17 18 19 20 21
  name?: string
  app?:
    | boolean
    | {
        js?: boolean
        kotlin?: boolean
        swift?: boolean
      }
  [key: string]: any
}

fxy060608's avatar
fxy060608 已提交
22 23 24 25 26 27
export type Define =
  | string
  | string[]
  | Record<string, string | DefineOptions>
  | false
export type Defines = {
fxy060608's avatar
fxy060608 已提交
28 29 30
  [name: string]: Define
}

fxy060608's avatar
fxy060608 已提交
31
export interface Exports {
fxy060608's avatar
fxy060608 已提交
32 33 34
  [name: string]: Define | Defines | false
}

fxy060608's avatar
fxy060608 已提交
35 36 37 38 39 40
const extApiProviders: {
  plugin: string
  service: string
  name?: string
  servicePlugin?: string
}[] = []
41

42 43
const extApiPlugins = new Set<string>()

44 45 46 47
export function getUniExtApiProviders() {
  return extApiProviders
}

48 49 50 51 52 53
export function getUniExtApiPlugins() {
  return [...extApiPlugins].map((plugin) => {
    return { plugin }
  })
}

54 55 56 57 58 59 60 61 62
export function getUniExtApiProviderRegisters() {
  const result: { name: string; service: string; class: string }[] = []
  extApiProviders.forEach((provider) => {
    if (provider.name && provider.service) {
      result.push({
        name: provider.name,
        service: provider.service,
        class: `uts.sdk.modules.${camelize(provider.plugin)}.${capitalize(
          camelize(
fxy060608's avatar
fxy060608 已提交
63
            'uni-' + provider.service + '-' + provider.name + '-provider'
64 65 66 67 68 69 70 71
          )
        )}`,
      })
    }
  })
  return result
}

fxy060608's avatar
fxy060608 已提交
72 73
export function parseUniExtApis(
  vite = true,
fxy060608's avatar
fxy060608 已提交
74
  platform: typeof process.env.UNI_UTS_PLATFORM,
fxy060608's avatar
fxy060608 已提交
75
  language: UTSTargetLanguage = 'javascript'
76
) {
fxy060608's avatar
fxy060608 已提交
77 78 79
  if (!process.env.UNI_INPUT_DIR) {
    return {}
  }
fxy060608's avatar
fxy060608 已提交
80 81
  const uniModulesDir = path.resolve(process.env.UNI_INPUT_DIR, 'uni_modules')
  if (!fs.existsSync(uniModulesDir)) {
fxy060608's avatar
fxy060608 已提交
82
    return {}
fxy060608's avatar
fxy060608 已提交
83
  }
fxy060608's avatar
fxy060608 已提交
84

fxy060608's avatar
fxy060608 已提交
85
  const injects: Injects = {}
86
  extApiProviders.length = 0
87
  extApiPlugins.clear()
fxy060608's avatar
fxy060608 已提交
88
  fs.readdirSync(uniModulesDir).forEach((uniModuleDir) => {
fxy060608's avatar
fxy060608 已提交
89 90 91 92
    // 必须以 uni- 开头
    if (!uniModuleDir.startsWith('uni-')) {
      return
    }
fxy060608's avatar
fxy060608 已提交
93 94
    const uniModuleRootDir = path.resolve(uniModulesDir, uniModuleDir)
    const pkgPath = path.resolve(uniModuleRootDir, 'package.json')
fxy060608's avatar
fxy060608 已提交
95 96 97
    if (!fs.existsSync(pkgPath)) {
      return
    }
fxy060608's avatar
fxy060608 已提交
98
    try {
D
DCloud_LXH 已提交
99 100 101 102 103
      let exports: Exports | undefined
      const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
      if (pkg && pkg.uni_modules && pkg.uni_modules['uni-ext-api']) {
        exports = pkg.uni_modules['uni-ext-api']
      }
fxy060608's avatar
fxy060608 已提交
104
      if (exports) {
105 106 107 108 109
        const provider = exports.provider as any
        if (provider && provider.service) {
          provider.plugin = uniModuleDir
          extApiProviders.push(provider)
        }
110
        extApiPlugins.add(uniModuleDir)
fxy060608's avatar
fxy060608 已提交
111 112 113
        const curInjects = parseInjects(
          vite,
          platform,
fxy060608's avatar
fxy060608 已提交
114
          language,
fxy060608's avatar
fxy060608 已提交
115
          `@/uni_modules/${uniModuleDir}`,
fxy060608's avatar
fxy060608 已提交
116
          uniModuleRootDir,
fxy060608's avatar
fxy060608 已提交
117
          exports
fxy060608's avatar
fxy060608 已提交
118
        )
fxy060608's avatar
fxy060608 已提交
119
        Object.assign(injects, curInjects)
fxy060608's avatar
fxy060608 已提交
120 121
      }
    } catch (e) {}
fxy060608's avatar
fxy060608 已提交
122
  })
fxy060608's avatar
fxy060608 已提交
123
  return injects
fxy060608's avatar
fxy060608 已提交
124 125
}

fxy060608's avatar
fxy060608 已提交
126
type Inject = string | string[]
127
export type Injects = {
fxy060608's avatar
fxy060608 已提交
128 129 130 131 132
  [name: string]:
    | string
    | [string, string]
    | [string, string, DefineOptions['app']]
    | false
fxy060608's avatar
fxy060608 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
}
/**
 *  uni:'getBatteryInfo'
 * import getBatteryInfo from '..'
 *
 * uni:['getBatteryInfo']
 * import { getBatteryInfo } from '..'
 *
 * uni:['openLocation','chooseLocation']
 * import { openLocation, chooseLocation } from '..'
 *
 * uni:{
 *  onUserCaptureScreen: "onCaptureScreen"
 *  offUserCaptureScreen: "offCaptureScreen"
 * }
 *
 * uni.getBatteryInfo = getBatteryInfo
 * @param source
 * @param globalObject
 * @param define
 * @returns
 */
fxy060608's avatar
fxy060608 已提交
155
export function parseInjects(
fxy060608's avatar
fxy060608 已提交
156
  vite = true,
fxy060608's avatar
fxy060608 已提交
157 158
  platform: typeof process.env.UNI_UTS_PLATFORM,
  language: UTSTargetLanguage,
fxy060608's avatar
fxy060608 已提交
159
  source: string,
fxy060608's avatar
fxy060608 已提交
160
  uniModuleRootDir: string,
fxy060608's avatar
fxy060608 已提交
161 162
  exports: Exports = {}
) {
163 164 165
  if (platform === 'app-plus') {
    platform = 'app'
  }
fxy060608's avatar
fxy060608 已提交
166 167 168 169 170 171 172
  let rootDefines: Defines = {}
  Object.keys(exports).forEach((name) => {
    if (name.startsWith('uni')) {
      rootDefines[name] = exports[name] as Inject
    }
  })
  const injects: Injects = {}
fxy060608's avatar
fxy060608 已提交
173
  if (Object.keys(rootDefines).length) {
fxy060608's avatar
fxy060608 已提交
174 175 176 177 178 179 180 181 182 183
    const platformIndexFileName = path.resolve(
      uniModuleRootDir,
      'utssdk',
      platform
    )
    const rootIndexFileName = path.resolve(
      uniModuleRootDir,
      'utssdk',
      'index.uts'
    )
184
    let hasPlatformFile = uniModuleRootDir
fxy060608's avatar
fxy060608 已提交
185
      ? fs.existsSync(rootIndexFileName) || fs.existsSync(platformIndexFileName)
fxy060608's avatar
fxy060608 已提交
186
      : true
187 188 189 190 191 192
    if (!hasPlatformFile) {
      if (platform === 'app') {
        hasPlatformFile =
          fs.existsSync(
            path.resolve(uniModuleRootDir, 'utssdk', 'app-android')
          ) ||
193 194
          fs.existsSync(path.resolve(uniModuleRootDir, 'utssdk', 'app-ios')) ||
          fs.existsSync(path.resolve(uniModuleRootDir, 'utssdk', 'app-harmony'))
195 196
      }
    }
fxy060608's avatar
fxy060608 已提交
197
    // 其他平台修改source,直接指向目标文件,否则 uts2js 找不到类型信息
fxy060608's avatar
fxy060608 已提交
198 199 200
    if (
      platform !== 'app' &&
      platform !== 'app-android' &&
201 202
      platform !== 'app-ios' &&
      platform !== 'app-harmony'
fxy060608's avatar
fxy060608 已提交
203
    ) {
fxy060608's avatar
fxy060608 已提交
204 205 206 207 208
      if (fs.existsSync(platformIndexFileName)) {
        source = `${source}/utssdk/${platform}/index.uts`
      } else if (fs.existsSync(rootIndexFileName)) {
        source = `${source}/utssdk/index.uts`
      }
209 210 211 212 213 214 215 216
    } else if (process.env.UNI_APP_X_UVUE_SCRIPT_ENGINE === 'js') {
      if (
        fs.existsSync(
          path.resolve(uniModuleRootDir, 'utssdk', 'app-js', 'index.uts')
        )
      ) {
        source = `${source}/utssdk/app-js/index.uts`
      }
fxy060608's avatar
fxy060608 已提交
217
    }
fxy060608's avatar
fxy060608 已提交
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

    for (const key in rootDefines) {
      Object.assign(
        injects,
        parseInject(
          vite,
          platform,
          language,
          source,
          'uni',
          rootDefines[key],
          hasPlatformFile
        )
      )
    }
fxy060608's avatar
fxy060608 已提交
233 234 235 236
  }
  return injects
}

fxy060608's avatar
fxy060608 已提交
237
function parseInject(
fxy060608's avatar
fxy060608 已提交
238
  vite = true,
fxy060608's avatar
fxy060608 已提交
239 240
  platform: typeof process.env.UNI_UTS_PLATFORM,
  language: UTSTargetLanguage,
fxy060608's avatar
fxy060608 已提交
241 242
  source: string,
  globalObject: string,
fxy060608's avatar
fxy060608 已提交
243 244
  define: Define,
  hasPlatformFile: boolean
fxy060608's avatar
fxy060608 已提交
245 246 247 248 249
) {
  const injects: Injects = {}
  if (define === false) {
  } else if (typeof define === 'string') {
    // {'uni.getBatteryInfo' : '@dcloudio/uni-getbatteryinfo'}
fxy060608's avatar
fxy060608 已提交
250 251 252
    if (hasPlatformFile) {
      injects[globalObject + '.' + define] = vite ? source : [source, 'default']
    }
fxy060608's avatar
fxy060608 已提交
253
  } else if (Array.isArray(define)) {
fxy060608's avatar
fxy060608 已提交
254
    // {'uni.getBatteryInfo' : ['@dcloudio/uni-getbatteryinfo','getBatteryInfo]}
fxy060608's avatar
fxy060608 已提交
255 256 257 258 259
    if (hasPlatformFile) {
      define.forEach((d) => {
        injects[globalObject + '.' + d] = [source, d]
      })
    }
fxy060608's avatar
fxy060608 已提交
260
  } else {
fxy060608's avatar
fxy060608 已提交
261 262
    const keys = Object.keys(define)
    keys.forEach((d) => {
fxy060608's avatar
fxy060608 已提交
263
      if (typeof define[d] === 'string') {
fxy060608's avatar
fxy060608 已提交
264 265 266
        if (hasPlatformFile) {
          injects[globalObject + '.' + d] = [source, define[d] as string]
        }
fxy060608's avatar
fxy060608 已提交
267 268
      } else {
        const defineOptions = define[d] as DefineOptions
fxy060608's avatar
fxy060608 已提交
269
        const p =
270 271 272
          platform === 'app-android' ||
          platform === 'app-ios' ||
          platform === 'app-harmony'
fxy060608's avatar
fxy060608 已提交
273 274 275 276
            ? 'app'
            : platform
        if (!(p in defineOptions)) {
          if (hasPlatformFile) {
fxy060608's avatar
fxy060608 已提交
277 278
            injects[globalObject + '.' + d] = [source, defineOptions.name || d]
          }
fxy060608's avatar
fxy060608 已提交
279 280 281 282 283
        } else {
          if (defineOptions[p] !== false) {
            if (p === 'app') {
              const appOptions = defineOptions.app
              if (isPlainObject(appOptions)) {
284 285 286 287 288
                // js engine 下且存在 app-js,不检查
                const skipCheck =
                  process.env.UNI_APP_X_UVUE_SCRIPT_ENGINE === 'js' &&
                  source.includes('app-js')
                if (!skipCheck) {
fxy060608's avatar
fxy060608 已提交
289 290 291 292
                  const targetLanguage =
                    language === 'javascript' ? 'js' : language
                  if (targetLanguage && appOptions[targetLanguage] === false) {
                    return
fxy060608's avatar
fxy060608 已提交
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
                  }
                }
              }
              injects[globalObject + '.' + d] = [
                source,
                defineOptions.name || d,
                defineOptions.app,
              ]
            } else {
              injects[globalObject + '.' + d] = [
                source,
                defineOptions.name || d,
              ]
            }
          }
fxy060608's avatar
fxy060608 已提交
308 309
        }
      }
fxy060608's avatar
fxy060608 已提交
310 311
    })
  }
fxy060608's avatar
fxy060608 已提交
312
  return injects
fxy060608's avatar
fxy060608 已提交
313
}
fxy060608's avatar
fxy060608 已提交
314 315 316 317 318 319 320

const objectToString = Object.prototype.toString
const toTypeString = (value: unknown): string => objectToString.call(value)

function isPlainObject(val: unknown): val is object {
  return toTypeString(val) === '[object Object]'
}
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343

const cacheStringFunction = <T extends (str: string) => string>(fn: T): T => {
  const cache: Record<string, string> = Object.create(null)
  return ((str: string) => {
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }) as T
}

const camelizeRE = /-(\w)/g
/**
 * @private
 */
export const camelize = cacheStringFunction((str: string): string => {
  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
})

/**
 * @private
 */
export const capitalize = cacheStringFunction(
  (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
)
fxy060608's avatar
fxy060608 已提交
344 345 346 347 348 349 350 351 352 353 354 355 356

/**
 * 解析 UTS 类型的模块依赖列表
 * @param deps
 * @param inputDir
 * @returns
 */
export function parseUTSModuleDeps(deps: string[], inputDir: string): string[] {
  const modulesDir = path.resolve(inputDir, 'uni_modules')
  return deps.filter((dep) => {
    return fs.existsSync(path.resolve(modulesDir, dep, 'utssdk'))
  })
}
fxy060608's avatar
fxy060608 已提交
357

fxy060608's avatar
fxy060608 已提交
358
export function genEncryptEasyComModuleIndex(
fxy060608's avatar
fxy060608 已提交
359
  platform: typeof process.env.UNI_UTS_PLATFORM,
fxy060608's avatar
fxy060608 已提交
360 361
  components: Record<string, '.vue' | '.uvue'>
) {
fxy060608's avatar
fxy060608 已提交
362 363
  const imports: string[] = []
  const ids: string[] = []
fxy060608's avatar
fxy060608 已提交
364
  Object.keys(components).forEach((component) => {
fxy060608's avatar
fxy060608 已提交
365
    const id = capitalize(camelize(component))
fxy060608's avatar
fxy060608 已提交
366

fxy060608's avatar
fxy060608 已提交
367 368 369 370 371
    ids.push(id)
    if (platform === 'app-android') {
      // 类型
      ids.push(genUTSComponentPublicInstanceIdent(component))
    }
fxy060608's avatar
fxy060608 已提交
372
    imports.push(
fxy060608's avatar
fxy060608 已提交
373
      `import ${id} from './components/${component}/${component}${components[component]}'`
fxy060608's avatar
fxy060608 已提交
374 375 376 377
    )
  })
  return `
${imports.join('\n')}
fxy060608's avatar
fxy060608 已提交
378 379 380
export { 
  ${ids.join(',\n  ')} 
}
fxy060608's avatar
fxy060608 已提交
381 382 383
`
}

fxy060608's avatar
fxy060608 已提交
384 385
// 目前该函数仅在云端使用(目前仅限iOS/web),云端编译时,提交上来的uni_modules是过滤好的
export function parseUniModulesWithComponents(inputDir: string) {
fxy060608's avatar
fxy060608 已提交
386
  const modulesDir = path.resolve(inputDir, 'uni_modules')
fxy060608's avatar
fxy060608 已提交
387
  const uniModules: Record<string, Record<string, '.vue' | '.uvue'>> = {}
fxy060608's avatar
fxy060608 已提交
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
  if (fs.existsSync(modulesDir)) {
    fs.readdirSync(modulesDir).forEach((uniModuleDir) => {
      if (
        !fs.existsSync(path.resolve(modulesDir, uniModuleDir, 'package.json'))
      ) {
        return
      }
      // 解析加密的 easyCom 插件列表
      const components = parseEasyComComponents(uniModuleDir, inputDir, false)
      uniModules[uniModuleDir] = components
    })
  }
  return uniModules
}

fxy060608's avatar
fxy060608 已提交
403
/**
fxy060608's avatar
fxy060608 已提交
404
 * 解析 easyCom 组件列表
fxy060608's avatar
fxy060608 已提交
405 406 407 408
 * @param pluginId
 * @param inputDir
 * @returns
 */
fxy060608's avatar
fxy060608 已提交
409
export function parseEasyComComponents(
fxy060608's avatar
fxy060608 已提交
410 411 412 413 414 415 416 417 418 419
  pluginId: string,
  inputDir: string,
  detectBinary = true
) {
  const componentsDir = path.resolve(
    inputDir,
    'uni_modules',
    pluginId,
    'components'
  )
fxy060608's avatar
fxy060608 已提交
420
  const components: Record<string, '.vue' | '.uvue'> = {}
fxy060608's avatar
fxy060608 已提交
421 422 423 424 425 426 427
  if (fs.existsSync(componentsDir)) {
    fs.readdirSync(componentsDir).forEach((componentDir) => {
      const componentFile = path.resolve(
        componentsDir,
        componentDir,
        componentDir
      )
fxy060608's avatar
fxy060608 已提交
428 429 430 431 432 433 434
      const extname = ['.vue', '.uvue'].find((extname) => {
        const filename = componentFile + extname
        // 探测 filename 是否是二进制文件
        if (fs.existsSync(filename)) {
          if (detectBinary) {
            // 延迟require,这个是新增的依赖,无法及时同步到内部测试版本HBuilderX中,导致报错,所以延迟require吧
            if (require('isbinaryfile').isBinaryFileSync(filename)) {
fxy060608's avatar
fxy060608 已提交
435 436
              return true
            }
fxy060608's avatar
fxy060608 已提交
437 438
          } else {
            return true
fxy060608's avatar
fxy060608 已提交
439
          }
fxy060608's avatar
fxy060608 已提交
440 441 442 443
        }
      })
      if (extname) {
        components[componentDir] = extname as '.vue' | '.uvue'
fxy060608's avatar
fxy060608 已提交
444 445 446 447 448
      }
    })
  }
  return components
}
fxy060608's avatar
fxy060608 已提交
449

fxy060608's avatar
fxy060608 已提交
450 451
// 查找所有普通加密插件 uni_modules
export function findEncryptUniModules(inputDir: string, cacheDir: string = '') {
fxy060608's avatar
fxy060608 已提交
452
  const modulesDir = path.resolve(inputDir, 'uni_modules')
fxy060608's avatar
fxy060608 已提交
453
  const uniModules: Record<string, EncryptPackageJson | undefined> = {}
fxy060608's avatar
fxy060608 已提交
454 455
  if (fs.existsSync(modulesDir)) {
    fs.readdirSync(modulesDir).forEach((uniModuleDir) => {
fxy060608's avatar
fxy060608 已提交
456
      const uniModuleRootDir = path.resolve(modulesDir, uniModuleDir)
fxy060608's avatar
fxy060608 已提交
457
      if (!fs.existsSync(path.resolve(uniModuleRootDir, 'encrypt'))) {
fxy060608's avatar
fxy060608 已提交
458 459
        return
      }
fxy060608's avatar
fxy060608 已提交
460
      // 仅扫描普通加密插件,无需依赖
fxy060608's avatar
fxy060608 已提交
461 462
      if (fs.existsSync(path.resolve(uniModuleRootDir, 'utssdk'))) {
        return
fxy060608's avatar
fxy060608 已提交
463
      }
fxy060608's avatar
fxy060608 已提交
464 465 466 467 468 469
      const pkg = require(path.resolve(uniModuleRootDir, 'package.json'))
      uniModules[uniModuleDir] = findEncryptUniModuleCache(
        uniModuleDir,
        cacheDir,
        { version: pkg.version, env: initCheckEnv() }
      )
fxy060608's avatar
fxy060608 已提交
470 471
    })
  }
fxy060608's avatar
fxy060608 已提交
472 473 474 475 476 477 478
  return uniModules
}

export function findUploadEncryptUniModulesFiles(
  uniModules: Record<string, EncryptPackageJson | undefined>,
  platform: typeof process.env.UNI_UTS_PLATFORM,
  inputDir: string
fxy060608's avatar
fxy060608 已提交
479 480
): Record<string, string[]> {
  const modules: Record<string, string[]> = {}
fxy060608's avatar
fxy060608 已提交
481 482
  Object.keys(uniModules).forEach((uniModuleId) => {
    if (!uniModules[uniModuleId]) {
fxy060608's avatar
fxy060608 已提交
483
      modules[uniModuleId] = findUniModuleFiles(platform, uniModuleId, inputDir)
fxy060608's avatar
fxy060608 已提交
484 485
    }
  })
fxy060608's avatar
fxy060608 已提交
486
  return modules
fxy060608's avatar
fxy060608 已提交
487
}
fxy060608's avatar
fxy060608 已提交
488

fxy060608's avatar
fxy060608 已提交
489
export function packUploadEncryptUniModules(
fxy060608's avatar
fxy060608 已提交
490 491 492
  uniModules: Record<string, EncryptPackageJson | undefined>,
  platform: typeof process.env.UNI_UTS_PLATFORM,
  inputDir: string,
fxy060608's avatar
fxy060608 已提交
493
  cacheDir: string
fxy060608's avatar
fxy060608 已提交
494
) {
fxy060608's avatar
fxy060608 已提交
495 496 497 498 499 500 501
  const modules = findUploadEncryptUniModulesFiles(
    uniModules,
    platform,
    inputDir
  )
  const uploadModuleIds = Object.keys(modules)
  if (uploadModuleIds.length) {
fxy060608's avatar
fxy060608 已提交
502 503 504
    // 延迟 require,避免 vue2 编译器需要安装此依赖,目前该方法仅在 vite 编译器中使用
    const AdmZip = require('adm-zip')
    const zip = new AdmZip()
fxy060608's avatar
fxy060608 已提交
505 506 507 508
    uploadModuleIds.forEach((moduleId) => {
      modules[moduleId].forEach((file) => {
        zip.addLocalFile(file, path.dirname(path.relative(inputDir, file)))
      })
fxy060608's avatar
fxy060608 已提交
509
    })
fxy060608's avatar
fxy060608 已提交
510
    const zipFile = path.resolve(cacheDir, 'cloud-compile-plugins.zip')
fxy060608's avatar
fxy060608 已提交
511
    zip.writeZip(zipFile)
fxy060608's avatar
fxy060608 已提交
512 513 514 515 516 517 518 519
    return {
      zipFile,
      modules: uploadModuleIds,
    }
  }
  return {
    zipFile: '',
    modules: [],
fxy060608's avatar
fxy060608 已提交
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
  }
}

function isEnvExpired(
  value: Record<string, unknown>,
  other: Record<string, unknown>
) {
  const valueKeys = Object.keys(value)
  const otherKeys = Object.keys(other)
  if (valueKeys.length !== otherKeys.length) {
    return true
  }
  if (valueKeys.find((name) => value[name] !== other[name])) {
    return true
  }
  return false
}

interface EncryptPackageJson {
  id: string
  version: string
  uni_modules: {
    artifacts: {
      env: {
        compilerVersion: string
      } & Record<string, any>
      apis: string[]
      components: string[]
fxy060608's avatar
fxy060608 已提交
548 549
      scopedSlots: string[]
      declaration: string
fxy060608's avatar
fxy060608 已提交
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
    }
  }
}

function findEncryptUniModuleCache(
  uniModuleId: string,
  cacheDir: string,
  options: {
    version: string
    env: Record<string, string>
  }
): EncryptPackageJson | undefined {
  if (!cacheDir) {
    return
  }
  const uniModuleCacheDir = path.resolve(cacheDir, 'uni_modules', uniModuleId)
  if (fs.existsSync(uniModuleCacheDir)) {
fxy060608's avatar
fxy060608 已提交
567 568 569 570
    const pkg = require(path.resolve(
      uniModuleCacheDir,
      'package.json'
    )) as EncryptPackageJson
fxy060608's avatar
fxy060608 已提交
571 572 573
    // 插件版本以及各种环境一致
    if (
      pkg.version === options.version &&
fxy060608's avatar
fxy060608 已提交
574
      !isEnvExpired(pkg.uni_modules?.artifacts?.env || {}, options.env)
fxy060608's avatar
fxy060608 已提交
575
    ) {
fxy060608's avatar
fxy060608 已提交
576 577
      const declaration = path.resolve(
        uniModuleCacheDir,
fxy060608's avatar
fxy060608 已提交
578
        'utssdk/app-android/index.d.uts'
fxy060608's avatar
fxy060608 已提交
579 580 581 582
      )
      pkg.uni_modules.artifacts.declaration = fs.existsSync(declaration)
        ? declaration
        : ''
fxy060608's avatar
fxy060608 已提交
583 584
      return pkg
    }
fxy060608's avatar
fxy060608 已提交
585
    console.log(`插件${uniModuleId} 缓存已过期,需要重新云编译。`)
fxy060608's avatar
fxy060608 已提交
586 587 588 589 590 591
    // 已过期的插件,删除缓存
    fs.rmSync(uniModuleCacheDir, { recursive: true })
  }
}

const KNOWN_ASSET_TYPES = [
fxy060608's avatar
fxy060608 已提交
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
  // images
  'png',
  'jpe?g',
  'gif',
  'svg',
  'ico',
  'webp',
  'avif',

  // media
  'mp4',
  'webm',
  'ogg',
  'mp3',
  'wav',
  'flac',
  'aac',

  // fonts
  'woff2?',
  'eot',
  'ttf',
  'otf',

  // other
  'pdf',
  'txt',
]

fxy060608's avatar
fxy060608 已提交
621
function findUniModuleFiles(
fxy060608's avatar
fxy060608 已提交
622 623 624
  platform: typeof process.env.UNI_UTS_PLATFORM,
  id: string,
  inputDir: string
fxy060608's avatar
fxy060608 已提交
625 626 627
) {
  return sync(`uni_modules/${id}/**/*`, {
    cwd: inputDir,
fxy060608's avatar
fxy060608 已提交
628
    absolute: true,
fxy060608's avatar
fxy060608 已提交
629 630 631
    ignore: [
      '**/*.md',
      ...(platform !== 'app-android' // 非 android 平台不需要扫描 assets
fxy060608's avatar
fxy060608 已提交
632
        ? [`**/*.{${KNOWN_ASSET_TYPES.join(',')}}`]
fxy060608's avatar
fxy060608 已提交
633 634
        : []),
    ],
fxy060608's avatar
fxy060608 已提交
635 636 637
  })
}

fxy060608's avatar
fxy060608 已提交
638 639 640 641 642
export function initCheckEnv(): Record<string, string> {
  return {
    // 云端编译的版本号不带日期及小版本
    compilerVersion: process.env.UNI_COMPILER_VERSION,
  }
fxy060608's avatar
fxy060608 已提交
643
}
fxy060608's avatar
fxy060608 已提交
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658

function findLastIndex<T>(
  array: Array<T>,
  predicate: (value: T, index: number, array: T[]) => unknown
) {
  for (let i = array.length - 1; i >= 0; i--) {
    if (predicate(array[i], i, array)) {
      return i
    }
  }
  return -1
}

let encryptUniModules: ReturnType<typeof findEncryptUniModules> = {}

fxy060608's avatar
fxy060608 已提交
659 660 661 662 663
export function resolveEncryptUniModule(
  id: string,
  platform: typeof process.env.UNI_UTS_PLATFORM,
  isX: boolean = true
) {
fxy060608's avatar
fxy060608 已提交
664 665 666 667 668
  const parts = id.split('/')
  const index = findLastIndex(parts, (part) => part === 'uni_modules')
  if (index !== -1) {
    const uniModuleId = parts[index + 1]
    if (uniModuleId in encryptUniModules) {
fxy060608's avatar
fxy060608 已提交
669 670 671 672 673 674 675 676
      if (parts[index + 2]) {
        console.warn(
          M['uni_modules.import']
            .replace('{0}', uniModuleId)
            .replace('{1}', uniModuleId)
            .replace('{2}', parts.slice(index + 2).join('/'))
        )
      }
fxy060608's avatar
fxy060608 已提交
677
      // 原生平台走旧的uts-proxy
fxy060608's avatar
fxy060608 已提交
678 679 680 681 682 683 684 685
      return normalizePath(
        path.join(
          process.env.UNI_INPUT_DIR,
          `uni_modules/${uniModuleId}?${
            isX && platform === 'app-android' ? 'uts-proxy' : 'uni_helpers'
          }`
        )
      )
fxy060608's avatar
fxy060608 已提交
686 687 688 689 690 691 692 693
    }
  }
}

export async function checkEncryptUniModules(
  inputDir: string,
  params: {
    mode: 'development' | 'production'
fxy060608's avatar
fxy060608 已提交
694
    packType: 'debug' | 'release'
fxy060608's avatar
fxy060608 已提交
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
    compilerVersion: string // hxVersion
    appid: string
    appname: string
    platform: typeof process.env.UNI_UTS_PLATFORM // app-android | app-ios | web
    'uni-app-x': boolean
  }
) {
  // 扫描加密插件云编译
  encryptUniModules = findEncryptUniModules(
    inputDir,
    process.env.UNI_MODULES_ENCRYPT_CACHE_DIR
  )
  if (!Object.keys(encryptUniModules).length) {
    return {}
  }
  if (!process.env.UNI_HBUILDERX_PLUGINS) {
    return {}
  }

  const cacheDir = process.env.UNI_MODULES_ENCRYPT_CACHE_DIR!
fxy060608's avatar
fxy060608 已提交
715
  const { zipFile, modules } = packUploadEncryptUniModules(
fxy060608's avatar
fxy060608 已提交
716 717 718 719 720 721 722
    encryptUniModules,
    process.env.UNI_UTS_PLATFORM,
    inputDir,
    cacheDir
  )
  if (zipFile) {
    const downloadFile = path.resolve(cacheDir, 'uni_modules.download.zip')
fxy060608's avatar
fxy060608 已提交
723
    const { C, D, R, U } = require(path.join(
fxy060608's avatar
fxy060608 已提交
724 725 726 727
      process.env.UNI_HBUILDERX_PLUGINS,
      'uni_helpers'
    ))
    try {
fxy060608's avatar
fxy060608 已提交
728 729 730 731 732 733
      const isLogin = await C()
      console.log(
        `正在云编译插件${isLogin ? '' : '(请先登录)'}${modules.join(
          ','
        )}...`
      )
fxy060608's avatar
fxy060608 已提交
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
      let downloadUrl = ''
      try {
        downloadUrl = await U({
          params,
          attachment: zipFile,
        })
      } catch (e: any) {
        if (e.message && e.message === '{"error":"UserNotLogin"}') {
          console.log(
            '当前项目包含需要云编译的付费插件,需要您先登录HBuilderX账号。'
          )
        } else {
          console.error(e)
        }
        process.exit(0)
      }
fxy060608's avatar
fxy060608 已提交
750 751 752 753 754 755 756
      await D(downloadUrl, downloadFile)
      // unzip
      const AdmZip = require('adm-zip')
      const zip = new AdmZip(downloadFile)
      zip.extractAllTo(cacheDir, true)
      fs.unlinkSync(zipFile)
      fs.unlinkSync(downloadFile)
fxy060608's avatar
fxy060608 已提交
757 758 759 760
      R({
        dir: process.env.UNI_INPUT_DIR,
        cacheDir: process.env.UNI_MODULES_ENCRYPT_CACHE_DIR,
      })
fxy060608's avatar
fxy060608 已提交
761
      console.log(`云编译已完成`)
fxy060608's avatar
fxy060608 已提交
762
      console.log(`正在编译中...`)
fxy060608's avatar
fxy060608 已提交
763 764 765 766 767 768
    } catch (e) {
      fs.existsSync(zipFile) && fs.unlinkSync(zipFile)
      fs.existsSync(downloadFile) && fs.unlinkSync(downloadFile)
      console.error(e)
      process.exit(0)
    }
fxy060608's avatar
fxy060608 已提交
769 770 771 772 773 774 775 776 777 778 779 780
  } else {
    // android 平台需要在这里初始化
    if (params.platform === 'app-android') {
      const { R } = require(path.join(
        process.env.UNI_HBUILDERX_PLUGINS,
        'uni_helpers'
      ))
      R({
        dir: process.env.UNI_INPUT_DIR,
        cacheDir: process.env.UNI_MODULES_ENCRYPT_CACHE_DIR,
      })
    }
fxy060608's avatar
fxy060608 已提交
781 782 783 784 785 786
  }
  encryptUniModules = findEncryptUniModules(
    inputDir,
    process.env.UNI_MODULES_ENCRYPT_CACHE_DIR
  )
}
fxy060608's avatar
fxy060608 已提交
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807

export function parseUniModulesArtifacts() {
  const res: {
    name: string
    package: string
    scopedSlots: string[]
    declaration: string
  }[] = []
  Object.keys(encryptUniModules).forEach((uniModuleId) => {
    const pkg = encryptUniModules[uniModuleId]
    if (pkg?.uni_modules?.artifacts) {
      res.push({
        name: uniModuleId,
        package: `uts.sdk.modules.${camelize(uniModuleId)}`,
        scopedSlots: pkg.uni_modules.artifacts.scopedSlots || [],
        declaration: pkg.uni_modules.artifacts.declaration,
      })
    }
  })
  return res
}