携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第9期,链接:www.yuque.com/ruochuan12/…。
摘要
今天学习的源码内容是create-vue,一个基于 vite 创建 vue 项目的脚手架。就好比之前常用的 Vue CLI 是基于 webpack 创建 vue 项目的。
create-vue 初体验
首先我们要执行一下,npm init vue@3 。然后有若干配置项,配置选择完毕后,项目创建就成功啦,非常的快。
然后一次执行后三条命令,启动项目,打开本地地址,就可以看到项目的首页。
这里我们研究下这个命令是怎么回事——npm init vue@3 。npm init 的官网介绍
实际上执行 npm init vue@3 就是执行了 npm exec create-vue@3
PS:在这里我使用 node@16.16.0 版本执行命令 会报如下错误,还没弄清是从哪块出现的问题:
源码分析
接下来,就是对create-vue 的源码进行分析。
开启调试
我这里使用的是 VSCode,首先选择需要调试的文件,这里就是 index.js , 然后如图点击运行与调试
就进入了调试模式啦!
介绍引入的包
minimist 是一个用于处理命令行调用node指令时,处理node之后的一系列参数的模块。
prompts 轻量级、美观、用户友好的交互式提示。
kolorist 给控制台输入内容上色。
import minimist from 'minimist'
import prompts from 'prompts'
import { red, green, bold } from 'kolorist'
后续调试中会遇到 __dirname is not defined, 问题这里添加如下内容来解决
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
关键函数 init
获取路径、参数
让我们看看关键的异步函数 async function init(),这个函数就是我们执行 时会调用的函数,可以携带如下命令行参数:
- possible options:
- --default
- --typescript / --ts
- --jsx
- --router / --vue-router
- --vuex
- --with-tests / --tests / --cypress
- --force (for force overwriting)
首先获取当前脚本工作目录 cwd (pathname of the current working directory) ,以及执行命令时携带的参数。
const cwd = process.cwd()
const argv = minimist(process.argv.slice(2), {
alias: {
typescript: ['ts'],
'with-tests': ['tests', 'cypress'],
router: ['vue-router']
},
// all arguments are treated as booleans
boolean: true
})
交互式配置选项
然后是一些交互式的选项配置内容,例如下面的就是让用户输入项目名称,默认是 vue-project。
{
name: 'projectName',
type: targetDir ? null : 'text',
message: 'Project name:',
initial: defaultProjectName,
onState: (state) => (targetDir = String(state.value).trim() || defaultProjectName)
},
此外还包括如图的一些配置项:
根据配置复制模板
当获得了用户的配置之后,脚本接下来的工作内容就是将如下的模板文件,复制到用户创建项目的位置,基本的文件都在base 里,其它文件夹下的文件内容大家可以自行查看,这里就不列举啦。
以下就是将base的代码复制过去,其中的关键函数是renderTemplate, 位于 utils\renderTemplate.js,它的功能就是递归地将文件复制。
const render = function render(templateName) {
const templateDir = path.resolve(templateRoot, templateName)
renderTemplate(templateDir, root)
}
// Render base template
render('base')
我觉得这里实际上做的就是复制粘贴的工作,只不过它是定制化的。而我个人的工作经验遇到的情况都是,有人做了一个面向项目组的或者小组的这么一个模板,放到 git 上,然后有新项目了就拉下来删删改改的。目前还没遇到做一个这样的定制化脚手架,能够提高项目创建效率的用武之地。
配置了 ts
如果配置了 ts,那么就要将所有 js 结尾的文件重命名为 ts结尾的文件。同时也要修改入口文件里的引入文件名称。
if (needsTypeScript) {
// rename all `.js` files to `.ts`
// rename jsconfig.json to tsconfig.json
preOrderDirectoryTraverse(
root,
() => {},
(filepath) => {
if (filepath.endsWith('.js')) {
fs.renameSync(filepath, filepath.replace(/\.js$/, '.ts'))
} else if (path.basename(filepath) === 'jsconfig.json') {
fs.renameSync(filepath, filepath.replace(/jsconfig\.json$/, 'tsconfig.json'))
}
}
)
// Rename entry in `index.html`
const indexHtmlPath = path.resolve(root, 'index.html')
const indexHtmlContent = fs.readFileSync(indexHtmlPath, 'utf8')
fs.writeFileSync(indexHtmlPath, indexHtmlContent.replace('src/main.js', 'src/main.ts'))
}
不需要测试
项目创建的时候默认配置是带测试的,如果不需要测试,那么会删除相应的文件
if (!needsTests) {
// All templates assumes the need of tests.
// If the user doesn't need it:
// rm -rf cypress **/__tests__/
preOrderDirectoryTraverse(
root,
(dirpath) => {
const dirname = path.basename(dirpath)
if (dirname === 'cypress' || dirname === '__tests__') {
emptyDir(dirpath)
fs.rmdirSync(dirpath)
}
},
() => {}
)
}
生成 readme
根据创建项目时使用的包管理器,来生成相应的版本的 readme 文件
const packageManager = /pnpm/.test(process.env.npm_execpath)
? 'pnpm'
: /yarn/.test(process.env.npm_execpath)
? 'yarn'
: 'npm'
// README generation
fs.writeFileSync(
path.resolve(root, 'README.md'),
generateReadme({
projectName: result.projectName || defaultProjectName,
packageManager,
needsTypeScript,
needsTests
})
)
总结与收获
本次源码阅读了解了 create-vue 脚手架是如何创建项目的,本项目的源码比较简单,易于理解。收获如下:
- 了解了手脚架的基本功能;
- 了解了
minimistpromptskolorist三个包的功能; - 学会了在 VSCode 中直接调试文件的方法;