-
Notifications
You must be signed in to change notification settings - Fork 4.8k
/
Copy pathpage.ts
295 lines (262 loc) · 9.75 KB
/
page.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
import * as path from 'node:path'
import { CompilerType, createPage as createPageBinding, CSSType, FrameworkType, NpmType, PeriodType } from '@tarojs/binding'
import { babelKit, chalk, DEFAULT_TEMPLATE_SRC, fs, getUserHomeDir, resolveScriptPath, TARO_BASE_CONFIG, TARO_CONFIG_FOLDER } from '@tarojs/helper'
import { getPkgVersion, getRootPath, isNil } from '../util'
import { modifyPagesOrSubPackages } from '../util/createPage'
import { TEMPLATE_CREATOR } from './constants'
import Creator from './creator'
import fetchTemplate from './fetchTemplate'
export interface IPageConf {
projectDir: string
projectName: string
npm: NpmType
template: string
clone?: boolean
templateSource?: string
description?: string
pageName: string
date?: string
framework: FrameworkType
css: CSSType
typescript?: boolean
compiler?: CompilerType
isCustomTemplate?: boolean
customTemplatePath?: string
pageDir?: string
subPkg?: string
}
interface IPageArgs extends IPageConf {
modifyCustomTemplateConfig : TGetCustomTemplate
afterCreate?: TAfterCreate
}
interface ITemplateInfo {
css: CSSType
typescript?: boolean
compiler?: CompilerType
template?: string
templateSource?: string
clone?: boolean
}
type TCustomTemplateInfo = Omit<ITemplateInfo & {
isCustomTemplate?: boolean
customTemplatePath?: string
}, 'template'>
export type TSetCustomTemplateConfig = (customTemplateConfig: TCustomTemplateInfo) => void
type TGetCustomTemplate = (cb: TSetCustomTemplateConfig) => Promise<void>
type TAfterCreate = (state: boolean) => void
const DEFAULT_TEMPLATE_INFO = {
name: 'default',
css: CSSType.None,
typescript: false,
compiler: CompilerType.Webpack5,
framework: FrameworkType.React
}
export enum ConfigModificationState {
Success,
Fail,
NeedLess
}
export type ModifyCallback = (state: ConfigModificationState) => void
export default class Page extends Creator {
public rootPath: string
public conf: IPageConf
private modifyCustomTemplateConfig: TGetCustomTemplate
private afterCreate: TAfterCreate | undefined
private pageEntryPath: string = ''
constructor (args: IPageArgs) {
super()
this.rootPath = this._rootPath
const { modifyCustomTemplateConfig, afterCreate, ...otherOptions } = args
this.conf = Object.assign(
{
projectDir: '',
projectName: '',
template: '',
description: '',
pageDir: ''
},
otherOptions
)
this.conf.projectName = path.basename(this.conf.projectDir)
this.modifyCustomTemplateConfig = modifyCustomTemplateConfig
this.afterCreate = afterCreate
this.processPageName()
}
processPageName () {
const { pageName } = this.conf
// todo 目前还没有对 subPkg 和 pageName 这两个字段做 格式验证或者处理
const lastDirSplitSymbolIndex = pageName.lastIndexOf('/')
if (lastDirSplitSymbolIndex !== -1) {
this.conf.pageDir = pageName.substring(0, lastDirSplitSymbolIndex)
this.conf.pageName = pageName.substring(lastDirSplitSymbolIndex + 1)
}
}
getPkgPath () {
const projectDir = this.conf.projectDir as string
let pkgPath = path.join(projectDir, 'package.json')
if (!fs.existsSync(pkgPath)) {
// 适配 云开发 项目
pkgPath = path.join(projectDir, 'client', 'package.json')
if (!fs.existsSync(pkgPath)) {
console.log(chalk.yellow('请在项目根目录下执行 taro create 命令!'))
process.exit(0)
}
}
return pkgPath
}
getPkgTemplateInfo () {
const pkg = fs.readJSONSync(this.getPkgPath())
const templateInfo = pkg.templateInfo || DEFAULT_TEMPLATE_INFO
// set template name
templateInfo.template = templateInfo.name
delete templateInfo.name
return templateInfo
}
setPageEntryPath (files: string[], handler) {
const configFileName = files.find((filename) => /\.config\.(js|ts)$/.test(filename))
if (!configFileName) return
const getPageFn = handler[configFileName]
const { setPageName = '', setSubPkgName = '' } = getPageFn?.(() => {}, this.conf) || {}
if (this.conf.subPkg) {
this.pageEntryPath = setSubPkgName.replace(/\.config\.(js|ts)$/, '')
} else {
this.pageEntryPath = setPageName.replace(/\.config\.(js|ts)$/, '')
}
}
setCustomTemplateConfig (customTemplateConfig: TCustomTemplateInfo) {
const pkgTemplateInfo = this.getPkgTemplateInfo()
const { compiler, css, customTemplatePath, typescript } = customTemplateConfig
const conf = {
compiler: compiler || pkgTemplateInfo.compiler,
css: css || pkgTemplateInfo.css,
typescript: !isNil(typescript) ? typescript : pkgTemplateInfo.typescript,
customTemplatePath,
isCustomTemplate: true,
}
this.setTemplateConfig(conf)
}
setTemplateConfig (templateInfo: ITemplateInfo) {
this.conf = Object.assign(this.conf, templateInfo)
}
async fetchTemplates () {
const homedir = getUserHomeDir()
let templateSource = DEFAULT_TEMPLATE_SRC
if (!homedir) chalk.yellow('找不到用户根目录,使用默认模版源!')
if (this.conf.templateSource) {
templateSource = this.conf.templateSource
} else {
const taroConfigPath = path.join(homedir, TARO_CONFIG_FOLDER)
const taroConfig = path.join(taroConfigPath, TARO_BASE_CONFIG)
if (fs.existsSync(taroConfig)) {
const config = await fs.readJSON(taroConfig)
templateSource = config && config.templateSource ? config.templateSource : DEFAULT_TEMPLATE_SRC
} else {
await fs.createFile(taroConfig)
await fs.writeJSON(taroConfig, { templateSource })
templateSource = DEFAULT_TEMPLATE_SRC
}
}
// 从模板源下载模板
await fetchTemplate(templateSource, this.templatePath(''), this.conf.clone)
}
async create () {
const date = new Date()
this.conf.date = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
// apply 插件,由插件设置自定义模版 config
await this.modifyCustomTemplateConfig(this.setCustomTemplateConfig.bind(this))
if (!this.conf.isCustomTemplate) {
const pkgTemplateInfo = this.getPkgTemplateInfo()
this.setTemplateConfig(pkgTemplateInfo)
if (!fs.existsSync(this.templatePath(this.conf.template))) {
await this.fetchTemplates()
}
}
this.write()
}
updateAppConfig () {
const { parse, generate, traverse } = babelKit
let modifyState: ConfigModificationState = ConfigModificationState.Fail
const { subPkg, projectDir, typescript } = this.conf
const [sourceString, pageString] = this.pageEntryPath.split('/src/')
const appConfigPath = resolveScriptPath(path.join(projectDir, sourceString, 'src', 'app.config'))
if (!fs.existsSync(appConfigPath)) {
return console.log(
`${chalk.red('x ')}${chalk.grey(`无法获取 ${appConfigPath} 配置文件,请手动到配置文件中补全新页面信息`)}`
)
}
const configFileContent = fs.readFileSync(appConfigPath, 'utf-8')
const ast = parse(configFileContent, {
sourceType: 'module',
plugins: typescript ? ['typescript'] : []
})
traverse(ast, {
ExportDefaultDeclaration (path) {
modifyPagesOrSubPackages({
path,
fullPagePath: pageString,
subPkgRootPath: subPkg,
callback: (state: ConfigModificationState) => { modifyState = state }
})
},
})
switch (modifyState as ConfigModificationState) {
case ConfigModificationState.Fail:
console.log(`${chalk.red('x ')}${chalk.grey(`自动补全新页面信息失败, 请手动到 ${appConfigPath} 文件中补全新页面信息`)}`)
break
case ConfigModificationState.Success:
{
const newCode = generate(ast, { retainLines: true })
fs.writeFileSync(appConfigPath, newCode.code)
console.log(`${chalk.green('✔ ')}${chalk.grey(`新页面信息已在 ${appConfigPath} 文件中自动补全`)}`)
break
}
case ConfigModificationState.NeedLess:
console.log(`${chalk.green('✔ ')}${chalk.grey(`新页面信息已存在在 ${appConfigPath} 文件中,不需要补全`)}`)
break
}
}
write () {
const { projectName, projectDir, template, pageName, isCustomTemplate, customTemplatePath, subPkg, pageDir } = this.conf as IPageConf
let templatePath
if (isCustomTemplate) {
templatePath = customTemplatePath
} else {
templatePath = this.templatePath(template)
}
if (!fs.existsSync(templatePath)) return console.log(chalk.red(`创建页面错误:找不到模板${templatePath}`))
// 引入模板编写者的自定义逻辑
const handlerPath = path.join(templatePath, TEMPLATE_CREATOR)
const basePageFiles = fs.existsSync(handlerPath) ? require(handlerPath).basePageFiles : []
const files = Array.isArray(basePageFiles) ? basePageFiles : []
const handler = fs.existsSync(handlerPath) ? require(handlerPath).handler : {}
this.setPageEntryPath(files, handler)
createPageBinding({
pageDir,
subPkg,
projectDir,
projectName,
template,
framework: this.conf.framework,
css: this.conf.css || CSSType.None,
typescript: this.conf.typescript,
compiler: this.conf.compiler,
templateRoot: getRootPath(),
version: getPkgVersion(),
date: this.conf.date,
description: this.conf.description,
pageName,
isCustomTemplate,
customTemplatePath,
basePageFiles: files,
period: PeriodType.CreatePage,
}, handler).then(() => {
console.log(`${chalk.green('✔ ')}${chalk.grey(`创建页面 ${this.conf.pageName} 成功!`)}`)
this.updateAppConfig()
this.afterCreate && this.afterCreate(true)
}).catch(err => {
console.log(err)
this.afterCreate && this.afterCreate(false)
})
}
}
export type { Page as PageCreator }