create-vite 主要是用于创建一个项目并根据用户选择配置的
template
将模板文件写入当前创建的目录中。vite
提供了多个模板及其ts
版本。
使用minimist
解析命令行参数使用prompts
包来实现命令行指引配置的功能。使用kolorist
包实现不同颜色的关键词。
来看一下这些包的简单使用,方便后续查看create-vite
中的源码。
通过process.argv
获取命令行参数的字符串数组,前两个值是固定的,第一个是node
程序路径,第二个则是当前执行的文件路径。之后的才是输入的各种参数
const { argv } = process;
console.log('argv', argv);
> node src/index.js-v vv -f
argv ['/usr/local/Cellar/node@16/16.18.1/bin/node','/Users/zgm/Documents/Web/node/node-js/src/index.js','-v','vv','-f']
使用minimist
后可以把输入的参数进行解析,使用_
保存命令中的各种参数,当匹配到-
或者--
字符时忽略后边的所有的参数。-
、--
字符后边的命令会添加到对象中,当命令后边有参数(非options
),那么该命令的值就是后边的参数,否则值为true
。
const argv = minimist(process.argv.slice(2), { string: ['_'] }); // _ 中的属性会被转为字符串类型
console.log(argv);
> node src/index.js-v vv -f
{ _: [], v: 'vv', f: true }
源码:
const argv = minimist(process.argv.slice(2), { string: ['_'] });
console.log(argv);
> node src/index.js _asdf 123 321-v vv -f
{ _: [ '_asdf', '123', '321' ], v: 'vv', f: true }
轻量级,美观且用户友好的交互式提示库。
单个提示的传入一个对象即可
const prompts = require('prompts');
(async () => {const response = await prompts({type: 'text',name: 'weather',message: 'What is the weather today?',});console.log(response);
})();
多个就需要传入数组
const prompts = require('prompts');
(async () => {const response = await prompts([{type: 'text',name: 'weather',message: 'What is the weather today?',},{type: 'confirm',name: 'out',message: 'Are you going out for fun now?',},]);console.log(response);
})();
动态prompts
当type
为null
时可以跳过当前这个prompt
。
当type
为一个函数时,有 3 个参数,第一个是上一条prompt
的值,第二个是之前所有prompts
的键值组成的对象,第三个则是当前prompt
对象。
(async () => {const response = await prompts([{type: 'text',name: 'weather',message: 'What is the weather today?',},{type: 'confirm',name: 'out',message: 'Are you going out for fun now?',},{type: (pre) => (pre ? 'text' : null),name: 'fun',message: 'Have fun then',},]);console.log(response);
})();
用于输出不同颜色的字符。
const { yellow, green, cyan, blue } = require('kolorist');
console.log(yellow('yellow'));
console.log(green('green'));
console.log(cyan('cyan'));
console.log(blue('blue'));
源码可以去 clone 若川
大大的相关仓库
获取命令后的第一个参数默认为项目名,当未获取到参数时提示用户进行配置,否则跳过进行下一步。
// 命令行第一个参数,替换反斜杠 / 为空字符串
let targetDir = formatTargetDir(argv._[0]); // 如果字符串最后一个字符是 '/',则去除
// 命令行参数 --template 或者 -t
let template = argv.template || argv.t;
const defaultTargetDir = 'vite-project';
// 获取项目名
const getProjectName = () => (targetDir === '.' ? path.basename(path.resolve()) : targetDir);
try {result = await prompts([// 项目名{type: targetDir ? null : 'text',name: 'projectName',message: reset('Project name:'), // reset() 是 kolorist 中的 apiinitial: defaultTargetDir, // 默认为 vite-projectonState: (state) => {targetDir = formatTargetDir(state.value) || defaultTargetDir;},},// 判断是否需要重写目录(是否为空文件夹){type: () => (!fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm'),name: 'overwrite',message: () =>(targetDir === '.' ? 'Current directory' : `Target directory "${targetDir}"`) +` is not empty. Remove existing files and continue?`,},{type: (_, { overwrite } = {}) => {// 用户不要重写if (overwrite === false) {throw new Error(red('✖') + ' Operation cancelled');}return null;},name: 'overwriteChecker',},// 验证项目名{type: () => (isValidPackageName(getProjectName()) ? null : 'text'),name: 'packageName',message: reset('Package name:'),initial: () => toValidPackageName(getProjectName()),validate: (dir) => isValidPackageName(dir) || 'Invalid package.json name',},// 选择框架模板{type: template && TEMPLATES.includes(template) ? null : 'select',name: 'framework',message:typeof template === 'string' && !TEMPLATES.includes(template)? reset(`"${template}" isn't a valid template. Please choose from below: `): reset('Select a framework:'),initial: 0,choices: FRAMEWORKS.map((framework) => {const frameworkColor = framework.color;return {title: frameworkColor(framework.name),value: framework,};}),},// 选择模版对应的语言如 ts{type: (framework) => (framework && framework.variants ? 'select' : null),name: 'variant',message: reset('Select a variant:'),// @ts-ignorechoices: (framework) =>framework.variants.map((variant) => {const variantColor = variant.color;return {title: variantColor(variant.name),value: variant.name,};}),},],{onCancel: () => {throw new Error(red('✖') + ' Operation cancelled');},},);
} catch (cancelled) {console.log(cancelled.message);return;
}
// user choice associated with prompts
const { framework, overwrite, packageName, variant } = result;
// 目录
const root = path.join(cwd, targetDir);
if (overwrite) {// 删除文件夹emptyDir(root);
} else if (!fs.existsSync(root)) {// 新建文件夹fs.mkdirSync(root, { recursive: true });
}
// 递归删除文件夹,相当于 rm -rf xxx。
function emptyDir(dir) {if (!fs.existsSync(dir)) {return;}for (const file of fs.readdirSync(dir)) {fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });}
}
// determine template
template = variant || framework || template;
console.log(`\nScaffolding project in ${root}...`);
// 获取模板对应文件夹
const templateDir = path.resolve(fileURLToPath(import.meta.url), '..', `template-${template}`);
const write = (file, content) => {//这里的 renameFiles,是因为在某些编辑器或者电脑上不支持.gitignore。const targetPath = renameFiles[file] ? path.join(root, renameFiles[file]) : path.join(root, file);// 如果要针对某个文件做自定义写入if (content) {fs.writeFileSync(targetPath, content);} else {copy(path.join(templateDir, file), targetPath);}
};
const files = fs.readdirSync(templateDir);
for (const file of files.filter((f) => f !== 'package.json')) {write(file);
}
/* package.json 文件单独处理 */
// 将 json 数据转为 js 对象
const pkg = JSON.parse(fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8'));
// 修改项目名为输入的或文件夹名
pkg.name = packageName || getProjectName();
// 将修改后的内容转为 json 格式,写入文件
write('package.json', JSON.stringify(pkg, null, 2));
/* --------- */
/* 打印安装完成后的信息 */
// 获取包管理软件的信息
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
const pkgManager = pkgInfo ? pkgInfo.name : 'npm';
console.log(`\nDone. Now run:\n`);
// 如果命令行不在项目根目录
if (root !== cwd) {console.log(`cd ${path.relative(cwd, root)}`);
}
switch (pkgManager) {case 'yarn':console.log('yarn');console.log('yarn dev');break;default:console.log(`${pkgManager} install`);console.log(`${pkgManager} run dev`);break;
}
console.log();
如果是文件夹用 copyDir 拷贝
function copy(src, dest) {const stat = fs.statSync(src);if (stat.isDirectory()) {copyDir(src, dest);} else {fs.copyFileSync(src, dest);}
}
/**
* @param {string} srcDir
* @param {string} destDir
*/
function copyDir(srcDir, destDir) {fs.mkdirSync(destDir, { recursive: true });for (const file of fs.readdirSync(srcDir)) {const srcFile = path.resolve(srcDir, file);const destFile = path.resolve(destDir, file);copy(srcFile, destFile);}
}
resolve 和 join 都用来进行路径片段的连接,但是区别有两点:
1.resolve 会生成绝对路径,而 join 只是返回当前连接的路径。
2.resolve 会以最后出现的 ‘/’为起点,作为根路径,忽略前面的片段,而 join 不会。
console.log(path.resolve()) // returns /workspace/demo
console.log(path.resolve('')) // returns /workspace/demo
console.log(path.resolve(\_\_dirname)) // returns /workspace/demo
console.log(path.resolve('/img/books', '/net')) // returns '/net'
console.log(path.resolve('img/books', '/net')) // returns '/net'
console.log(path.resolve('img/books', './net')) // returns '/workspace/demo/img/books/net'
console.log(path.resolve('/img/books', './net')) // returns '/img/books/net'
console.log(path.resolve('/img/books', 'net')) // returns '/img/books/net'
console.log(path.resolve('/img/books', '../net')) // returns '/img/net'
console.log(path.resolve('src','/img/books', '../net')) // returns '/img/net'
console.log(path.resolve('src','./img/books', '../net')) // returns '/workspace/demo/src/img/net'
console.log(path.resolve('src','img/books', '../net')) // returns '/workspace/demo/src/img/net'
path.join('/img', 'book', 'net/abc', 'inter', '..'); // returns /img/book/net/abc
console.log(path.join('/img/books', '../net')) // returns /img/net
console.log(path.join('img/books', '../net')) // returns img/net
console.log(path.join('/img/books', './net')) // returns /img/books/net
console.log(path.join('img/books', './net')) // returns img/books/net
console.log(path.join('/img/books', 'net')) // returns /img/books/net
console.log(path.join('img/books', 'net')) // returns /img/books/net
console.log(path.join('/img/books', '/net')) // returns /img/books/net
console.log(path.join('img/books', '/net')) // returns img/books/net
最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。
有需要的小伙伴,可以点击下方卡片领取,无偿分享