亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

手把手帶你搭建一個(gè)node cli的方法示例

 更新時(shí)間:2020年08月07日 08:42:30   作者:一江不想說話  
這篇文章主要介紹了手把手帶你搭建一個(gè)node cli的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

前端日常開發(fā)中,會(huì)遇見各種各樣的 cli,使用 vue 技術(shù)棧的你一定用過 @vue/cli ,同樣使用 react 技術(shù)棧的人也一定知道 create-react-app 。利用這些工具能夠?qū)崿F(xiàn)一行命令生成我們想要的代碼模版,極大地方便了我們的日常開發(fā),讓計(jì)算機(jī)自己去干繁瑣的工作,而我們,就可以節(jié)省出大量的時(shí)間用于學(xué)習(xí)、交流、開發(fā)。

cli 工具的作用在于它能夠?qū)⑽覀冮_發(fā)過程中經(jīng)常需要重復(fù)做的事情利用一行代碼來解決,比如我們?cè)趯懶枨蟮臅r(shí)候每新增一個(gè)頁(yè)面就需要相應(yīng)的增加該頁(yè)面的初始化代碼,而相同文件類型的初始化代碼往往是一樣的,比如 example.vue。同時(shí)我們還需要增加對(duì)應(yīng)的路由,比如在 router.js 中增加對(duì)應(yīng)的路由規(guī)則。這些工作都是很繁瑣又重復(fù)的,每次遇到這種情況都重復(fù)一遍嗎?是時(shí)候作出改變了,編寫自己的 cli 工具,一行命令,3 秒鐘進(jìn)入 coding 狀態(tài)!

本文以自己的 fc-vue-cli 為例,將開發(fā)到發(fā)布過程完整記錄下來,看完本文,你將學(xué)會(huì)如何從零開發(fā)一個(gè) cli 項(xiàng)目,以及如何使用 npm 發(fā)布自己的包。

提前放上該項(xiàng)目地址

源代碼地址: 源代碼

npm 地址: npm

原文地址(github上):

github

要實(shí)現(xiàn)的功能

fc-vue add-page
通過這行命令來新增一個(gè)頁(yè)面的模版文件,省去了手動(dòng)新建文件,手動(dòng)復(fù)制初始化代碼的麻煩,同時(shí)添加上對(duì)應(yīng)的路由配置

腳手架的名字定為 fc-vue,這個(gè)是通過 package.json 里面的 name 字段來定義的。

目錄結(jié)構(gòu)

 

入口 (bin/index.js)

入口文件只做了一件事,那就是判斷當(dāng)前node的版本是否大于10,如果版本號(hào)<10則提醒用戶升級(jí)node

#!/usr/bin/env node

// 'use strict';
const chalk = require('chalk');

const currentNodeVersion = process.versions.node;
const major = currentNodeVersion.split('.')[0];
if (major < 10) {
 console.error(
 chalk.red(
  `You are running Node \n${currentNodeVersion} \nvue-assist-cli requires Node 10 or higher.\nPlease update your version of Node`
 )
 );
 process.exit(1);
}

require('../packages/init');

初始化命令 (packages/init.js)

在這里初始化你要實(shí)現(xiàn)的命令,比如我要實(shí)現(xiàn) add-page 功能,這里要用到的 commander 庫(kù)。

const { program } = require('commander');
const { log } = require('./lib/util');

// 初始化版本,我們直接獲取package.json里面的版本號(hào)就可以了
program.version(require('../package.json').version);
//開始添加命令 [name] 說明這個(gè)參數(shù)是可選的,我們想做到兼容不同的使用方法所以把這個(gè)參數(shù)設(shè)置未可選
//.description里面可以寫上這個(gè)命名的一些描述,當(dāng)用戶fc-vue help add-page 的時(shí)候可以提供幫助文檔
//.option 用來添加可選的參數(shù)
//.action用來響應(yīng)用戶的輸入,這里我們單獨(dú)用一個(gè)文件./commands/add-page來處理
program
 .command('add-page [name]')
 .description(
  'add a page, 默認(rèn)加在./src/views 或 ./src/pages 或./src/page目錄下,同時(shí)添加路由\n支持"/"來創(chuàng)建子目錄例如:add-page user/login\n使用時(shí),支持 fc-vue add-page 【回車】 來選擇輸入信息'
 )
 .option('-s, --simple', '創(chuàng)建簡(jiǎn)單版的頁(yè)面,只新增一個(gè).vue文件')
 .option('-t, --title <title>', '頁(yè)面標(biāo)題')
 .action(require('./commands/add-page'))
 .on('--help', () => {
 log('支持 fc-vue add-page 【回車】 來選擇輸入信息');
 });
//格式化命令行參數(shù)
program.parse(process.argv);

處理用戶輸入的命令 (packages/commands/add-page.js)

這里需要使用到幾個(gè)庫(kù), shelljs 用來處理 shell 命令的,我們用來操作文件, chalk 用來給打印輸出增加樣式。函數(shù)通過 name,cmdObj 來獲取用戶的輸入,其中 name 是.command('add-page [name]')里面的 name, cmdObj 對(duì)象里面則包括其他參數(shù)

const fs = require('fs');
const shell = require('shelljs');
const chalk = require('chalk');
const { askQuestions, askCss } = require('../lib/ask-page');
const checkContext = require('../lib/checkContext');
const copyTemplate = require('../lib/copy-template');
const addRouter = require('../lib/add-router');
const { error, log, success } = require('../lib/util');
shell.config.fatal = true;

module.exports = async (name, cmdObj) => {
 try {
 //默認(rèn)使用less,
 let cssType = 'less';
 let simple = cmdObj.simple;
 let title = cmdObj.title;
 if (!name && (simple || title)) {
  error('錯(cuò)誤的命令,缺少頁(yè)面名稱');
  process.exit(1);
 }
 //如果用戶沒有輸入name,[fc-vue add-page] 則進(jìn)入問答模式,通過一問一答獲取用戶的輸入
 if (!name) {
  const answers = await askQuestions();
  // console.log(answers);
  name = answers.FILENAME;
  title = answers.TITLE;
  simple = answers.SIMPLE;
  if (!simple) {
  const res = await askCss();
  cssType = res.CSS_TYPE;
  }
 }
 //其他情況則可以通過option拿到參數(shù)
 // console.log(process.cwd());
 //檢查上下文環(huán)境,并返回目標(biāo)文件目錄路徑
 let { destDir, destDirRootName, rootDir } = checkContext(
  name,
  cmdObj,
  'page'
 );
 //復(fù)制模版到目標(biāo)文件
 let { destFile } = copyTemplate(destDir, simple, cssType);

 if (fs.existsSync(destFile)) {
  await addRouter(name, rootDir, simple, destDirRootName, title);
  log(`成功創(chuàng)建${name},請(qǐng)?jiān)?{destDir}下查看`);
 } else {
  console.error(
  chalk.red(`創(chuàng)建失敗,請(qǐng)到項(xiàng)目【根目錄】或者【@src】目錄下執(zhí)行該操作`)
  );
 }
 } catch (error) {
 console.error(chalk.red(error));
 console.error(
  chalk.red(
  `創(chuàng)建頁(yè)面失敗,請(qǐng)確保在項(xiàng)目【根目錄】或者【@src】目錄下執(zhí)行該操作\n,否則請(qǐng)聯(lián)系@zhongyi`
  )
 );
 }
};

問答模式 (packages/lib/ask-page.js)

這里需要用到 inquirer 。這個(gè)就很簡(jiǎn)單了,基本上就是以數(shù)組的方式列出你想讓用戶輸入的內(nèi)容,每個(gè)問題的交互可以選擇 input 輸入,list 選擇等等。在這里獲取到的用戶輸入我們就可以在 packages/commands/add-page.js 調(diào)用,然后拿到這些參數(shù)。

const inquirer = require('inquirer');

const askQuestions = () => {
 const questions = [
 {
  name: 'FILENAME',
  type: 'input',
  message: '請(qǐng)輸入頁(yè)面的名稱?[支持多級(jí)目錄,例如:user/login]',
 },
 {
  name: 'TITLE',
  type: 'input',
  message: '請(qǐng)輸入頁(yè)面標(biāo)題(meta.title)',
 },
 {
  type: 'list',
  name: 'SIMPLE',
  message: 'What is the template type?',
  choices: [
  'normal:【同時(shí)創(chuàng)建 .vue .js .[style]】 ',
  'simple: 【只創(chuàng)建 .vue】',
  ],
  filter: function (val) {
  return val.split(':')[0] === 'simple' ? true : false;
  },
 },
 ];
 return inquirer.prompt(questions);
};

檢查用戶執(zhí)行命令時(shí)所在的環(huán)境 (packages/lib/checkContext.js)

因?yàn)槲覀儾淮_定用戶會(huì)不會(huì)按照我們所期望的方式來使用,所以在這里我們加上一些判斷,來確保用戶的行為規(guī)范,否則就拋出錯(cuò)誤,提示用戶該怎么使用。主要就是確保用戶在項(xiàng)目根目錄或者 src 目錄路徑下執(zhí)行命令。然后還要確認(rèn)用戶所在項(xiàng)目的目錄結(jié)構(gòu)是否符合我們所提供的規(guī)范(基本上也是社區(qū)的規(guī)范)。最后當(dāng)然還要判斷下這個(gè)需要添加的頁(yè)面是否已經(jīng)存在。

const fs = require('fs');
const path = require('path');
const { error } = require('./util');
/**
 * 檢查 用戶是否在項(xiàng)目根目錄或者./src目錄下執(zhí)行,是否有約定的項(xiàng)目目錄結(jié)構(gòu),是否已經(jīng)存在該組件
 * @param {Stirng} name
 * @param {Object} cmdObj
 * @return {Object} {destDirRootName ,destDir,rootDir} 目標(biāo)文件夾名稱,目標(biāo)文件路徑,項(xiàng)目所在目錄
 */
const checkContext = (name, cmdObj, type) => {
 // console.log(process.cwd());
 let destDir, destDirRoot, destDirRootName;
 const curDir = path.resolve('.');
 let rootDir = '.';
 const basename = path.basename(curDir);

 //兼容 用戶在 ./src目錄下執(zhí)行該命令
 if (basename === 'src') {
 rootDir = path.resolve('..', rootDir);
 }
 //判斷下項(xiàng)目根目錄rootDir下面有沒有src目錄,如果沒有那說明用戶沒有在正確的路徑下執(zhí)行該命令
 if (!fs.existsSync(path.join(rootDir, 'src'))) {
 error(`創(chuàng)建頁(yè)面失敗,請(qǐng)到項(xiàng)目【根目錄】或者【@src】目錄下執(zhí)行該操作`);
 process.exit(1);
 }
 // -c
 if (type === 'component') {
 //創(chuàng)建一個(gè)組件。兼容組件不同的目錄名稱 支持 src/components src/component 三種任一種

 if (fs.existsSync(path.resolve(rootDir, 'src/components'))) {
  destDir = path.resolve(rootDir, 'src/components', name);
 } else if (fs.existsSync(path.resolve(rootDir, 'src/component'))) {
  destDir = path.resolve(rootDir, 'src/component', name);
 } else {
  error('您的通用組件存放文件目錄不符合規(guī)范,請(qǐng)將其放在 /src/components下');
 }
 } else {
 // 兼容路由頁(yè)面不同的目錄名稱 支持 src/views src/pages src/page 三種任一種
 if (fs.existsSync(path.resolve(rootDir, 'src/views'))) {
  destDir = path.resolve(rootDir, 'src/views', name);
  destDirRootName = 'views';
 } else if (fs.existsSync(path.resolve(rootDir, 'src/pages'))) {
  destDir = path.resolve(rootDir, 'src/pages', name);
  destDirRootName = 'pages';
 } else if (fs.existsSync(path.resolve(rootDir, 'src/page'))) {
  destDir = path.resolve(rootDir, 'src/page', name);
  destDirRootName = 'page';
 } else {
  error(
  '您的頁(yè)面組件存放文件目錄不符合規(guī)范,請(qǐng)將其放在 /src/view 或者 /src/pages 或者 /src/page 目錄'
  );
 }
 }

 //是否已經(jīng)存在該組件
 if (
 (cmdObj.simple && fs.existsSync(destDir + '.vue')) ||
 (!cmdObj.simple && fs.existsSync(destDir + '/index.vue'))
 ) {
 error(`${name} 頁(yè)面/組件 已經(jīng)存在,創(chuàng)建失??!`);
 process.exit(1);
 }
 return { destDirRootName, destDir, rootDir };
};

module.exports = checkContext;

復(fù)制模版到目標(biāo)路徑 (packages/lib/copy-template.js)

當(dāng)確認(rèn)過上下文環(huán)境,拿到了用戶的輸入?yún)?shù),這個(gè)時(shí)候我們就可以愉快的進(jìn)行頁(yè)面添加工作了,也就是復(fù)制我們事先準(zhǔn)備好的模版到目標(biāo)文件。這里需要考慮用戶選擇的是 normal 還是 simple 類型的根據(jù)不同的類型來添加不通的頁(yè)面模版。當(dāng)然同時(shí)還支持 less,scss 等。 比如用戶執(zhí)行 fc-vue add-page user/login --title=登錄頁(yè) 這個(gè)時(shí)候?qū)?huì)在 src/views/user/login 下創(chuàng)建初始化的模版文件包括 .js .vue .less

const shell = require('shelljs');
const path = require('path');
shell.config.fatal = true;

/**
 *
 * @param {String} destDir 目標(biāo)文件路徑
 * @param {Boolean} simple
 * @param {less,scss,sass,stylus} cssType
 * @return { sourceDir, destFile} 模版原文件,生成的目標(biāo)文件
 */
const copyTemplate = (destDir, simple, cssType) => {
 let sourceDir, destFile;
 // -s
 if (simple) {
 //創(chuàng)建一個(gè)簡(jiǎn)單版.vue文件
 sourceDir = path.resolve(
  __dirname,
  '../../template/vue-page-simple-template.vue'
 );
 shell.mkdir('-p', destDir.slice(0, destDir.lastIndexOf('/')));
 destDir += '.vue';
 shell.cp('-R', sourceDir, destDir);
 destFile = destDir;
 } else {
 shell.mkdir('-p', destDir);
 sourceDir = path.resolve(
  __dirname,
  `../../template/vue-page-template-${cssType}/*`
 );
 shell.cp('-R', sourceDir, destDir);
 destFile = path.resolve(destDir, 'index.vue');
 }
 return { sourceDir, destFile };
};

module.exports = copyTemplate;

添加路由 (package/lib/add-router.js)

添加頁(yè)面模版的同時(shí)我們希望能夠自動(dòng)配置上路由。其實(shí)思路很簡(jiǎn)單,就是讀取 router.js 然后往里面插入用戶添加的頁(yè)面所在的路由。我們約定 src/views 目錄下面的組件都是頁(yè)面級(jí)的,也就是說/user/login/index.vue 對(duì)應(yīng)的路由就是/user/login。 比如用戶執(zhí)行 fc-vue add-page user/login --title=登錄頁(yè) ,那么在 src/router/index.js 里面就會(huì)加上一條路由規(guī)則,如下(src/router/index.js)

import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
******這里有很多其他代碼*****
 {
  path: '/user/login',
  name: 'user/login',
  meta: {
  title: '登錄頁(yè)'
  },
  component: () =>
  import(/* webpackChunkName: "user/login" */ './views/user/login/index.vue'),
 }
 ];

const router = new VueRouter({
 mode: 'history',
 base: process.env.BASE_URL,
 routes,
});

export default router;

回到添加路由配置的實(shí)現(xiàn),packages/lib/add-router.js。

const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

/**
 *
 * @param {String} name 頁(yè)面名稱
 * @param {String} rootDir 項(xiàng)目所在目錄
 * @param {Boolean} simple 簡(jiǎn)單模式
 * @param {String} destDirRootName 目標(biāo)文件夾的名稱 pages views page
 * @param {String} title 頁(yè)面標(biāo)題
 */
const addRouter = async (name, rootDir, simple, destDirRootName, title) => {
 let routerPath, pagePath;
 if (fs.existsSync(path.resolve(rootDir, './src/router.js'))) {
 routerPath = path.resolve(rootDir, './src/router.js');
 } else if (fs.existsSync(path.resolve(rootDir, './src/router/index.js'))) {
 routerPath = path.resolve(rootDir, './src/router/index.js');
 } else {
 error(
  '您的項(xiàng)目路由文件不符合規(guī)范,請(qǐng)將其放在/src/router.js或者/src/router/index.js'
 );
 }
 pagePath = `./${destDirRootName}/${name}/index.vue`;
 if (simple) {
 pagePath = `./${destDirRootName}/${name}.vue`;
 }
 try {
 let content = await readFile(routerPath, 'utf-8');
 //找到 const routes = 與 ]; 之間的內(nèi)容,也就是routes數(shù)組
 const reg = /const\s+routes\s*\=([\s\S]*)\]\s*\;/;

 const pathStr = `path: '/${name}',`;
 const nameStr = `name: '${name}',`;
 const metaStr = title
  ? `meta: {
  title: '${title}'
  },`
  : '';
 let componentStr = `component: () =>
  import(/* webpackChunkName: "${name}" */ '${pagePath}'),`;

 content = content.replace(reg, function (match, $1, index) {
  $1 = $1.trim();
  if (!$1.endsWith(',')) {
  $1 += ',';
  }
  if (title) {
  return `const routes = ${$1}
 {
 ${pathStr}
 ${nameStr}
 ${metaStr}
 ${componentStr}
 }
];`;
  } else {
  return `const routes = ${$1}
 {
 ${pathStr}
 ${nameStr}
 ${componentStr}
 }
];`;
  }
 });
 try {
  await writeFile(routerPath, content, 'utf-8');
 } catch (err) {
  error(err);
 }
 } catch (err) {
 error(err);
 }
};

module.exports = addRouter;

發(fā)布到 npm

主要是配置好 package.json 文件。bin 里面定義好 npm 包的入口。

 "name": "fc-vue",
 "version": "1.0.6",
 "bin": {
 "fc-vue": "bin/index.js"
 },

運(yùn)行npm login 先登錄

npm publish 發(fā)布,每次發(fā)布的版本號(hào)不能重復(fù)復(fù)制代碼

安裝使用

$ npm i -g fc-vue
$ fc-vue add-page

使用演示

 

結(jié)束

這樣就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 fc-vue add-page 功能,是不是很簡(jiǎn)單。

源代碼地址: 源代碼

npm 地址:npm

到此這篇關(guān)于手把手帶你搭建一個(gè) node cli的文章就介紹到這了,更多相關(guān)手把手帶你搭建一個(gè) node cli內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解nodejs爬蟲程序解決gbk等中文編碼問題

    詳解nodejs爬蟲程序解決gbk等中文編碼問題

    本篇文章主要介紹了nodejs爬蟲程序解決gbk等中文編碼問題,解決了網(wǎng)頁(yè)的編碼與nodejs默認(rèn)編碼不一致造成的亂碼問題,有興趣的可以了解一下
    2017-04-04
  • nodejs async異步常用函數(shù)總結(jié)(推薦)

    nodejs async異步常用函數(shù)總結(jié)(推薦)

    這篇文章主要介紹了nodejs async異步常用函數(shù)總結(jié)的相關(guān)資料,需要的朋友可以參考下
    2017-11-11
  • 詳解基于Vue+Koa的pm2配置

    詳解基于Vue+Koa的pm2配置

    這篇文章主要介紹了詳解基于Vue+Koa的pm2配置,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-10-10
  • 詳解Nodejs基于mongoose模塊的增刪改查的操作

    詳解Nodejs基于mongoose模塊的增刪改查的操作

    本篇文章主要介紹了Nodejs基于mongoose模塊的增刪改查的操作,Mongoose是MongoDB的一個(gè)對(duì)象模型工具,封裝了MongoDB對(duì)文檔的的一些增刪改查等常用方法,讓NodeJS操作Mongodb數(shù)據(jù)庫(kù)變得更加靈活簡(jiǎn)單。
    2016-12-12
  • node.js中的fs.link方法使用說明

    node.js中的fs.link方法使用說明

    這篇文章主要介紹了node.js中的fs.link方法使用說明,本文介紹了fs.link的方法說明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • 使用node.js 獲取客戶端信息代碼分享

    使用node.js 獲取客戶端信息代碼分享

    本文給大家分享一段使用node.js 獲取客戶端信息的代碼,非常的簡(jiǎn)潔,推薦給大家。
    2014-11-11
  • 基于nodejs的雪碧圖制作工具的示例代碼

    基于nodejs的雪碧圖制作工具的示例代碼

    雪碧圖就是把很多小圖標(biāo)合并為一張圖片,這篇文章主要介紹了基于nodejs的雪碧圖制作工具的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • node.js express中app.param的用法詳解

    node.js express中app.param的用法詳解

    express.js是nodejs的一個(gè)MVC開發(fā)框架,并且支持jade等多種模板。下面這篇文章主要給大家介紹了關(guān)于node.js express中app.param用法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-07-07
  • Mac下通過brew安裝指定版本的nodejs教程

    Mac下通過brew安裝指定版本的nodejs教程

    今天小編就為大家分享一篇Mac下通過brew安裝指定版本的nodejs教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • npx的使用及原理分析

    npx的使用及原理分析

    這篇文章主要介紹了npx的使用及原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-02-02

最新評(píng)論