<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          美團前端架構(gòu) Vue CLI 組件代碼生成插件開發(fā)實戰(zhàn)

          共 11392字,需瀏覽 23分鐘

           ·

          2020-12-30 01:34

          前言

          近期工作的過程中跟 Vue CLI 的插件打交道比較多,想了想自己在學校寫項目的時候最煩的就是項目創(chuàng)建之后手動創(chuàng)建組件/頁面和配置路由,于是突發(fā)奇想決定寫一個腳手架的插件,自動實現(xiàn)創(chuàng)建組件/頁面和配置路由的功能

          本文會一步一步教你如何編寫一個自己的 Vue CLI 插件,并發(fā)布至 npm,為所有因為這個問題而煩惱的同學解放雙手。

          關(guān)注 「Hello FE」 獲取更多實戰(zhàn)教程,正好最近在抽獎,查看歷史文章即可獲取抽獎方法~

          本教程的插件完整代碼放在了我的 GitHub 上,歡迎大家 Starvue-cli-plugin-generators[1]

          同時,我也將這個插件發(fā)布到了 npm,大家可以直接使用 npm 安裝并體驗添加組件的能力。

          PS:添加頁面和配置路由的能力還在開發(fā)中。

          體驗方式:

          1. 通過 npm 安裝
          npm?install?vue-cli-plugin-generators?-D
          vue?invoke?vue-cli-plugin-generators
          1. 通過 yarn 安裝
          yarn?add?vue-cli-plugin-generators?-D
          vue?invoke?vue-cli-plugin-generators
          1. 通過 Vue CLI 安裝(推薦)
          vue?add?vue-cli-plugin-generators

          注意:一定要注意是復(fù)數(shù)形式的 generators,不是單數(shù)形式的 generator,generator 被前輩的占領(lǐng)了。

          廢話不多說,我們直接開始吧!

          前置知識

          要做好一個 Vue CLI 插件,除了要了解 Vue CLI 插件的開發(fā)規(guī)范之外,我們還需要了解幾個 npm 包:

          • chalk 讓你的控制臺輸出好看一點,為文字或背景上色
          • glob 讓你可以使用 Shell 腳本的方式匹配文件
          • inquirer 讓你可以使用交互式的命令行來獲取需要的信息

          主要出現(xiàn)的 npm 包就只有這三個,其他的都是基于 Node.js 的各種模塊,比如 fspath,了解過 Node.js 的同學應(yīng)該不陌生。

          項目初始化

          創(chuàng)建一個空的文件夾,名字最好就是你的插件的名字。

          這里我的名字是 vue-cli-plugin-generators,你可以取一個自己喜歡的名字,不過最好是見名知義的那種,比如 vue-cli-plugin-component-generator 或者 vue-cli-plugin-page-generator,一看就知道是組件生成器和頁面生成器。

          至于為什么一定要帶上 vue-cli-plugin 的前綴這個問題,可以看一下官方文檔:命名和可發(fā)現(xiàn)性[2]。

          然后初始化我們的項目:

          npm?init

          輸入一些基本的信息,這些信息會被寫入 package.json 文件中。

          創(chuàng)建一個基本的目錄結(jié)構(gòu):

          .
          ├──?LICENSE
          ├──?README.md
          ├──?generator
          │???├──?index.js
          │???└──?template
          │???????└──?component
          │???????????├──?jsx
          │???????????│???└──?Template.jsx
          │???????????├──?sfc
          │???????????│???└──?Template.vue
          │???????????├──?style
          │???????????│???├──?index.css
          │???????????│???├──?index.less
          │???????????│???├──?index.sass
          │???????????│???├──?index.scss
          │???????????│???└──?index.styl
          │???????????└──?tsx
          │???????????????└──?Template.tsx
          ├──?index.js
          ├──?package.json
          ├──?src
          │???├──?add-component.js
          │???├──?add-page.js
          │???└──?utils
          │???????├──?log.js
          │???????└──?suffix.js
          └──?yarn.lock

          目錄結(jié)構(gòu)創(chuàng)建好了之后就可以開始編碼了。

          目錄解析

          一些不重要的文件就不講解了,主要講解一下作為一個優(yōu)秀的 Vue CLI 插件,需要哪些部分:

          .
          ├──?README.md
          ├──?generator.js??#?Generator(可選)
          ├──?index.js??????#?Service?插件
          ├──?package.json
          ├──?prompts.js????#?Prompt?文件(可選)
          └──?ui.js?????????#?Vue?UI?集成(可選)

          主要分為 4 個部分:Generator/Service/Prompt/UI。

          其中,Service 是必須的,其他的部分都是可選項。

          先來講一下各個部分的作用:

          Generator

          Generator 可以為你的項目創(chuàng)建文件、編輯文件、添加依賴。

          Generator 應(yīng)該放在根目錄下,被命名為 generator.js 或者放在 generator 目錄下,被命名為 index.js,它會在調(diào)用 vue add 或者 vue invoke 時被執(zhí)行。

          來看下我們這個項目的 generator/index.js

          /**
          ?*?@file?Generator
          ?*/

          'use?strict';

          //?前置知識中提到的美化控制臺輸出的包
          const?chalk?=?require('chalk');

          //?封裝的打印函數(shù)
          const?log?=?require('../src/utils/log');

          module.exports?=?(api)?=>?{
          ??//?執(zhí)行腳本
          ??const?extendScript?=?{
          ????scripts:?{
          ??????'add-component':?'vue-cli-service?add-component',
          ??????'add-page':?'vue-cli-service?add-page'
          ????}
          ??};
          ??//?拓展?package.json?為其中的?scripts?中添加?add-component?和?add-page?兩條指令
          ??api.extendPackage(extendScript);

          ??//?插件安裝成功后?輸出一些提示?可以忽略
          ??console.log('');
          ??log.success(`Success:?Add?plugin?success.`);
          ??console.log('');
          ??console.log('You?can?use?it?with:');
          ??console.log('');
          ??console.log(`???${chalk.cyan('yarn?add-component')}`);
          ??console.log('???or');
          ??console.log(`???${chalk.cyan('yarn?add-page')}`);
          ??console.log('');
          ??console.log('to?create?a?component?or?page.');
          ??console.log('');
          ??console.log(`${chalk.green.bold('Enjoy?it!')}`);
          ??console.log('');
          };

          所以,當我們執(zhí)行 vue add vue-cli-plugin-generators 的時候,generator/index.js 會被執(zhí)行,你就可以看到你的控制臺輸出了這樣的指引信息:

          vue add

          同時你還會發(fā)現(xiàn),執(zhí)行了 vue add vue-cli-plugin-generators 的項目中,package.json 發(fā)生了變化:

          package.json

          添加了兩條指令,讓我們可以通過 yarn add-componentyarn add-page 去添加組件/頁面。

          雖然添加了這兩條指令,但是現(xiàn)在這兩條指令還沒有被注冊到 vue-cli-service 中,這時候我們就需要開始編寫 Service 了。

          Service

          Service 可以為你的項目修改 Webpack 配置、創(chuàng)建 vue-cli-service 命令、修改 vue-cli-service 命令

          Service 應(yīng)該放在根目錄下,被命名為 index.js,它會在調(diào)用 vue-cli-service 時被執(zhí)行。

          來看一下我們這個項目的 index.js

          /**
          ?*?@file?Service?插件
          ?*/

          'use?strict';

          const?addComponent?=?require('./src/add-component');
          const?addPage?=?require('./src/add-page');

          module.exports?=?(api,?options)?=>?{
          ??//?向?vue-cli-service?中注冊?add-component?指令
          ??api.registerCommand('add-component',?async?()?=>?{
          ????await?addComponent(api);
          ??});

          ??//?向?vue-cli-service?中注冊?add-page?指令
          ??api.registerCommand('add-page',?async?()?=>?{
          ????await?addPage(api);
          ??});
          };

          為了代碼的可讀性,我們把 add-componentadd-page 指令的回調(diào)函數(shù)單獨抽了出來,分別放在了 src/add-component.jssrc/add-page.js 中:

          前方代碼量較大,建議先閱讀注釋理解思路。

          /**
          ?*?@file?Add?Component?邏輯
          ?*/

          'use?strict';

          const?fs?=?require('fs');
          const?path?=?require('path');
          const?glob?=?require('glob');
          const?chalk?=?require('chalk');
          const?inquirer?=?require('inquirer');

          const?log?=?require('./utils/log');
          const?suffix?=?require('./utils/suffix');

          module.exports?=?async?(api)?=>?{
          ??//?交互式命令行參數(shù)?獲取組件信息
          ??//?componentName?{string}?組件名稱?默認?HelloWorld
          ??const?{?componentName?}?=?await?inquirer.prompt([
          ????{
          ??????name:?'componentName',
          ??????type:?'input',
          ??????message:?`Please?input?your?component?name.?${chalk.yellow(
          ????????'(?PascalCase?)'
          ??????)}
          `
          ,
          ??????description:?`You?should?input?a?${chalk.yellow(
          ????????'PascalCase'
          ??????)}
          ,?it?will?be?used?to?name?new?component.`
          ,
          ??????default:?'HelloWorld'
          ????}
          ??]);

          ??//?組件名稱校驗
          ??if?(!componentName.trim()?||?/[^A-Za-z0-9]/g.test(componentName))?{
          ????log.error(
          ??????`Error:?Please?input?a?correct?name.?${chalk.bold('(?PascalCase?)')}`
          ????);
          ????return;
          ??}

          ??//?項目中組件文件路徑?Vue?CLI?創(chuàng)建的項目中默認路徑為?src/components
          ??const?baseDir?=?`${api.getCwd()}/src/components`;
          ??//?遍歷組件文件?返回組件路徑列表
          ??const?existComponent?=?glob.sync(`${baseDir}/*`);

          ??//?替換組件路徑列表中的基礎(chǔ)路徑?返回組件名稱列表
          ??const?existComponentName?=?existComponent.map((name)?=>
          ????name.replace(`${baseDir}/`,?'')
          ??);

          ??//?判斷組件是否已存在
          ??const?isExist?=?existComponentName.some((name)?=>?{
          ????//?正則表達式匹配從控制臺輸入的組件名稱是否已經(jīng)存在
          ????const?reg?=?new?RegExp(
          ??????`^(${componentName}.[vue|jsx|tsx])$|^(${componentName})$`,
          ??????'g'
          ????);
          ????return?reg.test(name);
          ??});

          ??//?存在則報錯并退出
          ??if?(isExist)?{
          ????log.error(`Error:?Component?${chalk.bold(componentName)}?already?exists.`);
          ????return;
          ??}

          ??//?交互式命令行?獲取組件信息
          ??//?componentType?{'sfc'|'tsx'|'jsx'}?組件類型?默認?sfc
          ??//?componentStyleType?{'.css'|'.scss'|'.sass'|'.less'|'.stylus'}?組件樣式類型?默認?.scss
          ??//?shouldMkdir?{boolean}?是否需要為組件創(chuàng)建文件夾?默認?true
          ??const?{
          ????componentType,
          ????componentStyleType,
          ????shouldMkdir
          ??}?=?await?inquirer.prompt([
          ????{
          ??????name:?'componentType',
          ??????type:?'list',
          ??????message:?`Please?select?your?component?type.?${chalk.yellow(
          ????????'(?.vue?/?.tsx?/?.jsx?)'
          ??????)}
          `
          ,
          ??????choices:?[
          ????????{?name:?'SFC?(.vue)',?value:?'sfc'?},
          ????????{?name:?'TSX?(.tsx)',?value:?'tsx'?},
          ????????{?name:?'JSX?(.jsx)',?value:?'jsx'?}
          ??????],
          ??????default:?'sfc'
          ????},
          ????{
          ??????name:?'componentStyleType',
          ??????type:?'list',
          ??????message:?`Please?select?your?component?style?type.?${chalk.yellow(
          ????????'(?.css?/?.sass?/?.scss?/?.less?/?.styl?)'
          ??????)}
          `
          ,
          ??????choices:?[
          ????????{?name:?'CSS?(.css)',?value:?'.css'?},
          ????????{?name:?'SCSS?(.scss)',?value:?'.scss'?},
          ????????{?name:?'Sass?(.sass)',?value:?'.sass'?},
          ????????{?name:?'Less?(.less)',?value:?'.less'?},
          ????????{?name:?'Stylus?(.styl)',?value:?'.styl'?}
          ??????],
          ??????default:?'.scss'
          ????},
          ????{
          ??????name:?'shouldMkdir',
          ??????type:?'confirm',
          ??????message:?`Should?make?a?directory?for?new?component??${chalk.yellow(
          ????????'(?Suggest?to?create.?)'
          ??????)}
          `
          ,
          ??????default:?true
          ????}
          ??]);

          ??//?根據(jù)不同的組件類型?生成對應(yīng)的?template?路徑
          ??let?src?=?path.resolve(
          ????__dirname,
          ????`../generator/template/component/${componentType}/Template${suffix(
          ??????componentType
          ????)}
          `

          ??);
          ??//?組件目標路徑?默認未生成組件文件夾
          ??let?dist?=?`${baseDir}/${componentName}${suffix(componentType)}`;
          ??//?根據(jù)不同的組件樣式類型?生成對應(yīng)的?template?路徑
          ??let?styleSrc?=?path.resolve(
          ????__dirname,
          ????`../generator/template/component/style/index${componentStyleType}`
          ??);
          ??//?組件樣式目標路徑?默認未生成組件文件夾
          ??let?styleDist?=?`${baseDir}/${componentName}${componentStyleType}`;

          ??//?需要為組件創(chuàng)建文件夾
          ??if?(shouldMkdir)?{
          ????try?{
          ??????//?創(chuàng)建組件文件夾
          ??????fs.mkdirSync(`${baseDir}/${componentName}`);
          ??????//?修改組件目標路徑
          ??????dist?=?`${baseDir}/${componentName}/${componentName}${suffix(
          ????????componentType
          ??????)}
          `
          ;
          ??????//?修改組件樣式目標路徑
          ??????styleDist?=?`${baseDir}/${componentName}/index${componentStyleType}`;
          ????}?catch?(e)?{
          ??????log.error(e);
          ??????return;
          ????}
          ??}

          ??//?生成?SFC/TSX/JSX?及?CSS/SCSS/Sass/Less/Stylus
          ??try?{
          ????//?讀取組件?template
          ????//?替換組件名稱為控制臺輸入的組件名稱
          ????const?template?=?fs
          ??????.readFileSync(src)
          ??????.toString()
          ??????.replace(/helloworld/gi,?componentName);
          ????//?讀取組件樣式?template
          ????//?替換組件類名為控制臺輸入的組件名稱
          ????const?style?=?fs
          ??????.readFileSync(styleSrc)
          ??????.toString()
          ??????.replace(/helloworld/gi,?componentName);
          ????if?(componentType?===?'sfc')?{
          ??????//?創(chuàng)建的組件類型為?SFC?則將組件樣式?template?注入??標簽中并添加樣式類型
          ??????fs.writeFileSync(
          ????????dist,
          ????????template
          ??????????//?替換組件樣式為?template?并添加樣式類型
          ??????????.replace(
          ????????????/`
          ??????????)
          ??????);
          ????}?else?{
          ??????//?創(chuàng)建的組件類型為?TSX/JSX?則將組件樣式?template?注入單獨的樣式文件
          ??????fs.writeFileSync(
          ????????dist,
          ????????template.replace(
          ??????????//?當不需要創(chuàng)建組件文件夾時?樣式文件應(yīng)該以?[組件名稱].[組件樣式類型]?的方式引入
          ??????????/import?'\.\/index\.css';/gi,
          ??????????`import?'./${
          ????????????shouldMkdir???'index'?:?`${componentName}`
          ??????????}
          ${componentStyleType}';`

          ????????)
          ??????);
          ??????fs.writeFileSync(styleDist,?style);
          ????}
          ????//?組件創(chuàng)建完成?打印組件名稱和組件文件路徑
          ????log.success(
          ??????`Success:?Component?${chalk.bold(
          ????????componentName
          ??????)}
          ?was?created?in?${chalk.bold(dist)}`

          ????);
          ??}?catch?(e)?{
          ????log.error(e);
          ????return;
          ??}
          };

          上面的代碼是 add-component 指令的執(zhí)行邏輯,比較長,可以稍微有點耐心閱讀一下。

          由于 add-page 指令的執(zhí)行邏輯還在開發(fā)過程中,這里就不貼出來了,大家可以自己思考一下,歡迎有好想法的同學為這個倉庫提 PR:vue-cli-plugin-generators[3]。

          現(xiàn)在我們可以來執(zhí)行一下 yarn add-component 來體驗一下功能了:

          yarn add-component

          這里我們分別創(chuàng)建了 SFC/TSX/JSX 三種類型的組件,目錄結(jié)構(gòu)如下:

          .
          ├──?HelloJSX
          │???├──?HelloJSX.jsx
          │???└──?index.scss
          ├──?HelloSFC
          │???└──?HelloSFC.vue
          ├──?HelloTSX
          │???├──?HelloTSX.tsx
          │???└──?index.scss
          └──?HelloWorld.vue

          其中 HelloWorld.vueVue CLI 創(chuàng)建時自動生成的。

          對應(yīng)的文件中組件名稱和組件樣式類名也被替換了。

          到這里我們就算完成了一個能夠自動生成組件的 Vue CLI 插件了。

          但是,還不夠!

          Prompt

          Prompt 會在創(chuàng)建新的項目或者在項目中添加新的插件時輸出交互式命令行,獲取 Generator 需要的信息,這些信息會在用戶輸入完成后以 options 的形式傳遞給 Generator,供 Generator 中的 ejs 模板渲染。

          Prompt 應(yīng)該放在根目錄下,被命名為 prompt.js,它會在調(diào)用 vue add 或者 vue invoke 時被執(zhí)行,執(zhí)行順序位于 Generator 前。

          在我們的插件中,我們并不需要在調(diào)用 vue add 或者 vue invoke 時就創(chuàng)建組件/頁面,因此不需要在這個時候獲取組件的相關(guān)信息。

          UI

          UI 會在使用 vue ui 指令打開圖形化操作界面后給到用戶一個圖形化的插件配置功能。

          這個部分的內(nèi)容比較復(fù)雜,講解起來比較費勁,大家可以到官網(wǎng)上閱讀:UI 集成[4]。

          在我們的插件中,我們并不需要使用 vue ui 啟動圖形化操作界面,因此不需要編寫 UI 相關(guān)的代碼。

          深入學習

          我們可以到 `Vue CLI` 插件開發(fā)指南[5]中查看更詳細的指南,建議閱讀英文文檔,沒有什么教程比官方文檔更加合適了。

          總結(jié)

          一個優(yōu)秀的 Vue CLI 插件應(yīng)該有四個部分:

          .
          ├──?README.md
          ├──?generator.js??#?Generator(可選)
          ├──?index.js??????#?Service?插件
          ├──?package.json
          ├──?prompts.js????#?Prompt?文件(可選)
          └──?ui.js?????????#?Vue?UI?集成(可選)
          • Generator 可以為你的項目創(chuàng)建文件、編輯文件、添加依賴。

          • Service 可以為你的項目修改 Webpack 配置、創(chuàng)建 vue-cli-service 命令、修改 vue-cli-service 命令。

          • Prompt 會在創(chuàng)建新的項目或者在項目中添加新的插件時輸出交互式命令行,獲取 Generator 需要的信息,這些信息會在用戶輸入完成后以 options 的形式傳遞給 Generator,供 Generator 中的 ejs 模板渲染。

          • UI 會在使用 vue ui 指令打開圖形化操作界面后給到用戶一個圖形化的插件配置功能。

          四個部分各司其職才能更好地實現(xiàn)一個完美的插件!

          本教程的插件完整代碼放在了我的 GitHub 上,歡迎大家 Starvue-cli-plugin-generators[6]

          也歡迎大家通過 npm/yarn 安裝到自己的項目中體驗~

          關(guān)注 「Hello FE」 獲取更多實戰(zhàn)教程

          參考資料

          • `Vue CLI` 插件開發(fā)指南[7]

          參考資料

          [1]

          vue-cli-plugin-generators: https://github.com/wjq990112/vue-cli-plugin-generators

          [2]

          命名和可發(fā)現(xiàn)性: https://cli.vuejs.org/zh/dev-guide/plugin-dev.html#%E5%91%BD%E5%90%8D%E5%92%8C%E5%8F%AF%E5%8F%91%E7%8E%B0%E6%80%A7

          [3]

          vue-cli-plugin-generators: https://github.com/wjq990112/vue-cli-plugin-generators

          [4]

          UI 集成: https://cli.vuejs.org/zh/dev-guide/plugin-dev.html#ui-%E9%9B%86%E6%88%90

          [5]

          Vue CLI 插件開發(fā)指南: https://cli.vuejs.org/zh/dev-guide/plugin-dev.html

          [6]

          vue-cli-plugin-generators: https://github.com/wjq990112/vue-cli-plugin-generators

          [7]

          Vue CLI 插件開發(fā)指南: https://cli.vuejs.org/zh/dev-guide/plugin-dev.html

          瀏覽 62
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码视频免费播放 | 三级网站在线播放 | 青青草18 | 中国女人的生活毛片 | 激情一区二区三区欧美 |