How to Build Scalable Web SPA Systems with NEJ: Module Architecture Explained
This article walks through using NetEase's NEJ framework to decompose a complex single‑page web application into hierarchical modules, create and format dependency trees, register external and layout modules, define module directories, implement module logic and messaging, and finally integrate and package the system for scalable deployment.
Example Analysis
Using NetEase's NEJ framework, this article demonstrates how to achieve high scalability for a complex web interactive system by analysing and applying module‑level architecture.
System Decomposition
When a complex system is obtained, a hierarchical relationship diagram of its modules is drawn from the interaction mock‑up, and the modules that are externally accessible are identified.
Abstract Dependency Tree
From the hierarchical diagram, an abstract dependency tree is easily derived.
The tree is then formatted according to UMI rules: a root node named “/” is added, and each node receives a default “/” child node.
Resulting characteristics:
For any node (except the root), the UMI value is the concatenation of node names on the path to the root, separated by “/” (e.g., the list node has UMI "/m/blog/list").
Modules registered on a node depend on modules registered on all its ancestor nodes.
Registering External Modules
Five externally accessible modules—log, tag, profile, experience, permission—are mapped to appropriate leaf nodes in the dependency tree.
Registering Layout Modules
Traversing from each external module toward the root, the first node where two modules intersect becomes the layout module registration point; system‑wide components are registered at the root so that any module can rely on them.
Mapping Module Functions
The principle is that a common parent node implements the shared functionality of its child modules. For example, the blog and setting nodes share the parent node “m”, so the functionality common to both is implemented in the “m” module.
Decomposing Complex Modules
Complex modules are further split into reusable modules (e.g., a log list that can appear on a page or in a popup) and loosely coupled modules (e.g., a tag list that does not affect the log list).
Resulting Dependency Trees
Two trees are produced: one for external modules and one for private modules.
Module Specification Table
The specification table illustrates how modules can be combined or kept separate depending on the project’s needs.
Project Directory Structure
The project directory is organised as follows:
webroot // front‑end development related directory
|- res // static resources (can be versioned during packaging)
|- src // source code (not deployed to production)
|- html
|- module // single‑page modules; all module implementations reside here
|- app.html // entry pageModule Unit Structure
Each module unit consists of:
Module test – independent testing page for the module.
Module structure – a set of templates extracted from the static structure.
Module logic – business logic inheriting from the module base class.
Module style – module‑specific CSS (usually placed under the css directory).
Module Implementation – Structure
Static pages are assumed to be completed; the module implementation focuses on splitting static structures into NEJ templates. External resources must use relative paths and be marked with @TEMPLATE for later packaging.
Module Implementation – Logic
Modules extend _$$ModuleAbstract and implement lifecycle methods: __doBuild – build structure, cache nodes, initialise component configuration. __onShow – place module into container, allocate components, bind events. __onRefresh – fetch data based on input parameters and render. __onHide – recycle components and restore the state after hide.
/* Project module base class */
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;
});Specific module implementation example:
/* Project module implementation */
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;
});Message Communication
Modules can send point‑to‑point messages via __doSendMessage and receive them by implementing __onMessage. They can also publish and subscribe to broadcast messages.
// Sending a message
_pro.__doSomething = function(){
this.__doSendMessage('/m/setting/account/',{a:'aaaaaa',b:'bbbbbbbbb'});
};
// Receiving a message
_pro.__onMessage = function(_event){
// _event.from – source
// _event.data – payload
// TODO
};System Integration
During integration, map the nodes that need module registration in the dependency tree to their implementation files. The article shows examples for both external and private modules.
Configuration Examples
Rule configuration (rewrite, title, alias) and module mapping are defined in JSON‑like objects.
rules:{
rewrite:{'404':'/m/blog/list/','/m/blog/list/':'/m/blog/','/m/setting/account/':'/m/setting/'},
title:{'/m/blog/tag/':'Log Tag','/m/blog/list/':'Log List','/m/setting/permission/':'Permission Management','/m/setting/account/':'Profile','/m/setting/account/edu/':'Education'},
alias:{'system-tab':'/?/tab/','blog-tab':'/?/blog/tab/','layout-system':'/m',...}
}
modules:{
'/?/tab/':'module/tab/index.html',
'/m':'module/layout/system/index.html',
'/m/blog/list/':{module:'module/layout/blog.list/index.html',composite:{box:'/?/blog/box/',tag:'/?/blog/tag/',list:'/?/blog/list/',clazz:'/?/blog/class/'}},
...
}Application Startup
The dispatcher is started with the defined rules and modules, automatically handling routing, alias resolution, and module composition.
NEJ.define(['util/dispatcher/dispatcher'],function(_e){
_e._$startup({
rules:{...},
modules:{...}
});
});Packaging and Release
Packaging details are covered in the NEJ toolset documentation.
System Changes
When requirements evolve, new modules can be added or obsolete ones removed simply by updating the configuration without touching business logic.
Adding a module involves implementing it as described above and, if the functionality already exists elsewhere, just adding an alias entry such as:
alias:{'blog-tag':['/m/blog/tag/','/m/setting/tag/']}Removing a module only requires deleting its UMI entry from the module map.
Conclusion
With the rapid growth of SPA technology, scalability of both the platform and its modules becomes critical. The NEJ framework provides a comprehensive solution for modular decomposition, dependency management, and flexible configuration, as demonstrated through real NetEase products like Cloud Music PC, Yixin WebIM, and others.
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.
