Advanced Development Guide for the Jimu Sketch Plugin – Platformization, Multi‑Business Switching, and UI Optimization

The guide details how the Jimu Sketch plugin was transformed from a simple internal tool into a platform‑wide solution that supports multi‑business switching, automated library conversion, UI/UX enhancements, performance caching, a native inspector panel, and deep JavaScript‑Objective‑C integration for scalable design‑system workflows.

Meituan Technology Team
Meituan Technology Team
Meituan Technology Team
Advanced Development Guide for the Jimu Sketch Plugin – Platformization, Multi‑Business Switching, and UI Optimization

The article introduces the evolution of the Jimu Sketch plugin, originally a simple tool for the Meituan Waimai design team that unexpectedly gained wide popularity. To meet the growing demand for consistent design standards across many product teams, the plugin was transformed into a platform‑based product.

Background

After the first introductory article, the plugin attracted attention from multiple Meituan business groups and external developers. This highlighted the universal need for UI consistency and efficient design‑to‑code workflows, prompting the creation of the "Jimu" brand and a dedicated UI co‑construction project.

1. Platformization of the Plugin

Platformization means the plugin can be integrated with any business team’s design system. The core idea is to make design elements strongly tied to business‑specific assets (colors, icons, grids, etc.) and allow easy switching between them.

2. Multi‑Business Switching

To support dynamic business selection, the following steps are required:

Define global variables (e.g., businessType, theme, commonRequestParams) in each module’s Redux state.

Exchange data between the WebView and the native Sketch side using window.postMessage and webViewContents.on.

When a user selects a module, retrieve the stored business info and render the appropriate assets.

Key code snippets:

export const ACTION_TYPE = 'roo/colorData/index';
const intialState = {
  id: 'color',
  title: '颜色库',
  theme: 'black',
  businessType: 'waimai-c',
  commonRequestParams: { userInfo: '' },
};
export default reducerCreator(intialState, ACTION_TYPE);

Data exchange from WebView to plugin:

window.postMessage('onBusinessSelected', item);

Receiving on the plugin side:

webViewContents.on('onBusinessSelected', item => {
  Settings.setSettingForKey('onBusinessSelected', JSON.stringify(item));
});
// Inject data back to WebView if needed
browserWindow.webContents.executeJavaScript(`localStorage.setItem("${key}", '${data}')`);

Initializing the business data in the WebView:

const button = NSButton.alloc().initWithFrame(rect);
button.setCOSJSTargetFunction(() => {
  initWebviewData(browserWindow);
  browserWindow.focus();
  browserWindow.show();
});
function initWebviewData(browserWindow) {
  const businessItem = Settings.settingForKey('onBusinessSelected');
  browserWindow.webContents.executeJavaScript(`initBusinessData(${businessItem})`);
}

Rendering the React UI with the injected data:

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
window.initBusinessData = data => {
  const businessItem = {};
  Object.keys(data).forEach(key => {
    businessItem[key] = { $set: initialData[key] };
  });
  store.dispatch(update(businessItem));
};
const update = payload => ({ type: ACTION_TYPE, payload });

3. Library File Automation

The plugin must convert Sketch .sketch Library files into JSON that the WebView can consume, and generate thumbnail images for each Symbol.

Key steps:

Subscribe to remote libraries via RSS URLs using Library.getRemoteLibraryWithRSS.

Parse the Library document, extract Symbol masters, and split their names by “/” to build a hierarchical JSON structure.

Export Symbol thumbnails either via MSSymbolPreviewGenerator or sketchDOM.export.

Example of extracting symbols and building JSON:

const document = library.getDocument();
const symbols = [];
_.forEach(document.pages, page => {
  _.forEach(page.layers, l => {
    if (l.type && l.type === 'SymbolMaster') {
      symbols.push(l);
    }
  });
});
for (let i = 0; i < symbols.length; i++) {
  const name = symbols[i].name;
  const subNames = name.split('/');
  const groupName = subNames[0].replace(/\s/g, '');
  const typeName = subNames[1].replace(/\s/g, '');
  const symbolName = subNames.join('/').replace(/\s/g, '');
  result[groupName] = result[groupName] || {};
  result[groupName][typeName] = result[groupName][typeName] || [];
  result[groupName][typeName].push({ symbolID, name: symbolName });
}

Sample JSON output:

{
  "美团外卖C端组件库": {
    "icon": [
      { "symbolID": "E35D2CE8-4276-45A1-972D-E14A06B0CD23", "name": "28/问号" },
      { "symbolID": "E57D2CE8-4276-45A1-962D-E14A06B0CD61", "name": "27/花朵" }
    ]
  }
}

4. UI/UX Optimizations

To improve the user experience, the plugin adds:

Feedback collection via Meituan’s TT system and in‑plugin channels.

WebView tweaks: disable user‑select, hide overflow, prevent right‑click context menu in production.

Native toolbar redesign using macOS AppKit (NSStackView, NSButton) with proper Auto Layout constraints.

Button click visual feedback by changing background color.

Image loading support for both local bundle resources and remote HTTPS URLs.

Example of native toolbar button layout:

const toolbar = NSStackView.alloc().initWithFrame(NSMakeRect(0,0,45,400));
toolbar.setSpacing(7);
const button = NSButton.alloc().initWithFrame(rect);
button.widthAnchor().constraintEqualToConstant(rect.size.width).setActive(1);
button.heightAnchor().constraintEqualToConstant(rect.size.height).setActive(1);
button.setBordered(false);
button.setCOSJSTargetFunction(onClickListener);
toolbar.addView_inGravity(button, inGravityType);

Click effect implementation:

onClickListener: sender => {
  const threadDictionary = NSThread.mainThread().threadDictionary();
  const currentButton = threadDictionary[identifier];
  if (currentButton.state() === NSOnState) {
    currentButton.setBackgroundColor(NSColor.colorWithHex('#E3E3E3'));
  } else {
    currentButton.setBackgroundColor(NSColor.windowBackgroundColor());
  }
}

Image loading (local and remote):

// Local image
const localImageUrl = context.plugin.url()
  .URLByAppendingPathComponent('Contents')
  .URLByAppendingPathComponent('Resources')
  .URLByAppendingPathComponent(`${imageurl}.png`);
// Remote image
const remoteImageUrl = NSURL.URLWithString(imageUrl);
const nsImage = NSImage.alloc().initWithContentsOfURL(imageURL);
nsImage.setSize(size);
nsImage.setScalesWhenResized(true);

5. Performance Enhancements

Because component libraries can become large, the plugin adds:

Pre‑loading of remote libraries on document open via Action API handlers.

Cache verification of processed JSON and thumbnail assets, skipping re‑processing when versions match.

Cache verification example:

verifyLibraryCache(businessType, libraryVersion) {
  const temp = Settings.settingForKey('libraryJsonCache');
  const libraryJsonCache = temp ? JSON.parse(temp) : null;
  if (libraryJsonCache.version.businessType !== libraryVersion) return null;
  const home = getAssertURL(this.mContext, 'libraryImage');
  const path = join(home, businessType);
  if (!fs.existsSync(path) || !fs.readdirSync(path)) return null;
  if (libraryJsonCache[businessType]) {
    console.info(`当前${businessType}命中缓存`);
    return libraryJsonCache;
  }
  return null;
}

6. Custom Inspector Panel (Native + JS)

To allow designers to edit component overrides directly, a native Inspector panel is built with Objective‑C and exposed to the JavaScript side via the Mocha bridge. The workflow includes:

Detecting selection changes and passing the context to JS.

Using MSAvailableOverride objects to modify text, image, or symbol overrides.

Swizzling reloadWithViewControllers: to refresh the panel without flicker.

Selection change handling (Objective‑C):

+ (instancetype)onSelectionChanged:(id)context {
  [self setSharedCommand:[context valueForKeyPath:@"command"]];
  NSString *key = [NSString stringWithFormat:@"%@-RooSketchPluginNativeUI", [document description]];
  __block RooSketchPluginNativeUI *instance = [[Mocha sharedRuntime] valueForKey:key];
  NSArray *selection = [context valueForKeyPath:@"actionContext.document.selectedLayers"];
  [instance onSelectionChange:selection];
  return instance;
}

Swizzle example:

[NSClassFromString(@"MSInspectorStackView") swizzleMethod:@selector(reloadWithViewControllers:)
    withMethod:@selector(roo_reloadWithViewControllers:) error:nil];

7. Debugging & Build Integration

Because many native APIs are undocumented, the team relies on Xcode debugging, attaching the Sketch process, and mixing JavaScript with compiled Objective‑C frameworks via the Mocha loadFrameworkWithName_inDirectory method.

Framework loading snippet:

const framework = require('../../RooSketchPluginXCodeProject/.../contents.xcworkspacedata');
var mocha = Mocha.sharedRuntime();
var frameworkName = 'RooSketchPluginXCodeProject';
var directory = frameworkPath;
if (mocha.valueForKey(frameworkName)) {
  console.info(`JSloadFramework: `${frameworkName}` has loaded.`);
  return true;
} else if (mocha.loadFrameworkWithName_inDirectory(frameworkName, directory)) {
  console.info(`JSloadFramework: `${frameworkName}` success!`);
  mocha.setValue_forKey_(true, frameworkName);
  return true;
} else {
  console.error('JSloadFramework load failed');
  return false;
}

Conclusion

The guide demonstrates how to evolve a simple Sketch plugin into a robust, platform‑wide tool that supports multiple business lines, efficient asset management, native UI enhancements, performance optimizations, and deep integration between JavaScript and Objective‑C. The shared experiences, code patterns, and architectural decisions provide a valuable reference for developers building complex design‑system tooling within Sketch.

JavaScriptfrontend developmentUI optimizationdesign systemmulti‑businessSketch PluginCocoaScript
Meituan Technology Team
Written by

Meituan Technology Team

Over 10,000 engineers powering China’s leading lifestyle services e‑commerce platform. Supporting hundreds of millions of consumers, millions of merchants across 2,000+ industries. This is the public channel for the tech teams behind Meituan, Dianping, Meituan Waimai, Meituan Select, and related services.

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.