Re從零開始的前端腳手架工具搭建

前言

記得一開始接觸腳手架時,就以爲這是個很神奇的東西。不用複雜的配置,不用去查文檔,拿@vue/cli爲例,直接一行vue create my-project命令,按一下回車,ok。接下來就是愉快的等待時間,看着項目快速的自動生成出來,與以前一項一項手動配置相比,效率不是快了一星半點。這簡直就是解放生產力的工具。項目與項目之間有不少配置和結構都是能夠複用的,並且項目的類型是有限的,能夠枚舉出來的,因此再搭配上相應的模板的話,就能夠快速開始一個新項目了。vue

分析

目前採用最多而且耦合度最低的一種思路是腳手架工具與模板項目分離,即模板項目放遠程倉庫或者什麼地方,腳手架工具將模板拉取到本地,再根據相關配置命令生成相應的項目。一句話就解釋完了,好像沒什麼難度。這一篇主要分析腳手架工具,至於模板就是另外一個話題了,下一篇《Re從零開始的路由模塊編寫》會講到。接下來看看這個工具是實現的細節。node

安裝與使用

咱們最終使用時,應該要如同@vue/cli同樣,能在命令行中全局使用。這就不得不提到npm配置文件package.js中的bin字段了。git

//就像這樣去配置,myCli是自定義的命令名,'./bin/myCli.js'則是引導文件
{
    "bin": {
        "myCli""./bin/myCli.js"
    }
}
複製代碼

npm install時npm會分析這個字段github

  • 若是是全局安裝,除了將包安裝到npm安裝目錄下的node_modules外,還會在npm安裝目錄的bin文件下建立一個軟鏈,鏈向node_modules/my-cli/bin/myCli.js,此時在PATH中也會加入相關的路徑映射,在使用myCli create myProject時,至關於在執行node /npm安裝路徑/bin/myCli create myProject
image
image
  • 若是不是全局安裝,則會在項目中的node_modules/.bin下建立軟鏈,鏈向node_modules/my-cli/bin/myCli.js,在npm run時,會將相關路徑映射加入到PATH中。

這樣就能作到在命令行中使用這個全局命令了web

項目結構

先來看看項目結構sql

iamge
iamge

結構並不複雜npm

  • sc.js就是上面介紹的引導文件
  • cli.js將匹配到的命令分發到不一樣的處理文件(cmd目錄下一堆增刪改查處理程序)
  • format.js工具函數
  • templates.json保存相關配置記錄

接收命令輸入

這一步須要處理的功能點有幾個json

  1. 能在命令行上直接輸入命令
  2. 須要接收來自命令行的輸入
  3. 輸入的過程是異步的,須要等待全部配置命令接收到以後,才能作相應的處理
  4. 處理可能發生的錯誤,如輸入錯誤,網絡錯誤等等

仔細一想,從命令行獲的命令和分步處理輸入的命令彷佛不是那麼容易完成,但問題不大。後端

prompts

站在巨人的肩膀上,在完成前3步以前,先介紹一個很關鍵的插件。prompts這個包對命令行相關處理進行了很好的封裝,配置簡單,支持分步輸入。安全

// 簡單的配置便可得到相關參數
(async () => {
    const preOption = [{
        type'text',
        name'project',
        message'項目名稱?',
        validatevalue => value ? true : '請輸入內容'
    }, {
        type'text',
        name'url',
        message'項目倉庫地址?',
        validatevalue => value ? true : '請輸入內容'
    }]
    const preResponse = await prompts(preOption);
})()
複製代碼
// 還能設置命令提示信息
program
    .version(packageInfo.version)

program
    .command('init')
    .description('初始化一個項目')
    .alias('i')
    .action(() => {
        require('./cmd/init').init();
    });

program
    .command('add')
    .description('新建一個項目')
    .alias('a')
    .action(() => {
        require('./cmd/add').add();
    });
複製代碼

處理相關輸入

這裏最核心的功能就是自動初始化項目了,除此以外還有對項目列表及配置的增刪改查,畢竟咱們的模板不止一個,須要維護的一個項目列表。下面看一下幾段核心的代碼。

初始化一個項目

(async () => {
    const preOption = [{
        type'text',
        name'project',
        message'項目名稱?',
        validatevalue => value ? true : '請輸入內容'
    }]
    format.table(templates)
    const preResponse = await prompts(preOption);

    if (!Object.keys(preResponse).length) {
        console.log(chalk.yellow('程序中斷'))
        process.exit();
    }

    const {
        project
    } = preResponse

    if (!templates[project] || !project) {
        console.log(chalk.red('模板名不爲空或模板不存在'))
        process.exit()
    }

    const {
        url,
        branch
    } = templates[project]

    spinner.start('正在初始化項目');

    exec(`git clone ${url} ${project} && cd ${project} && git checkout ${branch}`, (err) => {
        // 刪除 git 文件
        exec('cd ' + project + ' && rm -rf .git', (err, out) => {
            spinner.succeed('模板拉取成功')
            process.exit()
        });
    });
})()
複製代碼

增長一條項目記錄

(async () => {
    const option = [{
        type'text',
        name'project',
        message'項目名稱?',
        validatevalue => value ? true : '請輸入內容'
    }, {
        type'text',
        name'url',
        message'項目倉庫地址?',
        validatevalue => value ? true : '請輸入內容'
    }, {
        type'text',
        name'branch',
        message'項目分支?',
        initial'master',
        validatevalue => value ? true : '請輸入內容'
    }, {
        type'text',
        name'des',
        message'項目描述?',
        validatevalue => value ? true : '請輸入內容'
    }]
    const response = await prompts(option);

    if (!Object.keys(response).length) {
        console.log(chalk.yellow('程序中斷'))
        process.exit();
    }

    const {
        project
    } = response

    if (templates[project] || !project) {
        console.log(chalk.red('模板名不爲空或模板已存在'))
        process.exit()
    }

    templates[project] = response

    fs.writeFile(__dirname + '/../../templates.json'JSON.stringify(templates), 'utf-8', (err) => {
        console.log(chalk.green('添加成功'))
        process.exit();
    });
})();
複製代碼

刪除一條項目記錄

(async () => {
    const option = [{
        type'text',
        name'project',
        message'項目名稱?',
        validatevalue => value ? true : '請輸入內容'
    }]

    const response = await prompts(option);

    if (!Object.keys(response).length) {
        console.log(chalk.yellow('程序中斷'))
        process.exit();
    }

    const {
        project
    } = response

    if (!templates[project] || !project) {
        console.log(chalk.red('模板名不爲空或模板不存在'))
        process.exit()
    }

    if (project in templates) {
        delete templates[project]
        fs.writeFile(__dirname + '/../../templates.json'JSON.stringify(templates), 'utf-8', (err) => {
            console.log(chalk.green('刪除成功'))
            format.table(templates)
            process.exit();
        });
    } else {
        console.log(chalk.red('沒有該模板'))
        process.exit();
    }
})()
複製代碼

例如像更新功能就同上面的差很少,有些細節能夠查看完整代碼。固然還有不少功能沒有實現,這只是個簡易版本,幫助理解像相似@vue/cli這樣的腳手架工具是怎麼作的。

發佈項目

不會吧,不會吧,2020年了竟然有人不會在npm上發佈項目,直接npm publish就行了,什麼竟然報錯?詳情可看《Re從零開始的組件庫構建與發佈流程》

完整代碼

倉庫地址 https://github.com/GoldWorker/slucky-cli

結束

其實這樣的工具仍是很簡單的,實現上借用prompts這個包過程跳過了相對繁瑣的處理命令行的部分。固然若是想要像真正的@vue/cli那樣的功能,只須要繼續添加相關的命令與處理過程便可。

或者你感興趣的內容

Re從零開始系列

有趣的工具

web安全系列

相關文章
相關標籤/搜索