create-vite 源码阅读

前言

create-vite 主要是用于创建一个项目并根据用户选择配置的template将模板文件写入当前创建的目录中。 vite提供了多个模板及其ts版本。

重要插件

使用minimist解析命令行参数使用prompts包来实现命令行指引配置的功能。使用kolorist包实现不同颜色的关键词。

来看一下这些包的简单使用,方便后续查看create-vite中的源码。

minimist

通过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 } 

prompts

轻量级,美观且用户友好的交互式提示库。

单个提示的传入一个对象即可

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

typenull时可以跳过当前这个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);
})(); 

kolorist

用于输出不同颜色的字符。

const { yellow, green, cyan, blue } = require('kolorist');

console.log(yellow('yellow'));
console.log(green('green'));
console.log(cyan('cyan'));
console.log(blue('blue')); 

源码解析 create-vite/index.js

源码可以去 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(); 

延伸函数 copy && copyDir

如果是文件夹用 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 的区别

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个技巧》。内容比较详实,对各个知识点的讲解也十分到位。

create-vite 源码阅读_第1张图片
create-vite 源码阅读_第2张图片
create-vite 源码阅读_第3张图片

有需要的小伙伴,可以点击下方卡片领取,无偿分享

你可能感兴趣的:(javascript,前端,html)