前端脚手架构建实践

26次阅读

共计 3498 个字符,预计需要花费 9 分钟才能阅读完成。

前面的话

在前端工程化过程中,为了解决多项目中,相似度高的工作,便诞生许多前端脚手架,这里记录下自己实现一个简易前端脚手架过程的实践。主要是解决多个页面相似内容的复制粘贴问题,功能类似于 Webstorm 的 Live template,或者 Vscode 的 Snippets。

思路
  • 预先配置页面模板,预留关键字变量
  • 用户填写关键字变量,生成页面模板,输出到制定目录
用到的包
  • fs

    读写文件模块,这里主要用于读入用户配置文件,输出模板到文件

  • commander

    NodeJs 命令行工具,提供了用户命令行输入和参数解析,用户解析用户输入

  • inquirer

    NodeJs 交互式命令行工具,询问操作者问题,获取用户输入,校验回答的合法性

  • metalsmith

    文件处理,读写操作

  • handlebars

    将模板中的变量替换为用户输入,编译模板,类似框架如:artTemplate,Jade

  • path

    NodeJs 的路径操作库,如合并路径

  • chalk

    命令行输出样式美化

具体实现
  1. 首先在一个新的文件夹,如 xxx-tools 下 npm init 创建一个 node 项目,因为是要做成一个 npm 包的脚手架,所以在包的取名上一定要唯一,即 package.jsonname字段,避免在发包的时候和网上已经存在的 npm 包重名,报 403 没有权限的错。
  2. 在 xxx-tools 文件夹下创建 bin 文件夹,同时在 bin 文件夹下创建脚本 tempTool 文件,内容如下:
#!/usr/bin/env node

console.log('Hello World');

注意哦,#!/usr/bin/env node 这个是 Linux 规范,用来指明了这个执行脚本的解释程序,要是没有这一行,默认用当前 Shell 去解释这个脚本

  1. package.json 中增加 bin 配置:
 "bin": {"tempTool": "./bin/tempTool"},
  1. 到目前为止,一个简单的前端脚手架实现了,在 npm 官网注册,在项目里执行 npm login 登录,之后 npm publish 如果一切顺利,npm 包提交完毕,可以在其它项目中执行 npm i -g xxx-tools,安装这个包,执行xxx-tools 命令,输出 Hello World,脚手架开发过程中,也涉及到在本地调试,可以直执行node ./bin/xxx-tools
  2. 现在来加入具体的开发流程,用户的输入,输入信息的读取等等,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 函数,看一下这个函数

// 把模板中的变量替换为用户输入的变量,输出模板到制定文件夹
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 的源码。

正文完
 0