アセンブリライブラリを作成するためのプロジェクト足場ツール(クラスVue-cli 3)の構築


に縁を付ける


最近、社内に私有のnpm倉庫を建てたいと思っています.普段使っている回数がかなり頻繁なツールやコンポーネントを独立して管理しやすく、プロジェクトの規模が大きくなるにつれて、数量が多くなり、単純な複製粘着は優雅さと実用性の面で私たちのニーズを満たすことができないに違いありません.だから、さらなるモジュール化は必然です.
しかし、コンポーネントライブラリの構築は非常に面倒なプロセスであり、基礎的なwebpackの構成は言うまでもなく、es-lintなどのツールを追加してチームメンバーのコードを規範化する必要があります.開発の過程で、使用例をロードするためにディレクトリが必要です.devというコンポーネントを便利にします.その後、プライベートnpm倉庫に公開するパッケージ仕様を確立する必要があります.
これにより、積極性が大幅に低下するに違いありません.モジュールパッケージを構築するための足場ツールを作成するほうが、プロジェクトの初期化が容易です.
tips:最終完成品は下部にあります

私有NPM


ここでは、私有npmの構築について簡単に言及します.
npm i verdaccio -g
pm2 start verdaccio
nrmに合わせてクイック切替倉庫アドレスを使用することを推奨します
verdaccio github
イタリア名全体を返して、本当に洋風です.

ツール


本題に入る前に、いくつかのポイントとツールを紹介します.このキーがあれば、書くのは簡単です.

npm bin


グローバルにインストールされたツールを考えたことがありますか.彼はどのようにコマンドラインで自由に呼び出すことができますか.
実はこれはnpmが提供するリンク機能です
// package.json
{
  "name": "lucky-for-you",
  "bin": {
    "lucky": "bin/lucky"
  }
}

このようなモジュールがパブリッシュされると、-gパラメータを使用してグローバルにインストールされると
sudo npm i luck-for-you -g
/usr/local/bin/lucky ->/usr/local/lib/node_modules/luckytiger-package-cli/bin/lucky#npmリンク
npmは実際にリンクしてオペレーティングシステムのPathにリンクしますが、Luckyというコマンドを叩くとpathから対応するプログラムを見つけることができます.
もう1つは、リンク実行用のファイルには、bashがどのように実行すべきかを正しく認識できるように、冒頭に次のような内容を加えることが一般的です.
#!/usr/bin/env node
//      node      
// next script

Commander.js


tj大神の作品は、コマンドラインツールを書くのに便利です.ヘルプコマンドを自動的に生成
const program = require('commander');

program.version('0.0.1').usage(' [options]');

program
  .command('create ')
  .description('        npm     ')
  .action((name, cmd) => {
    const options = cleanArgs(cmd);
    require('../lib/create')(name, options);
  });

//               
if (!process.argv.slice(2).length) {
  program.outputHelp();
}

program.parse(process.argv);

Commander.js github

inquirer


実は私が初めてvue-cli 3を使ったとき.0の时、中のコマンドラインのフォームは本当に惊いて、vue-cli 3のソースコードをひっくり返してこのツールを见つけて、コマンドラインのフォームに使います.より直感的な構成オプションが可能です.
inquirer
  .prompt([
    {
      type: 'list',
      name: 'template',
      message: 'template:          ',
      choices: [
        {
          key: '1',
          name: 'JavaScript Library -       JS  ',
          value: 'js-lib',
        },
        {
          key: '2',
          name: 'Vue-components -     Vue    ',
          value: 'vue-component',
        },
      ],
    },
    {
      type: 'input',
      name: 'author',
      message: 'author:        ',
      validate: function(value) {
        return !!value;
      },
    },
    {
      type: 'input',
      name: 'desc',
      message: 'desc:        ',
      validate: function(value) {
        return !!value;
      },
    },
    {
      type: 'confirm',
      name: 'confirm',
      message: 'confirm:      ?',
      default: false,
    },
  ])
  .then(answers => {
    console.log(answers.template);
    console.log(answers.author);
    console.log(answers.desc);
  });

まだ多くのフォームタイプがありますが、ここで最も簡単なlist+input+confirmで十分です.
inquire github

構築の開始


今から私の構築プロセスを共有します.コード量が大きいので、ファイルごとに投稿する必要はありません.ここでは簡単に紹介します.具体的にはgithubプロジェクトを見ることができます.
私はcliツールを大きく2つの部分に分けてtemplate + z作成器の主な機能は、テンプレートに基づいてコピー+レンダリングを行うユーザーのオプションを吸収することです.Vue-cli3.0この部分の操作はもっと複雑で、彼はテンプレートの中の具体的な機能を抽象的にPluginにして、必要に応じてテンプレートを作ることができて、普遍的な大衆に対してもちろんもっと良いです.
しかし、私のこのプロジェクトは社内用なので、あまり一般的な設計は必要ありません.テンプレートで直接問題を解決し、モデルを簡略化すればいいです.たとえば、Vueのコンポーネントライブラリを作成するテンプレート、Reactのコンポーネントライブラリを作成するテンプレート、JavaScriptのツール関数クラスライブラリを作成するテンプレートなどです.
これにより、template はある程度デカップリングできます.つまり、作成部のコードを変更する必要がなく、後でより多くのタイプのテンプレートが必要になります.

ディレクトリ構造

├── README.md
├── bin
│   └── lucky #   
├── lib
│   ├── copy.js #  
│   └── create.js #    
├── package-lock.json
├── package.json
├── templates
│   ├── config.js #       
│   ├── js-lib #    1
│   └── vue-component #    2
├── utils #     
│   └── dir.js

package.json

{
  "name": "luckytiger-package-cli",
  "version": "1.1.14",
  "description": "package-cli",
  "bin": {
    "lucky": "bin/lucky"
  },
  "scripts": {
    "lucky": "node bin/lucky",
    "bootstarp": "cnpm i && cd ./templates/js-lib/ &&  cnpm i   && cd ../vue-component/ && cnpm i  ",
    "dev:js-lib": "cd templates/js-lib  && npm run dev",
    "dev:vue-component": "cd templates/vue-component && npm run dev",
    "dev:create": "rm -rf test-app && node bin/lucky create test-app",
    "clear": "sudo rm -rf node_modules && sudo rm -rf templates/js-lib/node_modules && sudo rm -rf templates/vue-component/node_modules"
  },
  "author": "zhangzhengyi",
  "license": "ISC",
  "dependencies": {
    "chalk": "^2.4.2",
    "commander": "^2.20.0",
    "ejs": "^2.6.2",
    "inquirer": "^6.4.1",
    "validate-npm-package-name": "^3.0.0"
  }
}

DEVテンプレートの高速化に便利なスクリプトがいくつか用意されています.
このように運行する
npm run dev:js-lib
js-libというテンプレートを表示して開発することができます

しゅプログラム


bin/lucky
#!/usr/bin/env node

const program = require('commander')

program.version('0.0.1').usage(' [options]')

program
  .command('create ')
  .description('        npm     ')
  .action((name, cmd) => {
    const options = cleanArgs(cmd)
    require('../lib/create')(name, options)
  })

if (!process.argv.slice(2).length) {
  program.outputHelp()
}

program.parse(process.argv)

// commander passes the Command object itself as options,
// extract only actual options into a fresh object.
function cleanArgs(cmd) {
  const args = {}
  cmd.options.forEach(o => {
    const key = camelize(o.long.replace(/^--/, ''))
    // if an option is not present and Command has a method with the same name
    // it should not be copied
    if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') {
      args[key] = cmd[key]
    }
  })
  return args
}

このファイルは主に基本的なコマンド設定をしてcommanderというライブラリを利用しています
ユーザが作成コマンドを呼び出すとlib/create.js処理に転送される

プライマリ作成


lib/cerate.js
const path = require('path')
const inquirer = require('inquirer')
const validateProjectName = require('validate-npm-package-name')
const chalk = require('chalk')
const copy = require('./copy')
const fs = require('fs')
const dir = require('../utils/dir')
const templates = require('../templates/config')

async function create(projectName, options) {
  const cwd = options.cwd || process.cwd()
  const inCurrent = projectName === '.'
  const name = inCurrent ? path.relative('../', cwd) : projectName
  const targetDir = path.resolve(cwd, projectName || '.')

  const result = validateProjectName(name)
  if (!result.validForNewPackages) {
    console.error(chalk.red(`      : "${name}"`))
    result.errors &&
      result.errors.forEach(err => {
        console.error(chalk.red.dim('Error: ' + err))
      })
    result.warnings &&
      result.warnings.forEach(warn => {
        console.error(chalk.red.dim('Warning: ' + warn))
      })
    return
  }

  if (!dir.isDir(targetDir)) {
    fs.mkdirSync(targetDir)
  } else {
    console.error(chalk.red(`                       `))
    return
  }

  const answers = await inquirer.prompt([
    {
      type: 'list',
      name: 'template',
      message: 'template:        ',
      choices: templates.map((v, i) => ({
        key: i,
        name: v.name,
        value: v.dir
      }))
    },
    {
      type: 'input',
      name: 'author',
      message: 'author:        ',
      validate: function(value) {
        return !!value
      }
    },
    {
      type: 'input',
      name: 'desc',
      message: 'desc:        ',
      validate: function(value) {
        return !!value
      }
    },
    {
      type: 'confirm',
      name: 'confirm',
      message: 'confirm:      ?',
      default: false
    }
  ])

  //       
  const sourceDir = path.resolve(__dirname, '..', 'templates', answers.template)
  console.log(chalk.blue(`        ...`))

  try {
    await copy({
      from: sourceDir,
      to: targetDir,
      renderData: {
        desc: answers.desc,
        author: answers.author,
        name: projectName
      },
      ignore: ['node_modules', 'package.json']
    })
  } catch (e) {
    console.error(chalk.red(e))
    return
  }

  console.log(chalk.green('        !'))
  console.log()
  console.log(chalk.cyan(` $ cd ${projectName}`))
  console.log(chalk.cyan(` $ npm i && npm run dev`))
}

module.exports = create

ここでは主にいくつかのことをしました.
  • プロジェクト名が合法であることを保証する.
  • 確認項目が現在のディレクトリに存在しない
  • ユーザの記入情報を収集する
  • .
  • レプリケーションプロセス
  • を開始
    この中のchalkというライブラリは色付きのコマンドラインを出力することができて、少し美しいです.
    テンプレートのいくつかの構成情報をtemplates/config.jsに配置しました.目的はデカップリングです.
    //templates/config.js
    module.exports = [
      {
        name: 'JavaScript Library -       JS  ',
        dir: 'js-lib'
      },
      {
        name: 'Vue-components -     Vue    ',
        dir: 'vue-component'
      }
    ]

    次に、レプリケーション・プロセスについて説明します.

    コピー


    lib/copy
    const fs = require('fs')
    const path = require('path')
    const dir = require('../utils/dir')
    const ejs = require('ejs')
    
    async function copy({ from, to, renderData, ignore = [] }) {
      let files = fs.readdirSync(from)
      //           
      let rFiles = []
      let dirs = []
      for (const fileName of files) {
        if (dir.isDir(path.resolve(from, fileName))) {
          dirs.push(fileName)
        } else {
          rFiles.push(fileName)
        }
      }
    
      //        
      rFiles.forEach(fileName => {
        //     
        if (ignore.some(v => v === fileName)) {
          return
        }
        let content = fs.readFileSync(path.resolve(from, fileName), 'utf-8')
        //         ejs         
        if (/ejs$/.test(fileName)) {
          content = ejs.render(content, renderData)
          fileName = fileName.replace('.ejs', '')
        }
        fs.writeFileSync(path.resolve(to, fileName), content)
      })
    
      //        
      dirs.forEach(dirName => {
        //     
        if (ignore.some(v => v === dirName)) {
          return
        }
        const fromDir = path.resolve(from, dirName)
        const toDir = path.resolve(to, dirName)
        if (!dir.isDir(toDir)) {
          fs.mkdirSync(toDir)
        }
        copy({ from: fromDir, to: toDir, renderData, ignore })
      })
    }
    
    module.exports = copy
    

    copyはファイルとディレクトリを再帰的にコピーする構造で、深さが優先されます.
    ここで、彼は4つのパラメータソースフォルダ、ターゲットフォルダ、レンダリングデータ、無視リストを持っています.
    私たちのテンプレートには、生成されたpackageなど、必要に応じてコンテンツをレンダリングする能力が必要です.jsonは、ユーザーが作成したときに記入したプロジェクト名、作成者、説明などの情報を持つ必要があります.ここではEJSテンプレートエンジンを使用してレンダリングを行いました.ejsの最後のファイルは、エンジン+レンダリングデータのレンダリングを経て、 package.json.ejsなどの出力を行います.
    テンプレートを開発する過程で必要なファイルがあり、実際に生成する際にフィルタリングが必要なため、無視された設計も行われています.
    すべて同期APIを採用しています.私たちのファイルは比較的小さいので、サーバーではなく、ブロックしても問題ありません.

    テンプレートの構築


    ここでは、Vue-componentコンポーネントライブラリテンプレートとJSライブラリテンプレートの2つのプリセットテンプレートを設計しました(例はVueに基づいています).もし似たような需要があれば、行ってみてください.この2つのテンプレートはvue-cli 3を先に使用します.0生成してから改装する.
    改装の目的は,コンポーネントライブラリというニーズに適合するためであり,通常のプロジェクトとは異なり,コンポーネントライブラリはDEVモードでコンポーネントをテスト開発し,その後,このコンポーネントを単独でパッケージ化する能力を有し,その後発表する必要がある.
    具体的にはコードを直接見ることができます
    構築中にいくつかのピットに注意が必要です
    テンプレートの内部には2つのpackageがあるはずです.jsonファイル
    package.jsonテンプレート用DEVモード
    package.json.ejs作成時の最終エクスポート
    そしてpackageではありません.jsonではfilesフィールドを使用してファイルpublishホワイトリストを作成します.これにより、cliツール全体が正常にテンプレート全体を公開できません(これはテンプレート内部のpackage.jsonがcliツール全体のpackage.jsonと上書き関係を持つはずです).
    テンプレートの内部gitignoreファイルを追加します.ejs
    同様にcli publishの時正常にテンプレートの中をアップロードすることができません.gitignoreファイルなので、ejsを追加して普通のファイルに偽装することができます.
    だからnpmパッケージのネストは少し干渉しやすいのではないかと思います.

    types推奨


    ここでは、コンポーネントライブラリを書くときにTSのタイプ宣言typesを手書きで書くことをお勧めします.VSCodeの下で非常に良いコードヒント効果を得ることができます.
    まずコンポーネントライブラリのpackageが必要です.jsonに属性を追加
    {
      "typings": "types/index.d.ts",
    }

    簡単な関数を書きます
    //     
    export default {
      say (name) {
        return `your name: ${name}`
      }
    }
    // index.d.ts
    function say(name: String): String
    
    export default {
      say
    }

    このようにVSCodeは、このモジュールを使用するときに、より健全なヒントを与えることができます.
    ここで追加の注意の下で、私の研究を経て、element-uiのこのようなコンポーネントライブラリ、propsのヒントがあることができるのは人のveturコンポーネントが専門的に開いた裏口のためで、typesを書くのはJSレベルのヒントしか持っていないので、Vue-templateを書く時依然としてなくて、後続の支持を期待します.

    リファレンス


    vue-cli
    Vue cli 3ライブラリモードコンポーネントライブラリを構築しnpmに公開するプロセス
    element-ui
    私のブログ