Skip to content

Commit

Permalink
feat(cli): taro 项目模板抽离到远程获取
Browse files Browse the repository at this point in the history
  • Loading branch information
Chen-jj committed Jun 17, 2019
1 parent 93388cc commit ac347f7
Show file tree
Hide file tree
Showing 100 changed files with 319 additions and 3,270 deletions.
51 changes: 51 additions & 0 deletions packages/taro-cli/src/create/fetchTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as fs from 'fs-extra'
import * as path from 'path'
import { exec } from 'child_process'
import * as chalk from 'chalk'
import * as ora from 'ora'
import Project from './project'

const TEMP_DOWNLOAD_FLODER = 'taro-temp'

export default async function fetchTemplate (creater: Project, cb) {
const { templateSource, gitAddress, template } = creater.conf
const templateRootPath = creater.templatePath('')
const templatePath = template ? creater.templatePath(template) : ''

if (templateSource === 'default') {
creater.conf.template = 'default'
cb()
} else if (templateSource === 'git') {
if (fs.existsSync(templatePath)) {
await fs.remove(templatePath)
// return console.log(`${chalk.grey('模板已缓存可直接使用')}`)
}

const tempPath = path.join(templateRootPath, TEMP_DOWNLOAD_FLODER)
if (fs.existsSync(tempPath)) await fs.remove(tempPath)
await fs.mkdir(tempPath)

const spinner = ora(`git clone ${gitAddress}`).start()

exec(`git clone ${gitAddress} ${tempPath}`, async error => {
if (error) {
spinner.color = 'red'
spinner.fail(chalk.red('拉取远程模板仓库失败!'))
await fs.remove(tempPath)
return console.log(error)
}
spinner.color = 'green'
spinner.succeed(`${chalk.grey('git clone done')}`)

if (!fs.existsSync(path.join(tempPath, template))) {
console.log(chalk.red('git 仓库不存在此模板!'))
await fs.remove(tempPath)
return
}

await fs.move(path.join(tempPath, template), templatePath)
await fs.remove(tempPath)
typeof cb === 'function' && cb()
})
}
}
175 changes: 175 additions & 0 deletions packages/taro-cli/src/create/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import * as fs from 'fs-extra'
import * as path from 'path'
import * as chalk from 'chalk'
import { exec } from 'child_process'
import * as ora from 'ora'
import { IProjectConf } from './project'
import Creator from './creator'
import * as helper from '../util'

const CONFIG_DIR_NAME = 'config'
const TEMPLATE_CREATOR = 'template_creator.js'

const styleExtMap = {
sass: 'scss',
less: 'less',
stylus: 'styl',
none: 'css'
}

const doNotCopyFiles = [
'.DS_Store',
'.npmrc',
TEMPLATE_CREATOR
]

export async function createApp (
creater: Creator,
params: IProjectConf,
cb
) {
const {
projectName,
projectDir,
description,
template,
typescript,
date,
css
} = params
const logs = []
// path
const templatePath = creater.templatePath(template)
const projectPath = path.join(projectDir, projectName)

// npm & yarn
const version = helper.getPkgVersion()
const shouldUseYarn = helper.shouldUseYarn()
const useNpmrc = !shouldUseYarn
const yarnLockfilePath = path.join('yarn-lockfiles', `${version}-yarn.lock`)
const useYarnLock = shouldUseYarn && fs.existsSync(creater.templatePath(template, yarnLockfilePath))

if (useNpmrc) {
creater.template(template, '.npmrc', path.join(projectPath, '.npmrc'))
logs.push(`${chalk.green('✔ ')}${chalk.grey(`创建文件: ${projectName}/.npmrc`)}`)
}
if (useYarnLock) {
creater.template(template, yarnLockfilePath, path.join(projectPath, 'yarn.lock'))
logs.push(`${chalk.green('✔ ')}${chalk.grey(`创建文件: ${projectName}/yarn.lock`)}`)
}

const currentStyleExt = styleExtMap[css] || 'css'

// 遍历出模板中所有文件
const files = await helper.getAllFilesInFloder(templatePath, doNotCopyFiles)

// 引入模板编写者的自定义逻辑
const handlerPath = path.join(templatePath, TEMPLATE_CREATOR)
let handler = {}
let globalChangeExt = true
if (fs.existsSync(handlerPath)) {
handler = require(handlerPath).handler
} else {
// 模板库模板,直接创建,不需要改后缀
globalChangeExt = false
}

// 为所有文件进行创建
files.forEach(file => {
const fileRePath = file.replace(templatePath, '')
let externalConfig: any = null

// 跑自定义逻辑,确定是否创建此文件
if (typeof handler[fileRePath] === 'function') {
externalConfig = handler[fileRePath](params)
if (!externalConfig) return
}

let changeExt = globalChangeExt
if (externalConfig && typeof externalConfig === 'object') {
if (externalConfig.changeExt === false) {
changeExt = false
}
}

// 合并自定义 config
const config = Object.assign({}, {
description,
projectName,
version,
css,
cssExt: currentStyleExt,
date,
typescript,
template
}, externalConfig)

// 处理 .js 和 .css 的后缀
let destRePath = fileRePath
if (
typescript &&
changeExt &&
!destRePath.startsWith(`/${CONFIG_DIR_NAME}`) &&
(path.extname(destRePath) === '.js' || path.extname(destRePath) === '.jsx')
) {
destRePath = destRePath.replace('.js', '.ts')
}
if (changeExt && path.extname(destRePath).includes('.css')) {
destRePath = destRePath.replace('.css', `.${currentStyleExt}`)
}

// 创建
creater.template(template, fileRePath, path.join(projectPath, destRePath), config)
logs.push(`${chalk.green('✔ ')}${chalk.grey(`创建文件: ${path.join(projectName, destRePath)}`)}`)
})

// fs commit
creater.fs.commit(() => {
// logs
console.log()
console.log(`${chalk.green('✔ ')}${chalk.grey(`创建项目: ${chalk.grey.bold(projectName)}`)}`)
logs.forEach(log => console.log(log))
console.log()

// git init
const gitInitSpinner = ora(`cd ${chalk.cyan.bold(projectName)}, 执行 ${chalk.cyan.bold('git init')}`).start()
process.chdir(projectPath)
const gitInit = exec('git init')
gitInit.on('close', code => {
if (code === 0) {
gitInitSpinner.color = 'green'
gitInitSpinner.succeed(gitInit.stdout.read())
} else {
gitInitSpinner.color = 'red'
gitInitSpinner.fail(gitInit.stderr.read())
}
})

// packages install
let command: string
if (shouldUseYarn) {
command = 'yarn install'
} else if (helper.shouldUseCnpm()) {
command = 'cnpm install'
} else {
command = 'npm install'
}
const installSpinner = ora(`执行安装项目依赖 ${chalk.cyan.bold(command)}, 需要一会儿...`).start()
exec(command, (error, stdout, stderr) => {
if (error) {
installSpinner.color = 'red'
installSpinner.fail(chalk.red('安装项目依赖失败,请自行重新安装!'))
console.log(error)
} else {
installSpinner.color = 'green'
installSpinner.succeed('安装成功')
console.log(`${stderr}${stdout}`)
}
console.log(chalk.green(`创建项目 ${chalk.green.bold(projectName)} 成功!`))
console.log(chalk.green(`请进入项目目录 ${chalk.green.bold(projectName)} 开始工作吧!😝`))
if (typeof cb === 'function') {
cb()
}
})
})
}
106 changes: 55 additions & 51 deletions packages/taro-cli/src/create/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@ import * as fs from 'fs-extra'
import chalk from 'chalk'
import * as inquirer from 'inquirer'
import * as semver from 'semver'

import { createApp } from './init'
import fetchTemplate from './fetchTemplate'
import Creator from './creator'

import {
shouldUseYarn,
shouldUseCnpm,
getPkgVersion
} from '../util'
import CONFIG from '../config'

interface IProjectConf {
export interface IProjectConf {
projectName: string,
projectDir: string,
template: 'default' | 'mobx' | 'redux',
templateSource: 'default' | 'git' | 'market',
gitAddress?: string,
template: string,
description?: string,
typescript?: boolean,
css: 'none' | 'sass' | 'stylus' | 'less',
Expand Down Expand Up @@ -134,59 +131,66 @@ export default class Project extends Creator {
})
}

const templateChoices = [{
name: '默认模板',
value: 'default'
}, {
name: 'Redux 模板',
value: 'redux'
}, {
name: 'Mobx 模板',
value: 'mobx'
}, {
name: '云开发模板',
value: 'wxcloud'
}, {
name: '微信小程序插件模板',
value: 'wxplugin'
}]

if (typeof conf.template !== 'string') {
prompts.push({
type: 'list',
name: 'template',
message: '请选择模板',
choices: templateChoices
})
} else {
let isTemplateExist = false
templateChoices.forEach(item => {
if (item.value === conf.template) {
isTemplateExist = true
name: 'templateSource',
message: '请选择模板来源',
choices: [{
name: '默认模板',
value: 'default'
}, {
name: 'git',
value: 'git'
}
/* , {
name: '模板市场',
value: 'market'
} */
]
})
if (!isTemplateExist) {
console.log(chalk.red('你选择的模板不存在!'))
console.log(chalk.red('目前提供了以下模板以供使用:'))
console.log()
templateChoices.forEach(item => {
console.log(chalk.green(`- ${item.name}`))
})
process.exit(1)
}

prompts.push({
type: 'input',
name: 'gitAddress',
message: '请输入 git 地址',
validate (input) {
if (!input) return 'git 地址不能为空!'
return true
},
when: answers => answers.templateSource === 'git'
}, {
type: 'input',
name: 'template',
message: '请输入模板名',
validate (input) {
if (!input) return '模板名不能为空!'
return true
},
when: answers => answers.templateSource === 'git'
})

/* prompts.push({
type: 'input',
name: 'template',
message: '请输入模板 ID',
validate (input) {
if (!input) return '模板 ID 不能为空!'
return true
},
when: answers => answers.templateSource === 'market'
}) */
}

return inquirer.prompt(prompts)
}

write (cb?: () => void) {
const { template } = this.conf
this.conf.src = CONFIG.SOURCE_DIR
const { createApp } = require(path.join(this.templatePath(), template, 'index.js'))
createApp(this, this.conf, {
shouldUseYarn,
shouldUseCnpm,
getPkgVersion
}, cb)
fetchTemplate(this, () => {
createApp(this, this.conf, cb)
.catch(err => console.log(err))
})
.catch(err => console.log(err))
}
}
24 changes: 24 additions & 0 deletions packages/taro-cli/src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,3 +662,27 @@ export function uglifyJS (resCode: string, filePath: string, root: string, uglif
}
return resCode
}

export const getAllFilesInFloder = async (
floder: string,
filter: string[] = []
): Promise<string[]> => {
let files: string[] = []
const list = await ((fs.readdir(floder, {
withFileTypes: true
} as any) as any) as Promise<fs.Dirent[]>)

await Promise.all(
list.map(async item => {
const itemPath = path.join(floder, item.name)
if (item.isDirectory()) {
const _files = await getAllFilesInFloder(itemPath, filter)
files = [...files, ..._files]
} else if (item.isFile()) {
if (!filter.find(rule => rule === item.name)) files.push(itemPath)
}
})
)

return files
}
File renamed without changes.
Loading

0 comments on commit ac347f7

Please sign in to comment.