热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Vue团队公开快如闪电的全新脚手架工具,未来将替代VueCLI,才300余行代码,学它

1.前言美国时间2021年10月7日早晨,Vue团队等主要贡献者举办了一个VueContributorDays在线会议,蒋豪群(知乎胖茶[

1. 前言

美国时间 2021 年 10 月 7 日早晨,Vue 团队等主要贡献者举办了一个 Vue Contributor Days 在线会议,蒋豪群(知乎胖茶[2],Vue.js 官方团队成员,Vue-CLI 核心开发),在会上公开了`create-vue`,一个全新的脚手架工具。

create-vue使用npm init vue@next一行命令,就能快如闪电般初始化好基于viteVue3项目。

本文就是通过调试和大家一起学习这个300余行的源码。

阅读本文,你将学到:

1. 学会全新的官方脚手架工具 create-vue 的使用和原理
2. 学会使用 VSCode 直接打开 github 项目
3. 学会使用测试用例调试源码
4. 学以致用,为公司初始化项目写脚手架工具。
5. 等等

2. 使用 npm init vue@next 初始化 vue3 项目


create-vue github README[4]上写着,An easy way to start a Vue project。一种简单的初始化vue项目的方式。

npm init vue@next
估计大多数读者,第一反应是这样竟然也可以,这么简单快捷?

忍不住想动手在控制台输出命令,我在终端试过,见下图。

npm init vue@next

最终cd vue3-projectnpm install 、npm run dev打开页面http://localhost:3000

初始化页面

2.1 npm init && npx

为啥 npm init 也可以直接初始化一个项目,带着疑问,我们翻看 npm 文档。

npm init

npm init 用法:

npm init [--force|-f|--yes|-y|--scope]
npm init <&#64;scope> (same as &#96;npx <&#64;scope>/create&#96;)
npm init [<&#64;scope>/] (same as &#96;npx [<&#64;scope>/]create-&#96;)

npm init  时转换成npx命令&#xff1a;

  • npm init foo -> npx create-foo

  • npm init &#64;usr/foo -> npx &#64;usr/create-foo

  • npm init &#64;usr -> npx &#64;usr/create

看完文档&#xff0c;我们也就理解了&#xff1a;

# 运行
npm init vue&#64;next
# 相当于
npx create-vue&#64;next

 

我们可以在这里create-vue&#xff0c;找到一些信息。或者在npm create-vue找到版本等信息。

其中&#64;next是指定版本&#xff0c;通过npm dist-tag ls create-vue命令可以看出&#xff0c;next版本目前对应的是3.0.0-beta.6

npm dist-tag ls create-vue
- latest: 3.0.0-beta.6
- next: 3.0.0-beta.6

 

发布时 npm publish --tag next 这种写法指定 tag。默认标签是latest。

可能有读者对 npx 不熟悉&#xff0c;这时找到阮一峰老师博客 npx 介绍、nodejs.cn npx

npx 是一个非常强大的命令&#xff0c;从 npm 的 5.2 版本&#xff08;发布于 2017 年 7 月&#xff09;开始可用。

简单说下容易忽略且常用的场景&#xff0c;npx有点类似小程序提出的随用随走。

轻松地运行本地命令

node_modules/.bin/vite -v
# vite/2.6.5 linux-x64 node-v14.16.0# 等同于
# package.json script: "vite -v"
# npm run vitenpx vite -v
# vite/2.6.5 linux-x64 node-v14.16.0

使用不同的 Node.js 版本运行代码某些场景下可以临时切换 node 版本&#xff0c;有时比 nvm 包管理方便些。

npx node&#64;14 -v
# v14.18.0npx -p node&#64;14 node -v
# v14.18.0

 无需安装的命令执行

# 启动本地静态服务
npx http-server

# 无需全局安装
npx &#64;vue/cli create vue-project
# &#64;vue/cli 相比 npm init vue&#64;next npx create-vue&#64;next 很慢。# 全局安装
npm i -g &#64;vue/cli
vue create vue-project

 

npx vue-cli
npm init vue&#64;next &#xff08;npx create-vue&#64;next&#xff09; 快的原因&#xff0c;主要在于依赖少&#xff08;能不依赖包就不依赖&#xff09;&#xff0c;源码行数少&#xff0c;目前index.js只有300余行。

3.2 package.json 分析

// create-vue/package.json
{"name": "create-vue","version": "3.0.0-beta.6","description": "An easy way to start a Vue project","type": "module","bin": {"create-vue": "outfile.cjs"},
}

bin指定可执行脚本。也就是我们可以使用 npx create-vue 的原因。

outfile.cjs 是打包输出的JS文件

{"scripts": {"build": "esbuild --bundle index.js --format&#61;cjs --platform&#61;node --outfile&#61;outfile.cjs","snapshot": "node snapshot.js","pretest": "run-s build snapshot","test": "node test.js"},
}

执行 npm run test 时&#xff0c;会先执行钩子函数 pretest。run-s 是 npm-run-all[13] 提供的命令。run-s build snapshot 命令相当于 npm run build && npm run snapshot。

根据脚本提示&#xff0c;我们来看 snapshot.js 文件。


3.3 生成快照 snapshot.js


这个文件主要作用是根据const featureFlags &#61; [&#39;typescript&#39;, &#39;jsx&#39;, &#39;router&#39;, &#39;vuex&#39;, &#39;with-tests&#39;] 组合生成31种加上 default 共计 32种 组合&#xff0c;生成快照在 playground目录。

因为打包生成的 outfile.cjs 代码有做一些处理&#xff0c;不方便调试&#xff0c;我们可以修改为index.js便于调试。
 

// 路径 create-vue/snapshot.js
const bin &#61; path.resolve(__dirname, &#39;./outfile.cjs&#39;)
// 改成 index.js 便于调试
const bin &#61; path.resolve(__dirname, &#39;./index.js&#39;)

 

 

我们可以在for和 createProjectWithFeatureFlags 打上断点。

createProjectWithFeatureFlags其实类似在终端输入如下执行这样的命令

node ./index.js --xxx --xxx --force

function createProjectWithFeatureFlags(flags) {const projectName &#61; flags.join(&#39;-&#39;)console.log(&#96;Creating project ${projectName}&#96;)const { status } &#61; spawnSync(&#39;node&#39;,[bin, projectName, ...flags.map((flag) &#61;> &#96;--${flag}&#96;), &#39;--force&#39;],{cwd: playgroundDir,stdio: [&#39;pipe&#39;, &#39;pipe&#39;, &#39;inherit&#39;]})if (status !&#61;&#61; 0) {process.exit(status)}
}// 路径 create-vue/snapshot.js
for (const flags of flagCombinations) {createProjectWithFeatureFlags(flags)
}

调试&#xff1a;VSCode打开项目&#xff0c;VSCode高版本(1.50&#43;)可以在 create-vue/package.json &#61;> scripts &#61;> "test": "node test.js"。鼠标悬停在test上会有调试脚本提示&#xff0c;选择调试脚本。如果对调试不熟悉&#xff0c;可以看我之前的文章koa-compose

调试时&#xff0c;大概率你会遇到&#xff1a;create-vue/index.js 文件中&#xff0c;__dirname 报错问题。可以按照如下方法解决。在 import 的语句后&#xff0c;添加如下语句&#xff0c;就能愉快的调试了。

// 路径 create-vue/index.js
// 解决办法和nodejs issues
// https://stackoverflow.com/questions/64383909/dirname-is-not-defined-in-node-14-version
// https://github.com/nodejs/help/issues/2907import { fileURLToPath } from &#39;url&#39;;
import { dirname } from &#39;path&#39;;const __filename &#61; fileURLToPath(import.meta.url);
const __dirname &#61; dirname(__filename);

接着我们调试 index.js 文件&#xff0c;来学习。

4. 调试 index.js 主流程

回顾下上文 npm init vue&#64;next 初始化项目的。

npm init vue&#64;next

单从初始化项目输出图来看。主要是三个步骤。

1. 输入项目名称&#xff0c;默认值是 vue-project
2. 询问一些配置 渲染模板等
3. 完成创建项目&#xff0c;输出运行提示 

 

async function init() {// 省略放在后文详细讲述
}// async 函数返回的是Promise 可以用 catch 报错
init().catch((e) &#61;> {console.error(e)
})

4.1 解析命令行参数

// 返回运行当前脚本的工作目录的路径。
const cwd &#61; process.cwd()
// possible options:
// --default
// --typescript / --ts
// --jsx
// --router / --vue-router
// --vuex
// --with-tests / --tests / --cypress
// --force (for force overwriting)
const argv &#61; minimist(process.argv.slice(2), {alias: {typescript: [&#39;ts&#39;],&#39;with-tests&#39;: [&#39;tests&#39;, &#39;cypress&#39;],router: [&#39;vue-router&#39;]},// all arguments are treated as booleansboolean: true
})

minimist

简单说&#xff0c;这个库&#xff0c;就是解析命令行参数的。看例子&#xff0c;我们比较容易看懂传参和解析结果。

$ node example/parse.js -a beep -b boop
{ _: [], a: &#39;beep&#39;, b: &#39;boop&#39; }$ node example/parse.js -x 3 -y 4 -n5 -abc --beep&#61;boop foo bar baz
{ _: [ &#39;foo&#39;, &#39;bar&#39;, &#39;baz&#39; ],x: 3,y: 4,n: 5,a: true,b: true,c: true,beep: &#39;boop&#39; }

比如

npm init vue&#64;next --vuex --force

4.2 如果设置了 feature flags 跳过 prompts 询问

这种写法方便代码测试等。直接跳过交互式询问&#xff0c;同时也可以省时间。

// if any of the feature flags is set, we would skip the feature prompts// use &#96;??&#96; instead of &#96;||&#96; once we drop Node.js 12 supportconst isFeatureFlagsUsed &#61;typeof (argv.default || argv.ts || argv.jsx || argv.router || argv.vuex || argv.tests) &#61;&#61;&#61;&#39;boolean&#39;// 生成目录let targetDir &#61; argv._[0]// 默认 vue-projectsconst defaultProjectName &#61; !targetDir ? &#39;vue-project&#39; : targetDir// 强制重写文件夹&#xff0c;当同名文件夹存在时const forceOverwrite &#61; argv.force

4.3 交互式询问一些配置

如上文npm init vue&#64;next 初始化的图示

  • 输入项目名称

  • 还有是否删除已经存在的同名目录

  • 询问使用需要 JSX Router vuex cypress 等。

 

 

let result &#61; {}try {// Prompts:// - Project name:// - whether to overwrite the existing directory or not?// - enter a valid package name for package.json// - Project language: Javascript / TypeScript// - Add JSX Support?// - Install Vue Router for SPA development?// - Install Vuex for state management? (TODO)// - Add Cypress for testing?result &#61; await prompts([{name: &#39;projectName&#39;,type: targetDir ? null : &#39;text&#39;,message: &#39;Project name:&#39;,initial: defaultProjectName,onState: (state) &#61;> (targetDir &#61; String(state.value).trim() || defaultProjectName)},// 省略若干配置{name: &#39;needsTests&#39;,type: () &#61;> (isFeatureFlagsUsed ? null : &#39;toggle&#39;),message: &#39;Add Cypress for testing?&#39;,initial: false,active: &#39;Yes&#39;,inactive: &#39;No&#39;}],{onCancel: () &#61;> {throw new Error(red(&#39;✖&#39;) &#43; &#39; Operation cancelled&#39;)}}])} catch (cancelled) {console.log(cancelled.message)// 退出当前进程。process.exit(1)}

4.4 初始化询问用户给到的参数&#xff0c;同时也会给到默认值 

// &#96;initial&#96; won&#39;t take effect if the prompt type is null// so we still have to assign the default values hereconst {packageName &#61; toValidPackageName(defaultProjectName),shouldOverwrite,needsJsx &#61; argv.jsx,needsTypeScript &#61; argv.typescript,needsRouter &#61; argv.router,needsVuex &#61; argv.vuex,needsTests &#61; argv.tests} &#61; resultconst root &#61; path.join(cwd, targetDir)// 如果需要强制重写&#xff0c;清空文件夹if (shouldOverwrite) {emptyDir(root)// 如果不存在文件夹&#xff0c;则创建} else if (!fs.existsSync(root)) {fs.mkdirSync(root)}// 脚手架项目目录console.log(&#96;\nScaffolding project in ${root}...&#96;)// 生成 package.json 文件const pkg &#61; { name: packageName, version: &#39;0.0.0&#39; }fs.writeFileSync(path.resolve(root, &#39;package.json&#39;), JSON.stringify(pkg, null, 2))

4.5 根据模板文件生成初始化项目所需文件

// todo:// work around the esbuild issue that &#96;import.meta.url&#96; cannot be correctly transpiled// when bundling for node and the format is cjs// const templateRoot &#61; new URL(&#39;./template&#39;, import.meta.url).pathnameconst templateRoot &#61; path.resolve(__dirname, &#39;template&#39;)const render &#61; function render(templateName) {const templateDir &#61; path.resolve(templateRoot, templateName)renderTemplate(templateDir, root)}// Render base templaterender(&#39;base&#39;)// 添加配置// Add configs.if (needsJsx) {render(&#39;config/jsx&#39;)}if (needsRouter) {render(&#39;config/router&#39;)}if (needsVuex) {render(&#39;config/vuex&#39;)}if (needsTests) {render(&#39;config/cypress&#39;)}if (needsTypeScript) {render(&#39;config/typescript&#39;)}

4.6 渲染生成代码模板

// Render code template.// prettier-ignoreconst codeTemplate &#61;(needsTypeScript ? &#39;typescript-&#39; : &#39;&#39;) &#43;(needsRouter ? &#39;router&#39; : &#39;default&#39;)render(&#96;code/${codeTemplate}&#96;)// Render entry file (main.js/ts).if (needsVuex && needsRouter) {render(&#39;entry/vuex-and-router&#39;)} else if (needsVuex) {render(&#39;entry/vuex&#39;)} else if (needsRouter) {render(&#39;entry/router&#39;)} else {render(&#39;entry/default&#39;)}

4.7 如果配置了需要 ts


重命名所有的 .js 文件改成 .ts。重命名 jsconfig.json 文件为 tsconfig.json 文件。

jsconfig.json 是VSCode的配置文件&#xff0c;可用于配置跳转等。

把index.html 文件里的 main.js 重命名为 main.ts。

// Cleanup.if (needsTypeScript) {// rename all &#96;.js&#96; files to &#96;.ts&#96;// rename jsconfig.json to tsconfig.jsonpreOrderDirectoryTraverse(root,() &#61;> {},(filepath) &#61;> {if (filepath.endsWith(&#39;.js&#39;)) {fs.renameSync(filepath, filepath.replace(/\.js$/, &#39;.ts&#39;))} else if (path.basename(filepath) &#61;&#61;&#61; &#39;jsconfig.json&#39;) {fs.renameSync(filepath, filepath.replace(/jsconfig\.json$/, &#39;tsconfig.json&#39;))}})// Rename entry in &#96;index.html&#96;const indexHtmlPath &#61; path.resolve(root, &#39;index.html&#39;)const indexHtmlContent &#61; fs.readFileSync(indexHtmlPath, &#39;utf8&#39;)fs.writeFileSync(indexHtmlPath, indexHtmlContent.replace(&#39;src/main.js&#39;, &#39;src/main.ts&#39;))}

4.8 配置了不需要测试

因为所有的模板都有测试文件&#xff0c;所以不需要测试时&#xff0c;执行删除 cypress/__tests__/ 文件夹

if (!needsTests) {// All templates assumes the need of tests.// If the user doesn&#39;t need it:// rm -rf cypress **/__tests__/preOrderDirectoryTraverse(root,(dirpath) &#61;> {const dirname &#61; path.basename(dirpath)if (dirname &#61;&#61;&#61; &#39;cypress&#39; || dirname &#61;&#61;&#61; &#39;__tests__&#39;) {emptyDir(dirpath)fs.rmdirSync(dirpath)}},() &#61;> {})}

4.9 根据使用的 npm / yarn / pnpm 生成README.md 文件&#xff0c;给出运行项目的提示

// Instructions:// Supported package managers: pnpm > yarn > npm// Note: until is resolved,// it is not possible to tell if the command is called by &#96;pnpm init&#96;.const packageManager &#61; /pnpm/.test(process.env.npm_execpath)? &#39;pnpm&#39;: /yarn/.test(process.env.npm_execpath)? &#39;yarn&#39;: &#39;npm&#39;// README generationfs.writeFileSync(path.resolve(root, &#39;README.md&#39;),generateReadme({projectName: result.projectName || defaultProjectName,packageManager,needsTypeScript,needsTests}))console.log(&#96;\nDone. Now run:\n&#96;)if (root !&#61;&#61; cwd) {console.log(&#96; ${bold(green(&#96;cd ${path.relative(cwd, root)}&#96;))}&#96;)}console.log(&#96; ${bold(green(getCommand(packageManager, &#39;install&#39;)))}&#96;)console.log(&#96; ${bold(green(getCommand(packageManager, &#39;dev&#39;)))}&#96;)console.log()

5. npm run test &#61;> node test.js 测试

// create-vue/test.js
import fs from &#39;fs&#39;
import path from &#39;path&#39;
import { fileURLToPath } from &#39;url&#39;import { spawnSync } from &#39;child_process&#39;const __dirname &#61; path.dirname(fileURLToPath(import.meta.url))
const playgroundDir &#61; path.resolve(__dirname, &#39;./playground/&#39;)for (const projectName of fs.readdirSync(playgroundDir)) {if (projectName.endsWith(&#39;with-tests&#39;)) {console.log(&#96;Running unit tests in ${projectName}&#96;)const unitTestResult &#61; spawnSync(&#39;pnpm&#39;, [&#39;test:unit:ci&#39;], {cwd: path.resolve(playgroundDir, projectName),stdio: &#39;inherit&#39;,shell: true})if (unitTestResult.status !&#61;&#61; 0) {throw new Error(&#96;Unit tests failed in ${projectName}&#96;)}console.log(&#96;Running e2e tests in ${projectName}&#96;)const e2eTestResult &#61; spawnSync(&#39;pnpm&#39;, [&#39;test:e2e:ci&#39;], {cwd: path.resolve(playgroundDir, projectName),stdio: &#39;inherit&#39;,shell: true})if (e2eTestResult.status !&#61;&#61; 0) {throw new Error(&#96;E2E tests failed in ${projectName}&#96;)}}
}

主要对生成快照时生成的在 playground 32个文件夹&#xff0c;进行如下测试。

pnpm test:unit:cipnpm test:e2e:ci


推荐阅读
  • 微信民众号商城/小顺序商城开源项目介绍及使用教程
    本文介绍了一个基于WeiPHP5.0开发的微信民众号商城/小顺序商城的开源项目,包括前端和后端的目录结构,以及所使用的技术栈。同时提供了项目的运行和打包方法,并分享了一些调试和开发经验。最后还附上了在线预览和GitHub商城源码的链接,以及加入前端交流QQ群的方式。 ... [详细]
  • Node.js学习笔记(一)package.json及cnpm
    本文介绍了Node.js中包的概念,以及如何使用包来统一管理具有相互依赖关系的模块。同时还介绍了NPM(Node Package Manager)的基本介绍和使用方法,以及如何通过NPM下载第三方模块。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 本文讨论了如何使用Web.Config进行自定义配置节的配置转换。作者提到,他将msbuild设置为详细模式,但转换却忽略了带有替换转换的自定义部分的存在。 ... [详细]
  • 本文介绍了JavaScript进化到TypeScript的历史和背景,解释了TypeScript相对于JavaScript的优势和特点。作者分享了自己对TypeScript的观察和认识,并提到了在项目开发中使用TypeScript的好处。最后,作者表示对TypeScript进行尝试和探索的态度。 ... [详细]
  • loader资源模块加载器webpack资源模块加载webpack内部(内部loader)默认只会处理javascript文件,也就是说它会把打包过程中所有遇到的 ... [详细]
  • Vue基础一、什么是Vue1.1概念Vue(读音vjuː,类似于view)是一套用于构建用户界面的渐进式JavaScript框架,与其它大型框架不 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
author-avatar
码天下
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有