1. 程式人生 > 實用技巧 >從npm init vite-app <project-name>學到的知識記錄

從npm init vite-app <project-name>學到的知識記錄

起初我想通過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 的開發工具.