Mastering Conditional Compilation for Efficient Cross‑Platform Mini‑Program Development

This article explains how conditional compilation can eliminate redundant code in cross‑platform mini‑program projects, describes the syntax and implementation details in MorJS, and shows practical examples for code‑level and file‑level compilation strategies that improve performance, maintainability, and product consistency.

Alibaba Terminal Technology
Alibaba Terminal Technology
Alibaba Terminal Technology
Mastering Conditional Compilation for Efficient Cross‑Platform Mini‑Program Development

Introduction

Cross‑platform development allows a single codebase to run on multiple devices such as mobile apps, desktop browsers, and various mini‑program platforms (WeChat, Alipay, Douyin, etc.). While it reduces development and maintenance costs, platform‑specific differences often force developers to write large conditional blocks or maintain separate codebases, leading to performance loss, maintenance difficulty, and loss of the "write once, run everywhere" advantage.

Why Conditional Compilation?

The ideal solution is to compile only the code relevant to the target platform, producing a minimal bundle without unnecessary platform code. This approach is known as conditional compilation.

Basic Syntax

In C‑like languages the common directives are:

#ifdef IDENTIFIER
#if IDENTIFIER
#ifndef IDENTIFIER
#endif

MorJS extends this concept to JavaScript/TypeScript, XML‑like files, and JSONC/JSON5.

Code‑Dimension Conditional Compilation

MorJS parses source files, detects conditional blocks using regular expressions, and processes them with a recursive matcher based on XRegExp.matchRecursive. The core steps are:

Determine file type ( js or xml) based on extension.

Apply the appropriate regex rules to locate start and end markers.

Split the source into between, left, match, and right parts.

Execute a processor callback that evaluates the condition against the provided context and either keeps or discards the match block.

The main processing functions are:

export function preprocess(sourceCode: string, context: Record<string, any>, ext: string, filePath?: string): string {
  let type: string;
  if (JsLikeFileExts.includes(ext as JsLikeFileExtType)) {
    type = 'js';
  } else if (XmlLikeFileExts.includes(ext as XmlLikeFileExtType)) {
    type = 'xml';
  }
  if (!type) return sourceCode;
  return preprocessor(sourceCode, context, { type: RegexRules[type], srcEol: getEolType(sourceCode) }, undefined, filePath);
}

function replaceRecursive(rv: string, rule: PreprocessRule, processor: PreprocessProcessor): string {
  const startRegex = new RegExp(rule.start, 'mi');
  const endRegex = new RegExp(rule.end, 'mi');
  function matchReplacePass(content: string): string {
    const matches = XRegExp.matchRecursive(content, rule.start, rule.end, 'gmi', {
      valueNames: ['between', 'left', 'match', 'right']
    });
    if (matches.length === 0) return content;
    const matchGroup: any = { left: null, match: null, right: null };
    return matches.reduce((builder, match) => {
      switch (match.name) {
        case 'between':
          builder += match.value;
          break;
        case 'left':
          matchGroup.left = startRegex.exec(match.value);
          break;
        case 'match':
          matchGroup.match = match.value;
          break;
        case 'right':
          matchGroup.right = endRegex.exec(match.value);
          builder += processor(matchGroup.left, matchGroup.right, matchGroup.match, matchReplacePass);
          break;
      }
      return builder;
    }, '');
  }
  return matchReplacePass(rv);
}

const processor: PreprocessProcessor = (startMatches, endMatches, include, recurse) => {
  if (!startMatches || !endMatches || !include) return '';
  const variant = startMatches[1];
  const test = (startMatches[2] || '').trim();
  switch (variant) {
    case 'if': {
      let testResult = testPasses(test, context) as any;
      if (testResult instanceof ReferenceError) {
        logger.warn(`Condition variable not found, treating as false.
Condition: ${test}
Error: ${testResult.message}`);
      }
      if (typeof testResult !== 'boolean') testResult = false;
      return testResult ? recurse(include) : '';
    }
    case 'ifdef':
      return typeof getDeepPropFromObj(context, test) !== 'undefined' ? recurse(include) : '';
    case 'ifndef':
      return typeof getDeepPropFromObj(context, test) === 'undefined' ? recurse(include) : '';
    default:
      throw new Error('Unknown if variant ' + variant + '.');
  }
};

File‑Dimension Conditional Compilation

During the build, MorJS constructs a dependency tree and selects files based on suffix priority. Developers can configure custom suffixes via conditionalCompile.fileExt. The priority order (higher value = higher priority) is:

Custom entry (1000)

Conditional file (base 20, step 5 per position)

Native DSL files (15)

WeChat DSL files (10)

Alipay DSL files (5)

Normal files (0)

The priority calculation function:

enum EntryPriority {
  CustomEntry = 1000,
  Conditional = 20,
  Native = 15,
  Wechat = 10,
  Alipay = 5,
  Normal = 0
}

function calculateEntryPriority(extname: string, isConditionalFile: boolean, priorityAmplifier: number = 0, entryType: EntryType): EntryPriority {
  if (entryType === EntryType.custom) return EntryPriority.CustomEntry;
  if (isConditionalFile) return EntryPriority.Conditional + 5 * priorityAmplifier;
  if (this.targetFileTypes.template === extname || this.targetFileTypes.style === extname) return EntryPriority.Native;
  if (this.wechatFileTypes.template === extname || this.wechatFileTypes.style === extname || this.targetFileTypes.sjs === extname) return EntryPriority.Wechat;
  if (this.alipayFileTypes.template === extname || this.alipayFileTypes.style === extname || this.alipayFileTypes.sjs === extname) return EntryPriority.Alipay;
  return EntryPriority.Normal;
}

Practical Examples

Code‑level directives:

<!-- #ifdef wechat -->
<view>Only shown on WeChat</view>
<!-- #endif -->

<!-- #ifdef alipay -->
<view>Only shown on Alipay</view>
<!-- #endif -->

/* #if name == 'wechat' */
console.log('This runs on WeChat');
/* #endif */

/* #if name == 'alipay' */
console.log('This runs on Alipay');
/* #endif */

File‑level example: a component may have the following files:

components/demo/index.axml
components/demo/index.acss
components/demo/index.js
components/demo/index.json
components/demo/index.wx.axml   // WeChat‑specific version
components/demo/index.wx.acss   // WeChat‑specific style
components/demo/index.wx.js     // WeChat‑specific logic

When building for WeChat, MorJS picks the .wx variants first, producing a clean bundle without the other platform files.

Effect

Conditional compilation reduces bundle size, eliminates platform‑irrelevant code, and simplifies maintenance while preserving the ability to customize behavior per platform.

Conclusion

By leveraging conditional compilation, MorJS enables a truly "write once, run everywhere" workflow for mini‑programs, addressing platform differences and product customization needs without sacrificing performance or code quality.

GitHub: https://github.com/eleme/morjs
Cross‑platformfrontend developmentcode optimizationmini-programMorJSconditional compilation
Alibaba Terminal Technology
Written by

Alibaba Terminal Technology

Official public account of Alibaba Terminal

0 followers
Reader feedback

How this landed with the community

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.