Frontend Development 21 min read

Cross‑Platform Mini‑Program Development with Remax: Background, Usage, and Engineering Practices

This article examines the need for cross‑platform mini‑program frameworks, compares compile‑time and runtime solutions such as Kbone, Taro, Nanachi, Rax and Remax, demonstrates Remax installation and basic usage, and shares engineering strategies for integrating native mini‑program code and managing multi‑team projects.

Ctrip Technology
Ctrip Technology
Ctrip Technology
Cross‑Platform Mini‑Program Development with Remax: Background, Usage, and Engineering Practices

1. Project Background

With the rapid growth of mini‑programs on various platforms, developers face divergent syntax and maintenance challenges; conversion tools are often inadequate, prompting the need for a cross‑platform framework that enables one‑time development and multi‑platform deployment.

The mainstream frameworks follow either React or Vue stacks; this article focuses on React‑based solutions, which are divided into compile‑time and runtime categories.

Key frameworks include:

Kbone (Tencent) – runtime solution that simulates DOM/BOM for Web‑compatible WeChat mini‑programs.

Taro 1/2 (JD) – static compile‑time framework using React syntax.

Nanachi (Qunar) – static compile‑time React framework.

Rax (Alibaba) – primarily runtime with optional compile‑time support.

Remax (Ant Finance) – runtime framework that allows native React code and supports Web builds from version 2.0.

Taro 3 (JD) – runtime framework compatible with multiple DSLs.

2. Effect Demonstration

Images (omitted) show that the same codebase renders correctly on Web and WeChat mini‑programs for home, list, and detail pages.

3. Basic Usage

3.1 Create/Install/Run

Initialize a Remax project:

npx create-remax-app my-app
? name my-app //填写你的appName
? author test<[email protected]>
? description Remax Project //填写项目描述
? platform 
❯ 跨平台小程序
  微信小程序
  阿里(支付宝)小程序
  头条小程序
  百度小程序

Run the project:

cd my-app && npm install
npm run dev web   // web preview
npm run dev wechat // 微信小程序端

The build generates dist/wechat and dist/ali directories that can be imported into the respective mini‑program IDEs.

The typical directory layout is shown (image omitted).

3.2 Remax Cross‑Platform Mechanism

Remax provides only nine core components and five routing APIs; additional platform‑specific components can be used directly without useComponents . File‑suffix conventions allow platform‑specific implementations.

Environment variables differentiate platforms, e.g.:

if (process.env.REMAX_PLATFORM==='wechat') { /* WeChat specific code */ }

Example of a runtime plugin to merge native mini‑program logic:

function createBothFun(remaxConfig, originConfig, key) {
  const remaxFun = remaxConfig[key];
  const originFun = originConfig[key];
  return function () {
    // this refers to the mini‑program app instance
    remaxFun.apply(this, arguments);
    originFun.apply(this, arguments);
  };
}
const lifeCycles = ['onLaunch','onShow','onHide','onPageNotFound','onError'];
function tryMergeConfig(remaxConfig, originConfig) {
  for (const key in originConfig) {
    if (key === 'constructor') {
      console.log('xxx');
    } else if (lifeCycles.indexOf(key) >= 0) {
      remaxConfig[key] = createBothFun(remaxConfig, originConfig, key);
    } else {
      remaxConfig[key] = originConfig[key];
    }
  }
}
const mergeConfig = (remaxConfig, originConfig) => {
  tryMergeConfig(remaxConfig, originConfig);
  return remaxConfig;
};
export default {
  onAppConfig({ config }) {
    let __app = App;
    let originConfig;
    App = function (origin) { originConfig = origin; };
    __non_webpack_require__('./app-origin.js');
    App = __app;
    config = mergeConfig(config, originConfig);
    const onLaunch = config.onLaunch;
    config.onLaunch = (...args) => { if (onLaunch) { onLaunch.apply(config, args); } };
    return config;
  },
};

3.3 Modal API Wrapper

Remax’s Modal component is implemented via createPortal . The following HOC creates a modal wrapper:

import { createPortal } from '@remax/runtime';
import { ReactReconcilerInst } from '@remax/runtime/esm/render';

const styles = { modalContainer: { /* ... */ } };
export default function withModal(TargetComponent) {
  const WrappedModalComponent = (props) => {
    const { mask, ...other } = props;
    const component = useRef();
    const container = getCurrentPage().modalContainer;
    return createPortal(
,
      container
    );
  };
  WrappedModalComponent.hide = (context) => {
    const container = getCurrentPage().modalContainer;
    if (container._rootContainer) {
      ReactReconcilerInst.updateContainer(null, container._rootContainer, null, function () {});
    }
    container.applyUpdate();
  };
  WrappedModalComponent.show = (props) => {
    const container = getCurrentPage().modalContainer;
    if (!container._rootContainer) {
      container._rootContainer = ReactReconcilerInst.createContainer(container, false, false);
    }
    const element = React.createElement(WrappedModalComponent, props);
    ReactReconcilerInst.updateContainer(element, container._rootContainer, null, function () {});
    context.modalContainer.applyUpdate();
  };
  return WrappedModalComponent;
}
export { withModal };

Usage example:

// Decorator style (if supported)
@withModal
export default function MyComponent(props) { return
{...}
; }

// Without decorator
function MyComponent(props) { return
{...}
; }
const ModaledComponent = withModal(MyComponent);
ModaledComponent.show(props); // show modal
ModaledComponent.hide(); // hide modal

4. Engineering Practices

To avoid rewriting the entire codebase, native mini‑program files are placed in the public directory and merged during the build. The remax.config.js can dynamically generate app.json by merging the original configuration with Remax’s configuration.

onAppConfig() {
  // Get original config and Remax config, merge pages and subPackages
  const appJSON = JSON.parse(JSON.stringify(originConfig));
  tmp.pages.forEach(function (item) {
    if (appJSON.pages.indexOf(item) == -1) { appJSON.pages.push(item); }
  });
  tmp.subPackages.forEach(function (item) {
    let needAdd = true;
    for (let i = 0, a = appJSON.subPackages; i < a.length; i++) {
      if (a[i].root === item.root) { needAdd = false; a[i] = item; break; }
    }
    if (needAdd) { appJSON.subPackages.push(item); }
  });
  // ... other merge logic
  return appJSON;
}

For large organizations, a shell project is used to host multiple Remax sub‑projects. The bundle.js file lists the sub‑projects:

module.exports = {
  remaxA: { git: "[email protected]" },
  remaxB: { gitL "[email protected]" }
};

During the build, each sub‑project is cloned into a packages folder, and page files are generated dynamically:

const template = (projectName, path) => {
  return `import ${projectName} from '~packages/${projectName}/src/pages${path ? `/${path}` : ''}';
${projectName}.prototype.onShareAppMessage;
${projectName}.prototype.onPageScroll;
${projectName}.prototype.onShareTimeline;
export default ${projectName};`;
};

const pageHandler = (projectName) => {
  const projectPath = `${rootDir}/packages/${projectName}`;
  shell.cd(projectPath);
  let conf = require(`${projectPath}/src/app.config.js`);
  let platConf = conf[platform] || conf;
  const projectAllPages = [];
  // ...process pages and subPackages, replace paths
  let allPages = [...platConf.pages];
  allPages.push(path.join(subPackage.root, page));
  allPages.forEach((mapPath) => {
    const pagePath = path.resolve(rootDir, 'src', 'pages', projectName, `${mapPath}.js`);
    fse.ensureFileSync(pagePath);
    const data = template(projectName, mapPath);
    fs.writeFileSync(pagePath, data);
  });
};

const complier = () => {
  // fetch shell project, clone sub‑projects, generate pages
  const packagesPath = path.resolve(rootDir, 'packages');
  const subDirs = fs.readdirSync(packagesPath);
  subDirs.forEach((name) => {
    const file = fs.statSync(`${packagesPath}/${name}`);
    if (file.isDirectory()) { pageHandler(name); }
  });
  // ...other logic
};
module.exports = complier;

Generated page code example:

import remaxA from '~packages/remaxA/src/pages/index/index';
remaxA.prototype.onShareAppMessage;
remaxA.prototype.onPageScroll;
remaxA.prototype.onShareTimeline;
export default remaxA;

Webpack configuration is customized to split common code among sub‑projects and keep the mini‑program bundle size within limits:

configWebpack: function (options) {
  let config = options.config;
  let subpackageGroups = {};
  Object.keys(projects).forEach((key) => {
    let packagePages = projectsPages[key];
    let allPages = packagePages.allPages.map((page) => `pages/${key}/${page}`);
    let pages = packagePages.pages;
    subpackageGroups[`${key}Common`] = {
      name: `package-${key}-common`,
      test: (module) => new RegExp(`[\\/]packages[\\/]${key}[\\/]src[\\/]`).test(module.userRequest),
      chunks: 'all',
      minChunks: 2,
      minSize: 0,
      priority: 91,
      filename: `pages/${key}/package-${key}-common.js`
    };
  });
  config.optimization.merge({
    splitChunks: {
      maxAsyncRequests: 100,
      maxInitialRequests: 100,
      automaticNameDelimiter: '-',
      enforceSizeThreshold: 50000,
      cacheGroups: { ...subpackageGroups }
    }
  });
},

5. Experience Summary

Remax enables write‑once‑run‑anywhere but requires additional setup due to limited built‑in cross‑platform components.

Performance on complex pages may lag compared to native mini‑programs; custom native components can mitigate this.

Remax does not currently support DOM/BOM APIs or React Native out of the box.

6. Conclusion

The article provides practical guidance for selecting and integrating a React‑based cross‑platform mini‑program framework, highlighting trade‑offs between compile‑time and runtime approaches, and sharing engineering solutions for large‑scale, multi‑team deployments.

Frontendcross-platformReactmini-programRemax
Ctrip Technology
Written by

Ctrip Technology

Official Ctrip Technology account, sharing and discussing growth.

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.