jiuye-cli
Version:
A simple CLI for scaffolding Jiuye projects.
226 lines (201 loc) • 7.03 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') // 自定义工具-用于日志打印
// register handlebars helper 注册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)
// })
// 来自@zhuangwei的测试
Handlebars.registerHelper('if_zw_test', function (name, opts) {
return name === 'zw' ? opts.fn(this) : opts.inverse(this)
})
/**
* Generate a template given a `src` and `dest`.
*
* @param {String} name
* @param {String} src
* @param {String} dest
* @param {Function} done
*/
module.exports = function generate (name, src, dest, done) {
// 获取配置meta.json数据
const opts = getOptions(name, src)
// console.log('options:', opts)
// 初始化Metalsmith对象 为:
// {
// plugins: [],
// ignores: [],
// _directory: 'C:\\Users\\zhuangwei\\.vue-templates\\tempalte-test\\template',
// _metadata: {},
// _source: 'src',
// _destination: 'build',
// _concurrency: Infinity,
// _clean: true,
// _frontmatter: true
// }
const metalsmith = Metalsmith(path.join(src, 'template'))
// 添加一些变量至metalsmith中,并获取metalsmith中全部变量
const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})
// 注册配置对象中的helper
// opts.helpers: {
// if_or: [Function: if_or],
// template_version: [Function: template_version]
// }
// opts.helpers && Object.keys(opts.helpers).map(key => {
// Handlebars.registerHelper(key, opts.helpers[key]) // opts.helpers[key] 为函数
// })
// const helpers = {
// chalk,
// logger
// }
// 配置对象是否有before函数,是则执行
// 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)) // 渲染模板文件
// 配置对象是否有after函数,是则执行
// 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) // clean 设置是否在写入之前删除目标目录,或获取当前设置。默认为true
.source('.') // 设置相path对于源目录的相对,或者如果未path提供则获取完整的目录。源目录默认为./src
.destination(dest)
.build((err, files) => {
done(err)
// 配置对象有complete函数则执行
if (typeof opts.complete === 'function') {
const helpers = {
chalk,
logger,
files
}
opts.complete(data, helpers)
} else {
// 配置对象有completeMessage,执行logMessage函数
logMessage(opts.completeMessage, data)
}
})
return data
}
/**
* Create a middleware for asking questions.
*
* @param {Object} prompts
* @return {Function}
*/
function askQuestions (prompts) {
return (files, metalsmith, done) => {
// console.log(1313213213213, prompts, metalsmith.metadata())
ask(prompts, metalsmith.metadata(), done)
}
}
/**
* Create a middleware for filtering files.
*
* @param {Object} filters
* @return {Function}
*/
// function filterFiles (filters) {
// return (files, metalsmith, done) => {
// // console.log('filters---metalsmith:', metalsmith.metadata(), filters)
// filter(files, filters, metalsmith.metadata(), done)
// }
// }
/**
* Template in place plugin.
*
* @param {Object} files
* @param {Metalsmith} metalsmith
* @param {Function} done
*/
// 渲染模板文件
function renderTemplateFiles (skipInterpolation) {
// console.log('skipInterpolation:::::', skipInterpolation, typeof skipInterpolation)
// skipInterpolation = typeof skipInterpolation === 'string' ? [skipInterpolation] : skipInterpolation // 保证skipInterpolation是一个数组
return (files, metalsmith, done) => {
const keys = Object.keys(files) // 获取files的所有key
const metalsmithMetadata = metalsmith.metadata() // 获取metalsmith的所有变量
async.each(
keys,
(file, next) => {
// 异步处理所有files
// skipping files with skipInterpolation option
// 跳过符合skipInterpolation的要求的file
// if (skipInterpolation && multimatch([file], skipInterpolation, {
// dot: true
// }).length) {
// return next()
// }
// 获取文件的文本内容
const str = files[file].contents.toString()
// do not attempt to render files that do not have mustaches
// 跳过不符合handlebars语法的file
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
)
}
}
/**
* Display template complete message.
*
* @param {String} message
* @param {Object} data
*/
function logMessage (message, data) {
if (!message) return // 没有message直接退出函数
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')
)
}
})
}