index.ts 7.7 KB
Newer Older
fxy060608's avatar
fxy060608 已提交
1
import path from 'path'
fxy060608's avatar
fxy060608 已提交
2
import fs from 'fs-extra'
fxy060608's avatar
fxy060608 已提交
3
import glob from 'fast-glob'
fxy060608's avatar
fxy060608 已提交
4

fxy060608's avatar
fxy060608 已提交
5
import chokidar from 'chokidar'
fxy060608's avatar
fxy060608 已提交
6

fxy060608's avatar
fxy060608 已提交
7
import { toKotlin, toSwift } from './api'
fxy060608's avatar
fxy060608 已提交
8
import type {
fxy060608's avatar
fxy060608 已提交
9 10 11
  UtsInputOptions,
  UtsOptions,
  UtsOutputOptions,
fxy060608's avatar
fxy060608 已提交
12
  UtsResult,
fxy060608's avatar
fxy060608 已提交
13
} from './types'
fxy060608's avatar
fxy060608 已提交
14 15 16 17 18 19 20
import {
  printDone,
  printStartup,
  printUtsResult,
  printUtsResults,
  timeEnd,
} from './utils'
fxy060608's avatar
fxy060608 已提交
21

fxy060608's avatar
fxy060608 已提交
22
export enum UtsTarget {
fxy060608's avatar
fxy060608 已提交
23 24 25 26 27 28
  KOTLIN = 'kotlin',
  SWIFT = 'swift',
}

export type UtsMode = 'dev' | 'build'

fxy060608's avatar
fxy060608 已提交
29
const UtsTargetDirs = {
fxy060608's avatar
fxy060608 已提交
30 31 32
  [UtsTarget.KOTLIN]: 'android',
  [UtsTarget.SWIFT]: 'ios',
} as const
fxy060608's avatar
fxy060608 已提交
33 34 35 36 37

export const UtsTargetExtNames = {
  [UtsTarget.KOTLIN]: 'kt',
  [UtsTarget.SWIFT]: 'swift',
} as const
fxy060608's avatar
fxy060608 已提交
38
export interface ToOptions {
fxy060608's avatar
fxy060608 已提交
39 40 41 42
  /**
   * 为 true 时,禁用日志输出,默认为 false
   */
  silent?: boolean
fxy060608's avatar
fxy060608 已提交
43
  input: {
fxy060608's avatar
fxy060608 已提交
44 45 46
    /**
     * 插件根目录
     */
fxy060608's avatar
fxy060608 已提交
47
    dir: string
fxy060608's avatar
fxy060608 已提交
48 49 50
    /**
     * 文件后缀,默认 .uts
     */
fxy060608's avatar
fxy060608 已提交
51 52 53
    extname?: string
  }
  output: {
fxy060608's avatar
fxy060608 已提交
54 55 56
    /**
     * 输出目录
     */
fxy060608's avatar
fxy060608 已提交
57
    dir: string
fxy060608's avatar
fxy060608 已提交
58 59 60
    /**
     * 是否生成 sourceMap,为 string 时,表示生成的 sourceMap 目标目录
     */
fxy060608's avatar
fxy060608 已提交
61
    sourceMap: boolean | string
fxy060608's avatar
fxy060608 已提交
62 63 64
    /**
     * sourceMap 中是否包含源码
     */
fxy060608's avatar
fxy060608 已提交
65
    inlineSourcesContent?: boolean
fxy060608's avatar
fxy060608 已提交
66
    extname: string
fxy060608's avatar
fxy060608 已提交
67 68 69
  }
}

fxy060608's avatar
fxy060608 已提交
70 71
function resolveDefaultOutputDir(mode: UtsMode, inputDir: string) {
  return path.resolve(inputDir, '../dist/' + mode)
fxy060608's avatar
fxy060608 已提交
72
}
fxy060608's avatar
fxy060608 已提交
73 74 75
function parseOptions(
  mode: UtsMode,
  target: UtsTarget,
fxy060608's avatar
fxy060608 已提交
76 77
  opts: Partial<ToOptions>
): ToOptions {
fxy060608's avatar
fxy060608 已提交
78 79
  const { input } = opts
  if (!input?.dir) {
fxy060608's avatar
fxy060608 已提交
80
    throw new Error(`input.dir is required.`)
fxy060608's avatar
fxy060608 已提交
81 82
  }
  if (!fs.existsSync(input.dir)) {
fxy060608's avatar
fxy060608 已提交
83 84
    throw new Error(`${input} is not found.`)
  }
fxy060608's avatar
fxy060608 已提交
85 86

  const inputSrcDir: string = resolveSrcDir(target, input.dir)
fxy060608's avatar
fxy060608 已提交
87 88 89 90 91

  if (!fs.existsSync(inputSrcDir)) {
    throw new Error(`${inputSrcDir} is not found.`)
  }

fxy060608's avatar
fxy060608 已提交
92 93 94 95
  if (!opts.output) {
    opts.output = {
      dir: '',
      sourceMap: '',
fxy060608's avatar
fxy060608 已提交
96
      extname: UtsTargetExtNames[target],
fxy060608's avatar
fxy060608 已提交
97 98 99 100 101
    }
  }
  if (!opts.output.dir) {
    opts.output.dir = resolveDefaultOutputDir(mode, input.dir)
  }
fxy060608's avatar
fxy060608 已提交
102
  opts.silent = opts.silent === true
fxy060608's avatar
fxy060608 已提交
103
  return opts as ToOptions
fxy060608's avatar
fxy060608 已提交
104 105 106
}

const EXTNAME = '.uts'
fxy060608's avatar
fxy060608 已提交
107

fxy060608's avatar
fxy060608 已提交
108 109
function resolveSrcDir(target: UtsTarget, dir: string) {
  return path.join(dir, UtsTargetDirs[target] + '/src')
fxy060608's avatar
fxy060608 已提交
110 111
}

fxy060608's avatar
fxy060608 已提交
112
function initInputOptions(_: UtsTarget, root: string): UtsInputOptions {
fxy060608's avatar
fxy060608 已提交
113 114 115 116 117 118
  return {
    root,
    filename: '',
  }
}

fxy060608's avatar
fxy060608 已提交
119 120
function initOutputOptions(
  target: UtsTarget,
fxy060608's avatar
fxy060608 已提交
121 122 123
  outDir: string,
  sourceMap: string | boolean,
  inlineSourcesContent: boolean
fxy060608's avatar
fxy060608 已提交
124
): UtsOutputOptions {
fxy060608's avatar
fxy060608 已提交
125 126 127 128
  return {
    outDir,
    sourceMap,
    inlineSourcesContent,
fxy060608's avatar
fxy060608 已提交
129
    extname: UtsTargetExtNames[target],
fxy060608's avatar
fxy060608 已提交
130 131
  }
}
fxy060608's avatar
fxy060608 已提交
132

fxy060608's avatar
fxy060608 已提交
133
function initOptions(
fxy060608's avatar
fxy060608 已提交
134 135
  target: UtsTarget,
  {
fxy060608's avatar
fxy060608 已提交
136
    input: { dir: inputDir },
fxy060608's avatar
fxy060608 已提交
137 138 139
    output: { dir: outputDir, sourceMap, inlineSourcesContent },
  }: ToOptions
) {
fxy060608's avatar
fxy060608 已提交
140 141
  const inputSrcDir = resolveSrcDir(target, inputDir)
  const outputSrcDir = resolveSrcDir(target, outputDir)
fxy060608's avatar
fxy060608 已提交
142

fxy060608's avatar
fxy060608 已提交
143
  const input = initInputOptions(target, inputSrcDir)
fxy060608's avatar
fxy060608 已提交
144 145
  const output = initOutputOptions(
    target,
fxy060608's avatar
fxy060608 已提交
146 147 148 149
    outputSrcDir,
    sourceMap,
    !!inlineSourcesContent
  )
fxy060608's avatar
fxy060608 已提交
150 151
  return { input, output }
}
fxy060608's avatar
fxy060608 已提交
152

fxy060608's avatar
fxy060608 已提交
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
async function watch(target: UtsTarget, toOptions: ToOptions) {
  fs.emptyDirSync(toOptions.output.dir)

  const { input, output } = initOptions(target, toOptions)
  const inputDir = toOptions.input.dir
  const outputDir = toOptions.output.dir
  const inputSrcDir = input.root
  const outputSrcDir = output.outDir
  const extname = toOptions.input.extname || EXTNAME
  const silent = !!toOptions.silent
  // 先完整编译后,再启用监听
  doBuild(target, {
    watch: true,
    input,
    output,
    inputDir,
    outputDir,
    inputSrcDir,
    outputSrcDir,
    extname,
    silent,
  }).then(() => {
    // TODO 监听动态添加的资源文件
    chokidar
      .watch('**/*' + extname, {
        cwd: inputSrcDir,
        ignored: ['**/*.d' + extname],
        ignoreInitial: true,
fxy060608's avatar
fxy060608 已提交
181
      })
fxy060608's avatar
fxy060608 已提交
182 183 184 185 186 187 188 189 190 191 192 193
      .on('add', (filename) => {
        buildFile(
          target,
          path.resolve(inputSrcDir, filename),
          input,
          output
        ).then((res) => {
          if (!silent) {
            printUtsResult(res)
            printDone(true)
          }
        })
fxy060608's avatar
fxy060608 已提交
194
      })
fxy060608's avatar
fxy060608 已提交
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
      .on('change', (filename) => {
        buildFile(
          target,
          path.resolve(inputSrcDir, filename),
          input,
          output
        ).then((res) => {
          if (!silent) {
            printUtsResult(res)
            printDone(true)
          }
        })
      })
      .on('unlink', (filename) => {
        try {
          fs.unlinkSync(path.resolve(outputSrcDir, filename))
        } catch (e) {}
      })
  })
}

interface DoBuildOptions {
  watch: boolean
  silent: boolean
  input: UtsInputOptions
  output: UtsOutputOptions
  inputDir: string
  inputSrcDir: string
  outputDir: string
  outputSrcDir: string
  extname: string
fxy060608's avatar
fxy060608 已提交
226
}
fxy060608's avatar
fxy060608 已提交
227

fxy060608's avatar
fxy060608 已提交
228
function doBuild(
fxy060608's avatar
fxy060608 已提交
229 230
  target: UtsTarget,
  {
fxy060608's avatar
fxy060608 已提交
231
    watch,
fxy060608's avatar
fxy060608 已提交
232
    silent,
fxy060608's avatar
fxy060608 已提交
233 234 235 236 237 238 239
    extname,
    inputDir,
    inputSrcDir,
    outputDir,
    input,
    output,
  }: DoBuildOptions
fxy060608's avatar
fxy060608 已提交
240
) {
fxy060608's avatar
fxy060608 已提交
241
  const files = glob.sync('**/*' + extname, {
fxy060608's avatar
fxy060608 已提交
242
    absolute: true,
fxy060608's avatar
fxy060608 已提交
243
    cwd: inputSrcDir,
fxy060608's avatar
fxy060608 已提交
244
    ignore: ['**/*.d' + extname],
fxy060608's avatar
fxy060608 已提交
245
  })
fxy060608's avatar
fxy060608 已提交
246

fxy060608's avatar
fxy060608 已提交
247
  return Promise.all(
fxy060608's avatar
fxy060608 已提交
248
    files.map((filename) =>
fxy060608's avatar
fxy060608 已提交
249
      buildFile(target, filename, input, output).catch((error) => {
fxy060608's avatar
fxy060608 已提交
250 251 252 253 254
        return {
          error,
        } as UtsResult
      })
    )
fxy060608's avatar
fxy060608 已提交
255 256 257 258 259 260 261
  )
    .then((res) => {
      return copyAssets(UtsTarget.KOTLIN, inputDir, outputDir, extname!).then(
        () => res
      )
    })
    .then((res) => {
fxy060608's avatar
fxy060608 已提交
262
      !silent && printUtsResults(res, watch)
fxy060608's avatar
fxy060608 已提交
263 264
      return res
    })
fxy060608's avatar
fxy060608 已提交
265 266
}

fxy060608's avatar
fxy060608 已提交
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
function build(target: UtsTarget, toOptions: ToOptions) {
  fs.emptyDirSync(toOptions.output.dir)
  const { input, output } = initOptions(target, toOptions)
  const inputDir = toOptions.input.dir
  const outputDir = toOptions.output.dir
  const inputSrcDir = input.root
  const outputSrcDir = output.outDir
  const extname = toOptions.input.extname || EXTNAME
  const silent = !!toOptions.silent
  return doBuild(target, {
    watch: false,
    input,
    output,
    inputDir,
    outputDir,
    inputSrcDir,
    outputSrcDir,
    extname,
    silent,
  })
}

fxy060608's avatar
fxy060608 已提交
289 290 291 292 293 294 295 296
function copyAssets(
  target: UtsTarget,
  inputDir: string,
  outputDir: string,
  extname: string
) {
  inputDir = path.resolve(inputDir)
  outputDir = path.resolve(outputDir)
fxy060608's avatar
fxy060608 已提交
297 298
  const kotlinRootDir = path.join(inputDir, UtsTargetDirs[UtsTarget.KOTLIN])
  const swiftRootDir = path.join(inputDir, UtsTargetDirs[UtsTarget.SWIFT])
fxy060608's avatar
fxy060608 已提交
299 300
  return fs.copy(inputDir, outputDir, {
    filter(src) {
fxy060608's avatar
fxy060608 已提交
301 302 303 304 305 306 307 308 309
      if (target === UtsTarget.KOTLIN) {
        if (src === swiftRootDir) {
          return false
        }
      } else if (target === UtsTarget.SWIFT) {
        if (src === kotlinRootDir) {
          return false
        }
      }
fxy060608's avatar
fxy060608 已提交
310 311 312 313 314 315
      if (path.basename(src).startsWith('.')) {
        return false
      }
      if (fs.lstatSync(src).isDirectory()) {
        return false
      }
fxy060608's avatar
fxy060608 已提交
316
      return ![extname, '.ts'].includes(path.extname(src))
fxy060608's avatar
fxy060608 已提交
317 318
    },
  })
fxy060608's avatar
fxy060608 已提交
319 320
}

fxy060608's avatar
fxy060608 已提交
321 322
function buildFile(
  target: UtsTarget,
fxy060608's avatar
fxy060608 已提交
323
  filename: string,
fxy060608's avatar
fxy060608 已提交
324 325
  input: UtsInputOptions,
  output: UtsOutputOptions
fxy060608's avatar
fxy060608 已提交
326
) {
fxy060608's avatar
fxy060608 已提交
327
  const toOptions: UtsOptions = {
fxy060608's avatar
fxy060608 已提交
328 329 330 331 332 333 334 335
    input: {
      ...input,
      filename,
      namespace: '',
    },
    output: {
      ...output,
    },
fxy060608's avatar
fxy060608 已提交
336
  }
fxy060608's avatar
fxy060608 已提交
337
  const start = process.hrtime()
fxy060608's avatar
fxy060608 已提交
338 339 340
  return (
    target === UtsTarget.KOTLIN ? toKotlin(toOptions) : toSwift(toOptions)
  ).then((res) => {
fxy060608's avatar
fxy060608 已提交
341 342
    res.time = timeEnd(start)
    return res
fxy060608's avatar
fxy060608 已提交
343
  })
fxy060608's avatar
fxy060608 已提交
344 345
}

fxy060608's avatar
fxy060608 已提交
346 347
export { parse } from './api'

fxy060608's avatar
fxy060608 已提交
348 349 350
export function runDev(target: UtsTarget, opts: ToOptions) {
  opts = parseOptions('dev', target, opts)
  !opts.silent && printStartup(target, 'development')
fxy060608's avatar
fxy060608 已提交
351
  watch(target, opts)
fxy060608's avatar
fxy060608 已提交
352 353
}

fxy060608's avatar
fxy060608 已提交
354 355 356
export function runBuild(target: UtsTarget, opts: ToOptions) {
  opts = parseOptions('build', target, opts)
  !opts.silent && printStartup(target, 'production')
fxy060608's avatar
fxy060608 已提交
357
  build(target, opts)
fxy060608's avatar
fxy060608 已提交
358
}