집 >위챗 애플릿 >미니 프로그램 개발 >Medusa WeChat 미니 프로그램 엔지니어링 실습 계획에 대한 자세한 설명
관련 학습 추천: WeChat Mini 프로그램 튜토리얼
저는 WeChat Mini 프로그램을 탐색할 때 "실습 장 - WeChat Mini 프로그램 엔지니어링 탐구 - Webpack"이라는 기사를 게시한 적이 있습니다. 엔지니어링 단계. 처음에는 위챗 애플릿과 웹팩이 결합될 수 있는지 확인하고 싶었고(주로 기술에 대한 호기심 때문이었습니다) 지속적인 전달 엔지니어링에 대해서는 별로 생각하지 않았습니다. 그러나 내부 요구의 끊임없는 영향으로 WeChat 미니 프로그램 개발의 어려움을 엔지니어링 방법을 통해 지속적으로 단순화한다는 아이디어가 최종 제품이 Medusa의 이름을 딴 WeChat 미니 프로그램용 빠른 개발 솔루션이었습니다.
다음으로, 이 솔루션을 달성하는 실제 프로세스를 더 자세히 공유하겠습니다. 아래에 언급된 도구는 모든 사람이 다운로드하여 사용할 수 있도록 npm에도 게시되었습니다. 이번 글은 이전에 게재된 글의 내용을 모두 다루며, 내용이 더욱 풍부해질 예정이므로, 인내심을 가지고 읽어보시기 바랍니다.
webpack-build-miniprogram은 Medusa 솔루션의 기초이자 핵심입니다. 이 툴킷은 webpack으로 WeChat 미니 프로그램을 구축하는 기능을 제공하며, 우리는 webpack 생태계를 사용하여 계속해서 기능을 풍부하게 할 수 있습니다. 메두사의. 기본 구성 구성에 대해 이야기하기 전에 먼저 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 이 도구는 이제 프론트엔드 엔지니어에게 필수적인 기술이 되었습니다. 복잡한 작동 원리로 인해 우리는 항상 경외감을 느끼게 됩니다. 따라서 WeChat 미니 프로그램 전략을 구축하는 과정에서 우리는 먼저 단순히 "교통 도구"로 이해됩니다. 소스 코드 디렉터리의 파일에 대해 일부 처리를 수행한 다음 대상 디렉터리에 출력합니다. 이제 WeChat 애플릿과 관련된 주요 파일은 다음과 같습니다.
.js
.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
.json
.wxml
.wxss
.less
.scss
.wxs
assets/
class EntryExtractPlugin { constructor(options) {} apply(compiler) { compiler.hooks.entryOption.tap('EntryExtractPlugin', () => { ... }); compiler.hooks.watchRun.tap('EntryExtractPlugin', () => { ... }); } }复制代码위의 간단한 구성으로 논리 파일과 미리 컴파일된 언어 파일을 제외한 전송 작업을 구현했습니다. 구성에는
SOURCE
및 DESTINATION
두 개의 상수가 나타납니다. 각각 소스 코드 디렉터리와 대상 코드 디렉터리의 절대 경로를 나타냅니다. 별도의 사전 파일로 추출합니다. 🎜/** 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) } ] } });复制代码🎜위로 이동한 파일은 특별한 콘텐츠 처리가 필요하지 않으므로 플러그인으로 완전히 구현됩니다. 나머지 두 가지 유형의 파일에 대해서는 webpack의 🎜entry🎜, 🎜plugin🎜 및 🎜loader🎜를 사용하여 함께 작업하여 이동 작업을 완료해야 합니다. 🎜
path
및 fsnode.js
/code> 모듈 함수는 app.json 파일에 구성된 경로를 기반으로 각 페이지가 의존하는 구성 요소 경로를 반복적으로 검색하고 최종적으로 동일한 배열로 통합합니다. 🎜🎜webpack에서 제공하는 SingleEntryPlugin 및 MultiEntryPlugin 플러그인을 사용하여 첫 번째 단계에서 수집된 경로 배열을 EntryOption 수명 주기 후크의 빌드에 삽입하여 🎜항목을 형성합니다. 🎜🎜🎜모니터를 구축하는 과정에서 새 페이지가 추가되면 watchRun 라이프사이클을 통해 이전 🎜entry🎜에 새 항목이 추가됩니다. 🎜🎜🎜위의 세 가지 사항은 🎜항목 생성🎜 기능을 구현하기 위한 핵심 아이디어입니다. 핵심 구현 아이디어 외에도 웹팩 플러그인 작성 방법을 간략하게 설명하고 싶습니다. 🎜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 以下几点功能:
原生微信小程序只支持相对路径的引入方式,但是我们难免会遇到必须移动某些文件的情况,假设这个文件在多处被引用,那就头疼了。所以我们通过 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 这个标识符然后进行相应的替换功能,这样就能保证代码的正常运作。
编码规范已经是老生常谈的话题了,今年我所处的前端团队扩容到二十几人,在多人共同协作和不同成员维护的情况下,我们迫切的需要统一的编码规范来减少维护的成本。(在规范落地上,我还有两句题外话要说,当我们需要落地一项规范的时候,不要停留在讨论规则上,也不要妄想用人的自律去约束编码。如果你的团队也需要实施这些东西,希望你能够成为先驱者将规则先定下初版,然后找一个合适的工具,通过工具去约束人的行为。)接下来我们就来看下我们所需要的检查工具是如何配置的:
/** 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-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 在日常开发中有几点不便:
为了解决上述的三个问题,我将 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 WeChat 미니 프로그램 엔지니어링 실습 계획에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!