前端如何搭建一個成熟的腳手架

Yoki發表於2019-03-25

前言

有了之前的基礎(前端如何搭建一個簡單的腳手架),我們現在可以講講一個成熟的腳手架是怎麼做了。

這裡我們參考vue-cli的原始碼,基於rollup和typescript一步步搭建。vue-cli作為vue的腳手架,給如此多的前端開發者使用,已經算是成熟了吧。

開始

以下我們的命令仍然是ds~,模板是ds-cli-lib-template

目錄結構

├─ bin            # 打包檔案目錄
│  ├─ ds.js       # package.json裡的bin欄位引用檔案 
├─ src
│  ├─ lib         # 具體命令目錄
│     ├─ list     # ds list
│     ├─ init     # ds init
│  ├─ utils       # 工具函式
├─ main.ts        # 入口檔案
├─ typings        # typescript型別檔案目錄
├─ rullup.config.js # rollpu構建配置
├── test            #  測試用例
複製程式碼

編寫構建配置

現如今,webpack用來開發應用(熱更新hmr,程式碼拆分等),rollup用來開發類庫(簡單易上手,打包後程式碼能讀懂,至於其他的特性webpack目前基本已支援)。

現在來明確我們的需求

  • 使用typescript編寫模組程式碼
  • 打包成umd模組規範的程式碼
  • 可以引用commonjs規範的包(因為歷史原因,大多數包都不是ES模組規範)
  • 壓縮打包程式碼,減少體積
//rollup.config.js
import typescript from "rollup-plugin-typescript2";
import commonjs from 'rollup-plugin-commonjs'
import { uglify } from 'rollup-plugin-uglify'
export default {
    //入口檔案
  input: "src/main.ts",
  output: [
    {
      banner: "#!/usr/bin/env node",
      /**
       * 頭部插入這段程式碼
       * */
      name: "ds",
      file: "bin/ds.js",
      //打包成umd模組規範
      format: "umd"
    }
  ],
  plugins: [
    typescript(),
    commonjs({
      include: "node_modules/**",
      extensions: ['.js', '.ts']
    }),
    uglify()
  ],
};
複製程式碼

npm指令碼命令("scripts"欄位)

{
"clean": "rm -rf ./bin && mkdir bin",
"build": "npm run clean && rollup --config"
}
複製程式碼

編寫入口檔案

是一些非常基礎的東西,我們一般不放很複雜的邏輯在入口檔案裡。

const cmd = require('commander');
const config = require('../package.json');

//這裡cli-init.ts和cli-list.ts我們可以簡單匯出一個函式,如
// export default function(...args) {
//     console.log('init')
// }
import init from './lib/init/cli-init'; 
import list from './lib/list/cli-list';

const command= {
    init,
    list
};

//map對應的type,從而執行
function exec(type, ...args) {
    config.debug = args[0].debug;
    command[type](...args);
}

cmd
  .usage('<command>')
  .version(config.version)
  .description('歡迎使用ds-cli');

cmd
  .command('init')
  .description('初始化元件模板')
  .action((...args) => exec('init', ...args));

cmd
  .command('list')
  .description('檢視線上元件模板')
  .action((...args) => exec('list', ...args));

cmd.command('help')
  .description('檢視幫助')
  .action(() => cmd.help());

// 解析輸入的引數
cmd.parse(process.argv);
if (!cmd.args.length) {

  cmd.help();
}
複製程式碼

我們打包(執行npm run build)到bin資料夾下後,配置一下package.json的bin欄位為bin/ds.js,然後釋出npm(參考快速釋出一個vue-fullpage元件到npm試一下命令。

如果失敗,請重新審視上述之流程。

初始化模板

我們對模板的要求

  • 檔案目錄必須含有template資料夾,並且所需模板檔案放在該目錄下

前端如何搭建一個成熟的腳手架

  • 可用meta.js提高自定義程度(所謂動態化模板)
  • 模板檔名命名規範是ds-cli-‘name’-template,方便腳手架拉取
  • 可以不用線上的模板,如果本地有模板,可直接用之。如果是線上模板,那麼應該先下載到本地使用者目錄的.ds-templates目錄下(~/.ds-templates)

期望命令

ds init <template-name> <app-name> #模板名字和應用名字
複製程式碼

流程與邏輯

if(當前目錄下構建){
    詢問一下是不是當前目錄,是的話進入run函式
}else{
    進入主流程run函式
}
//run函式
function run(){
    if(模板路徑是本地檔案路徑){
        //支援本地模板如ds init /usr/webpack test
        if(路徑存在){
            //動態構造模板到你的目錄如test
            generate()
        }else{
            //報錯日誌
        }
    }else{
        //1.檢查當前process的node版本,大於6才可以用
        //2.檢查當前package.json的版本,跟遠端倉庫的版本比較一下。如果不一樣,就提醒一下使用者有新版本
        //3.下載遠端倉庫到本地(本地一般存放使用者目錄裡的.ds-template資料夾下),然後執行generate函式
    }
}
複製程式碼

動態模板

大家也看到了,其實最重要的就是generate函式~

  • 而如果我們去掉這一步generate模板的話,其實就是相當於下載一個靜態模板,如果我們對於使用者自定義沒要求的話,其實可以跳過這一步。
  • generate函式裡面用到了metalsmith,這個就相當於我們之前用的gulp,通過不斷地編寫中介軟體來優化打包後的結果。
function generate(){
    const opts = getOptions(name, templatePath) as meta;  // 獲取meta.js配置,存到opts裡
    // 我們把所需檔案放在原始檔的template目錄下,其他一些如測試放在外面。初始化一下metalsmith
    const metalsmith = Metalsmith(path.join(templatePath, 'template')) //我們約定,將模板所有檔案放在ds-cli-lib-template/template裡
    //中介軟體
     metalsmith.use(askQuestions(opts.prompts))  // 詢問問題,將資訊存metalsmith.metadata()
    .use(filterFiles(opts.filters))  // 通過問題互動過濾掉不需要的檔案
    .use(renderTemplateFiles()); // 模板裡面可以使用handlebar語法,作為佔位符,我們這裡重新渲染模板檔案

    // 源目錄打包到目標目錄to
    metalsmith.clean(false)
    .source('.') 
    .destination(to)
    .build((err, files) => {
      done(err);
    });
}
複製程式碼
  • 我們在模板(如 ds-cli-lib-template)目錄下需要構造meta.js,自定義我們所需的欄位
module.exports={
    //會通過中介軟體把這些欄位存在metalsmith.metadata(),方便接下來的中介軟體呼叫
    prompts:{
        //格式可以參考https://github.com/SBoudrias/Inquirer.js/#question
        name: {
            type: 'string',
            required: true,
            message: 'Project name',
        },
        author: {
            type: 'string',
            message: 'Author',
        },
        description: {
            type: 'string',
            required: false,
            message: 'Project description',
            default: '構建一個lib',
        },
        lint: {
            "type": "confirm",
            "message": "是否用tslint"
        },
    },
    
    filters: {
        //當上面prompts的lint為false的時候,就過濾掉檔案
        "tslint.json": "lint",
        "tsconfig.json": "lint"
    }
}
複製程式碼

至此,這裡我們已經說明了基本原理,你應該已經可以搭出適合你團隊的腳手架和模板了。

列舉模板倉庫

一般情況下都會有list命令

感謝github給我們提供了api,有以下比較好玩的介面


const logSymbols = require('log-symbols');
const chalk = require('chalk');


export default async function(...args) {

    // 獲取倉庫列表
    const res=await request({
        url: 'https://api.github.com/users/yokiyokiyoki/repos',
        method: 'GET'
        }
    );
    let list;
    if(res.status===200) {
        console.log(logSymbols.info,chalk.green('共有下列模板'));
        list=res.data.filter((item)=> {
            return item.name.includes('ds-cli')&&item.name.includes('template');
        }).forEach(item=> {
            console.log();
            console.log(chalk.green(item.name));
        });
    } else {
        console.log(logSymbols.error,`獲取倉庫列表失敗${res.data}`);
    }
}
複製程式碼

前端如何搭建一個成熟的腳手架

需要注意的是,有時候github抽風,請求的會比較慢,這樣對使用者體驗不友好(嘗試了幾次ds list如果很慢,開發者會失去耐心)。

如果對使用者體驗有追求的同學,這裡提供一個思路,就是快取。可以把該結果寫入檔案,快取到本地存模板的資料夾裡(~/.ds-templates),同時把該請求時間(主要是用來判斷是否是今天)也進去,然後我們在程式碼裡判斷一下:下次請求的時候,是否本本地有該檔案,有的話就讀一下,讀了之後判斷裡面的時間和現在的時候是否是相隔一天,相隔一天就重新請求(時間間隔自己把握),覆蓋原有快取檔案。

參考

  • ds-cli:目前暫時命令有init和list(獲取所有模板列表),含有詳細註釋,不懂你來!!!
  • ds-cli-lib-template:基於rollup和typescript構建類庫
  • ds-cli-doc-template:基於vuepress的文件模板

相關文章