当前位置: 首页 > 工具软件 > Mpx > 使用案例 >

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

广瑞
2023-12-01

前言

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

.
├── LICENSE
├── README.md
├── __test__ // 测试用脚手架中用户预设答案渲染模板命令
│   ├── testWxCrossNoWeb.js
│   ├── testWxCrossWeb.js
│   ├── testWxNoCross.js
│   └── testWxPlugin.js
├── meta.js // 模板配置入口文件
├── package-lock.json
├── package.json // 安装包
├── realMeta.js // 模板配置包括prompts/cumputed/filters/complete
├── template // 模板
│   ├── README.md // 模板说明
│   ├── build // 模板打包文件
│   │   ├── build.js // 打包配置
│   │   ├── mpx.plugin.conf.js // mpx配置
│   │   ├── webpack.conf.js // 常规webpack配置
│   │   └── webpack.plugin.conf.js // 插件配置
│   ├── config // 运行和打包配置
│   │   ├── dev.env.js // 运行配置
│   │   └── prod.env.js // 生产配置
│   ├── functions // 目前为空
│   ├── package.json // 安装包等配置
│   ├── project.config.json
│   ├── src //
│   │   ├── app.mpx // 入口文件
│   │   ├── app.ts // 使用ts的入口文件
│   │   ├── components // 组件
│   │   │   ├── list.mpx
│   │   │   └── list.ts
│   │   ├── index.html // 生成web端入口文件
│   │   ├── miniprogram // 插件
│   │   │   ├── app.mpx
│   │   │   └── pages 
│   │   │       └── index.mpx
│   │   ├── pages // 常规
│   │   │   ├── index.mpx // 首页
│   │   │   └── index.ts // 使用ts
│   │   └── plugin // 插件
│   │       ├── components // 插件的组件
│   │       │   └── list.mpx
│   │       └── plugin.json // 插件配置
│   ├── static // 静态资源配置
│   │   ├── ali
│   │   │   └── mini.project.json
│   │   └── wx
│   │       └── project.config.json
│   └── tsconfig.json // ts配置文件,选用ts时需要
└── testfile // 测试用meta文件
    ├── wx-cross-noweb-meta.js
    ├── wx-cross-web-meta.js
    ├── wx-no-cross-meta.js
    └── wx-plugin-meta.js

文件分析

realMeta.js

module.exports = {
  prompts: {
    mode: {
      type: 'list',
      required: true,
      message: '请选择小程序项目所属平台(目前仅微信下支持跨平台输出)',
      choices: ['wx', 'ali', 'swan', 'qq', 'tt'],
      default: 'wx'
    },
    cross: {
      when: 'mode === "wx"',
      message: '是否需要跨小程序平台',
      type: 'confirm',
      default: true
    },
    transWeb: {
      when: 'mode === "wx" && cross === true',
      message: '是否需要支持输出web',
      type: 'confirm',
      default: true
    },
    cloudFunc: {
      when: 'mode === "wx" && cross === false',
      message: '是否需要使用小程序云开发能力',
      type: 'confirm',
      default: false
    },
    tsSupport: {
      message: '是否需要使用TS?',
      type: 'confirm',
      default: false
    },
    name: {
      type: 'string',
      required: true,
      message: '项目名称'
    },
    description: {
      type: 'string',
      required: false,
      message: '项目描述',
      default: 'A mpx project'
    },
    author: {
      type: 'string',
      message: '作者'
    },
    isPlugin: {
      when: 'mode === "wx" && cross === false && cloudFunc === false',
      type: 'confirm',
      message: '是否是个插件项目?(不清楚请选 No !什么是插件项目请看微信官方文档!)',
      default: false
    },
    appid: {
      when: 'mode === "wx"',
      required: true,
      message: '请输入小程序的Appid',
      default: 'touristappid'
    },
    needEslint: {
      type: 'confirm',
      message: '是否需要ESlint',
      default: true
    }
  },
  computed: {
    dirFor () {
      switch (this.mode) {
        case 'wx':
          return 'wx:for'
        case 'ali':
          return 'a:for'
        case 'swan':
          return 's-for'
        case 'qq':
          return 'qq:for'
        case 'tt':
          return 'tt:for'
      }
    },
    dirKey () {
      switch (this.mode) {
        case 'wx':
          return 'wx:key'
        case 'ali':
          return 'a:key'
        case 'swan':
          return 's-key'
        case 'qq':
          return 'qq:key'
        case 'tt':
          return 'tt:key'
      }
    }
  },
  filters: {
    'src/@(miniprogram|plugin)/**/*': 'isPlugin',
    'build/webpack.plugin.conf.js': 'isPlugin',
    'src/index.html': 'transWeb',
    'src/!(miniprogram|plugin)/**/*': 'mode !== "wx" || !isPlugin',
    'src/*': 'mode !== "wx" || !isPlugin',
    'project.config.json': 'mode === "wx" && !cross',
    'static/**/*': 'cross',
    'tsconfig.json': 'tsSupport',
    '.eslintrc.js': 'needEslint',
    '**/*.ts': 'tsSupport',
    'functions/*': 'cloudFunc'
  },
  complete: function (data, { chalk }) {
    const green = chalk.green
    console.log(green('complete!'))
  }
}
  • prompts : 基于 Inquirer.js 的命令行交互配置

mode : 交互字段名称,可在后续条件交互或模板渲染时通过该字段读取到交互结果
type : 交互类型,有 input, confirm, list, rawlist, expand, checkbox, password, editor 八种类型
message : 交互的提示信息
when : 进行该条件交互的先决条件,在该例子中,cross 这个交互动作只在’mode === “wx”'时才会出现
default : 默认值,当用户输入为空时,交互结果即为此值
required : 默认为 false,该值是否为必填项
validate : 输入验证函数

  • filters : 根据命令行交互的结果过滤将要渲染的项目文件

辅助函数只可以控制文件内一部分内容的输出与否,有时候我们需要根据交互结果控制某些文件本身是否输出。 可以达到控制文件输出的效果:
module.exports = {
//…
“filters”: {
‘src/@(miniprogram|plugin)/**/*’: ‘isPlugin’,
‘build/webpack.plugin.conf.js’: ‘isPlugin’,
},
//… }
  filters 中键名是要控制输出的文件的路径,可使用字面量,也可使用 简化的 glob 表达式。键名对应的值为命令行交互中得到的数据。

  • computed: 用于获取到变量值后计算出其他值
  • complete : 将模板渲染为项目后,输出一些提示信息,取值为函数,函数最后需返回输出的字符串

以上这些命令将在脚手架中的generate函数中使用,具体如下,更多详细前往 @mpx/cli 脚手架源码解析

  (opts.mock
    ? metalsmith.use(mock(opts.mock))
    : metalsmith.use(askQuestions(opts.prompts))) // 询问问题
    .use(computed(opts.computed)) // 处理关键词
    .use(filterFiles(opts.filters)) // 过滤文件
    .use(renderTemplateFiles(opts.skipInterpolation)) // 渲染模板文件

关于模板中局部代码的渲染,则使用nunjucks插件,具体语法可查看nunjucks文档
如package.json文件:

{
  "name": "<$ name $>",
  "version": "1.0.0",
  "description": "<$ description $>",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "devbuild": "node ./build/build.js",
    "watch": "node ./build/build.js -w",
    "build": "node ./build/build.js -p",
    "prod": "node ./build/build.js -p",
    {% if cross %}
    "watch:cross": "{% if transWeb %}npx npm-run-all --parallel 'watch --wx --ali --swan --web' httpserver{% else %}npm run watch --wx --ali --swan{% endif %}",
    "build:cross": "npm run build --wx --ali --swan{% if transWeb %} --web{% endif %}",
    "prod:cross": "npm run prod --wx --ali --swan{% if transWeb %} --web{% endif %}",
    {% endif %}
    {% if transWeb %}
    "watchweb": "npx npm-run-all --parallel 'watch --web' httpserver",
    "httpserver": "npx http-server dist/web",
    {% endif %}
    {% if needEslint %}
    "lint": "eslint --ext .js,.mpx src/",
    {% endif %}
    "help": "node ./build/build.js --help"
  },
  "author": "<$ author $>",
  "license": "ISC",
  "dependencies": {
    "@mpxjs/api-proxy": "^2.3.0",
    {% if transWeb %}
    "vue": "^2.6.10",
    {% endif %}
    "@mpxjs/core": "^2.3.0"
  },
  "devDependencies": {
    "copy-webpack-plugin": "^5.0.3",
    "@mpxjs/webpack-plugin": "^2.3.0",
    {% if transWeb %}
    "http-server": "^0.12.0",
    "npm-run-all": "^4.1.5",
    "extract-text-webpack-plugin": "^3.0.2",
    "html-webpack-plugin": "^3.2.0",
    "vue-loader": "^14.2.4",
    "vue-router": "^3.1.3",
    "vue-template-compiler": "^2.6.10",
    "style-loader": "^1.0.1",
    {% endif %}
    {% if needEslint %}
    "eslint-loader": "^2.1.1",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.10.0",
    "eslint-config-babel": "^8.0.2",
    "eslint-config-standard": "^12.0.0",
    "eslint-friendly-formatter": "^4.0.1",
    "eslint-plugin-html": "^5.0.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-local-rules": "^0.1.0",
    "eslint-plugin-node": "^8.0.0",
    "eslint-plugin-prettier": "^2.6.2",
    "eslint-plugin-promise": "^4.0.1",
    "eslint-plugin-standard": "^4.0.0",
    {% endif %}
    {% if tsSupport %}
    "ts-loader": "^6.0.0",
    "typescript": "^3.5.0",
    {% endif %}
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.4",
    "babel-runtime": "^6.26.0",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-2": "^6.24.1",
    "chalk": "^2.3.2",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "html-loader": "^0.5.5",
    "ora": "^2.0.0",
    "path": "^0.12.7",
    "rimraf": "^2.6.2",
    "stylus": "^0.54.5",
    "stylus-loader": "^3.0.2",
    "url-loader": "^1.0.1",
    "webpack": "^4.16.1",
    "webpack-cli": "^3.1.0",
    "webpack-merge": "^4.1.2",
    "webpack-bundle-analyzer": "^3.3.2"
  }
}

{% if cross %} … {% endif %} 即为条件判断渲染语句。
另外为了避免变量插值标签与小程序的动态绑定语法冲突,可以自定义一个动态插值标签,如下:

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

页面中其他文件的动态输出类似;

总结

总体来说,整个模板分为几大块,一是自测的脚本和配置文件,二是实际的模板内容;其中模板内容包含了所有的相关文件,可以根据用户的选择配置渲染生成,包括过滤相关的文件,以及模板渲染语法来控制输出的代码内容。如果需要自定义模板的,在上面添加对应的配置即可。

 类似资料: