Vue多元件倉庫開發與釋出

Fanz發表於2019-02-25

在開發元件時,我們可能會期望一類元件放在同一個程式碼倉庫下,就像element那樣,我們可以使用element提供的腳手架,也可以使用vue cli 3建立一個更‘新’的專案。

專案建立

通過vue cli 3建立專案,建立資料夾packages用於存放元件。

單個元件目錄

packages下就是每一個元件,每個元件和單獨專案一樣,會有package.jsonREADME.mdsrcdist等檔案及目錄。

如何演示/除錯元件

在元件開發過稱中,我們需要對元件進行展示,所以建立了examples資料夾,用於存放每個元件示例。
通過一個列表展示出所有的元件,點選選擇當前開發的元件,進入對應的example
路由的根就是一個導航列表,然後每個元件對應一個路由,通過一個配置檔案的components.js來生成這個路由。

// 路由
import Navigation from "./Navigation";
import components from "./components";

let routes = components.map(component => ({
  path: `/${component.name}`,
  component: () => import(`../examples/${component.name}`)
}));

routes.unshift({
  path: "",
  component: Navigation
});

export default routes;
複製程式碼

自動化指令碼

建立/編譯/釋出

建立新的元件,需要修改components.js配置檔案,在examplespackages下建立對應目錄。
編譯/釋出元件,因為倉庫下會有多個元件,如果一次釋出多個,就需要進入每個資料夾下執行命令。
上面過程實現自動化,有很多種方式,比如可以通過npm run <script>,可以直接通過node命令等。這裡我參考element,採用了Makefile。

建立script資料夾,其中包括建立指令碼new.js和構建指令碼build.js

建立指令碼

建立指令碼主要就是目錄的建立與檔案的寫入,其中可能需要注意的可能就是格式問題。
一種方式是在``之間,按照規範格式去完成寫入內容,這樣做比較麻煩,而且可能面臨格式化要求修改問題。
另一種方式是在指令碼中引入eslint,指令碼中的eslint.CLIEngine可以根據配置檔案(比如.eslintrc.js)格式化檔案。需要注意的是需要比命令列中配置需要多新增fix: true配置, 如下

const CLIEngine = eslint.CLIEngine;
const cli = new CLIEngine({ ...require("../.eslintrc.js"), fix: true });
複製程式碼

eslint在指令碼中的使用方法,更具體的可以參考eslint文件中Node.js API部分

// scripts/new.js部分
...

components.push({
  label: newName,
  name: newName
})

const updateConfig = function(path, components) {
  writeFile(path, `module.exports = ${JSON.stringify(components)}`).then(() => {
    console.log("完成components.js")
    // 格式化
    CLIEngine.outputFixes(cli.executeOnFiles([configPath]))
  })
}

const createPackages = function(componentName) {
  try {
    const dir = path.resolve(__dirname, `../packages/${componentName}/`)
    // 建立資料夾
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir)
      console.log(`完成建立packages/${componentName}資料夾`)
    }
    // 寫入README
    if (!fs.existsSync(`${dir}/README.md`)) {
      writeFile(
        `${dir}/README.md`,
        `## ${componentName}
  
  ### 使用說明
            `
      ).then(() => {
        console.log("完成建立README")
      })
    }
    // 寫入package.json
    if (!fs.existsSync(`${dir}/package.json`)) {
      writeFile(
        `${dir}/package.json`,
        `{
  "name": "@hy/${componentName}",
  "version": "1.0.0",
  "description": "${componentName}",
  "main": "./dist/hy-${componentName}.umd.min.js",
  "keywords": [
    "${componentName}",
    "vue"
  ],
  "author": "",
  "license": "ISC"
}
        `
      ).then(() => {
        console.log("完成建立package.json")
      })
    }
    // 建立index.js
    if (!fs.existsSync(`${dir}/index.js`)) {
      writeFile(`${dir}/index.js`, `export {}`).then(() => {
        console.log("完成建立index.js")
        CLIEngine.outputFixes(cli.executeOnFiles([`${dir}/index.js`]))
      })
    }
  } catch (err) {
    console.error(err)
  }
}

const createExample = function(componentName) {
  try {
    const dir = path.resolve(__dirname, `../examples/${componentName}/`)
    // 建立資料夾
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir)
      console.log(`完成建立examples/${componentName}資料夾`)
    }
    // 寫入index.vue
    if (!fs.existsSync(`${dir}/index.vue`)) {
      writeFile(
        `${dir}/index.vue`,
        `<template>

</template>

<script>
import { } from '../../packages/${componentName}/index'

export default {
  components: {}
}

</script>
      `
      ).then(() => {
        console.log(`完成建立examples/${componentName}/index.vue檔案`)
        // 格式化index.vue
        CLIEngine.outputFixes(cli.executeOnFiles([`${dir}/index.vue`]))
      })
    }
  } catch (err) {
    console.error(err)
  }
}

...
複製程式碼

構建指令碼

// build.js
...

async function build() {
  for (let i = 0, len = components.length; i < len; i++) {
    const name = components[i].name
    await buildService.run(
      "build",
      {
        _: ["build", `${root}/packages/${name}/src/index.js`],
        target: "lib",
        name: `hy-${name}`,
        dest: `${root}/packages/${name}/dist`,
        // 生成格式: umd格式會同時成功demo.html commonjs,umd,umd-min
        formats: "commonjs,umd-min"
        // clean: false
      },
      ["--target=all", `./packages/${name}/src/index.js`]
    )
  }
}

...
複製程式碼

Lerna

lerna是一個多包倉庫管理的工具,可以幫助建立、管理、釋出多包倉庫中的包。
關於lerna我也沒有太深入得使用,只是用到了釋出。首先在專案下執行init初始化了專案,在每次commit之後,可以執行publishlerna會對應程式碼庫打tag,併發布到npm倉庫。

專案版本問題

0.0.1為不規範版本號,最小應該從1.0.0開始。npm publish無法釋出,但是lerna publish可以釋出。
導致結果安裝為固定版本號,而不是以^開頭的版本號範圍。outdate可以檢測到有更新,無法通過update升級。

元件開發

元件開發主要是在packages/<component name>/src目錄下進行,在example/<component name>/目錄下可以引入該元件src下的原始檔,用一些資料來進行開發測試。元件開發和專案中的元件開發基本相同。
作為元件庫中的元件,需要更多的考慮其通用性和易用性。不能為了通用而加入很多的屬性,而使其失去易用性;同樣也不能為了易用,而使其過於簡單,使用範圍過於侷限。
對於每一個屬性、每個丟擲去的方法,都需要認真考慮其必要性。

唯一不同的地方可能需要注意的是匯出的方式。
一種是直接匯出元件,這種形式在使用時需要引入,並且在components中宣告,也就是區域性註冊。
另一種是新增install方法後匯出。這種形式需要呼叫vue.use方法,相當於全域性註冊。

相關文章