首頁 >微信小程式 >小程式開發 >詳解Medusa 微信小程式工程化實務方案

詳解Medusa 微信小程式工程化實務方案

coldplay.xixi
coldplay.xixi轉載
2020-09-17 17:49:163203瀏覽

詳解Medusa 微信小程式工程化實務方案

相關學習推薦:微信小程式教學

前言

我曾發布過《 實戰篇--微信小程式工程化探索之webpack》一文,當時是我探索微信小程式工程化的第一階段。起初我只是為了驗證微信小程式與 webpack 是否能夠結合(很大程度是被對於技術的好奇心驅使),對於工程化的持續交付並沒有過多的思考。但在內部需求的不斷衝擊下,我開始萌生以工程化手段持續簡化微信小程式開發難度的想法,最終衍生的產物就是這套以 Medusa 命名的微信小程式快速開發方案。
接下來我將較為詳細的分享達成這項方案的實踐過程,下文中將提到的工具我也已經發佈在 npm 上供大家下載使用。這篇文章將會涵蓋之前發表的那篇文章的全部內容並且內容更加豐富,所以篇幅方面也較為長請讀者們耐心閱讀。

webpack-build-miniprogram

webpack-build-miniprogram 是Medusa 方案的基礎也是核心,而這個工具包提供了以webpack 建立微信小程式的能力,我們可以利用webpack的生態持續豐富Medusa 的功能。在講述基礎建置配置之前,我們先來看看 Medusa 的目錄結構基礎,有了對應的目錄約束才使得專案更加規範化。

|-- dist                        编译结果目录
|-- src                         源代码目录
|   |-- app.js                  项目入口文件
|   |-- app.json                小程序配置文件
|   |-- sitemap.json            sitemap配置文件
|   |-- assets                  静态资源存放目录
|   |   |-- .gitkeep
|   |-- components              公共组件存放目录
|   |   |-- .gitkeep
|   |-- dicts                   公共字典存放目录
|   |   |-- .gitkeep
|   |-- libs                    第三方工具库存放目录(外部引入)
|   |   |-- .gitkeep
|   |-- pages                   页面文件存放目录
|   |   |-- index
|   |       |-- index.js
|   |       |-- index.json
|   |       |-- index.less
|   |       |-- index.wxml
|   |-- scripts                 公共脚本存放目录(wxs)
|   |   |-- .gitkeep
|   |-- services                API服务存放目录
|   |   |-- .gitkeep
|   |-- styles
|   |   |-- index.less          项目总通用样式
|   |   |-- theme.less          项目主题样式
|   |-- templates               公共模板存放目录
|   |   |-- .gitkeep
|   |-- utils                   公共封装函数存放目录(自我封装)
|       |-- .gitkeep
|-- .env                        环境变量配置文件
|-- config.yaml                 编译配置文件
|-- webpack.config.js           webpack 配置扩展文件
|-- project.config.json         开发者工具配置文件
└── package.json复制代码

基礎篇

webpack 這個工具現在已經成為前端工程師的必備技能,複雜的工作原理讓我們對它總是有種敬畏感,所以在做微信小程序在建構策略過程中,我們先將它簡單的理解為一個「搬運工具」。它將原始碼目錄中的檔案加以某些處理之後再輸出到目標目錄中。現在我們明確一下我們要搬運哪些文件,微信小程式中所涉及的主要有:

  • 邏輯檔案.js 
  • 設定檔 .json 
  • 範本檔案.wxml 
  • 樣式檔案.wxss .less .scss  
  • 腳本檔案.wxs 
  • #靜態資源檔案assets/ 
##基礎搬運功能

接下來我們將書寫webpack 的公共部分配置,並利用copy-webpack-plugin 這個外掛程式來完成大部分文件的搬運工作。

/** config/webpack.common.js */const CopyPlugin = require("copy-webpack-plugin");const config = {  context: SOURCE,  devtool: 'none',  entry: {        app: './app.js'
  },  output: {    filename: '[name].js',    path: DESTINATION
  },  plugins: [    new CopyPlugin([
      {        from: 'assets/',        to: 'assets/',        toType: 'dir'
      },
      {        from: '**/*.wxml',        toType: 'dir'
      },
      {        from: '**/*.wxss',        toType: 'dir'
      },
      {        from: '**/*.json',        toType: 'dir'
      },
      {        from: '**/*.wxs',        toType: 'dir'
      }
    ])
  ]
};复制代码

以上簡單的設定我們就實作了除邏輯檔案與預編譯語言檔案以外的搬運工作,在設定中出現了

SOURCE 、 DESTINATION 兩個常數,它們分別代表的是原始碼目錄與目標程式碼目錄的絕對路徑,我們將它們抽離在單獨的字典檔案中:

/** libs/dicts.js */const path = require("path");

exports.ROOT = process.cwd();
exports.SOURCE = path.resolve(this.ROOT, 'src');
exports.DESTINATION = path.resolve(this.ROOT, 'dist');
exports.NODE_ENV = process.argv.splice(2, 1)[0];复制代码

上面搬運的檔案因為不需要特殊的內容處理,所以完全交由外掛程式去實現,剩餘兩種類型的檔案我們就需要使用到webpack 的

入口(entry)外掛程式(plugin)loader 協同合作才能完成搬運工作。

核心入口功能

首先我們要解決如何產生入口的問題,解決了入口產生的問題才能藉助 loader 去完成檔案內容的轉換。對於入口產生這個問題,我開發了另一個外掛程式 entry-extract-webpack-plugin 去解決。這一插件我並不打算詳細的講解實現的過程,我只會闡述它的核心實現思路(如果你有興趣進一步了解可以下載下來直接看源碼)。

微信小程式需要建立入口網路其實是有規律可循的,主套件、分包都會配置在 app.json 檔案中,頁面所需的元件也會配置在 [page].json 檔案中。抓住這個特點,我們可以將實現外掛功能的核心羅列為以下幾點:

    透過
  • node.js  提供的path  與fs  模組功能,以app.json 檔案中配置的路徑為基礎,遞歸的去尋找每個page 所依賴的component 路徑,最終整合在同個陣列中。
  • 利用 webpack 提供的 SingleEntryPlugin 和 MultiEntryPlugin 插件,在 entryOption 生命週期鉤子中將第一步收集的路徑數組注入到構建當中形成
  • 入口(entry)。
  • 在建構監聽的過程中如果有新的頁面添加,則透過 watchRun 生命週期將新的入口加入到先前的
  • 入口(entry)中。
以上三點是實現

生成入口這一功能的核心思路,除了核心的實現思路外,我還想簡單的講解下我們如何去寫一個webpack 插件:

class EntryExtractPlugin {  constructor(options) {}
  
  apply(compiler) {
    compiler.hooks.entryOption.tap('EntryExtractPlugin', () => {
        ...
    });
    compiler.hooks.watchRun.tap('EntryExtractPlugin', () => {
        ...
    });
  }
}复制代码

webpack 的插件大致是以的形式存在,当你使用插件时,它会自动执行 apply 方法, 然后使用 compiler.hooks 对象上的各种生命周期属性便可以将我们需要的处理逻辑植入到 webpack 的构建流程当中。

逻辑与样式

上面解决了生成入口(entry)的问题,接下来我们在原有的基础上完善一下策略。由于预编译语言的类型较多,我为了策略的可扩展性将样式部分的策略抽离为单独的部件,然后在通过 webpack-merge 这一工具将它们合并起来,完整的实现如下:

/** config/webpack.parts.js */exports.loadCSS = ({ reg = /\.css$/, include, exclude, use = [] }) => ({    module: {    rules: [
      {
        include,
        exclude,        test: reg,        use: [
          {            loader: require('mini-css-extract-plugin').loader
          },
          {            loader: 'css-loader'
          }
        ].concat(use)
      }
    ]
  }
});复制代码
/** config/webpack.common.js */const { merge } = require('webpack-merge');const MiniCssExtractPlugin = require('mini-css-extract-plugin');const parts = require('./webpack.parts.js');const config = {
  ...
  module: {    rules: [
      {        test: /\.js$/,        loader: 'babel-loader',        exclude: /node_modules/
      }
    ]
  },  plugins: [
    ...
    new MiniCssExtractPlugin({ filename: '[name].wxss' })
  ]
};module.export = merge([
    config,
  parts.loadCSS({    reg: /\.less$/,    use: ['less-loader']
  })
]);复制代码

以上就是基础的 webpack 策略,接下来我们书写一个工具包的可执行文件便可以在项目当中通过 medusa-server {mode} 使用 webpack 提供的功能了。我们需要在工具包的 package.json 文件中配置 bin 字段,它标志了我们通过命令会自动执行哪个文件。

{
  ...
  main: "index.js",
  bin: {    "medusa-server": "index.js"
  }
}复制代码
/** index.js */const webpack = require('webpack');const { merge } = require('webpack-merge');const chalk = require('chalk');const commonConfig = require('./config/webpack.common');const { NODE_ENV } = require('./libs/dicts');const config = (function(mode) {  if (mode === 'production') {    return merge([commonConfig, { mode }]);
  }  return merge([commonConfig, {    mode: 'development',    watch: true,    watchOptions: { ignored: /node_modules/ }
  }])
})(NODE_ENV);

webpack(config, (err, stats) => {  if (err) {    console.log(chalk.red(err.stack || err));    if (err.details) {        console.log(chalk.red(err.details));
    }    return undefined;
  }  const info = stats.toJson();  if (stats.hasErrors()) {    console.log(chalk.red(info.errors));
  }  if (stats.hasWarnings()) {    console.log(chalk.yellow(info.warnings));
  }
});复制代码

进阶篇

基础篇当中完成的 webpack 配置已经可以满足两点功能,一是对常规文件的输出,二是具备开发与生产两种不同的模式。对于基础篇我还有两点要说明一下:
为什么没有应用ES6(更改版本)转为ES5的相关插件?
因为在实践当中发现IOS的10.x版本存在async/await语法无法正常使用的情况,所以索性就让构建更加纯粹一些 ,然后启用微信开发者工具的 ES6 转 ES5增强编译 这两项功能,由官方工具去处理新特性。
为什么 devtool 要设置为 none,难道不需要 sourceMap 吗?
微信官方已经原生提供了SourceMap功能,这在你上传版本时开发者工具中就已经有体现了。
接下来就进入进阶篇的梳理,在满足正常的输出后其实与原生开发好像并没有太大差异,这完全体现不出 webpack 的作用,所以我们要利用 webpack 的能力及其相关的工具生态来扩展下 Medusa 的功能。接下来我们将赋予 Medusa 以下几点功能:

  • 路径别名 @
  • 根据环境自动注入相应的环境域名
  • ESLint、StyleLint代码规范检查
  • 路由功能
  • 公共代码抽取
  • 环境变量
  • webpack 可扩展

路径别名 @

原生微信小程序只支持相对路径的引入方式,但是我们难免会遇到必须移动某些文件的情况,假设这个文件在多处被引用,那就头疼了。所以我们通过 webpack 的能力以及搭配 jsconfig.json 配置文件可以让我们有更好的开发体验。

/** config/webpack.common.js */const config = {
  ...
  resolve: {    alias: {        '@': SOURCE
    }
  }
};复制代码
{  "compilerOptions": {    "baseUrl": ".",    "paths": {      "@/*": ["./src/*"],
    },    "target": "ES6",    "module": "commonjs",    "allowSyntheticDefaultImports": true,
  },  "include": ["src/**/*"],  "exclude": ["node_modules"],
}复制代码

通过 alias 别名配置,@ 符号就可以指代 src 源代码目录,然后在实际业务项目的根目录下创建一个 jsconfig.json 文件,通过以上的配置在使用 vscode 编辑器时就可以获得良好的智能路径提示功能。

自动注入相应的环境域名

在介绍这个功能之前,我需要先讲解一下 config.yaml 这个配置文件的作用。这是我一开始设想用来扩展各种功能的配置文件,它应用在实际业务项目的根目录中。现阶段它的功能还相当少,不过将来应该会逐步迭代增加的。

# 当前项目应用平台platform: wx# 样式单位 px 转换 rpx 的比例设定css_unit_ratio: 1# 域名配置development_host:  api: https://www.miniprogram.dev.comproduction_host:  api: https://www.miniprogram.pro.com复制代码

这其中的域名配置是可以自由增减的,书写形式形如例子当中所示。当我们在其中配置好对应环境的域名后,我们在业务代码当中便可以用 mc.$hosts.api 变量轻松的访问域名。在构建过程中工具会自动帮你将对应环境的域名注入代码当中,你再也不用关心如何去管理和切换不同环境域名的问题了。下面展示一下我是如何实现这一功能的:

/** libs/dicts */exports.CONFIG = path.resolve(this.ROOT, 'config.yaml');
exports.DEFAULT_CONFIG = {  platform: 'wx',  css_unit_ratio: 1,
};复制代码
/** libs/index.js */const fs = require('fs');const yaml = require('js-yaml');const { CONFIG, DEFAULT_CONFIG } = require('./dicts');

exports.yamlConfig = (function() {  try {    /** 将 yaml 格式的内容解析为 object 对象 */
    const config = yaml.load(fs.readFileSync(CONFIG, { encoding: 'utf-8' }));    return config;
  } catch(e) {    return DEFAULT_CONFIG;
  }
})();复制代码
/** webpack.common.js */const webpack = require('webpack');const { yamlConfig } = require('../libs');const { NODE_ENV } = require('../libs/dicts');const config = {
  ...
  plugins: [
    ...
    new webpack.DefinePlugin({        mc: JSON.stringify({        $hosts: yamlConfig[`${NODE_ENV}_host`] || {}
      })
    })
  ]
};复制代码

webpack.DefinePlugin 插件可以判别代码中 mc 这个标识符然后进行相应的替换功能,这样就能保证代码的正常运作。

ESLint 与 StyleLint

编码规范已经是老生常谈的话题了,今年我所处的前端团队扩容到二十几人,在多人共同协作和不同成员维护的情况下,我们迫切的需要统一的编码规范来减少维护的成本。(在规范落地上,我还有两句题外话要说,当我们需要落地一项规范的时候,不要停留在讨论规则上,也不要妄想用人的自律去约束编码。如果你的团队也需要实施这些东西,希望你能够成为先驱者将规则先定下初版,然后找一个合适的工具,通过工具去约束人的行为。)接下来我们就来看下我们所需要的检查工具是如何配置的:

/** webpack.common.js */const StylelintPlugin = require('stylelint-webpack-plugin');const config = {  module: {    rules: [
      {        enforce: 'pre',        test: /\.js$/,        exclude: /node_modules/,        loader: 'eslint-loader',        options: {            fix: true,          cache: false,          formatter: require('eslint-friendly-formatter')
        }
      }
    ]
  },  plugins: [    new StylelintPlugin({        fix: true,      files: '**/*.(sa|sc|le|wx|c)ss'
    })
  ]
};复制代码

以上配置的两个工具可以对 js 与样式文件按照一定的规则集进行检查,对于部分不符合规范的代码可以自行修复,无法自动修复的部分将会给出相应的提示。应用什么规则集你可以通过业务项目根目录中的 .eslintrc 和 .stylelintrc 文件去决定(下面的章节我会对规范这一块再进行相应的展开)。

公共代码抽取

在应用 webpack 之后,我们为微信小程序开发赋予了 npm 加载依赖的能力。在模块化打包的机制下,工具包会被加载进引用它的入口当中,这会导致微信小程序包大小及其容易就超出限定值了,所以我们的解决方案是将多次出现的代码抽离为单独的公共部分,具体的实施代码如下:

/** config/webpack.common.js */const config = {
  ...
  output: {
    ...,    globalObject: 'global'
  },  plugins: [
    ...
    new webpack.BannerPlugin({        raw: true,      include: 'app.js',      banner: 'const vendors = require("./vendors");\nconst commons = require("./commons");\nconst manifest = require("./manifest");'
    })
  ],  optimization: {    cacheGroups: {        vendors: {        chunks: 'initial',        name: 'vendors',        test: /[\\/]node_modules[\\/]/,        minChunks: 3,        priority: 20
      },      commons: {        chunks: 'initial',        name: 'commons',        test: /[\\/](utils|libs|services|apis|models|actions|layouts)[\\/]/,        minChunks: 3,        priority: 10
      }
    },    runtimeChunk: {        name: 'manifest'
    }
  }
};复制代码

微信小程序的全局变量可以通过 global 访问,所以我们需要将 output.globalObject  属性设置为 global。webpack 内置的 BannerPlugin 插件可以将我们需要的语句插入在指定的文件的头部,利用它我们就做到了将抽离出来的公共文件重新引入到依赖网格中。

环境变量

我们在使用 Vue 、React 的脚手架创建的项目时会见到 .env 这个文件,它的主要作用是扩展开发者所需的环境变量。在微信小程序扩展环境变量变量这样的需求可能少之又少,但是我们可以换一种思路,我们可以利用它来扩展开发者所需的全局变量。接下来我们将利用 dotenv-webpack 这一工具来实现这个功能,它可以读取业务项目根目录下的 .env 文件内容,使得我们在编码中也可以使用当中的变量值。

/** config/webpack.common.js */const Dotenv = require('dotenv-webpack');const { ENV_CONFIG } = require('../libs/dicts');const config = {
  ...
  plugins: [
    ...
    new Dotenv({ path: ENV_CONFIG })
  ]
};复制代码

webpack 扩展

通过 webpack 我已经实现了通用的许多功能,但是我所处的公司开发的微信小程序颇多,所以难免有一些项目需要个性化的定制策略。既然有这样的需求,我们就应该提供这样的能力给到业务开发者,一来我们可以从多个项目当中吸收更多需要集成的功能点,二来可以暂时减轻自身的负担。webpack-merge 这一工具其实在前面样式部分合并我已经使用过了,我们只需要提供业务项目 webpack 配置的路径即可,再修改下之前的执行文件。

/** index.js */const { WEBPACK_CONFIG } = require('./libs/dicts');const config = (function(mode) {  if (mode === 'production') {    return merge([commonConfig, { mode }, WEBPACK_CONFIG]);
  }  return merge([commonConfig, {    mode: 'development',    watch: true,    watchOptions: { ignored: /node_modules/ }
  }, WEBPACK_CONFIG])
})(NODE_ENV);

...复制代码

路由功能

微信官方提供了关于路由跳转的 API ,但我认为官方的 API 在日常开发中有几点不便:

  1. 需要输入完整的路径字符串,路径太长难以记忆不说,假如页面路径有所修改需要投入较高的维护成本。
  2. 跳转方式多样,四种不同类型的跳转 API 较为常用的是 navigateTo,因为 API 有多个并且参数也较多,所以使用时难免都省不了再去查阅一遍文档。
  3. 官方提供的任何一种 API 最终目标页面中接收到的 query 都是字符串类型,这一定程度上限制了我们的编码设计。

为了解决上述的三个问题,我将 API 进行了二次封装,从中抹除四种跳转类型的差异,通过统一的接口就可以达到四种跳转方法的效果。并且通过 webpack 的全局变量注入功能优化了路径字符串的获取,方便使用并且容易维护。
mc.$routes
我将页面的文件夹名称与路径相关联,两者形成映射关系的话,我们只需要书写文件夹名称便可。原来我们需要使用 pages/home/index 访问 home 页面,通过我的改造之后,我们可以通过 mc.$routes.home 访问 home 页面。
medusa-wx-router
medusa-wx-router 是我对路由功能进行二次封装后的工具包,具体的实现过程在这里我就不详述了,你可以自行下载使用或是依照你的需求进行再次改造,下面我只展示一下在业务代码中结合 mc.$routes 如何使用:

mc.routerTo({  url: mc.$routes.home,  type: 'push',  query: {    id: 0,    bool: true
  },  success: () => console.log('successfully')
});/** push 方式快捷形式 */mc.routerTo(mc.$routes.home, { id: 0, bool: true });复制代码

为了省去 import 路由工具包这一步骤,我使用了 webpack.ProvidePlugin 这一插件自动帮我们在有使用的地方补充 import 功能。

/** config/webpack.common.js */const config = {
  ...
  plugins: [
    ...
    new webpack.ProvidePlugin({      'mc.routerTo': ['medusa-wx-router', 'routerTo'],      'mc.decoding': ['medusa-wx-router', 'decoding'],      'mc.back': ['medusa-wx-router', 'back'],      'mc.goHome': ['medusa-wx-router', 'goHome'],
    })
  ]
};复制代码

规范

规范是工程的重要一环,一个团队必须遵照同一套规则进行编码,规范的存在使得代码的质量得以提升,有统一的规范认知使得成员互相交接项目更加轻松高效。在前面的实践当中,我已经将规范的检查工具集成在了构建流程当中,本节我将补充一下应用于微信小程序的规则集配置和相关编辑器插件,当然我还希望你阅读一下我对于命名规范的一些总结希望对你有所启发《你可能需要的统一命名规范》。
规则集其实就是一个包含规则的配置文件,接下来我会给出具体的配置内容。当然在考虑到规则集的团队定制性和升级的问题,我将 ESLint 和 StyleLint 的规则都制作成了 npm 包,这就解决了所有业务项目统一规则的问题。对应的 npm 包分别是 eslint-config-medusa 和 stylelint-config-medusa ,这是我所处的团队所需要的,所以对于你们在实践时可以结合你们团队的现有情况进行改造。

# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false复制代码

在业务项目的根目录中创建 .editorconfig 文件配合 vscode 的 EditorConfig for VS Code 插件便可以对常规的文件进行基础的编码约束。

module.exports = {  parserOptions: {    ecmaVersion: "2018",    sourceType: "module"
  },  parser: "babel-eslint",  env: {    node: true,    commonjs: true,    es6: true
  },  globals: {    mc: false,    wx: true,    swan: true,    App: true,    Page: true,    Component: true,    Behavior: true,    getApp: true,    getCurrentPages: true
  },  extends: ["airbnb-base"],  rules: {    // disallow use of console
    "no-console": "warn",    // disallow use of debugger
    "no-debugger": "error",    // disallow dangling underscores in identifiers
    'no-underscore-dangle': 'off',    // specify the maximum length of a line in your program
    "max-len": ["error", 120],    // require return statements to either always or never specify values
    "consistent-return": ["error", { "treatUndefinedAsUnspecified": true }],    // disallow usage of expressions in statement position
    "no-unused-expressions": "off"
  },  settings: {    "import/resolver": {      alias: {        map: [
          ["@", "./src"],
          ["utils", "./src/utils"],
          ["services", "./src/services"]
        ],        extensions: [".js", ".json", ".less"]
      }
    }
  }
};复制代码
module.exports = {  rules: {    // Disallow unknown type selectors
    'selector-type-no-unknown': null,    // Disallow unknown units
    'unit-no-unknown': null
  },  extends: [    'stylelint-config-standard',    'stylelint-config-recess-order'
  ],  plugins: ['stylelint-order']
};复制代码

上面展示的就是我所完成的两个规则集依赖包的入口文件,当我们 install 依赖包之后,我们就可以在业务项目的 .eslintrc 和 .stylelintrc 文件中通过 extends 字段将它们引入。搭配 vscode 编辑器的 ESLint 和 stylelint-plus 插件,它们可以在编码过程中就提醒你相应的错误规则。

脚手架

前面说的构建与规范全都是服务于具体的业务项目的,在拥有了基础的能力之后,我们就该思考如何使得业务开发人员能够快速并且符合要求的进行业务系统开发。让他们不再需要考虑目录应该怎么约定,工具如何集成,编码规范究竟如何应用诸如这些问题。为了达成快速开发这一要求,我着手制作具备初始化项目这一简易功能的脚手架。最终我将这一工具分为两个项目,其一是具备初始化项目结构、下载相关依赖包功能的 @chirono/medusa-cli,另外是约定好项目结构与必要配置文件的 miniprogram-base 。
业务开发者只需要通过 npm 全局安装 @chirono/medusa-cli 工具,便可以通过 medusa create 1b8b62ddc634804650a3eb289a01f98b 命令初始化一个工程项目,该工具还会提示必要的项目信息让开发者输入,用于完善业务项目的 package.json 文件。

结语

以上是我对 Medusa 这一工程的总结,现在这一工程已经进入较为稳定的阶段也已经顺利投入到多个实际项目当中。对于上述的总结可能有某些部分写得相对简略,如果你却有兴趣我建议你直接下载源码去研究,因为编码的实际操作确实无法用三言两语解释清楚。我写下这一篇文章主要想表达的是工程链路的完整性能够为实际的项目开发产生相当大的效益,而且实践工程当中不仅是单一工具的开发,要将工具串起来。接下来我可能还会对UI打包、TS编译、测试工具这些内容进行知识总结。最后,感谢你的阅读,如果你有疑问或是建议可以留言与我共同探讨。

想了解更多编程学习,敬请关注php培训栏目!

以上是詳解Medusa 微信小程式工程化實務方案的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除