Skip to content

Commit deca470

Browse files
authored
close #71, auto generate index.d.ts for apis (#72)
* feat(cli): #71, auto generate index.d.ts for apis * docs(cli): remove platform description * chore(mp): no need to build precommit * chore: add some publish scripts * perf(cli): fix global vars, add new config alias * perf(cli): fix ts declaration * perf(cli): add ReqFn ts declaration * feat(cli): close #71, release v0.4.0
1 parent 1a28dae commit deca470

File tree

12 files changed

+501
-7
lines changed

12 files changed

+501
-7
lines changed

packages/tua-mp-cli/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,31 @@ $ tuamp eject
108108
$ tuamp e
109109
```
110110

111+
### 生成接口声明命令 `declare` <Badge text="0.4.0+"/>
112+
这个命令将读取导出的 apis 然后自动生成 `index.d.ts`
113+
114+
```bash
115+
$ tuamp declare [apisPath]
116+
# OR
117+
$ tuamp d [apisPath]
118+
```
119+
120+
* `apisPath` 默认值为 `src/apis/index.js`
121+
* `index.d.ts` 将生成在 `apisPath` 同级目录下
122+
123+
由于使用 `require` 读取导出 apis 对象的文件,所以可能会碰到 `alias` 问题。例如项目中设置了 `@` 作为 `./src/` 的别名。虽然 `@tua-mp/cli` 已内置了一些 `alias`,但你仍然可以自由配置。
124+
125+
`tua.config.js` 中的 `alias` 选项会透传给 [babel-plugin-module-resolver](https://github.com/tleunen/babel-plugin-module-resolver),例如将 `foobar` 指向 './src/foobar' 可以这么配置:
126+
127+
```js
128+
// tua.config.js
129+
module.exports = {
130+
alias: {
131+
'foobar': './src/foobar',
132+
},
133+
}
134+
```
135+
111136
## 配置
112137
配置文件和 `@tua-mp/service` 一样,都是使用 `tua.config.js`
113138

@@ -117,6 +142,12 @@ $ tuamp e
117142

118143
自定义模板的路径。
119144

145+
### alias <Badge text="0.4.0+"/>
146+
* 类型:`Object`
147+
* 默认值:[请点击这里查看源码](https://github.com/tuateam/tua-mp/blob/master/packages/tua-mp-cli/lib/constants.js)
148+
149+
设置路径别名对象。
150+
120151
**读取模板的优先级逻辑是:**
121152

122153
1. 首先尝试使用 `tua.config.js` 中的 `templateDir` 字段
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const { readConfigFile } = require('../../lib/utils/')
2+
3+
exports.desc = 'generate api declaration file by [apisPath]'
4+
exports.aliases = 'd'
5+
exports.command = 'declare [apisPath]'
6+
7+
exports.builder = {}
8+
9+
exports.handler = (argv) => {
10+
argv.tuaConfig = readConfigFile()
11+
12+
return require('../../lib/declare')(argv)
13+
}

packages/tua-mp-cli/lib/addApi.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ module.exports = (options = {}) => {
3434

3535
// 小驼峰的名称
3636
const ccName = hyphenCaseToCamelCase(name)
37-
const outputStr = `小程序 api -> ${name}`
37+
const outputStr = ` api -> ${name}`
3838
const relativePath = 'src/apis'
3939
const treeLog = treeify.asTree({
4040
[relativePath]: {

packages/tua-mp-cli/lib/addPage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ module.exports = (options = {}) => {
3838
const hcName = camelCaseToHyphenCase(name)
3939
// 大驼峰的名称
4040
const uccName = hyphenCaseToUpperCamelCase(name)
41-
const outputStr = `小程序页面 -> ${hcName}`
41+
const outputStr = `页面 -> ${hcName}`
4242

4343
const pagesPath = 'src/pages'
4444
const appJsonPath = 'src/app/app.json'

packages/tua-mp-cli/lib/constants.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
const defaultTuaConfig = {
22
templateDir: '.templates',
3+
alias: {
4+
'@': './src',
5+
'@comps': './src/components',
6+
'@const': './src/scripts/constants',
7+
'@utils': './src/scripts/utils',
8+
'@styles': './src/styles',
9+
},
310
}
411

512
module.exports = {

packages/tua-mp-cli/lib/declare.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
4+
const {
5+
log,
6+
error,
7+
writeFile,
8+
promptAndRun,
9+
catchAndThrow,
10+
} = require('./utils')
11+
const { defaultTuaConfig } = require('./constants')
12+
13+
const cwd = process.cwd()
14+
15+
/**
16+
* 根据导出的 apis,生成相应的 index.d.ts 声明
17+
* @param {Object} options
18+
* @param {String} options.apisPath 导出 apis 对象的文件地址
19+
* @param {Object} options.tuaConfig 项目自定义配置
20+
*/
21+
module.exports = (options = {}) => {
22+
/* istanbul ignore next */
23+
const {
24+
apisPath = 'src/apis/index.js',
25+
tuaConfig: {
26+
alias = defaultTuaConfig.alias,
27+
} = defaultTuaConfig,
28+
} = options
29+
30+
const sourcePath = process.env.TUA_CLI_TEST_SRC ||
31+
/* istanbul ignore next */
32+
path.resolve(cwd, apisPath)
33+
34+
try {
35+
mockGlobalVars()
36+
37+
const isDir = fs.lstatSync(sourcePath).isDirectory()
38+
39+
const dir = isDir ? apisPath : path.dirname(apisPath)
40+
const name = isDir ? `index` : path.basename(apisPath, path.extname(apisPath))
41+
const dist = `${dir}/${name}.d.ts`
42+
const relativePath = path.relative(cwd, dist)
43+
const targetPath = process.env.TUA_CLI_TEST_DIST ||
44+
/* istanbul ignore next */
45+
path.resolve(cwd, dist)
46+
47+
require('@babel/register')({
48+
plugins: [
49+
[require('babel-plugin-module-resolver'), { root: cwd, alias }],
50+
],
51+
})
52+
53+
// get apis
54+
const apis = require(sourcePath)
55+
const code = genApiDeclarationCode(apis)
56+
const run = (isCover = false) => writeFile(targetPath, code)
57+
.then(() => {
58+
log(`成功${isCover ? '覆盖' : '生成'} api 声明 -> ${relativePath}\n`)
59+
})
60+
const message = 'Target file exists. Continue?'
61+
62+
return promptAndRun({ run, message, targetPath })
63+
} catch (e) {
64+
error(`Error loading ${sourcePath}:`)
65+
66+
return catchAndThrow(e)
67+
}
68+
}
69+
70+
// mock global vars
71+
function mockGlobalVars () {
72+
const location = {
73+
hash: '',
74+
host: '',
75+
href: '',
76+
port: '',
77+
origin: '',
78+
search: '',
79+
hostname: '',
80+
protocol: '',
81+
pathname: '',
82+
}
83+
const navigator = {
84+
appName: '',
85+
platform: '',
86+
userAgent: '',
87+
appCodeName: '',
88+
}
89+
90+
global.wx = global.wx || {}
91+
global.window = global.window || { location, navigator }
92+
global.location = global.location || location
93+
global.navigator = global.navigator || navigator
94+
}
95+
96+
/**
97+
* 根据 api config 生成 api 函数声明代码
98+
* @param {object} apis 属性为 tua-api 生成的请求对象
99+
*/
100+
function genApiDeclarationCode (apis) {
101+
// 类型声明
102+
const headCode = genCodeByLevel(`
103+
// default response result
104+
interface Result { code: number, data: any, msg?: string }
105+
interface ReqFn {
106+
key: string
107+
mock: any
108+
params: object | string[]
109+
}
110+
interface RuntimeOptions {
111+
// for jsonp
112+
callbackName?: string
113+
[key: string]: any
114+
}
115+
// request function without params
116+
interface NoParamsReqFn extends ReqFn {
117+
<T = Result>(params?: void, options?: RuntimeOptions): Promise<T>
118+
}`, 2)
119+
120+
// 各个 api 生成的声明代码
121+
const apisCode = Object.keys(apis)
122+
.map((key) => genCodeByLevel(`
123+
export const ${key}: {
124+
${genApiFnsCode(apis[key])}
125+
}`, 3)
126+
)
127+
.join(`\n\n`)
128+
129+
return headCode + `\n\n` + apisCode
130+
}
131+
132+
function genCodeByLevel (rawCode, level) {
133+
const sep = RegExp(`\\n\\s{${4 * level}}`)
134+
135+
return rawCode
136+
.split(sep)
137+
.filter(x => x)
138+
.join(`\n`)
139+
.replace(/ {4}/g, `\t`)
140+
}
141+
142+
/**
143+
* 生成单个 api 下各个函数的声明代码
144+
* @param {Object} api tua-api 生成的请求对象
145+
*/
146+
function genApiFnsCode (api) {
147+
return Object.keys(api)
148+
.map((fnKey) => {
149+
const attrsCode = genAttrsCode(api[fnKey].params)
150+
if (!attrsCode) return `${fnKey}: NoParamsReqFn`
151+
152+
return genCodeByLevel(
153+
`${fnKey}: ReqFn & {
154+
<T = Result>(
155+
params: { ${attrsCode} },
156+
options?: RuntimeOptions
157+
): Promise<T>
158+
}`, 3)
159+
})
160+
// 短的排前面
161+
.sort((x, y) => x.length - y.length)
162+
.join(`\n\t`)
163+
}
164+
165+
/**
166+
* 生成参数声明代码
167+
* @param {Object|Array} params 接口参数配置
168+
*/
169+
function genAttrsCode (params = []) {
170+
const attrsCodeArr = Array.isArray(params)
171+
// 数组形式的参数都认为是可选的
172+
? params.map(key => `${key}?: any`)
173+
: Object.keys(params).map((key) => {
174+
const param = params[key]
175+
const isRequired = param.required || param.isRequired
176+
return `${key}${isRequired ? '' : '?'}: any`
177+
})
178+
179+
return attrsCodeArr.join(`, `)
180+
}

packages/tua-mp-cli/lib/utils/logFns.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const error = out => logByType('error')(logSymbols.error + ' ' + chalk.red(out))
1717
const catchAndThrow = (err) => {
1818
err instanceof Error ? logByType('error')(logSymbols.error, err) : error(err)
1919

20-
return process.env.NODE_ENV === 'test' && Promise.reject(Error())
20+
return process.env.NODE_ENV === 'test' && Promise.reject(Error(err))
2121
}
2222

2323
module.exports = {

packages/tua-mp-cli/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tua-mp/cli",
3-
"version": "0.3.0",
3+
"version": "0.4.0",
44
"description": "tua-mp 命令行工具",
55
"bin": {
66
"tuamp": "bin/tuamp.js"
@@ -13,7 +13,10 @@
1313
"lint": "eslint --fix lib/ bin/ test/",
1414
"clean": "rimraf .temp && mkdir .temp/ .temp/src/ .temp/src/app/ .temp/src/apis/ .temp/src/pages/ .temp/src/comps/ && echo {} > .temp/src/app/app.json && echo \"module.exports = {\n\ttemplateDir: '.templates',\n}\" > .temp/tua.config.js",
1515
"precommit": "lint-staged",
16-
"pub": "npm test && npm publish"
16+
"next:pm": "npm --no-git-tag-version version preminor",
17+
"next:pr": "npm --no-git-tag-version version prerelease",
18+
"pub": "npm test && npm publish",
19+
"pub:n": "npm test && npm publish --tag next"
1720
},
1821
"lint-staged": {
1922
"bin/*": [
@@ -37,7 +40,10 @@
3740
"package.json"
3841
],
3942
"dependencies": {
43+
"@babel/core": "^7.4.3",
44+
"@babel/register": "^7.4.0",
4045
"@vue/cli-init": "^3.5.1",
46+
"babel-plugin-module-resolver": "^3.2.0",
4147
"babel-preset-env": "^1.7.0",
4248
"chalk": "^2.4.2",
4349
"fs-extra": "^7.0.1",

0 commit comments

Comments
 (0)