前言
有了之前的基础(),我们现在可以讲讲一个成熟的脚手架是怎么做了。
这里我们参考vue-cli的源码,基于rollup和typescript一步步搭建。vue-cli作为vue的脚手架,给如此多的前端开发者使用,已经算是成熟了吧。
开始
以下我们的命令仍然是~,模板是
目录结构
├─ 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.jsimport 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('') .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(参考试一下命令。
如果失败,请重新审视上述之流程。
初始化模板
我们对模板的要求
- 文件目录必须含有template文件夹,并且所需模板文件放在该目录下
- 可用meta.js提高自定义程度(所谓动态化模板)
- 模板文件名命名规范是ds-cli-‘name’-template,方便脚手架拉取
- 可以不用线上的模板,如果本地有模板,可直接用之。如果是线上模板,那么应该先下载到本地用户目录的.ds-templates目录下(~/.ds-templates)
期望命令
ds init#模板名字和应用名字复制代码
流程与逻辑
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函数里面用到了,这个就相当于我们之前用的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); });}复制代码
- 我们在模板(如 )目录下需要构造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,有以下比较好玩的接口
- 个人主要信息:
- 个人所有repo:
- repo详细信息:
- issues列表。
- 某条issue详情: issues都是以1,2,3这样的序列排号的。
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),同时把该请求时间(主要是用来判断是否是今天)也进去,然后我们在代码里判断一下:下次请求的时候,是否本本地有该文件,有的话就读一下,读了之后判断里面的时间和现在的时候是否是相隔一天,相隔一天就重新请求(时间间隔自己把握),覆盖原有缓存文件。
参考
- :目前暂时命令有init和list(获取所有模板列表),含有详细注释,不懂你来!!!
- :基于rollup和typescript构建类库
- :基于vuepress的文档模板