首页 > 技术文章 > 从npm init vite-app <project-name>学到的知识记录

wuzhiquan 2020-12-24 12:17 原文

起初我想通过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的说明:

* npm init foo -> npx create-foo
* npm init @usr/foo -> npx @usr/create-foo
* npm init @usr -> npx @usr/create

所以: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字段:

  "bin": {
    "create-vite-app": "index.js",
    "cva": "index.js"
  },

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

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

#!/usr/bin/env node

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

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

[https://docs.npmjs.com/cli/v6/commands/npm-init](npm init)

initializer in this case is an npm package named create-<initializer>, which will be installed by npx,
and then have its main bin executed -- presumably creating or updating package.json
and running any other initialization-related operations.

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

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

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

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

async function init() {
  const targetDir = argv._[0] || '.'
  const cwd = process.cwd()
  const root = path.join(cwd, targetDir)
  const renameFiles = {
    _gitignore: '.gitignore',
  }
  console.log(`Scaffolding project in ${root}...`)

  /**
   * 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,并退出程序
   */
  await fs.ensureDir(root)
  const existing = await fs.readdir(root)
  if (existing.length) {
    console.error(`Error: target directory is not empty.`)
    process.exit(1)
  }

  /**
   * 往下执行完便成功,会生成模板,并输出相应的console.log
   */

  const templateDir = path.join(
    __dirname,
    `template-${argv.t || argv.template || 'vue'}`
  )
  const write = async (file, content) => {
    const targetPath = renameFiles[file]
      ? path.join(root, renameFiles[file])
      : path.join(root, file)
    if (content) {
      await fs.writeFile(targetPath, content)
    } else {
      await fs.copy(path.join(templateDir, file), targetPath)
    }
  }

  const files = await fs.readdir(templateDir)
  for (const file of files.filter((f) => f !== 'package.json')) {
    await write(file)
  }

  const pkg = require(path.join(templateDir, `package.json`))
  pkg.name = path.basename(root)
  await write('package.json', JSON.stringify(pkg, null, 2))

  console.log(`\nDone. Now run:\n`)
  if (root !== cwd) {
    console.log(`  cd ${path.relative(cwd, root)}`)
  }
  console.log(`  npm install (or \`yarn\`)`)
  console.log(`  npm run dev (or \`yarn dev\`)`)
  console.log()
}

init().catch((e) => {
  console.error(e)
})

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

答:最近想做一个模板生成器,也就是使用命令生成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推荐阮一峰的[http://www.ruanyifeng.com/blog/2019/02/npx.html](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 的开发工具.

推荐阅读