在前端工程化过程当中,为了解决多项目中,类似度高的工做,便诞生许多前端脚手架,这里记录下本身实现一个简易前端脚手架过程的实践。主要是解决多个页面类似内容的复制粘贴问题,功能相似于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
的源码。