One, the introduction
What is the micro front end?
Already understand the micro front end friends can skip this section, a brief introduction to the micro front end, the micro front end is moreA differentiationA technical solution, similar to back-end microservices, as shown in the following figureYou can build test deployments independentlyAnd can beThe incremental upgradetheDifferent technology stacksApplications can be integrated into a dock application and presented together.
The micro front end is a kind of technical means and method strategy that multiple teams jointly build modern Web applications by releasing functions independently.
The micro front-end architecture has the following core values:
- Stack independent
The main framework does not limit the technology stack of access applications, and microapplications have full autonomy
- Independent development and deployment
The microapplication repository is independent, and the front and back ends can be independently developed. After deployment, the main framework can automatically complete synchronous update
- The incremental upgrade
In the face of a variety of complex scenarios, it is usually difficult to upgrade or reconstruct the existing system completely, and the micro front end is a very good means and strategy to implement the gradual reconstruction
- Independent run time
State is isolated between each microapplication and run time state is not shared
Demonstrate a micro front end project, which menu, map are micro applications, menu is VUE project, map is H5 project, map can run independently, integrated into the base of the original entryhtml
Will be converted into adiv
.html
In thecss
Will be converted tostyle
.js
Will be converted to a string and passedeval
The function executes directly.
What problems does the micro front end solve?
The micro front-end architecture is designed to solve the problem of unmaintainable application in a relatively long time span, when a single application evolves from a common application to a Frontend Monolith due to the increase and change of the number of people and teams involved. This type of problem is especially common in enterprise Web applications.
How to implement a micro front end?
The following technical problems need to be solved to realize the micro front end:
- Applications access
- Application gateways
- Application isolation
- Style isolation
- Application of communication
- Application of routing
Why qiankun?
- Some of the problems encountered in building microfront-end systems using Single SPA or other microapplication frameworks, such asStyle isolation,JS sandbox,Resource preloading,JS Side effect managementWait. All the capabilities you need are built in
qiankun
inside - So far, there have been about 200+ apps used
qiankun
To access their own microfront-end architecture.qiankun
It has been tested with a number of on-line systems both inside and outside the ant, so it is a reliable production-usable solution.
In just one year, Qiankun has become one of the most popular micro front-end frameworks. Although the source code has been updated, its core technologies remain the same: JS sandbox, CSS style isolation, application HTML entry access, application communication, application routing, etc., the next will be through the way of demo detailed description of the design and implementation of several technologies.
Second, JS sandbox isolation design and implementation
2.1 Introduction to JS sandbox
JS sandbox simply put, the main application has a global environment window, and the child application has a private global environment fakeWindow. All operations of the child application only take effect in the new global context. Such child applications are isolated from the main application by being packaged in boxes. Therefore, the main application loading child applications does not cause JS variables to pollute each other, JS side effects, CSS style overwriting, etc., and the global context of each child application is independent.
2.2 snapshotSandbox – snapshotSandbox
The snapshot sandbox is used to record snapshots during application sandbox mounting and unmounting, and to restore the environment during application switchover.
- The demo presentation
- The implementation code
// subapplication A
mountSnapshotSandbox();
window.a = 123;
console.log('Snapshot sandbox mounted after a:'.window.a); / / 123
unmountSnapshotSandbox();
console.log('Snapshot sandbox after unmounted A :'.window.a); // undefined
mountSnapshotSandbox();
console.log('Snapshot sandbox mounted again after a:'.window.a); / / 123
Copy the code
// snapshotSandbox.ts
// Iterate over the object key and pass it to the callback function for execution
function iter(obj: object, callbackFn: (prop: any) => void) {
for (const prop in obj) {
if(obj.hasOwnProperty(prop)) { callbackFn(prop); }}}// Mount the snapshot sandbox
mountSnapshotSandbox() {
// Record the current snapshot
this.windowSnapshot = {} as Window;
iter(window.(prop) = > {
this.windowSnapshot[prop] = window[prop];
});
// Restore the previous changes
Object.keys(this.modifyPropsMap).forEach((p: any) = > {
window[p] = this.modifyPropsMap[p];
});
}
// Uninstall the snapshot sandbox
unmountSnapshotSandbox() {
// Records the changed properties of the current snapshot
this.modifyPropsMap = {};
iter(window.(prop) = > {
if (window[prop] ! = =this.windowSnapshot[prop]) {
// Record the changes and restore the environment
this.modifyPropsMap[prop] = window[prop];
window[prop] = this.windowSnapshot[prop]; }}); }Copy the code
- advantages
- Compatible with almost all browsers
- disadvantages
- You cannot have multiple runtime snapshot sandboxes at the same time, otherwise the records modified on the Window will be cluttered and only one single-instance microapplication will run on a page
2.3 proxySandbox – proxySandbox
When there are multiple instances, such as A, B two application, A application will live in A sandbox, B live in B application sandbox, A and B can not interfere with each other such A sandbox is acting the sandbox, the sandbox implementation approach is through the proxy ES6, features through agent.
Proxy objects are used to create a Proxy for an object to intercept and customize basic operations (such as property lookup, assignment, enumeration, function calls, and so on).
Simply put, you can place a layer of interception on the target object. So whatever you do to the target object, you go through this layer of interception, right
- Proxy vs Object.defineProperty
Object. DefineProperty can also intercept and customize basic operations, so why Proxy? Because Proxy can solve the following problems:
- Deleting or adding object attributes cannot be listened on
- Array changes cannot be listened for(
vue2
Is usedObject.defineProperty
Hijack attributes,watch
Error: Failed to detect array changes
- The demo presentation
Simple version
Actual scenario Version
- The implementation code
- Simple version
const proxyA = new CreateProxySandbox({});
const proxyB = new CreateProxySandbox({});
proxyA.mountProxySandbox();
proxyB.mountProxySandbox();
(function(window) {
window.a = 'this is a';
console.log('Agent sandbox A :'.window.a); // undefined
})(proxyA.proxy);
(function(window) {
window.b = 'this is b';
console.log('Agent sandbox B :'.window.b); // undefined
})(proxyB.proxy);
proxyA.unmountProxySandbox();
proxyB.unmountProxySandbox();
(function(window) {
console.log('Agent sandbox A :'.window.a); // undefined
})(proxyA.proxy);
(function(window) {
console.log('Agent sandbox B :'.window.b); // undefined
})(proxyB.proxy);
Copy the code
- Real scene Version
<! DOCTYPEhtml>
<html lang="en">
<body data-qiankun-A>
<h5>Agent sandbox:</h5>
<button onclick="mountA()">Proxy sandbox mode mounts application A</button>
<button onclick="unmountA()">Uninstalling application A in proxy sandbox mode</button>
<button onclick="mountB()">Proxy sandbox mode mounts application B</button>
<button onclick="unmountB()">Uninstalling application B in proxy sandbox mode</button>
<script src="proxySandbox.js"></script>
<script src="index.js"></script>
</body>
</html>
Copy the code
A applies JS. All JS loaded during application A’s mount run in Proxya.proxy for application A
// a.js
window.a = 'this is a';
console.log(Agent sandbox 1 a:.window.a);
Copy the code
B application JS, all JS loaded during the mount of B application will run in the sandbox (proxyB.proxy) of B application
// b.js
window.b = 'this is b';
console.log('Agent sandbox B :'.window.b);
Copy the code
const proxyA = new CreateProxySandbox({});
const proxyB = new CreateProxySandbox({});
function mountA() {
proxyA.mountProxySandbox();
fetch('./a.js')
.then((response) = > response.text())
.then((scriptText) = > {
const sourceUrl = `//# sourceURL=a.js\n`;
window.proxy = proxyA.proxy;
eval(`; (function(window, self){with(window){;${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy); `)}); }function unmountA() {
proxyA.unmountProxySandbox();
fetch('./a.js')
.then((response) = > response.text())
.then((scriptText) = > {
const sourceUrl = `//# sourceURL=a.js\n`;
eval(`; (function(window, self){with(window){;${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy); `)}); }function mountB() {
proxyB.mountProxySandbox();
fetch('./b.js')
.then((response) = > response.text())
.then((scriptText) = > {
const sourceUrl = `//# sourceURL=b.js\n`;
window.proxy = proxyB.proxy;
eval(`; (function(window, self){with(window){;${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy); `)}); }function unmountB() {
proxyB.unmountProxySandbox();
fetch('./b.js')
.then((response) = > response.text())
.then((scriptText) = > {
const sourceUrl = `//# sourceURL=b.js\n`;
eval(`; (function(window, self){with(window){;${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy); `)}); }Copy the code
Proxy sandbox code
// proxySandbox.ts
function CreateProxySandbox(fakeWindow = {}) {
const _this = this;
_this.proxy = new Proxy(fakeWindow, {
set(target, p, value) {
if (_this.sandboxRunning) {
target[p] = value;
}
return true;
},
get(target, p) {
if (_this.sandboxRunning) {
return target[p];
}
return undefined; }}); _this.mountProxySandbox =() = > {
_this.sandboxRunning = true;
}
_this.unmountProxySandbox = () = > {
_this.sandboxRunning = false; }}Copy the code
- advantages
- Multiple sandboxes can be run simultaneously
- Does not pollute the Windows environment
- disadvantages
- Not compatible with ie
- Passes at the global scope
var
或function
Declared variables and functions cannot be hijacked by proxy sandboxes because proxy objectsProxy
Only attributes that exist on the object can be identified byvar
或function
Declaration of declared variables is opened up by new addresses that naturally cannot beProxy
Hijacking, for example
const proxy1 = new CreateProxySandbox({});
proxy1.mountProxySandbox();
(function(window) {
mountProxySandbox();
var a = 'this is proxySandbox1';
function b() {};
console.log('Agent sandbox 1 mounted a, b:'.window.a, window.b); // undefined undefined
})(proxy1.proxy)
proxy1.unmountProxySandbox();
(function(window) {
console.log('Agent sandbox 1 unloaded a, b:'.window.a, window.b); // undefined undefined
})(proxy1.proxy)
Copy the code
One solution is to declare global variables and functions without var and function, for example
var a = 1; / / failure
a = 1; / / effective
window.a = 1; / / effective
function b() {} / / failure
b = () = > {} / / effective
window.b = () = > {} / / effective
Copy the code
Design and implementation of CSS isolation
3.1 Introduction to CSS Isolation
When there are multiple micro-applications on A page, you need to isolate the style of the application to ensure that the style of application A does not affect the style of application B.
3.2 Dynamic Stylesheet – Dynamic Stylesheet
3.3 Engineering means – BEM, CSS Modules, CSS in JS
Through a series ofThe constraintandGenerate different class names at compile time,JS handles CSS to generate different class namesTo solve the isolation problem
3.4 Shadow DOM
Shadow DOM
Allow will be hiddenDOM
Tree attached to the regularDOM
In the tree — it takesshadow root
The node is the starting root node, and below this root node can be any element, and ordinaryDOM
Elements like, hiddenDOM
Style and restDOM
It’s completely isolated, likeiframe
The style isolation effect of.
Ionic’s component style isolation is a Shadow DOM solution that ensures that the styles of the same components don’t clash.
- The demo presentation
- Code implementation
<! DOCTYPEhtml>
<html lang="en">
<body data-qiankun-A>
<h5>Style isolation:</h5>
<p class="title">A line of words</p>
<script src="scopedCSS.js"></script>
<script src="index.js"></script>
</body>
</html>
Copy the code
// index.js
var bodyNode = document.getElementsByTagName('body') [0];
openShadow(bodyNode);
Copy the code
// scopedCss.js
function openShadow(domNode) {
var shadow = domNode.attachShadow({ mode: 'open' });
shadow.innerHTML = domNode.innerHTML;
domNode.innerHTML = "";
}
Copy the code
- advantages
- Fully insulate CSS styles
- disadvantages
- When using some popover components (popovers are often added to document.body by default) it skips the shadow boundary and goes into the main application, and the style is lost
3.5 Runtime Conversion Styles – Runtime CSS Transformer
To dynamically change the CSS, for example, one of the styles of the A application, P.twele, would become div[data-qiankun-A], div[data-Qiankun -A] was the outermost container node of the microapplication, Therefore, the style of A application was only effective under DIV [data-Qiankun -A].
- The demo presentation
- Code implementation
<! -- index.html -->
<html lang="en">
<head>
<style>
p.title {
font-size: 20px;
}
</style>
</head>
<body data-qiankun-A>
<p class="title">A line of words</p>
<script src="scopedCSS.js"></script>
<script>
var styleNode = document.getElementsByTagName('style') [0];
scopeCss(styleNode, 'body[data-qiankun-A]');
</script>
</body>
</html>
Copy the code
// scopedCSS.js
function scopeCss(styleNode, prefix) {
const css = ruleStyle(styleNode.sheet.cssRules[0], prefix);
styleNode.textContent = css;
}
function ruleStyle(rule, prefix) {
const rootSelectorRE = / ((? :[^\w\-.#]|^)(body|html|:root))/gm;
let { cssText } = rule;
// bind selector, a,span,p,div {... }
cssText = cssText.replace(/^[\s\S]+{/.(selectors) = >
selectors.replace(/(^|,\n?) ([^,]+)/g.(item, p, s) = > {
// bind div,body,span {... }
if (rootSelectorRE.test(item)) {
return item.replace(rootSelectorRE, (m) = > {
// Do not lose valid characters such as body, HTML or *:not(:root)
const whitePrevChars = [', '.'('];
if (m && whitePrevChars.includes(m[0]) {return `${m[0]}${prefix}`;
}
// Replace the root selector with a prefix
return prefix;
});
}
return `${p}${prefix} ${s.replace(/ ^ * /.' ')}`; }));return cssText;
}
Copy the code
- advantages
- Supports most style isolation requirements
- To solve the
Shadow DOM
The root node is lost due to the scheme
- disadvantages
- There is a performance penalty for reloading styles at run time
The design and implementation of removing JS side effects
4.1 Introduction to side effects of JS removal
When a child application uses global apis that need to be listened asynchronously, such as window.addEventListener and setInterval, in the sandbox, ensure that the child application also removes the corresponding listening events. Otherwise, other applications will have side effects.
4.2 Remove side effects of JS operations
- The demo presentation
- Code implementation
<! DOCTYPEhtml>
<html lang="en">
<body>
<h5>Clear window side effects:</h5>
<button onclick="mountSandbox()">Mount the sandbox and open the side effects</button>
<button onclick="unmountSandbox(true)">Uninstall the sandbox and close the side effects</button>
<button onclick="unmountSandbox()">General unloading sandbox</button>
<script src="proxySandbox.js"></script>
<script src="patchSideEffects.js"></script>
<script src="index.js"></script>
</body>
</html>
Copy the code
let mountingFreer;
const proxy2 = new CreateProxySandbox({});
function mountSandbox() {
proxy2.mountProxySandbox();
// Code executed in a sandbox environment
(function(window, self) {
with(window) {
// Record side effects
mountingFreer = patchSideEffects(window);
window.a = 'this is proxySandbox2';
console.log('Agent sandbox 2 mounted a:'.window.a); // undefined
// Set screen change listening
window.addEventListener('resize'.() = > {
console.log('resize');
});
// Timing output string
setInterval(() = > {
console.log('Interval');
}, 500);
}
}).bind(proxy2.proxy)(proxy2.proxy, proxy2.proxy);
}
/ * * *@param IsPatch whether to disable side effects */
function unmountSandbox(isPatch = false) {
proxy2.mountProxySandbox();
console.log('Agent sandbox 2 unloaded A :'.window.a); // undefined
if(isPatch) { mountingFreer(); }}Copy the code
// patchSideEffects.js
const rawAddEventListener = window.addEventListener;
const rawRemoveEventListener = window.removeEventListener;
const rawWindowInterval = window.setInterval;
const rawWindowClearInterval = window.clearInterval;
function patch(global) {
const listenerMap = new Map(a);let intervals = [];
global.addEventListener = (type, listener, options) = > {
const listeners = listenerMap.get(type) || [];
listenerMap.set(type, [...listeners, listener]);
return rawAddEventListener.call(window, type, listener, options);
};
global.removeEventListener = (type, listener, options) = > {
const storedTypeListeners = listenerMap.get(type);
if(storedTypeListeners && storedTypeListeners.length && storedTypeListeners.indexOf(listener) ! = = -1) {
storedTypeListeners.splice(storedTypeListeners.indexOf(listener), 1);
}
return rawRemoveEventListener.call(window, type, listener, options);
};
global.clearInterval = (intervalId) = > {
intervals = intervals.filter((id) = >id ! == intervalId);return rawWindowClearInterval(intervalId);
};
global.setInterval = (handler, timeout, ... args) = > {
constintervalId = rawWindowInterval(handler, timeout, ... args); intervals = [...intervals, intervalId];return intervalId;
};
return function free() {
listenerMap.forEach((listeners, type) = >
[...listeners].forEach((listener) = > global.removeEventListener(type, listener)),
);
global.addEventListener = rawAddEventListener;
global.removeEventListener = rawRemoveEventListener;
intervals.forEach((id) = > global.clearInterval(id));
global.setInterval = rawWindowInterval;
global.clearInterval = rawWindowClearInterval;
};
}
function patchSideEffects(global) {
return patch(global);
}
Copy the code
To be continued
In the next period, we will continue to explore the micro front-end technology from the design and implementation of application access, communication design and implementation, and application routing monitoring design and implementation. Please look forward to it. If you feel that the content of this article is helpful to you, please click the “like” support, your support is the power of occasionally update!
References:
Micro front End Serial 6/7: Micro front End Frame – Daba Good
Qiankun official file
Easter egg: Bytedance inside Push – Chengdu -2021.05
Bytedance Tiktok social team recruiting!!
Why bytes? As long as NB, finished work can come off work at any time! You can do whatever challenges you like as long as you want! As long as there are opinions, all kinds of feedback group, duty number, onCall at any time to find someone to mention!
Job description
- Front-end development Engineer – Tik Tok – experience unlimited as long as enough NB
- Responsible for the front-end development of Douyin social networking and local life, including the development of core functions and activities on the product side;
- Implement and promote product planning and requirements to ensure efficient and high-quality iteration of product functions;
- Focus on product process and product experience to create the ultimate user experience;
- Participate in technical planning and ensure development experience, r&d efficiency, system performance and safety.
Job requirements
1. Bachelor degree or above, major in computer science, communication and electronic information science, mathematics or related is preferred; 2. Solid computer and front-end skills, good logical understanding and learning ability; 3. Familiar with front-end basic technology and common development framework; 4. Good awareness of interaction design and user experience; 5. Positive and optimistic, strong sense of responsibility, good service awareness and communication and cooperation skills;
Please send your resume to [email protected]
Resume name: RESUME – Douyin Social front – Name – wechat number.pdf
Every person I send resume to provide interview preparation modification Suggestions, advice and resume resume through I would find you push to send code directly, not through will give Suggestions, push the benefit is that every interview link schedule are clear, direct docking people to follow up the progress can be found, waiting for what, opportunity never and others, threw resume can also enter the pool, Increase the chances of your next interview! That’s how I got into bytes, don’t be a coward!!