Mpx是一款致力于提高小程序开发体验的增强型小程序框架,通过Mpx,我们能够以最先进的web开发体验(Vue +
Webpack)来开发生产性能深度优化的小程序。
下面说说mpx脚手架的源码:
源码地址:https://github.com/didi/mpx/tree/master/packages/cli
目录结构:
.
├── README.md
├── bin
│ ├── mpx-init.js // init命令开始执行的内容
│ └── mpx.js // 命令入口文件
├── lib
│ ├── ask.js // 自定义工具-用于询问开发者
│ ├── check-version.js // 检查本地node和npm包版本
│ ├── eval.js // 在data的作用域执行exp表达式并返回其执行得到的值
│ ├── filter.js // 配合metalsmith删除过滤多余的文件
│ ├── generate.js // 模板下载后根据用户选择生成指定模板
│ ├── git-user.js // 用于获取本地的git配置的用户名和邮件,并返回格式 姓名<邮箱> 的字符串
│ ├── local-path.js // 判断本地文件是否存在
│ ├── logger.js // 记录日志
│ └── options.js // 获取模板的选项配置信息并初始化默认值
├── package-lock.json
└── package.json
{
"name": "@mpxjs/cli",
"version": "2.1.0",
"description": "mpx脚手架",
"bin": {
"mpx": "bin/mpx.js",
"mpx-init": "bin/mpx-init.js"
},
"dependencies": {
"async": "^2.4.0",
"chalk": "^2.1.0",
"commander": "^2.9.0",
"download-git-repo": "^1.0.1",
"inquirer": "6.3.1",
"metalsmith": "^2.1.0",
"minimatch": "^3.0.0",
"multimatch": "^2.1.0",
"nunjucks": "^3.1.2",
"ora": "^1.3.0",
"read-metadata": "^1.0.0",
"request": "^2.67.0",
"rimraf": "^2.5.0",
"semver": "^5.1.0",
"tildify": "^1.2.0",
"update-notifier": "^2.5.0",
"user-home": "^2.0.0",
"validate-npm-package-name": "^3.0.0"
},
"keywords": [
"mpx",
"cli"
],
"author": "donghongping",
"license": "Apache",
"main": "bin/mpx-init.js",
"directories": {
"bin": "bin",
"lib": "lib"
},
"files": [
"bin",
"lib"
],
"publishConfig": {
"registry": "https://registry.npmjs.org"
},
"repository": {
"type": "git",
"url": "[email protected]:didi/mpx.git"
},
"homepage": "https://didi.github.io/mpx/",
"bugs": {
"url": "https://github.com/didi/mpx/issues"
},
"engines": {
"node": ">=8.0.0"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
}
}
#!/usr/bin/env node
require('commander')
.version(require('../package').version)
.usage(' [options]' )
.command('init', 'generate a new project from a template')
.parse(process.argv)
主要是根据init命令执行max-init.js文件内容;
引入依赖包:忽略
指令引导提示:
// 指令引导
program
.usage('[project-name]')
.option('-c, --clone', 'use git clone')
.option('--offline [value]', 'use cached template or specific a local path to mpx-template')
.on('--help', () => {
console.log()
console.log(' Examples:')
console.log()
console.log(chalk.gray(' # create a new project with the specified dirname'))
console.log(' $ mpx init awesome-project')
console.log()
console.log(chalk.gray(' # create a new project in current directory'))
console.log(' $ mpx init')
console.log()
})
.parse(process.argv)
判断是否在当前目录创建项目
if (inPlace || exists(to)) {
inquirer.prompt([{
type: 'confirm',
message: inPlace
? 'Generate project in current directory?'
: 'Target directory exists. Continue?',
name: 'ok'
}]).then(answers => {
if (answers.ok) {
run()
}
}).catch(logger.fatal)
} else {
run()
}
判断inPlace和exists(to),true则询问开发者,当开发者回答“yes”的时候执行run函数,否则直接执行run函数。这里询问开发者的问题有如下两个:
Generate project in current directory? //是否在当前目录下构建项目
Target directory exists. Continue? //构建目录已存在,是否继续
重要的两个函数
run函数: 主要逻辑如下
可以选择选用本地自定义的模板或者是官方模板,如果是需要使用远程仓库的自定义模板,则可以在下载的时候配置自己指定的模板地址;
function run () {
// check if template is local 模板是否在本地
if (isLocalPath(template)) {
// 获取本地模板
const templatePath = getTemplatePath(template)
if (exists(templatePath)) {
// 本地模板存在,直接处理本地模板的配置
generate(name, templatePath, to, err => {
if (err) logger.fatal(err)
console.log()
logger.success('Generated "%s".', name)
})
} else {
// 本地模板不存在,记录错误日志
logger.fatal('Local template "%s" not found.', template)
}
} else {
// 检查版本号,下载官方模板
checkVersion(() => {
// use official templates
// 如果需要下载自己的模板即可在此处修改自定义模板的路径
const officialTemplate = 'mpx-ecology/' + template
downloadAndGenerate(officialTemplate)
})
}
}
downloadAndGenerate函数:下载函数
function downloadAndGenerate (template) {
const spinner = ora('downloading template') // loading 动画
spinner.start() // 动画开始
// Remove if local template exists
if (exists(tmp)) rm(tmp)
// 下载克隆
download(template, tmp, { clone }, err => {
spinner.stop() // 下载完成后,动画暂停
if (err) logger.fatal('Failed to download repo ' + template + ': ' + err.message.trim())
// 执行参数可选配置等操作,主要是执行meta.js里面的各项配置
generate(name, tmp, to, err => {
if (err) logger.fatal(err)
logger.success('Generated "%s".', name)
})
})
}
该文件主要处理模本的配置和生成
主要函数为generate函数,即上面mpx-init.js里面的generate函数
/**
* 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) {
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
}) // 添加一些变量至metalsmith中,并获取metalsmith中全部变量
const helpers = { chalk, logger }
//配置对象是否有before函数,是则执行
if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
}
(opts.mock
? metalsmith.use(mock(opts.mock))
: metalsmith.use(askQuestions(opts.prompts))) // 询问问题
.use(computed(opts.computed)) // 处理关键词
.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)
.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') {
// 配置对象有complete函数则执行
const helpers = { chalk, logger, files }
opts.complete(data, helpers)
} else {
// 配置对象有completeMessage,执行logMessage函数
logMessage(opts.completeMessage, data)
}
})
return data
}
generate的主要分为以下几步:
另外与vue-cli不同的是,mpx的脚手架添加了配置变量插值标签,以免渲染时能与小程序动态绑定语法冲突
nunjucks.configure({
tags: {
variableStart: '<$',
variableEnd: '$>'
},
autoescape: false,
trimBlocks: true,
lstripBlocks: true
})
const render = nunjucks.renderString
其他文件:正如上面目录介绍里面一样,主要是封装的一些工具函数,在上述方法中使用到,这里不做详细解释;