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.
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 fileModule 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
