在前端工程化過程當中,爲了解決多項目中,類似度高的工做,便誕生許多前端腳手架,這裏記錄下本身實現一個簡易前端腳手架過程的實踐。主要是解決多個頁面類似內容的複製粘貼問題,功能相似於Webstorm的Live template,或者Vscode的Snippets。前端
讀寫文件模塊,這裏主要用於讀入用戶配置文件,輸出模板到文件node
NodeJs命令行工具,提供了用戶命令行輸入和參數解析,用戶解析用戶輸入react
NodeJs交互式命令行工具,詢問操做者問題,獲取用戶輸入,校驗回答的合法性npm
文件處理,讀寫操做json
將模板中的變量替換爲用戶輸入,編譯模板,相似框架如:artTemplate,Jade前端工程化
NodeJs的路徑操做庫,如合併路徑babel
命令行輸出樣式美化框架
npm init
建立一個node項目,由於是要作成一個npm包的腳手架,因此在包的取名上必定要惟一,即package.json
中name
字段,避免在發包的時候和網上已經存在的npm包重名,報403沒有權限的錯。bin
文件夾,同時在bin
文件夾下建立腳本tempTool
文件,內容以下:#!/usr/bin/env node console.log('Hello World');
注意哦,#!/usr/bin/env node
這個是Linux規範,用來指明瞭這個執行腳本的解釋程序,要是沒有這一行,默認用當前Shell去解釋這個腳本less
package.json
中增長bin
配置:"bin": { "tempTool": "./bin/tempTool" },
npm login
登陸,以後npm publish
若是一切順利,npm包提交完畢,能夠在其它項目中執行npm i -g xxx-tools
,安裝這個包,執行xxx-tools
命令,輸出 Hello World
,腳手架開發過程當中,也涉及到在本地調試,能夠直執行node ./bin/xxx-tools
bin
文件修改以下#!/usr/bin/env node const program = require('commander'); const chalk = require('chalk'); const { loadTemplate } = require('../src/lib/writeTemp'); const log = data => console.log(chalk.green(data)); log('初始化模板配置'); program .command('create') .description('create template') .option('-d') .action(async function () { const result = await loadTemplate(); result ? null : log('配置完畢'); }); program.parse(process.argv);
用戶執行create
命令,在這裏調用了loadTemplate
函數,看一下這個函數async
// 把模板中的變量替換爲用戶輸入的變量,輸出模板到制定文件夾 const Metalsmith = require('metalsmith'); const Handlebars = require('handlebars'); const path = require('path'); const fs = require('fs'); const { askQuestion } = require('./askQuestion'); const loadTemplate = async () => { // 從toolrc.json文件讀取配置 const dirPath = process.cwd(); if (!fs.existsSync('toolrc.json')) { throw new Error('toolrc.json配置文件不存在'); } const configJson = path.join(dirPath, 'toolrc.json'); const config = fs.readFileSync(configJson); const { source, dist, questionConfig } = JSON.parse(config); const answer = await askQuestion(questionConfig); const metalsmith = Metalsmith(__dirname); metalsmith .metadata(answer) .source(path.join(dirPath, source)) .destination(path.join(dirPath, dist)) .use(function (files, metalsmith, done) { //遍歷替換模板 Object.keys(files).forEach(fileName => { const fileContentsString = files[fileName].contents.toString(); //Handlebar compile 前須要轉換爲字符串 files[fileName].contents = new Buffer(Handlebars.compile(fileContentsString)(metalsmith.metadata())); }); done(); }).build(function (err) { if (err) throw err; }); }; module.exports.loadTemplate = loadTemplate;
爲了方便用戶配置,須要用戶自行配置一個toolrc.json
文件,指明模板文件的輸入輸出目錄,和須要用到的
詢問變量,示例配置以下:
{ "source": "/src/template", "dist": "/build", "questionConfig": { "name": { "type": "string", "required": true, "label": "Module name" }, "description": { "type": "string", "required": true, "label": "Module description" }, "namespace": { "type": "string", "required": true, "label": "dva model namespace" } } }
source
配置了模板文件的位置,dist
爲輸出文件的位置,questionConfig
爲模板中的關鍵字,須要用戶在交互的命令行中輸入,下面這段爲利用inquirer
包,實現命令行交互。
// 遍歷問題模板,輸出提問 const inquirer = require('inquirer'); const askQuestion = async (prompts)=> { let promptsArr = Object.keys(prompts).map(key => ({ name: key, ...prompts[key] })); return inquirer.prompt(promptsArr); } module.exports.askQuestion = askQuestion
效果以下:
由於用了handlebars
包,模板的定義須要符合其規範,模板文件以下:
import React, { Component } from 'react'; import { connect } from 'dva'; import './style.less'; @connect(state => ({ loading: state.loading })) class {{name}} extends React.Component { state = {}; componentWillReceiveProps = (nextProps) => { }; render() { return (); } } export default {{name}};
最終輸出到 dist
目錄的文件,會替換其中雙括號裏的內容
這裏只是簡單的例子,能夠沉澱一些業務場景的模板,經過命令行的方式快速的建立,避免複製粘貼,其實本意是學習一下Node的腳手架工具的實現,有興趣的同窗能夠看看babel-cli
的源碼。