vincent-cli
Version:
基于vue全家桶的脚手架,主要包含webapp、一般网页的模板。
164 lines (145 loc) • 4.39 kB
JavaScript
const chalk = require('chalk')
const Metalsmith = require('metalsmith') // 能力高效地容易地创立语义化的模版
const Handlebars = require('handlebars') // 模板引擎库
const async = require('async')
const render = require('consolidate').handlebars.render // 模板引擎整合库
const path = require('path')
const multimatch = require('multimatch') // 正则表达式扩展
const getOptions = require('./options')
const ask = require('./ask')
const filter = require('./filter')
const logger = require('./logger')
// 注册handlebars helper
Handlebars.registerHelper('if_eq', function (a, b, opts) {
return a === b
? opts.fn(this)
: opts.inverse(this)
})
Handlebars.registerHelper('unless_eq', function (a, b, opts) {
return a === b
? opts.inverse(this)
: opts.fn(this)
})
/**
* 生成模板。得到src和dest
*
* @param {String} name
* @param {String} src
* @param {String} dest
* @param {Function} done
*/
module.exports = function generate (name, src, dest, done) {
const opts = getOptions(name, src)
const metalsmith = Metalsmith(path.join(src, 'template'))
const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})
// 注册模板中自定义的语法
opts.helpers && Object.keys(opts.helpers).map(key => {
Handlebars.registerHelper(key, opts.helpers[key])
})
const helpers = { chalk, logger }
// 渲染模板之前的钩子
if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
}
// 提问 -> 过滤 -> 渲染模板文件
metalsmith.use(askQuestions(opts.prompts))
.use(filterFiles(opts.filters))
.use(renderTemplateFiles(opts.skipInterpolation))
// 渲染完成的钩子
if (typeof opts.metalsmith === 'function') {
opts.metalsmith(metalsmith, opts, helpers)
} else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
opts.metalsmith.after(metalsmith, opts, helpers)
}
metalsmith.clean(false)
.source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
.destination(dest)
.build((err, files) => {
// 渲染完成回调
done(err)
if (typeof opts.complete === 'function') {
const helpers = { chalk, logger, files }
opts.complete(data, helpers)
} else {
logMessage(opts.completeMessage, data)
}
})
return data
}
/**
* 发起问题中间件
*
* @param {Object} prompts
* @return {Function}
*/
function askQuestions (prompts) {
return (files, metalsmith, done) => {
ask(prompts, metalsmith.metadata(), done)
}
}
/**
* 文件过滤中间件
*
* @param {Object} filters
* @return {Function}
*/
function filterFiles (filters) {
return (files, metalsmith, done) => {
filter(files, filters, metalsmith.metadata(), done)
}
}
/**
* 模板渲染
*
* @param {Object} files
* @param {Metalsmith} metalsmith
* @param {Function} done
*/
function renderTemplateFiles (skipInterpolation) {
skipInterpolation = typeof skipInterpolation === 'string'
? [skipInterpolation]
: skipInterpolation
return (files, metalsmith, done) => {
const keys = Object.keys(files)
const metalsmithMetadata = metalsmith.metadata()
async.each(keys, (file, next) => {
// 跳过Skip插值选项文件
if (skipInterpolation && multimatch([file], skipInterpolation, { dot: true }).length) {
return next()
}
const str = files[file].contents.toString()
// 不渲染没有mustaches的文件
if (!/{{([^{}]+)}}/g.test(str)) {
return next()
}
render(str, metalsmithMetadata, (err, res) => {
if (err) {
err.message = `[${file}] ${err.message}`
return next(err)
}
files[file].contents = new Buffer(res)
next()
})
}, done)
}
}
/**
* 显示模板渲染时的一些信息
*
* @param {String} message
* @param {Object} data
*/
function logMessage (message, data) {
if (!message) return
render(message, data, (err, res) => {
if (err) {
console.error('\n Error when rendering template complete message: ' + err.message.trim())
} else {
console.log('\n' + res.split(/\r?\n/g).map(line => ' ' + line).join('\n'))
}
})
}