Frontend Development 32 min read

Migrating WePY Mini‑Program to UniApp: Compiler Design and Implementation

The Vivo front‑end team built a custom compiler that automatically transforms WePY mini‑program files—including templates, scripts, components, and pages—into UniApp Vue code, cutting the estimated 25‑person‑day migration to seconds, delivering a bug‑free launch and paving the way for open‑sourcing the tool.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Migrating WePY Mini‑Program to UniApp: Compiler Design and Implementation

The article describes how the Vivo Internet Front‑End team tackled the inefficiency of the traditional "human‑wave" development model by upgrading the front‑end stack with compilation technology. The team needed to migrate an existing smart‑guide mini‑program built with WePY to a UniApp‑based architecture, and they designed a custom compiler to automate the conversion.

Background

WePY was initially chosen for its fast iteration capability, but over time its community activity declined and the project required features from Vue‑based stacks such as UniApp and Taro. To unify the technology stack, reduce maintenance cost, and improve development efficiency, the team decided to migrate from WePY to UniApp.

Migration Strategies

Two main migration approaches were considered:

Gradual migration by mixing both stacks in the same project, converting files step by step.

Developing a wepy‑webpack‑loader that compiles WePY files directly into UniApp code.

After evaluating the pros and cons, the team chose a full‑automatic compilation approach (Strategy 3) because it could reduce the original 25 person‑days workload to a few seconds.

Compiler Architecture

The compiler consists of several pipelines:

Project resource analysis – classifies source files and routes static assets directly to the UniApp output.

AST transformation – parses WePY .wpy files into an abstract syntax tree (AST) using Babel.

Code transformation – rewrites WePY‑specific syntax (templates, components, lifecycle hooks, event system) into Vue‑compatible code.

Code generation – emits the final UniApp .vue files and auxiliary resources.

Key conversion steps include handling template tags, component imports, property mapping, and event translation.

Template Conversion

WePY templates use native WXML syntax with special tags like <import/> and <include/> . The compiler transforms these into Vue components, splitting imported files into separate .vue files when necessary. Example of the import‑to‑component conversion logic:

transformImport() {
  // Get all
tags
  const imports = this.$('import');
  for (let i = 0; i < imports.length; i++) {
    const node = imports.eq(i);
    if (!node.is('import')) return;
    const importPath = node.attr('src');
    this.importPath.push(importPath);
    let compName = TransformTemplate.toLine(
      path.basename(importPath, path.extname(importPath))
    );
    let template = node.next('template');
    while (template.is('template')) {
      if (template.attr('is')) {
        const children = template.children();
        const comp = this.$(`<${compName} />`).attr(template.attr()).append(children);
        comp.attr(TransformTemplate.toLine(this.compName), comp.attr('is'));
        comp.removeAttr('is');
        template.replaceWith(comp);
      }
      template = template.next('template');
    }
    node.remove();
  }
}

Property and Dynamic Attribute Conversion

WePY uses double‑brace syntax ( {{}} ) for dynamic values. The compiler parses these expressions, attempts to evaluate them with Babel, and rewrites them into Vue's ${} syntax. The core conversion function is:

private transformValue(value: string): string {
  const exp = value.match(TransformTemplate.dbbraceRe)[1];
  try {
    let seq = false;
    traverse(parseSync(`(${exp})`), {
      enter(path) {
        if (path.isSequenceExpression()) {
          seq = true;
        }
      },
    });
    if (!seq) {
      return exp;
    }
    return `{${exp}}`;
  } catch (e) {
    return `{${exp}}`;
  }
}

App Conversion

WePY app files define lifecycle methods, config , and globalData . The compiler extracts these parts and generates a UniApp App.vue with equivalent methods. Example snippets:

// WePY app definition
import wepy from 'wepy';
export default class MyAPP extends wepy.app {
  customData = {};
  customFunction() {}
  onLaunch() {}
  onShow() {}
  config = {};
  globalData = {};
}
// Generated UniApp App.vue script
export default {
  globalData: { text: 'text' },
  onLaunch: function() { console.log('App Launch,app启动'); },
  onShow: function() { console.log('App Show,app展现在前台'); },
  onHide: function() { console.log('App Hide,app不再展现在前台'); },
  methods: {}
}

Component Conversion

The compiler separates script , template , and style sections. Styles are kept unchanged because they already follow Vue conventions. For the script part, the compiler collects class properties, lifecycle hooks, and custom methods, then merges them into a Vue export default object. Example of merging duplicate lifecycle hooks:

const sameList = ['created', 'attached', 'onLoad', 'onReady'];
appEvents.forEach((node, index) => {
  const name = node.key.name;
  if (sameList.includes(name)) createIndexs.push(index);
});
if (createIndexs.length > 1) {
  const originNode = appEvents[createIndexs[0]];
  const originBody = originNode.body.body;
  createIndexs.slice(1).forEach(idx => {
    const targetNode = appEvents[idx];
    originBody.push(...targetNode.body.body);
    appEvents.splice(idx, 1);
  });
}

Event handling is transformed by generating Vue‑style $eventBus.$on calls inside the merged created hook.

forEachListenEvents(targetNode) {
  this.clzProperty.listenEvents.forEach(item => {
    if (item?.key?.name) {
      const thisEx = t.thisExpression();
      const ide = t.identifier('$eventBus.$on');
      const om = t.memberExpression(thisEx, ide);
      const eventName = t.stringLiteral(item.key.name);
      const methodRef = t.memberExpression(t.thisExpression(), t.identifier(item.key.name));
      const call = t.callExpression(om, [eventName, methodRef]);
      targetNode.body.body.push(t.expressionStatement(call));
    }
  });
}

Page Conversion

WePY pages inherit from wepy.page and contain config , components , data , methods , and lifecycle hooks. The compiler parses these sections, converts them to Vue page components, and updates the pages.json configuration using synchronous file I/O to avoid race conditions.

// Synchronous pages.json update
const rawPagesJson = fs.readFileSync(path.join(dest, 'src/pages.json'));
// ... modify rawPagesJson ...
fs.writeFileSync(path.join(dest, 'src', 'pages.json'), prettJson(pagesJson));

WXML Conversion

WePY WXML files may contain multiple <template name="..."> blocks and use import / include tags. The compiler generates separate Vue components for each template and an additional component for the content introduced by include . Example of the generated component structure:

Results

After integrating the compiler, the migration workload dropped from an estimated 25 person‑days to roughly 10 seconds of automated conversion. The migrated smart‑guide mini‑program was tested with zero bugs and successfully launched. The team plans to open‑source the compiler for community contributions.

Conclusion

The case demonstrates that compiler‑driven migration can dramatically improve development efficiency and reduce knowledge‑transfer costs. Future work includes finer‑grained control over conversion features and better logging during the compilation process.

FrontendmigrationjavascriptCompilerBabelWePYuniapp
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

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.