Frontend Development 34 min read

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.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Progressive Optimization Journey of a WeChat Mini Program

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 = true

Request 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

WeChat Mini Programfrontend optimizationRoutingmodular architectureESLintES6GulpSubpackage
Sohu Tech Products
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.