Progressive Optimization Journey of a WeChat Mini Program
This article chronicles the step‑by‑step optimization of a WeChat mini‑program, covering native stack choices, ES6+ adoption, modular architecture, versioning, code formatting, request abstraction, UI component migration, ESLint, unit testing, routing, environment handling, Gulp workflow, CSS variables, and sub‑package strategies.
Progressive Optimization Journey of a WeChat Mini Program
After joining Sohu, the author’s first project was a WeChat mini‑program. The ecosystem’s closed nature, frequent breaking changes, and cumbersome developer tools motivated a series of native‑stack optimizations instead of using third‑party frameworks like mpvue or Taro.
Code Philosophy
Code is for humans to read, not just to run.
Robust structure outweighs clever design.
Keep the project simple; perfection means no further reduction.
Pre‑development Decisions
Use the native technology stack and avoid third‑party frameworks.
Embrace ES6+ syntax and promisify all wx APIs.
Since many APIs still use callbacks, the author wraps them with wxp (miniprogram‑api‑promise) to provide Promise‑based versions.
// utils/wxp.js
import { promisifyAll, promisify } from 'miniprogram-api-promise';
const wxp = {};
promisifyAll(wx, wxp);
promisify(wx.getUserProfile);
export { wxp };
export default wxp;
// page.js
import { wxp } from '../../utils/wxp';
await wxp.showModal({ showCancel: false, title: '提示', content: errMsg });Modularization
Common utilities (config, helpers, http, logger, etc.) are split into separate ES6 modules, keeping the global scope thin.
// utils directory structure
utils/
├─ Auth.js
├─ event-bus.js
├─ helpers.js
├─ http.js
├─ logger.js
├─ md5.js
└─ wxp.js
// page.js example
import { logger } from '../../utils/logger';
logger.log('...');Versioning and CHANGELOG
The project follows SemVer and maintains a CHANGELOG.md file.
## 3.3.0 (2021.08.16)
- A 新增 积分商城模块
- I 优化 分享到朋友圈功能
- I 优化 小程序体积,主包体积缩小 42% (950KB → 549KB)
- I 升级 富文本解析器版本
- F 修复 首页弹窗的异常样式Code Formatting with Prettier & .editorconfig
Prettier is configured via .prettierrc.js and style rules are enforced with .editorconfig .
# .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
tab_width = 2
trim_trailing_whitespace = true
insert_final_newline = trueRequest Layer Abstraction
A unified http wrapper around wx.request adds loading indicators, error handling, token injection, and custom interceptors.
export default ({ withToken = false, showLoading = true, loadingMsg = '加载中...', showError = true, errorMsg = '请求错误,请稍候再试', ...options }) =>
new Promise(async (resolve, reject) => {
if (showLoading) wx.showLoading({ title: loadingMsg, mask: true });
const header = options.header || { 'Content-Type': 'application/json' };
if (withToken && Auth.isLogged()) {
if (Auth.shouldRefreshToken()) await Auth.refreshToken();
header['X-SA-AUTH'] = Auth.getToken();
}
wx.request({
...options,
header: Object.assign(header, options['header'] || {}),
success: res => { /* success handling */ },
fail: err => { /* fail handling */ }
});
});App ↔ Page Data Interaction
Instead of scattering callbacks, a unified getUserInfo(cb) method in app.js stores pending callbacks until user info is ready, then resolves them.
App({
globalData: { isUserInfoInited: false, userInfoListeners: [], isLogged: false, userInfo: null },
async initUserInfo() { /* fetch and store user info */ },
getUserInfo(cb) {
if (this.globalData.isUserInfoInited) {
cb(new IAppUserInfo({ isLogged: this.globalData.isLogged, userInfo: this.globalData.userInfo }));
} else {
this.globalData.userInfoListeners.push(cb);
}
}
});Vant Weapp Migration
The project replaces WeUI with Vant Weapp, copying only needed components into vendor/vant and providing scripts to upgrade and publish components.
// cli/vant-upgrade.js
const shell = require('shelljs');
const chalk = require('chalk');
const path = require('path');
// clone, move, clean steps …ESLint Integration
ESLint is set up with .eslintrc.js and ignores third‑party libraries via .eslintignore . It works together with Prettier.
module.exports = {
env: { browser: true, node: true, es6: true, commonjs: true },
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
parser: '@babel/eslint-parser',
globals: { App: true, Page: true, Component: true, Behavior: true, wx: true, getApp: true, getCurrentPages: true },
rules: { 'no-unused-vars': 'warn', 'prettier/prettier': 'warn' }
};Unit Testing
Tests are written with miniprogram-simulate to verify component behavior.
import simulate from 'miniprogram-simulate';
import { randomStr } from '../utils';
import { CompSpec } from '../CompSpec';
describe('no-data component', () => {
test('Has default text', () => {
const comp = simulate.render(compId);
expect(comp.querySelector('.text').dom.innerHTML).toBe('目前还没有数据');
});
});Portal Page for Unified Navigation
A dedicated /pages/portal/portal page receives all external links, parses a pk parameter, and redirects to the appropriate internal page.
Component({
methods: {
onLoad(pageOptions) {
const launchOptions = wx.getLaunchOptionsSync();
// determine adapters based on scene or pk
const { pk, ...options } = optionsObj;
adapters[pk] ? adapters[pk](options) : reLaunch('Index');
}
}
});Custom Routing System
Routes are defined in router/routes.js and a high‑order function generates navigation helpers (switchTab, reLaunch, navigateTo, redirectTo).
export const Routes = {
Index: '/pages/index/index',
Activities: '/pages/tabs/activities/activities',
// … other routes
};
export const getRouteUrl = routeName => {
const target = Routes[routeName];
if (!target) console.error(`Invalid route: ${routeName}`);
return target;
};
export const navigateTo = generateRoute(wx.navigateTo);Project Organization by Proximity
Components and assets used only by a single page are placed alongside that page, while truly reusable assets stay in global components and assets folders, reducing bundle size.
Permission & Authentication Module
All WeChat permission handling lives in utils/Auth.js , providing methods like isWxAuthed and requireLogged to centralize login flow.
static async isWxAuthed(scope, withRejectGuide = true) {
const { authSetting } = await wxp.getSetting();
// logic to authorize or show guide
}
static requireLogged(cb, immediateDoCb = false, forceDoLogin = false) {
if (Auth.isLogged() && !forceDoLogin) {
cb && cb();
} else {
navigateTo('Login', null, { logged: () => { immediateDoCb && cb && cb(); } });
}
}Gulp Workflow
Gulp compiles SCSS to WXSS, minifies JS/CSS for production, and watches source files. The build output goes to dist , which is set as the mini‑program root.
// gulpfile.js (excerpt)
const isProd = process.env.NODE_ENV === 'production';
const SRC = path.resolve(__dirname, './src');
const DIST = path.resolve(__dirname, './dist');
const sass = () =>
gulp.src(`${SRC}/**/*.scss`)
.pipe(sass())
.pipe(gulpIf(isProd, uglifycss()))
.pipe(rename({ extname: '.wxss' }))
.pipe(gulp.dest(DIST));
exports.build = gulp.series(_clean, gulp.parallel(_wxml, _wxss, _js, sass));CSS Variables
Base sizes and theme colors are defined as native CSS variables via SCSS mixins, enabling easy theming without relying on SCSS variables at runtime.
// styles/_variables.scss (excerpt)
@mixin use-css-variables() {
$css-variable-prefix-size: size-;
$size-base: 28rpx;
$size-master: 36rpx;
$special-sizes: ('base': $size-base, 'master': $size-master);
@each $size, $value in $special-sizes {
--#{$css-variable-prefix-size}#{$size}: #{$value};
}
// colors …
}Stylelint for WXSS
Stylelint enforces consistent styling and ignores unknown selectors specific to the mini‑program environment.
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],
plugins: ['stylelint-order'],
rules: {
'selector-type-no-unknown': [true, { ignoreTypes: ['page', 'scroll-view', 'block'] }],
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
// … other rules
}
};Sub‑Package Strategy
Heavy modules such as the rich‑text parser are moved to a sub‑package, reducing the main bundle from ~985 KB to ~631 KB.
References
[1] Asynchronous API returns Promise – https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/api.html#API
[2] API Promiseization – https://developers.weixin.qq.com/miniprogram/dev/extended/utils/api-promise.html
[3] SemVer – https://semver.org/lang/zh-CN/
[4] EditorConfig – https://editorconfig.org/
[5] Prettier – https://prettier.io/
[6] Vant Weapp – https://youzan.github.io/vant-weapp/#/home
[7] shelljs – https://www.npmjs.com/package/shelljs
[8] chalk – https://www.npmjs.com/package/chalk
[9] commander.js – https://www.npmjs.com/package/commander
[10] Unit testing – https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/unit-test.html
[11] CI tool – https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html
[12] EventChannel – https://developers.weixin.qq.com/miniprogram/dev/api/route/EventChannel.html
[13] Bootstrap – https://getbootstrap.com/
[14] Mini‑program sub‑package mechanism – https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.