Mastering Zone.js: Isolate Execution Contexts and Run Multiple jQuery Versions
This article explains how Zone.js, the core Angular 2 component inspired by NodeJS Domain and Dart Zone, can be used to manage execution contexts for both synchronous and asynchronous code, enabling advanced scenarios such as running different jQuery versions side‑by‑side in a single application.
Zone.js is the core component that the Angular team created for Angular 2, taking inspiration from NodeJS's Domain and Dart's Zone.
At first I rejected Zone.js because I thought its only purpose was asynchronous error tracking, which I didn't need. However, the
execution contextcan do much more than just error tracking.
Understanding the execution context
<code>Zone.current.fork({}).run(function () {
Zone.current.inTheZone = true;
setTimeout(function () {
console.log('in the zone: ' + !!Zone.current.inTheZone); // 'in the zone: true'
}, 0);
});
console.log('in the zone: ' + !!Zone.current.inTheZone); // 'in the zone: false'
</code>We can think of an execution context as the context that is only related to the Zone instance created by fork .
The example shows that only inside the forked Zone we set
Zone.current.inTheZoneto
true, so the log outside the Zone prints
false.
How does it work?
Imagine the process as synchronous; the following code demonstrates creating a new Zone and switching back.
<code>const defaultZone = Zone.current
// create a new Zone
const zone = new Zone()
// set current zone
Zone.current = zone
// set a value in the current zone
Zone.current.inTheZone = true
console.log('in the zone: ' + !!Zone.current.inTheZone)
// exit the current zone
Zone.current = defaultZone
console.log('in the zone: ' + !!Zone.current.inTheZone)
</code>For asynchronous code, we simply add a guard at each async entry point.
<code>const defaultZone = Zone.current
// generate a new Zone
const zone = new Zone()
// set current zone
Zone.current = zone
Zone.current.inTheZone = true
const anonymousA = function () {
console.log('in the zone: ' + !!Zone.current.inTheZone); // 'in the zone: true'
}
// map the function to the zone
anonymousA._zone = zone
// exit current zone
Zone.current = defaultZone
setTimeout(() => {
// restore zone when the function runs
Zone.current = anonymousA._zone
anonymousA.call(this)
// exit again
Zone.current = defaultZone
}, 0)
console.log('in the zone: ' + !!Zone.current.inTheZone)
</code>Of course, the real implementation of
Zone.jsis far more complex; interested readers can explore the source code.
Using different jQuery versions in the same project
From the previous examples we see that we can store properties on a Zone instance that are only visible inside that Zone. By leveraging
Object.definePropertywe can achieve the goal of isolating different library versions.
First, we write a very simple module executor (just a demo, not production‑ready):
<code>!function (win, Zone) {
var map = {};
var noop = {};
var dependence = {};
var alias = {};
var hasSet = {};
// only supports define(name, factory) for demo purposes
function define(name, factory) {
if (typeof factory === 'function') {
map[name] = { factory: factory, exports: noop };
} else {
map[name] = { exports: factory };
}
}
function require(name) {
var module = map[name];
if (module.exports !== noop) return module.exports;
if (dependence[name]) {
var properties = {};
// map window.xxx -> require('xxx')
Object.keys(dependence[name]).forEach(function (key) {
var res = alias[key] || key;
properties[res] = require(key + '@' + dependence[name][key]);
if (!hasSet[res]) {
hasSet[res] = true;
Object.defineProperty(window, res, {
get: function () { return Zone.current.get(res); }
});
}
});
// fork a Zone for each module execution
Zone.current.fork({ properties: properties }).run(function () {
module.exports = module.factory();
});
} else {
module.exports = module.factory();
return module.exports;
}
}
function config(opt) {
Object.assign(dependence, opt.dep);
Object.assign(alias, opt.alias);
}
require.config = config;
window.define = define;
window.require = require;
}(window, Zone);
</code>Try it out:
<code>// simulate two jQuery versions
define('[email protected]', { version: '1.4', bind: function () { console.log('call bind'); } })
define('[email protected]', { version: '1.8', on: function () { console.log('call on'); } })
// just log the version, do nothing else
function logVersion() { console.log('version === ', $.version) }
// first module uses 1.8
define('module1', function module1() {
$.on();
setTimeout(logVersion, 100);
})
// second module uses 1.4
define('module2', function module2() {
$.bind();
setTimeout(logVersion, 300);
})
require.config({
dep: {
module1: { 'jquery': '1.8' },
module2: { 'jquery': '1.4' }
},
alias: { 'jquery': '$' }
})
require('module1')
require('module2')
</code>For the full implementation see the two-different-jquery repository.
Going further
Based on Zone.js we can build a sandbox that allows multiple technology stacks to coexist in a large legacy application without nasty conflicts.
We can also create a generic dependency‑injection solution that fully decouples modules from each other.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
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.