起初我想通过create-vite-app创建一个vite的vue项目,一开始使用全局安装create-vite-app的方法:

npm install -g create-vite-app

全局安装完之后,我们还需要调指令生成项目:

create-vite-app viteApp 或者 cva viteApp

然后我发现create-vite-app在npm官网中安装方法是这样的:

npm init vite-app <project-name>

通过这种方法安装,我感觉跟使用npx是一样的效果,都是避免了全局安装,安装完之后直接执行创建项目,也就是一步可以完成,并且不会产生全局安装(都是临时下载安装,安装完后就删除)
用过create-react-app都知道,目前都推荐使用npx create-react-app 创建react项目,同时也可以使用npm init react-app去创建:

发现create-vite-app和create-react-app前面都有create,于是去npm包官网查看了npm init的说明:

<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-kotlin">* npm <span style="color:#0000ff">init</span> foo -> npx create-foo
* npm <span style="color:#0000ff">init</span> <span style="color:#2b91af">@usr</span>/foo -> npx <span style="color:#2b91af">@usr</span>/create-foo
* npm <span style="color:#0000ff">init</span> <span style="color:#2b91af">@usr</span> -> npx <span style="color:#2b91af">@usr</span>/create
</code></span></span>

所以:npm init vite-app 和npx create-vite-app 是一样的,npm init一个以create-开头的和npx安装是一样的

我们顺便拓展几个问题点:

一、全局安装后,为什么可以在cmd下面使用create-vite-app或cva指令?

答:我们在安装完后,会发现全局包会被安装到这个目录下面:

C:\Users\J0201\AppData\Roaming\npm\node_modules\create-vite-app

同时在C:\Users\J0201\AppData\Roaming\npm下面会生成cmd文件:

node安装后,会默认将C:\Users\J0201\AppData\Roaming\npm添加至环境变量,如果没有添加成功,就手动修改:

(如果提示指令找不到,通常是环境变量没设置对)

有了这个环境变量,就能在cmd下运行当前变量下面的指令,然后按照执行~

二、create-vite-app和cva是怎么生成的?

答:我们打开create-vite-app源码(全局安装后在C:\Users\J0201\AppData\Roaming\npm\node_modules\create-vite-app这个文件夹下面),看下package.json文件,有个bin字段:

<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-json">  <span style="color:#ff0000">"bin"</span><span style="color:#444a44">:</span> <span style="color:#444a44">{</span>
    <span style="color:#ff0000">"create-vite-app"</span><span style="color:#444a44">:</span> <span style="color:#a31515">"index.js"</span><span style="color:#444a44">,</span>
    <span style="color:#ff0000">"cva"</span><span style="color:#444a44">:</span> <span style="color:#a31515">"index.js"</span>
  <span style="color:#444a44">}</span><span style="color:#444a44">,</span>
</code></span></span>

会根据bin字段下的属性生产对应的指令,同时执行指令会执行对应的js,也就是index.js:

同时,index.js首行需要加上,也就是如果该文件作为cmd下运行的文件,需要加上这一段在首行:

#!/usr/bin/env node

三、为何npm init vite-app 和npx create-react-app 不需要执行?

答:这个问题其实很简单,在npm官网有说明:

[npm-init | npm Docs](npm init)

<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-mipsasm">initializer in this case is an npm package named create-<initializer>, which will <span style="color:#0000ff">be </span><span style="color:#0000ff">installed </span><span style="color:#0000ff">by </span>npx,
<span style="color:#0000ff">and </span>then have its main <span style="color:#0000ff">bin </span>executed -- presumably creating <span style="color:#0000ff">or </span>updating package.<span style="color:#0000ff">json
</span><span style="color:#0000ff">and </span>running any other initialization-related operations.

</code></span></span>

也就是安装完后,对应的package.json中的bin对应的脚本会被执行。

四、学习下create-vite-app中的index.js

注释是个人理解加的,非尤大写的(万一哪里说的不对,不能污蔑尤大[哭笑])

<span style="color:#000000"><span style="background-color:#fefef2"><code class="language-javascript language-js"><span style="color:#2b91af">#!/usr/bin/env node</span>
<span style="color:#0000ff">const</span> path = <span style="color:#0000ff">require</span>(<span style="color:#a31515">'path'</span>)
<span style="color:#0000ff">const</span> fs = <span style="color:#0000ff">require</span>(<span style="color:#a31515">'fs-extra'</span>)
<span style="color:#008000">/**
 * process.argv获取到的是一个地址数组,第3项也就是我们前面指令中创建的projectName
 * 通过minimist,argv变量存储的是一个对象,如果没有传-t/-template字段,里面就只有一个属性'_',值是对应的projectName:({ _: projectName })
 * -t/-template是通过指令中传进来的,后面对对应的模板名,templateDir会根据值返回对应的模板,也就是cva viteApp -t vue-ts
 */</span>
<span style="color:#0000ff">const</span> argv = <span style="color:#0000ff">require</span>(<span style="color:#a31515">'minimist'</span>)(process.argv.<span style="color:#a31515">slice</span>(<span style="color:#880000">2</span>))

<span style="color:#0000ff">async</span> <span style="color:#0000ff">function</span> <span style="color:#a31515">init</span>() {
  <span style="color:#0000ff">const</span> targetDir = argv._[<span style="color:#880000">0</span>] || <span style="color:#a31515">'.'</span>
  <span style="color:#0000ff">const</span> cwd = process.<span style="color:#a31515">cwd</span>()
  <span style="color:#0000ff">const</span> root = path.<span style="color:#a31515">join</span>(cwd, targetDir)
  <span style="color:#0000ff">const</span> renameFiles = {
    <span style="color:#ff0000">_gitignore</span>: <span style="color:#a31515">'.gitignore'</span>,
  }
  <span style="color:#008000">console</span>.<span style="color:#a31515">log</span>(<span style="color:#a31515">`Scaffolding project in ${root}...`</span>)

  <span style="color:#008000">/**
   * ensureDir这个是fs创建文件夹,我们在D:根目录下创建,对应的root就是D:/projectNam
   * 如果我们不传projectName,根目录下,root就是D:,fs.ensureDir(root)会返回失败,往下便不再执行。如果不是根目录,则existing长度不为0,表示已经存在该目录,输出Error: target directory is not empty,并退出程序
   * 如果传入的projectName已存在,则existing长度不为0,表示已经存在该目录,输出Error: target directory is not empty,并退出程序
   */</span>
  <span style="color:#0000ff">await</span> fs.<span style="color:#a31515">ensureDir</span>(root)
  <span style="color:#0000ff">const</span> existing = <span style="color:#0000ff">await</span> fs.<span style="color:#a31515">readdir</span>(root)
  <span style="color:#0000ff">if</span> (existing.length) {
    <span style="color:#008000">console</span>.<span style="color:#a31515">error</span>(<span style="color:#a31515">`Error: target directory is not empty.`</span>)
    process.<span style="color:#a31515">exit</span>(<span style="color:#880000">1</span>)
  }

  <span style="color:#008000">/**
   * 往下执行完便成功,会生成模板,并输出相应的console.log
   */</span>

  <span style="color:#0000ff">const</span> templateDir = path.<span style="color:#a31515">join</span>(
    __dirname,
    <span style="color:#a31515">`template-${argv.t || argv.template || <span style="color:#a31515">'vue'</span>}`</span>
  )
  <span style="color:#0000ff">const</span> <span style="color:#a31515">write</span> = <span style="color:#0000ff">async</span> (file, content) => {
    <span style="color:#0000ff">const</span> targetPath = renameFiles[file]
      ? path.<span style="color:#a31515">join</span>(root, renameFiles[file])
      : path.<span style="color:#a31515">join</span>(root, file)
    <span style="color:#0000ff">if</span> (content) {
      <span style="color:#0000ff">await</span> fs.<span style="color:#a31515">writeFile</span>(targetPath, content)
    } <span style="color:#0000ff">else</span> {
      <span style="color:#0000ff">await</span> fs.<span style="color:#a31515">copy</span>(path.<span style="color:#a31515">join</span>(templateDir, file), targetPath)
    }
  }

  <span style="color:#0000ff">const</span> files = <span style="color:#0000ff">await</span> fs.<span style="color:#a31515">readdir</span>(templateDir)
  <span style="color:#0000ff">for</span> (<span style="color:#0000ff">const</span> file <span style="color:#0000ff">of</span> files.<span style="color:#a31515">filter</span>((f) => f !== <span style="color:#a31515">'package.json'</span>)) {
    <span style="color:#0000ff">await</span> <span style="color:#a31515">write</span>(file)
  }

  <span style="color:#0000ff">const</span> pkg = <span style="color:#0000ff">require</span>(path.<span style="color:#a31515">join</span>(templateDir, <span style="color:#a31515">`package.json`</span>))
  pkg.name = path.<span style="color:#a31515">basename</span>(root)
  <span style="color:#0000ff">await</span> <span style="color:#a31515">write</span>(<span style="color:#a31515">'package.json'</span>, <span style="color:#a31515">JSON</span>.<span style="color:#a31515">stringify</span>(pkg, <span style="color:#a31515">null</span>, <span style="color:#880000">2</span>))

  <span style="color:#008000">console</span>.<span style="color:#a31515">log</span>(<span style="color:#a31515">`\nDone. Now run:\n`</span>)
  <span style="color:#0000ff">if</span> (root !== cwd) {
    <span style="color:#008000">console</span>.<span style="color:#a31515">log</span>(<span style="color:#a31515">`  cd ${path.relative(cwd, root)}`</span>)
  }
  <span style="color:#008000">console</span>.<span style="color:#a31515">log</span>(<span style="color:#a31515">`  npm install (or \`yarn\`)`</span>)
  <span style="color:#008000">console</span>.<span style="color:#a31515">log</span>(<span style="color:#a31515">`  npm run dev (or \`yarn dev\`)`</span>)
  <span style="color:#008000">console</span>.<span style="color:#a31515">log</span>()
}

<span style="color:#a31515">init</span>().<span style="color:#a31515">catch</span>((e) => {
  <span style="color:#008000">console</span>.<span style="color:#a31515">error</span>(e)
})

</code></span></span>
折叠

五、我们会了这个,对我们有什么用,如何学以致用?

答:最近想做一个模板生成器,也就是使用命令生成vue的页面模板,生成后简单修改下配置,便是我们的完整页面,这样在团队协助中,能够加快开发速度。我觉得可以使用bin脚本去执行,通过生成cmd指令,执行指令后,结合模板生成器的代码,输出相应的页面。
如果公司有条件搭建了属于公司自己的私库,我们可以学习create-vite-app的做法,创建以create-开头的包,并上传到私库,然后全局安装也好,npm init/npx也好,就可以像create-vite-app一样,直接调用bin中的指令执行。
如果没有私库,代码不担心公开问题可以放npm官网。不行的话可以在当前包下面使用npm link,生成全局命令。npm link后,会把当前的包安装到全局\AppData\Roaming\npm下,同时也会生成bin中的指令,在\AppData\Roaming\npm当前包是以快捷方式的形式直接访问的,所以修改的时候,全局也会跟着修改。比较适合开发的时候。

这一步,我后面会做个简单的demo后会再记录一下。

六、拓展什么是npx和vite

npx推荐阮一峰的[npx 使用教程 - 阮一峰的网络日志](npm 使用教程)

最重要两点:

  1. 调用项目安装的模块
  2. 避免全局安装模块

什么是vite:

作者原话: Vite,一个基于浏览器原生 ES Modules 的开发服务器。利用浏览器去解析模块,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。

Vite(读音类似于[weɪt],法语,快的意思) 是一个由原生 ES Module 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于 Rollup 打包。

Vite的特点:

  • Lightning fast cold server start - 闪电般的冷启动速度
  • Instant hot module replacement (HMR) - 即时热模块更换(热更新)
  • True on-demand compilation - 真正的按需编译

为了实现上述特点,Vite 要求项目完全由 ES Module 模块组成,common.js 模块不能直接在 Vite 上使用。因此不能直接在生产环境使用。在打包上依旧还是使用 rollup 等传统打包工具。因此 Vite 目前更像是一个类似于 webpack-dev-server 的开发工具.

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐