持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
create-vue
使用
create-vue是生成vue模板项目的cli,我们可以使用
npm create vue@latest <packagName>
效果如下:
使用的体验就是非常的方便且高效,快速生成自己需要或者喜欢的项目模板,并且交互设计非常的人性化。
因此我也是抱着学习学习的态度来把这个开源的create-vue项目浅析一下。
项目结构
项目源码在vuejs/create-vue: (github.com),clone到本地分析一下。结构很简单
index是主要的cli代码,template存储了模板,untils有一些根据函数,例如render(),我们主要看index.ts。
使用的几个库
zx库,方便前端执行shell的库,以.mjs为后缀的文件,可以直接通过zx ./script.mjs运行,它主要是相对于shell,更加适合前端人员,且非常方便快速,而且可以使用async/await,console.log()等同于echo,$"..."执行shell命令。 内置了question(提问),chalk(文本颜色)等库
prompts,类似于question,只是它能够提供默认值,让用户选择,更加的人性化。
minimist,解析命令行参数。
fs, node的服务器文件管理模块,能够对文件做出很多操作。
以上几个库就能写出非常漂亮且人性化的cli,我也是浅写了个demo来尝试一下(上面这几个库没有中文文档,本人英语也差,资料也少,所以demo很简单)
#!/usr/bin/env zx
// import { chalk, question } from "zx/globals";
import prompts from "prompts";
// import { chalk } from "zx/.";
try {
const color = await question(`请选择一个风格: ${chalk.cyan("cyan")} | ${chalk.green("green")} | ${chalk.hex("#ffcccc").bold("pink")}: `, {
choice: ["蓝色", "绿色", "天蓝"]
});
let log = () => { };
switch (color) {
case "cyan": log = (string) => (console.log(chalk.cyan(string)))
break;
case "green": log = (string) => (console.log(chalk.green(string)));
break
case "pink": log = (string) => (console.log(chalk.hex("#ffcccc").bold(string)))
break
default: console.log("default");
break;
}
log(`现在使用${color}风格`);
log("");
(async () => {
const response = await prompts([
{
type: 'number',
name: 'value',
message: chalk.green('How old are you?'),
validate: value => value < 18 ? `Nightclub is 18+ only` : true
},
{
name: 'sex',
type: () => ('toggle'),
// yoggle
message: chalk.green("your sex?"),
initial: false,
active: '男',
inactive: '女'
},
{
name: 'name',
type: 'text',
message: chalk.green('your name?')
},
]);
const { value, sex, name } = response;
log(`你好!${name},你的性别是${sex},年龄是${value}`); // => { value: 24 }
})();
} catch (e) {
console.log(e)
}
init()
可以看到init函数就是入口函数,提供minimist读取参数
const cwd = process.cwd()
//cwd是提供node的cwdapi获取路径
const argv = minimist(process.argv.slice(2), {
//获取命令行参数
alias: {
typescript: ['ts'],
'with-tests': ['tests'],
router: ['vue-router']
},
// all arguments are treated as booleans
boolean: true
})
根据minimist获取的已有参数生成一个prompts选择列表,选择完成或返回一个string数组,
result = await prompts(
[
//name属性定义结果的属性名,message是问题,type是输入的类型
{
name: 'projectName',
type: targetDir ? null : 'text',
message: 'Project name:',
initial: defaultProjectName,
onState: (state) => (targetDir = String(state.value).trim() || defaultProjectName)
},
{
name: 'shouldOverwrite',
type: () => (canSkipEmptying(targetDir) || forceOverwrite ? null : 'confirm'),
message: () => {
const dirForPrompt =
targetDir === '.' ? 'Current directory' : `Target directory "${targetDir}"`
return `${dirForPrompt} is not empty. Remove existing files and continue?`
}
},
//... 很多,省略
],
{
onCancel: () => {
throw new Error(red('✖') + ' Operation cancelled')
}
}
)
接着创建文件
const root = path.join(cwd, targetDir)
if (fs.existsSync(root) && shouldOverwrite) {
emptyDir(root)
//这里主要是处理是否已存在6路径,
} else if (!fs.existsSync(root)) {
fs.mkdirSync(root)
}
往后就很单一了,创建基础模板,然后根据选择的添加项依次调用render()函数将各种选择渲染进基础模板。 我们再来看看renderTemplate函数
function renderTemplate(src, dest) {
const stats = fs.statSync(src)
if (stats.isDirectory()) {
// skip node_module
if (path.basename(src) === 'node_modules') {
return
}
fs.mkdirSync(dest, { recursive: true })
for (const file of fs.readdirSync(src)) {
renderTemplate(path.resolve(src, file), path.resolve(dest, file))
}
//这里是假如说仍然是一个路径,那就render这个路径。
return
}
const filename = path.basename(src)
//经过上面操作,现在filename就一定是file
//....后面做一些文件处理
fs.copyFileSync(src, dest)
}
可以看到,通篇都是fs和path操作,这里不熟悉fs/path的api头都要疼死了!我浅说一下这两个模块。
首先是Stats对象,它是路径对象,而fs.statSync("path")就能够返回一个Status对象,我们可以调用对象身上的一些方法isFile() isDirectory()
,很简单就是判断是文件夹还是文件。
path对象有许多方法,诸如获取相对/绝对路径,路径拼接等等。
结语
本次的文章到这里就结束啦!♥♥♥读者大大们认为写的不错的话点个赞再走哦 ♥♥♥
每天一个知识点,每天都在进步!♥♥