前言

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

文件分析

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": "git@github.com: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"}
}

安装依赖包

  • async:异步处理工具
  • chalk :用于高亮终端打印出来的信息
  • commander: 命令行处理工具
  • download-git-repo: 用于下载远程仓库至本地 支持GitHub、GitLab、Bitbucket
  • inquirer:用于命令行与开发者交互
  • metalsmith:静态网站生成器
  • minimatch:字符匹配工具
  • multimatch:可以支持多个条件的匹配
  • nunjucks:js模板引擎
  • ora:用于命令行上的加载效果
  • read-metadata:用于读取json或者yaml元数据文件并返回一个对象
  • request :发送http请求的工具
  • rimraf:相当于UNIX的“rm -rf”命令
  • semver:版本号处理工具
  • tildify:将绝对路径转换成带波浪符的路径
  • update-notifier:更新node和npm包
  • user-home: 用于获取用户的根目录
  • validate-npm-package-name:用于npm包的名字是否是合法的

入口文件

mpx.js
#!/usr/bin/env noderequire('commander').version(require('../package').version).usage('<command> [options]').command('init', 'generate a new project from a template').parse(process.argv)

主要是根据init命令执行max-init.js文件内容;

mpx-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/' + templatedownloadAndGenerate(officialTemplate)})}
}

downloadAndGenerate函数:下载函数

function downloadAndGenerate (template) {const spinner = ora('downloading template') // loading 动画spinner.start() // 动画开始// Remove if local template existsif (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)})})
}

lib文件

generate.js文件

该文件主要处理模本的配置和生成
主要函数为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的主要分为以下几步:

  1. 获取模板配置
  2. 初始化Metalsmith
  3. 添加一些变量至Metalsmith
  4. 配置对象中是否有before函数,有则执行
  5. 询问问题
  6. 处理变量
  7. 过滤文件
  8. 渲染模板文件
  9. 配置对象中是否有after函数,有则执行
  10. 最后构建项目内容
  11. 构建完成,成功若配置对象中有complete函数则执行,否则打印配置对象中的completeMessage信息,如果有错误,执行回调函数done(err)

另外与vue-cli不同的是,mpx的脚手架添加了配置变量插值标签,以免渲染时能与小程序动态绑定语法冲突

nunjucks.configure({tags: {variableStart: '<$',variableEnd: '$>'},autoescape: false,trimBlocks: true,lstripBlocks: true
})
const render = nunjucks.renderString

其他文件:正如上面目录介绍里面一样,主要是封装的一些工具函数,在上述方法中使用到,这里不做详细解释;

@mpx/cli 脚手架源码解析相关推荐

  1. mpx脚手架mpx-template模板源码解析

    前言 mpx脚手架中使用的模板为mpx-template,里面做了一些配置化的东西,如果了解源码后,可以自定义模板和脚手架. git地址(2019年12月19日版本):https://github.c ...

  2. vue cli3源码解析

    vue-cli3 源码解析 脚手架代码入口点 从package.json文件中可以看到"vue-cli-service": "bin/vue-cli-service.js ...

  3. 基于postCss的TaiWindCss源码解析

    基于postCss的TaiWindCss源码解析 前言 了解 postCss 什么 是 postCss? postCss的核心原理/工作流 TaiWindCss 源码解析 TaiWindCss 是什么 ...

  4. netty依赖_Netty系列之源码解析(一)

    接下来的时间灯塔君持续更新Netty系列一共九篇 当前:Netty 源码解析(一)开始 Netty 源码解析(二): Netty 的 Channel Netty 源码解析(三): Netty 的 Fu ...

  5. SpringBoot入门-源码解析(雷神)

    一.Spring Boot入门 视频学习资料(雷神): https://www.bilibili.com/video/BV19K4y1L7MT?p=1 github: https://github.c ...

  6. 【Vue3】源码解析

    [Vue3]源码解析 首先得知道 Proxy Reflect Symbol Map和Set diff算法 patchChildren diff算法具体做了什么(重点)? patchKeyedChild ...

  7. Vue2.0源码解析——编译原理

    Vue2.0源码解析--编译原理 前言:本篇文章主要对Vue2.0源码的编译原理进行一个粗浅的分析,其中涉及到正则.高阶函数等知识点,对js的考察是非常的深的,因此我们来好好啃一下这个编译原理的部分. ...

  8. babel源码解析之(@babel/preset-env)

    前言 还记得之前写过一篇文章:babel源码解析一,里面把babel的整个流程跑了一遍,最后还自定义了一个插件用来转换"箭头函数",通过前面的源码解析我们知道,preset其实就是 ...

  9. 01.Fabric源码解析---线头(王雅震)

    Fabric源码解析1--线头 Getting Started 简单提一下Fabric说明文档中的Getting Started部分.说明文档下载地址在 http://hyperledger-fabr ...

最新文章

  1. 网络信号管理大师怎么用_常说的OKR管理法,到底怎么用?
  2. c语言如何用循环语句一个字一个字的输出,怎样用c语言的for嵌套循环,用·画出泳字,求解,主要是怎样用循环语句打出,在某一行中既有空格又有·...
  3. 随想录(markdown基本语法)
  4. 计算机算法设计与分析 数字三角形
  5. Ubuntu的 g++ gcc版本升降级
  6. mysql 查看当前连接及修改连接数
  7. opengl 遇到的基础问题
  8. Python Class System
  9. 无人机计算机模拟飞行,学习无人机,怎么安装无人机模拟器?
  10. 【网络教程】Windows字体发虚,字体不清晰怎么办?
  11. 高并发分布式场景下的应用---分布式锁
  12. [洛谷P2184]贪婪大陆
  13. html如何做站内搜索,站内搜索-Search.html
  14. InstallShield可靠的 Windows 安装程序
  15. VSCode RemoteSSH 过程试图写入的管道不存在问题 解决
  16. 第十一章 第三将 项目风险管理
  17. VIM的初学配置文件
  18. 北航计算机2018年保研推免经历
  19. PMI-ACP练习题(14)
  20. 天梯赛习题:福到了(递归打印)

热门文章

  1. 警察规范执法案例_警察改革沉浸式技术可以改变执法方式
  2. 【配送路径规划】基于matlab蚁群算法求解配送路径最短问题【含Matlab源码 2222期】
  3. uoj#750-[UNR #6]小火车【二分,折半,鸽笼原理】
  4. 基于OSGi的企业级开发框架实践——OSGi Annotations
  5. SpringBoot专栏 | SpringBoot2.x系列教程之花样配置--自定义Banner
  6. python实现陷波滤波器、低通滤波器、高斯滤波器、巴特沃斯滤波器
  7. vim 删除每行前/后n个字符
  8. 11-1自动紧急制动AEB算法原理及系统组成
  9. ITOM(IT运维管理软件)
  10. 2022大学生免费(24元)申请个人软著专利(微信小程序)