<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>

          自動(dòng)生成組件代碼—— Vue CLI 插件開(kāi)發(fā)實(shí)戰(zhàn)

          共 21509字,需瀏覽 44分鐘

           ·

          2021-04-06 09:19

          點(diǎn)上方藍(lán)字關(guān)注公眾號(hào)「TianTianUp

          前言

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

          本文會(huì)一步一步教你如何編寫(xiě)一個(gè)自己的 Vue CLI 插件,并發(fā)布至 npm,為所有因?yàn)檫@個(gè)問(wèn)題而煩惱的同學(xué)解放雙手。

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

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

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

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

          體驗(yàn)方式:

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

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

          廢話不多說(shuō),我們直接開(kāi)始吧!

          前置知識(shí)

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

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

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

          項(xiàng)目初始化

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

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

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

          然后初始化我們的項(xiàng)目:

          npm init

          輸入一些基本的信息,這些信息會(huì)被寫(xiě)入 package.json 文件中。

          創(chuàng)建一個(gè)基本的目錄結(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)建好了之后就可以開(kāi)始編碼了。

          目錄解析

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

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

          主要分為 4 個(gè)部分:Generator/Service/Prompt/UI。

          其中,Service 是必須的,其他的部分都是可選項(xiàng)。

          先來(lái)講一下各個(gè)部分的作用:

          Generator

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

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

          來(lái)看下我們這個(gè)項(xiàng)目的 generator/index.js

          /**
           * @file Generator
           */

          'use strict';

          // 前置知識(shí)中提到的美化控制臺(tái)輸出的包
          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('');
          };

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

          vue add

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

          package.json

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

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

          Service

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

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

          來(lái)看一下我們這個(gè)項(xiàng)目的 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 中注冊(cè) add-component 指令
            api.registerCommand('add-component'async () => {
              await addComponent(api);
            });

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

          為了代碼的可讀性,我們把 add-componentadd-page 指令的回調(diào)函數(shù)單獨(dú)抽了出來(lái),分別放在了 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} 組件名稱(chēng) 默認(rèn) 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'
              }
            ]);

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

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

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

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

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

            // 交互式命令行 獲取組件信息
            // componentType {'sfc'|'tsx'|'jsx'} 組件類(lèi)型 默認(rèn) sfc
            // componentStyleType {'.css'|'.scss'|'.sass'|'.less'|'.stylus'} 組件樣式類(lèi)型 默認(rèn) .scss
            // shouldMkdir {boolean} 是否需要為組件創(chuàng)建文件夾 默認(rèn) 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. )'
                )}
          `
          ,
                defaulttrue
              }
            ]);

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

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

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

            // 生成 SFC/TSX/JSX 及 CSS/SCSS/Sass/Less/Stylus
            try {
              // 讀取組件 template
              // 替換組件名稱(chēng)為控制臺(tái)輸入的組件名稱(chēng)
              const template = fs
                .readFileSync(src)
                .toString()
                .replace(/helloworld/gi, componentName);
              // 讀取組件樣式 template
              // 替換組件類(lèi)名為控制臺(tái)輸入的組件名稱(chēng)
              const style = fs
                .readFileSync(styleSrc)
                .toString()
                .replace(/helloworld/gi, componentName);
              if (componentType === 'sfc') {
                // 創(chuàng)建的組件類(lèi)型為 SFC 則將組件樣式 template 注入 <style></style> 標(biāo)簽中并添加樣式類(lèi)型
                fs.writeFileSync(
                  dist,
                  template
                    // 替換組件樣式為 template 并添加樣式類(lèi)型
                    .replace(
                      /<style>\s<\/style>/gi,
                      () =>
                        `<style${
                          // 當(dāng)組件樣式類(lèi)型為 CSS 時(shí)不需要添加組件樣式類(lèi)型
                          componentStyleType !== '.css'
                            ? ` lang="${
                                // 當(dāng)組件樣式類(lèi)型為 Stylus 時(shí)需要做一下特殊處理
                                componentStyleType === '.styl'
                                  ? 'stylus'
                                  : componentStyleType.replace('.''')
                              }
          "`

                            : ''
                        }
          >\n${style}</style>`

                    )
                );
              } else {
                // 創(chuàng)建的組件類(lèi)型為 TSX/JSX 則將組件樣式 template 注入單獨(dú)的樣式文件
                fs.writeFileSync(
                  dist,
                  template.replace(
                    // 當(dāng)不需要?jiǎng)?chuàng)建組件文件夾時(shí) 樣式文件應(yīng)該以 [組件名稱(chēng)].[組件樣式類(lèi)型] 的方式引入
                    /import '\.\/index\.css';/gi,
                    `import './${
                      shouldMkdir ? 'index' : `${componentName}`
                    }
          ${componentStyleType}';`

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

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

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

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

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

          yarn add-component

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

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

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

          對(duì)應(yīng)的文件中組件名稱(chēng)和組件樣式類(lèi)名也被替換了。

          到這里我們就算完成了一個(gè)能夠自動(dòng)生成組件的 Vue CLI 插件了。

          但是,還不夠!

          Prompt

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

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

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

          UI

          UI 會(huì)在使用 vue ui 指令打開(kāi)圖形化操作界面后給到用戶(hù)一個(gè)圖形化的插件配置功能。

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

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

          深入學(xué)習(xí)

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

          總結(jié)

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

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

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

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

          • UI 會(huì)在使用 vue ui 指令打開(kāi)圖形化操作界面后給到用戶(hù)一個(gè)圖形化的插件配置功能。

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

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

          也歡迎大家通過(guò) npm/yarn 安裝到自己的項(xiàng)目中體驗(yàn)~

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

          參考資料

          • `Vue CLI` 插件開(kāi)發(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 插件開(kāi)發(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 插件開(kāi)發(fā)指南: https://cli.vuejs.org/zh/dev-guide/plugin-dev.html

          點(diǎn)個(gè)在看支持我吧



          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  成人精品三级 | 最新一区二区三区 | 抠逼操逼视频 | 国产精品久久久久久久久久久久久久久久久久 | www.俺去 |