Building a Scalable Web Interactive System with NEJ: Module Decomposition Guide

This article, the third in a series on constructing high‑scalable web interactive systems, demonstrates how to use NetEase's NEJ framework to decompose complex SPA modules, define hierarchical and dependency trees, register external and layout modules, map functionalities, and configure the entire application for scalable development and maintenance.

ITFLY8 Architecture Home
ITFLY8 Architecture Home
ITFLY8 Architecture Home
Building a Scalable Web Interactive System with NEJ: Module Decomposition Guide

This article is the third part of the "Building High‑Scalable Web Interactive System" series and uses NetEase's NEJ framework as an example to analyze module scalability.

Example Analysis

NEJ framework – based on the previous two articles, we implement this architecture pattern and use concrete examples to explain how to split a complex system, develop test modules, and integrate the system using NEJ's module scheduling system.

System Decomposition

Draw Hierarchical Diagram

When we obtain a complex system, we can draw a hierarchical relationship diagram of its modules from the interaction draft and identify the modules that are externally accessible.

Abstract Dependency Tree

From the hierarchical diagram we can easily abstract a dependency tree of modules:

We then format the abstracted dependency tree according to UMI rules. The main operations are:

Add a root node named "/" (or rename the "m" node to "/").

For each node, add a child node "/" as the default node.

The resulting dependency tree has the following characteristics:

For any node (except the root), the UMI value is obtained by concatenating the names of nodes on the path to the root with "/" (e.g., the UMI of the list node is "/m/blog/list").

Modules on any node depend on modules registered on its ancestor nodes; for example, both the blog and list nodes register modules, so the list node's module must be displayed after the blog node's module.

Determine External Module Registration Nodes

Five externally accessible modules: log, tag, profile, experience, permission. Locate appropriate leaf nodes in the dependency tree to register these modules:

Determine Layout Module Registration Nodes

Traverse from each external module registration node toward the root; the first node where two modules intersect becomes the layout module registration node. System‑wide components can be registered at the root node, ensuring they are loaded before any module uses them.

Map Module Functions

Principle: The common parent node implements the common functionality of the modules registered on its child nodes.

Example: The common parent of the blog and setting nodes is the "m" node, so the functionality shared by blog and setting modules is implemented in the "m" module.

Split Complex Modules

Further split complex modules. Typical split targets include:

Reusable modules, e.g., a log list that can appear on a log management page or in a popup.

Modules with no logical dependency, e.g., the tag list beside the log list; adding or removing either does not affect the other's business logic.

We obtain two dependency trees after splitting: an external module dependency tree and a private module dependency tree.

Draw Module Function Specification Table

In this example we split all possible modules for illustration. In real projects, only necessary modules are split. The specification table looks like:

Build Directory Structure

Project Directory

Project directory layout:

webroot                // Front‑end development related directory
 |- res                // Static resources (can be versioned during packaging)
 |- src                // Front‑end source code (not deployed to production)
     |- html
         |- module    // Single‑page module directory, all modules reside here
         |- app.html // Single‑page entry file

Module Unit Directory

A module unit consists of:

Module test – independent testing page for the module.

Module structure – a set of templates derived from the module's structure.

Module logic – business logic implemented according to module specifications, extending the module base class.

Module style – module‑specific CSS, usually placed under the css directory.

Structure example:

The complete directory tree of all modules is:

Module Implementation

Structure

Assuming static pages are already completed, module implementation focuses on splitting the static structure into NEJ templates and implementing business logic. Note:

External resources (css, js) referenced in templates must use paths relative to the module's HTML file.

External resources in template collections must be marked with the @TEMPLATE tag, which will be explained in the packaging chapter.

NEJ Template Explanation

Module Structure Example

<meta charset="utf-8"/>
<textarea name="txt" id="m-ifrm-module">
  <div class="n-login">
    <div class="iner j-flag">
      <span class="cls j-flag">×</span>
      <span class="min j-flag">-</span>
    </div>
    <div class="cnt j-cnt"></div>
  </div>
</textarea>
<!-- @TEMPLATE -->
<textarea name="js" data-src="./index.css"></textarea>
<textarea name="js" data-src="./index.js"></textarea>
<!-- /@TEMPLATE -->

Logic

Extending util/dispatcher/module from _$$ModuleAbstract to create a project‑specific module base class:

/*
 * ------------------------------------------
 * Project module base class implementation
 * @version  1.0
 * @author   genify([email protected])
 * ------------------------------------------ */
NEJ.define(['base/klass', 'util/dispatcher/module'], function(_k,_t,_p){
  var _pro;
  _p._$$Module = _k._$klass();
  _pro = _p._$$Module._$extend(_t._$$ModuleAbstract);
  _pro.__doSomething = function(_args){ /* TODO */ };
  return _p;
});

The module lifecycle interfaces are:

Build – __doBuild: construct module structure, cache nodes, initialize component configs.

Show – __onShow: place module into container, allocate components, add events, execute refresh logic.

Refresh – __onRefresh: fetch data based on input parameters and render.

Hide – __onHide: recycle components and events, restore to post‑build state.

Specific Module Implementation Example

/*
 * ------------------------------------------
 * Project module implementation
 * @version  1.0
 * @author   genify([email protected])
 * ------------------------------------------ */
NEJ.define(['base/klass','util/dispatcher/module','/path/to/project/module.js'],
function(_k,_e,_t,_p){
  var _pro;
  _p._$$ModuleDemo = _k._$klass();
  _pro = _p._$$ModuleDemo._$extend(_t._$$Module);
  _pro.__doBuild = function(){ this.__super(); /* TODO */ };
  _pro.__onShow = function(_options){ this.__super(_options); /* TODO */ };
  _pro.__onRefresh = function(_options){ this.__super(_options); /* TODO */ };
  _pro.__onHide = function(){ this.__super(); /* TODO */ };
  _e._$regist('umi_or_alias', _p._$$ModuleDemo);
  return _p;
});

Messaging

Point‑to‑point messages can be sent via __doSendMessage and received by implementing __onMessage:

_pro.__doSomething = function(){
  this.__doSendMessage('/m/setting/account/',{a:'aaaaaa',b:'bbbbbbbbb'});
};

_pro.__onMessage = function(_event){
  // _event.from – source
  // _event.data – {a:'aaaaaa',b:'bbbbbbbbb'}
  // TODO
};

Publish‑subscribe messages use __doPublishMessage and __doSubscribeMessage:

_pro.__doSomething = function(){
  this.__doPublishMessage('onok',{a:'aaaaaa',b:'bbbbbbbb'});
};

_pro.__doBuild = function(){
  this.__doSubscribeMessage('/m/message/account/','onok',this.__onMessageReceive._$bind(this));
};

System Integration

Map Dependency Trees

During integration, map the nodes that need module registration in the dependency tree to their implementation files.

External module integration:

Private module integration:

Extract System Configuration

Rule configuration example:

rules:{
  rewrite:{
    '404':'/m/blog/list/',
    '/m/blog/list/':'/m/blog/',
    '/m/setting/account/':'/m/setting/'
  },
  title:{
    '/m/blog/tag/':'日志标签',
    '/m/blog/list/':'日志列表',
    '/m/setting/permission/':'权限管理',
    '/m/setting/account/':'基本资料',
    '/m/setting/account/edu/':'教育经历'
  },
  alias:{
    'system-tab':'/?/tab/',
    'blog-tab':'/?/blog/tab/',
    'blog-list-box':'/?/blog/box/',
    'blog-list-tag':'/?/blog/tag/',
    'blog-list-class':'/?/blog/class/',
    'blog-list':'/?/blog/list/',
    'setting-tab':'/?/setting/tab/',
    'setting-account-tab':'/?/setting/account/tab/',
    'layout-system':'/m',
    'layout-blog':'/m/blog',
    'layout-blog-list':'/m/blog/list/',
    'layout-setting':'/m/setting',
    'layout-setting-account':'/m/setting/account',
    'blog-tag':'/m/blog/tag/',
    'setting-edu':'/m/setting/account/edu/',
    'setting-profile':'/m/setting/account/',
    'setting-permission':'/m/setting/permission/'
  }
}

Module configuration example:

modules:{
  '/?/tab/':'module/tab/index.html',
  '/?/blog/tab/':'module/blog/tab/index.html',
  '/?/blog/box/':'module/blog/list.box/index.html',
  '/?/blog/tag/':'module/blog/list.tag/index.html',
  '/?/blog/class/':'module/blog/list.class/index.html',
  '/?/blog/list/':'module/blog/list/index.html',
  '/?/setting/tab/':'module/setting/tab/index.html',
  '/?/setting/account/tab/':'module/setting/account.tab/index.html',
  '/m':{module:'module/layout/system/index.html',composite:{tab:'/?/tab/'}},
  '/m/blog':{module:'module/layout/blog/index.html',composite:{tab:'/?/blog/tab/'}},
  '/m/blog/list/':{module:'module/layout/blog.list/index.html',composite:{box:'/?/blog/box/',tag:'/?/blog/tag/',list:'/?/blog/list/',clazz:'/?/blog/class/'}},
  '/m/blog/tag/':'module/blog/tag/index.html',
  '/m/setting':{module:'module/layout/setting/index.html',composite:{tab:'/?/setting/tab/'}},
  '/m/setting/account':{module:'module/layout/setting.account/index.html',composite:{tab:'/?/setting/account/tab/'}},
  '/m/setting/account/':'module/setting/profile/index.html',
  '/m/setting/account/edu/':'module/setting/edu/index.html',
  '/m/setting/permission/':'module/setting/permission/index.html'
}

Module Composition

Modules expose a container via the __export property; the top‑level module can override __doParseParent to specify the application container.

_pro.__doBuild = function(){
  this.__body = _e._$html2node(_e._$getTextTemplate('module-id-l2'));
  var _list = _e._$getByClassName(this.__body,'j-flag');
  this.__export = {
    box:_list[0],
    clazz:_list[1],
    tag:_list[2],
    list:_list[3],
    parent:_list[3]
  };
};

Composite configuration can specify when sub‑modules are combined: onshow – combine only when the parent module is shown; subsequent refreshes do not re‑combine. onrefresh – combine on show and also re‑combine on each refresh.

No explicit flag – defaults to onrefresh behavior.

composite:{
  onshow:{/* combine on show only */},
  onrefresh:{/* combine on both show and refresh */}
}

Application Startup

Start the application with the configured rules and modules:

NEJ.define(['util/dispatcher/dispatcher'],function(_e){
  _e._$startup({
    rules:{
      rewrite:{/* rewrite rules */},
      title:{/* title rules */},
      alias:{/* alias rules */}
    },
    modules:{/* module UMI to file mapping */}
  });
});

Packaging and Release

Packaging details are described in the NEJ toolset documentation.

System Changes

When requirements change, adding, modifying, or deleting modules only requires updating the configuration or adding new module files.

Add Module

To add a new module, develop it following the steps above. If the functionality already exists, only the configuration needs to be updated. Example: add the tag module under settings with UMI /m/setting/tag/:

Modify alias configuration:

alias:{
  'blog-tag':['/m/blog/tag/','/m/setting/tag/']
}

Modify module configuration:

modules:{
  '/m/setting/tag/':'module/blog/tag/index.html'
}

Delete Module

To remove a deprecated module, simply delete its UMI entry from the module configuration without touching business logic.

Conclusion

With the rapid development of web technologies, single‑page applications (SPA) are increasingly common. As their complexity grows, scalability of the platform and modules becomes critical. This article explored how NetEase's NEJ framework addresses these challenges, drawing on real‑world projects such as NetEase Cloud Music PC, Yixin WebIM, NetEase Mail Assistant, and others. Interested readers are encouraged to further discuss and explore these solutions.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptfrontend developmentSPANEJmodule scalability
ITFLY8 Architecture Home
Written by

ITFLY8 Architecture Home

ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.

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.