{"id":15352261,"url":"https://github.com/hufeng/mppack","last_synced_at":"2026-03-17T17:36:27.885Z","repository":{"id":57400564,"uuid":"100785368","full_name":"hufeng/mppack","owner":"hufeng","description":"小程序的webpack","archived":false,"fork":false,"pushed_at":"2019-01-01T04:44:32.000Z","size":1470,"stargazers_count":39,"open_issues_count":1,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-15T05:53:58.626Z","etag":null,"topics":["babel","gulp","mixins","mpapp","nodemodules","typescript","wechat-app"],"latest_commit_sha":null,"homepage":"https://hufeng.github.io/mppack","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hufeng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-19T09:51:15.000Z","updated_at":"2023-08-19T04:49:55.000Z","dependencies_parsed_at":"2022-09-05T00:41:16.273Z","dependency_job_id":null,"html_url":"https://github.com/hufeng/mppack","commit_stats":null,"previous_names":["hufeng/wxpacker"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/hufeng/mppack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hufeng%2Fmppack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hufeng%2Fmppack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hufeng%2Fmppack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hufeng%2Fmppack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hufeng","download_url":"https://codeload.github.com/hufeng/mppack/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hufeng%2Fmppack/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265947514,"owners_count":23853383,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["babel","gulp","mixins","mpapp","nodemodules","typescript","wechat-app"],"created_at":"2024-10-01T12:09:08.133Z","updated_at":"2026-03-17T17:36:27.834Z","avatar_url":"https://github.com/hufeng.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mppack\n\n构建一个好用的小程序的打包工具，小程序界的 webpack 😆 😆 ^\\_^\n\n## why ?\n\n各种(钉钉，微信，支付宝)小程序以燎原之势席卷而来之后，对于小程序的开发需求就提上了日程\n\n小程序本身提供的开发方式非常简约，我们可以用更现代前端的开发方式来构建我们的应用\n\n1. \u003cdel\u003e不支持 node_modules\u003c/del\u003e 已经原生支持了, 不用麻烦了 点赞\n2. \u003cdel\u003e不支持模块的绝对路径（导致了不支持 node_modules）\u003c/del\u003e\n3. 不完整的支持 es6 或者 更想用 typescript(效率工具)\n4. Callback 回调方式如果逻辑重太难维护代码，更期待 promise/async/await 的解决方案\n5. \u003cdel\u003e组件的支持不够完整\u003c/del\u003e\n6. 样式可以用 css,less,sass,postcss....\n7. 图片可不可以自动优化体积\n\n## Goal?\n\n提供工程化的改进，而不是框架上的改进\n\n1.\u003cdel\u003e 自动支持 node_modules\u003c/del\u003e 已经支持了\n\n2. 无缝支持 babel（babel-preset-env） / typescript /借助 babel 可以做特殊特征优化\n\n3. 支持 async/await (babel-plugin-transform-runtime 并不能正常的在小程序中运行)\n\n4. 自动编译\n\n5. 样式支持 css，less 等\n\n## babel typescript async/await\n\nmppack 直接内置了@babel/preset-typescript 的插件无缝支持 typescript 的转化\n\n钉钉的小程序对 babel 的支持级别比较好的支持 es2015，\n\n实测可以支持到 async/await (es2017)希望不是 bug 😆 对于 npm 的支持也比较到位\n\n微信小程序虽然支持的 npm 但是仍有一套自己的规则，需要通过微信开发工具 npm build 一次，\n\n在 babel7 以后开启@babel/plugin-transform-runtime 之后会导入\n\n```javascript\nimport _asyncToGenerator from '@babel/runtime/helpers/asyncToGenerator';\nimport _defineProperty from '@babel/runtime/helpers/defineProperty';\nimport _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';\nimport _regeneratorRuntime from '@babel/runtime/regenerator/index';\n```\n\n从模块上的说这样非常好，将 api 或者语音特性模块化，最小化减少打包体积，\n\n但是微信小程序目前还不支持@开头的模块名，希望后面可以完美支持。\n\n怎么配置 babel 让微信小程序支持呢？\n\n首先不通过这样的方式来引入模块，去除@babel/plugin-transform-runtime,\n\n```javascript\nfunction _defineProperty(obj, key, value) {\n  if (key in obj) {\n    Object.defineProperty(obj, key, {\n      value: value,\n      enumerable: true,\n      configurable: true,\n      writable: true\n    });\n  } else {\n    obj[key] = value;\n  }\n  return obj;\n}\n\nfunction _toConsumableArray(arr) {\n  return (\n    _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread()\n  );\n}\n\nfunction _nonIterableSpread() {\n  throw new TypeError('Invalid attempt to spread non-iterable instance');\n}\n\nfunction _iterableToArray(iter) {\n  if (\n    Symbol.iterator in Object(iter) ||\n    Object.prototype.toString.call(iter) === '[object Arguments]'\n  )\n    return Array.from(iter);\n}\n\nfunction _arrayWithoutHoles(arr) {\n  if (Array.isArray(arr)) {\n    for (var i = 0, arr2 = new Array(arr.length); i \u003c arr.length; i++) {\n      arr2[i] = arr[i];\n    }\n    return arr2;\n  }\n}\n\nfunction asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {\n  try {\n    var info = gen[key](arg);\n    var value = info.value;\n  } catch (error) {\n    reject(error);\n    return;\n  }\n  if (info.done) {\n    resolve(value);\n  } else {\n    Promise.resolve(value).then(_next, _throw);\n  }\n}\n```\n\n然后单独解决 async/await 的问题就好。微信小程序可以使用如下 babel 的配置\n\n```javascript\n//.babelrc\n{\n  \"presets\": [\n    [\n      \"@babel/env\",\n      {\n        \"modules\": false,\n        \"targets\": {\n          \"browsers\": [\"\u003e 1%\", \"last 2 versions\", \"not ie \u003c= 8\"]\n        }\n      }\n    ]\n  ]\n}\n```\n\n添加依赖\n\n```javascript\n//package.json\n{\n  \"dependencies\": {\n    \"regenerator-runtime\": \"^0.13.1\"\n  }\n}\n```\n\n剩下的就交给我们的工具吧\n\nmppack 自动通过 babel-plugin-mpapp-pack 来解决 async/await 的问题。\n\n## getting started\n\ninstall\n\n```sh\n# 全局安装\nnpm install -g mppack / yarn global add mppack\n\n#项目依赖\nyarn add mppack --dev\n```\n\n```sh\ncd quick-start\n\nmppack\n```\n\n```text\nquick-start\n~/OSS/mppack/example/wxapp-todo next*\n❯ tree -L 3 -I node_modules\n.\n├── app.css\n├── app.js\n├── app.json\n├── build\n│   ├── app.js\n│   ├── app.json\n│   ├── app.wxss\n│   ├── package-lock.json\n│   ├── pages\n│   │   └── index\n│   └── utils\n│       └── util.js\n├── package-lock.json\n├── package.json\n├── pages\n│   └── index\n│       ├── domain\n│       ├── index.css\n│       ├── index.js\n│       └── index.wxml\n├── utils\n│   └── util.js\n└── yarn.lock\n\n8 directories, 15 files\n\n```\n\n## help\n\n```text\n❯ npx mppack --help\nUsage: mppack [-o path]\n\nOptions:\n  -V, --version                  output the version number\n  -o, --output [path]            Which bundle output\n  -v, --verbose                  show verbose log\n  -w, --watch                    watch mode\n  -c, --config [file]            specify a config file\n  -t, --target [wxapp|eapp]      specify a platform target\n  -m, --module [offline|online]  offline copy node_modules, online npm install\n  -h, --help                     output usage information\n\n```\n\n## For example\n\n```text\n~/OSS/mppack/example/eapp-hello next*\n❯ mppack -w -v\n ____________________________\n\u003c 🚀🚀mppack@1.0.0开始为您构建 \u003e\n ----------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||\n[15:12:47] 当前mppack版本 =\u003e 1.0.0\n[15:12:47] 输出目录 =\u003e build\n[15:12:47] watch模式 =\u003e true\n[15:12:47] verbose模式 =\u003e true\n[15:12:47] json: app.json =\u003e build/app.json\n[15:12:47] less: app.less =\u003e build/app.acss\n[15:12:47] ts: app.ts =\u003e build/app.js\n[15:12:47] image: snapshot.png =\u003e build/snapshot.png\n[15:12:47] axml: pages/index/index.axml =\u003e build/pages/index/index.axml\n[15:12:47] json: package-lock.json =\u003e build/package-lock.json\n[15:12:47] ts: pages/index/index.ts =\u003e build/pages/index/index.js\n[15:12:47] js: node_modules/bmue/lib/index.js =\u003e build/node_modules/bmue/lib/index.js\n[15:12:47] json: node_modules/bmue/package.json =\u003e build/node_modules/bmue/package.json\n[15:12:47] ts: pages/index/ql.ts =\u003e build/pages/index/ql.js\n[15:12:47] js: node_modules/bmue/lib/mixin.js =\u003e build/node_modules/bmue/lib/mixin.js\n[15:12:47] json: pages/index/index.json =\u003e build/pages/index/index.json\n[15:12:47] ts: pages/index/rl.ts =\u003e build/pages/index/rl.js\n[15:12:47] js: node_modules/bmue/lib/mue.js =\u003e build/node_modules/bmue/lib/mue.js\n[15:12:47] ts: pages/index/webapi.ts =\u003e build/pages/index/webapi.js\n[15:12:47] js: node_modules/bmue/lib/types.js =\u003e build/node_modules/bmue/lib/types.js\n[15:12:47] ts: node_modules/bmue/lib/typings/index.d.ts =\u003e build/node_modules/bmue/lib/typings/index.d.js\n[15:12:47] js: node_modules/bmue/lib/util.js =\u003e build/node_modules/bmue/lib/util.js\n[15:12:47] ts: node_modules/bmue/lib/typings/mixin.d.ts =\u003e build/node_modules/bmue/lib/typings/mixin.d.js\n[15:12:47] js: node_modules/bmue/lib/rx/index.js =\u003e build/node_modules/bmue/lib/rx/index.js\n[15:12:47] ts: node_modules/bmue/lib/typings/mue.d.ts =\u003e build/node_modules/bmue/lib/typings/mue.d.js\n[15:12:47] js: node_modules/bmue/lib/rx/ql.js =\u003e build/node_modules/bmue/lib/rx/ql.js\n[15:12:47] ts: node_modules/bmue/lib/typings/types.d.ts =\u003e build/node_modules/bmue/lib/typings/types.d.js\n[15:12:47] js: node_modules/bmue/lib/rx/rl.js =\u003e build/node_modules/bmue/lib/rx/rl.js\n[15:12:47] ts: node_modules/bmue/lib/typings/util.d.ts =\u003e build/node_modules/bmue/lib/typings/util.d.js\n[15:12:47] ts: node_modules/bmue/lib/typings/rx/index.d.ts =\u003e build/node_modules/bmue/lib/typings/rx/index.d.js\n[15:12:47] ts: node_modules/bmue/lib/typings/rx/ql.d.ts =\u003e build/node_modules/bmue/lib/typings/rx/ql.d.js\n[15:12:47] ts: node_modules/bmue/lib/typings/rx/rl.d.ts =\u003e build/node_modules/bmue/lib/typings/rx/rl.d.js\n[15:12:49] gulp-imagemin: Minified 1 image (saved 13.6 kB - 85.1%)\n⛽️ finish |\u003e: 2156.029ms\n[15:12:49] watching...\n```\n\n通过配置文件来配置,默认读取本地的 mppack.config.js\n\n可以通过-c 来指定文件的路径\n\n```js\n//wxpack.config.js\nmodule.exports = {\n  //不需要'./build', 默认相对当前的目录\n  output: 'build',\n  //是否开启watch模式\n  watch: true/false，\n  //开启或者关闭log详情\n  verbose: true/false\n}\n```\n\n# bmue\n\n## Why?\n\n小程序本身的 Api，如果 Page，App 等已经够满足自身的开发，不需要特别重的开发框架或者模式\n\n在基于这个基础上其实我们还可以做的更好一点，比如基于 Reactive 的设计可以做的更简单一点\n\n小程序本身也是 Reactive 的设计当我们 setData 的时候，UI 会自动 re-render\n\n其实数据的本身也是需要这种机制，遗憾小程序 UI 只能从 data 中获取数据，\n\n导致不能像 vue 一样有 compute 属性，好处是我们可以简单的实现，当 data 发生变化的时候有数据\nreactive 的计算的能力\n\n如果所有逻辑都放在 Page 中会导致 Page 比较臃肿，代码难以重用，我们可以做一些公共功能的分拆如 Pagination 等，然后自动 mixin 到 Page 的参数对象中去\n\n从这些点入手，我们可以设计一个简单的粘合层去自动帮我们做到这些。\n这就是 bmue 的初衷。\n\n## getting started\n\n```sh\nyarn add bmue\n```\n\n## Simple demo\n\n小程序 Page\n\n```javascript\nPage({\n  data: {\n    hello: 'hello mpapp'\n  },\n  onLoad() {\n    this.setData({\n      hello: 'hello mapp next'\n    });\n  }\n});\n```\n\nbmue Mue\n\n```javascript\nimport { Mue } from 'bmue';\nMue({\n  data: {\n    hello: 'hello mpapp'\n  },\n  onLoad() {\n    this.setData({\n      hello: 'hello mapp next'\n    });\n  }\n});\n```\n\nYes, 完全一样嘛！ O(∩_∩)O 哈哈~尴尬了嘛 No No No\n\n## Domain Object\n\n### Mixin\n\n像早期的 React 的 api 一样，我们可以设计一个 mixin 的体系，来合入公共的功能\n\n```javascript\nconst Hello = React.createClass({\n  mixins: [StoreMixin],\n\n  render() {\n    //...\n  }\n});\n```\n\nmixin 就是一个普通的对象(pojo =\u003e plain Ordinary javasript object)\n\n```typescript\nimport { Mixin, Mue } from 'bmue';\n\nexport const TabMixin = Mixin({\n  //auto merge into Page data\n  data: {\n    tabs: {\n      activeIndex: 0,\n      data: [\n        { title: '全部' },\n        { title: '待处理' },\n        { title: '已处理' },\n        { title: '已完成' }\n      ]\n    }\n  },\n\n  setTabActive(index: number) {\n    this.setData({\n      'tabs.activeIndex': index\n    });\n  }\n});\n\nMue({\n  mixins: [TabMixin]\n});\n```\n\n```typescript\nexport const PaginationMixin = (url: string) =\u003e {\n  return Mixin({\n    pageNo: 0,\n    pageSize: 20,\n    data: {\n      pageList: []\n    },\n\n    //lifecycle method\n    //auto merge into Page lifycle method\n    onLoad() {\n      this.refreshFetchData();\n    },\n    onPullDownRefresh() {\n      this.refreshFetchData();\n    },\n    onReachBottom() {\n      this.fetchData();\n    }\n\n    fetchData() {\n      //url\n      //params\n    },\n    refreshFetchData() {\n      //url,\n      //params\n      this.pageNo = 0;\n    }\n  });\n};\n\nMue({\n  mixins: [Pagination('http://......')]\n})\n```\n\n### QL = query-lang\n\n我们想基于 Reactive 设计数据计算的能力单元，这就是我们的 QL\n\n```javascript\nconst helloQL = QL('helloQL', [\n  //data path\n  //in order to compute data whether was changed.\n  'hello',\n  hello =\u003e `${hello}!!!`\n]);\n\nMue({\n  //when first loading, or data was changed\n  //helloQL was computed auto, and value was\n  //merged into data: {rx: {hello: 'hello mpapp!!'}}\n  getter: { hello: helloQL },\n  data: {\n    hello: 'hello mpapp'\n  },\n  onLoad() {\n    //setState can effect geteer\n    //setData can not do it.\n    this.setState({\n      hello: 'hello mpapp next'\n    });\n  }\n});\n```\n\n### EL = effect-lang\n\n我们可以看到 QL 是关注返回值的，有些时候在一些场景我们不需要返回值，\n\n比如 🔍 的时候，无论是搜索条件发生改变或者其他的影响到搜索的条件，\n\n我们希望可以自动的做搜索 api 的调用，这就是 el\n\n```javascript\nconst el = EL('helloEL', [\n  //data path, may Array or string\n  'searchParam',\n  searchParam =\u003e {\n    webapi.fetchResult(searchParam);\n  }\n]);\n\nMue({\n  effect: [el],\n  data: {\n    searchParam: {\n      key: ''\n    }\n  },\n\n  onSearchTap(key) {\n    this.setState({\n      'searchParam.key': key\n    });\n    //el was auto call\n  }\n});\n```\n\n### action = 响应 Page 事件\n\n```javascript\nexport default Action({\n  onLoad() {},\n\n  onButtonTap() {}\n});\n```\n\n### Mue = Page + Reactive\n\nMue 对象根据传入的参数进行数据的处理然后调用 Page\n\n```javascript\nMue({\n  //reactive data\n  //当setState数据发生变化的时候\n  //Mue会自动计算ql，如果数据发生了变化(自带缓存机制)\n  //会自动把变化的数据绑定到data: {rx: {}} rx内\n  //页面(wxml, axml)可以{{rx.hello}}这样获取\n  getter: {},\n\n  //reactive effect\n  //当setState数据发生变化，Mue自动判断数据的变化会不会影响到EL\n  //如果影响会自动的调用EL的函数\n  effect: [],\n\n  //Mue, 会自动的解析Mixin\n  //将Mixin中的所有的data和声明周期方法合并\n  //然后合入最终的Page需要的对象参数中去\n  mixins: [],\n\n  //当前页面的state,\n  //Mue会自动合入mixin中的所有的data，已经QL计算的结果\n  data: {},\n\n  //响应页面事件\n  Action: Action({})\n\n  //声明周期函数\n  //Mue会自动把mixin中的声明周期函数自动合并\n  onLoad() {},\n\n  //普通方法，\n  onButtonTap() {\n    //自动触发getter，effect的计算\n    //setData,没有办法做到\n    this.setState({\n      //\n    })\n  }\n});\n```\n\n### Demo\n\n![wxapp-todo](https://raw.githubusercontent.com/hufeng/mppack/next/screencast/mppack-todo.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhufeng%2Fmppack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhufeng%2Fmppack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhufeng%2Fmppack/lists"}