create-vite 源码阅读

本文详细介绍了create-vite的源码,包括使用minimist解析命令行参数,prompts进行交互式提示,kolorist实现彩色输出。深入解析了`create-vite/index.js`,探讨了配置项目名、处理文件夹操作及模板写入等步骤,同时还讲解了Node.js中`resolve`和`join`的区别。文章末尾推荐了一本关于Vue的实用技巧文档。

前言

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



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

<think>我们正在回答用户关于electron-vite软件的功能和使用指南的问题。根据之前的引用,我们可以总结出以下信息: 引用[1]提到:Electron-Vite 示例库指南为新老开发者提供了清晰的路径,无论是快速启动一个新项目还是探索Electron与Vite的深度整合,都能在此找到实用的信息与灵感。 引用[2]提到:Electron-Vite 开源项目指南,通过三个核心部分(项目目录结构、启动文件和配置文件)来解析此项目。 引用[3]提到:Electron-Vite-Boilerplate 是一个简单的 Electron 和 Vite 集成模板,基于TypeScript,提供开箱即用的功能。 因此,我们可以将回答分为以下几个部分: 1. Electron-Vite 简介 2. 主要功能 3. 使用指南(包括安装、项目结构、启动和配置等) 4. 相关资源 注意:由于引用内容有限,我们将结合已知的Electron和Vite知识以及引用中提到的信息进行回答,并确保真实可靠。 回答结构: - 首先介绍Electron-Vite是什么 - 然后介绍其主要功能 - 接着提供使用指南,分步骤说明 - 最后给出相关资源建议 同时,按照用户要求,生成相关问题。</think>### Electron-Vite 功能与使用指南 #### 一、Electron-Vite 简介 Electron-Vite 是一个现代化前端开发框架,将 **Electron**(跨平台桌面应用框架)与 **Vite**(下一代前端构建工具)深度整合。它通过优化开发体验和构建效率,解决了传统 Electron 开发中启动慢、热更新卡顿等问题[^1]。 #### 二、核心功能 1. **极速开发体验** - 利用 Vite 的即时服务器启动(< 100ms)和热模块替换(HMR) - 支持 Electron 主进程、渲染进程和预加载脚本的独立热重载 - 示例:修改代码后界面实时更新,无需手动刷新 2. **一体化配置** - 统一管理 Electron 和 Vite 的配置(`electron.vite.config.ts`) - 支持 TypeScript 开箱即用 - 内置进程通信安全处理机制 3. **开箱即用模板** - 提供标准化项目结构: ``` ├── src │ ├── main (主进程代码) │ ├── renderer (渲染进程代码) │ └── preload (预加载脚本) ├── electron.vite.config.ts └── package.json ``` 引用自项目目录结构指南[^2] 4. **跨平台构建** - 一键生成 Windows/macOS/Linux 应用安装包 - 自动处理原生模块兼容性问题 #### 三、使用指南 1. **快速创建项目** ```bash npm create electron-vite@latest my-app cd my-app npm install ``` 2. **开发模式运行** ```bash npm run dev # 同时启动Electron和Vite开发服务器 ``` 3. **项目配置** 修改 `electron.vite.config.ts` 实现: - 自定义入口文件 - 配置渲染器框架(React/Vue/Svelte) - 设置打包优化参数 ```typescript // 示例:配置React渲染器 import { defineConfig } from 'electron-vite' import react from '@vitejs/plugin-react' export default defineConfig({ renderer: { plugins: [react()] }, main: { build: { outDir: 'dist/main' } } }) ``` 引用自配置文件指南[^2] 4. **生产环境构建** ```bash npm run build # 生成可执行文件到dist目录 ``` #### 四、最佳实践 1. **进程通信安全** - 使用预加载脚本暴露有限 API: ```javascript // preload/index.js contextBridge.exposeInMainWorld('api', { send: (channel) => ipcRenderer.send(channel) }) ``` 2. **性能优化** - 启用 Vite 的 `build.minify` 压缩代码 - 使用 `@vitejs/plugin-legacy` 支持旧版浏览器 3. **调试技巧** - 主进程:`console.log` 输出到终端 - 渲染进程:浏览器开发者工具(Ctrl+Shift+I) > 提示:始终参考官方文档和示例源码获取最新实践方案[^1][^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值