开篇
本周开始五一假期,计划回家一趟,所以在回家前把上周割的内容补一下,之后可能会把文章整体搬迁,具体计划还在考虑中。
对于内容,也在考虑怎么才能让阅读过程所花费的时间变得有价值(不仅是对于自己,也对于所有花费时间来看的人)。当然内容主要还是自我总结和探讨为主,本期开始会追加动漫的推荐以点题。
技术
qiankun
基于single-spa
的qiankun
是目前微前端(容器)届比较亮的那颗星了,最近的工作中也应用qiankun
踩了一些网上不常见的坑,简单总结几点:
1. 关于angularjs和qiankun的整合
子项目是基于angularjs
的项目,使用fis
构建(百度出品,非webpack
)。对于非webpack
打包工程(例如:jquery
),qiankun
官方提供了qiankun
的标准方案
qiankun非webpack构建应用
但是对于angularjs
使用了require.js
,在require.js
中定义了require
,define
的变量,但是qiankun
的沙箱机制导致这些变量的有效范围发生了变化,需要在window
上挂载require
,define
,使得变量挂载到子应用到全局,这部分改动可以补充在require.js
底部,不过,建议可以用另一个文件引用require.js
,将这部分代码追加到新文件中。
// require.polyfill.js
include('require.js');
window.require = require;
window.requirejs = requirejs;
window.define = define;
复制代码
解决掉方法挂载,会发现require.js
依赖的模块加载,仍然提示define
未定义,然后审查页面元素,会发现插入部分的脚本脱离了qiankun
的沙箱机制机制,为什么呢?
这里涉及两个知识点:
1)requirejs
的脚本插入
require.js
中插入脚本的时候会先去查找<head>
节点,之后查找<base>
节点,如果找到了<base>
节点,会把<base>
节点的父节点作为插入位置,之后调用insertBefore
插入脚本
// Line:1802
if (isBrowser) {
head = s.head = document.getElementsByTagName('head')[0];
//If BASE tag is in play, using appendChild is a problem for IE6.
//When that browser dies, this can be removed. Details in this jQuery bug:
//http://dev.jquery.com/ticket/2709
baseElement = document.getElementsByTagName('base')[0];
if (baseElement) {
head = s.head = baseElement.parentNode;
}
}
// Line: 1904
if (baseElement) {
head.insertBefore(node, baseElement);
} else {
head.appendChild(node);
}
复制代码
2)qiankun
沙箱处理机制
qiankun
利用import-html-entry
的包,对所有加载入口文件index.html
进行了加载,对HTMLHeadElement
的insertBefore
和appendChild
进行了重载,并对HTMLBodyElement
的appendChild
进行了重载
// common.ts Line: 305 patchHTMLDynamicAppendPrototypeFunctions
if (
HTMLHeadElement.prototype.appendChild === rawHeadAppendChild &&
HTMLBodyElement.prototype.appendChild === rawBodyAppendChild &&
HTMLHeadElement.prototype.insertBefore === rawHeadInsertBefore
) {
HTMLHeadElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({
rawDOMAppendOrInsertBefore: rawHeadAppendChild,
containerConfigGetter,
isInvokedByMicroApp,
}) as typeof rawHeadAppendChild;
HTMLBodyElement.prototype.appendChild = getOverwrittenAppendChildOrInsertBefore({
rawDOMAppendOrInsertBefore: rawBodyAppendChild,
containerConfigGetter,
isInvokedByMicroApp,
}) as typeof rawBodyAppendChild;
HTMLHeadElement.prototype.insertBefore = getOverwrittenAppendChildOrInsertBefore({
rawDOMAppendOrInsertBefore: rawHeadInsertBefore as any,
containerConfigGetter,
isInvokedByMicroApp,
}) as typeof rawHeadInsertBefore;
}
复制代码
重载过程主要是处理<script>
和<style>
其中:getOverwrittenAppendChildOrInsertBefore方法源码,以上两个部分在结合使用到时候出现了一个问题,angular
中的<base>
原本是在<head>
中
<html>
<head>
<base href="/" />
</head>
</html>
复制代码
而在子应用插入qiankun
的时候,默认会移除掉<html>
节点和<head>
节点(暂时没看到这是在哪一步完成的),从而<base>
的父节点从HTMLHeadElement
变成了HTMLBodyElement
<div id="__qiankun_microapp_wrapper_for_xxxxx">
<base href="/" />
</div>
复制代码
刚才代码中我们看到qiankun
没有重载HTMLBodyElement
的insertBefore
方法(原因不明),导致该脚本直接被插入到了基座应用中,基座应用当然不包含之前暴露的window.define
导致报错。
因此解决方案,要么修改require.js
的查找机制,要么处理<base>
节点位置,最后和有经验的同事商量后,决定在require.js
加载前,动态在插入<base>
结点到基座的<head>
中,解决问题:
<script type="text/javascript">
if (!document.getElementsByTagName('head')[0].querySelector('base')) {
document.getElementsByTagName('head')[0].appendChild(document.getElementsByTagName('base')[0]);
}
</script>
复制代码
2. 关于静态资源加载
qiankun
沙箱对于style
的处理逻辑,会将link
的子应用作为<style>
插入基座应用中,如果子应用的CSS中使用了相对路径(例如:background:url(../xxx)
,字体文件等),则此时相对路径会被变成相对基座应用的路径,从而无法找到引用的文件,此时可以使用官方提供的方案:
资源加载404
简单来说,方案是将相对路径变成绝对路径,例如:webpack
打包的可以设置publicPath
进行路径替换
3. 其他的改造
main.js
是核心的改造,angularjs
的bootstrap
只允许调用一次,那么两个angularjs
应用切换的过程,需要进行额外的处理(render
方法中的逻辑),具体可参见以下内容:
function render(props) {
// Require JS Config File
require.config(__inline('require.config.json'));
const staticHTML = `<div ng-controller="AppCtrl">
<div ui-view></div>
<div uix-notify></div>
</div>`
// check angular frame is bootstrap, it need bootstrap only once
if (__state.instance) {
angular.element(__state.el).html(staticHTML);
const link = __angular__.$compile(angular.element(__state.el).contents())
link(__state.$scope);
getContainerEl().appendChild(__state.el);
} else {
const el = document.createElement('div');
el.innerHTML = staticHTML;
__state.el = el;
getContainerEl().appendChild(__state.el);
require(['appInit'], function (app) {
__state.instance = angular.bootstrap(el, [app.name], {
strictDi: true
});
// __state.$scope is just $rootScope
__state.$scope = angular.element(el).scope();
});
}
}
const mount = () => {
return new Promise((resolve, reject) => {
getHorn().then(() => {
render();
resolve();
});
});
}
const unmount = () => {
return new Promise((resolve, reject) => {
// don't destroy $rootScope, ui-xg components would not work, maybe some other unknown affects?
// just clear AppCtrl
angular.element(__state.el.querySelector('div[ng-controller=AppCtrl]')).scope().$destroy();
__state.el.innerHTML = '';
getContainerEl().removeChild(__state.el);
resolve();
});
}
const getContainerEl = () => {
let element = document.getElementById('app');
if (!element) {
throw new Error(`domElementGetter did not return a valid dom element`);
}
return element;
}
if (!window.__POWERED_BY_QIANKUN__) {
getHorn().then(() => {
render();
});
} else {
((global) => {
global['/xxxxx'] = {
bootstrap: () => {
return Promise.resolve();
},
mount: mount,
unmount: unmount,
};
})(window);
}
复制代码
整个改造过程花费了比较多的时间,也爬了比较多的坑,但是对于自身来说,收益是很明显的,很喜欢这个解决问题的整个过程和思路,也很感谢Leader对我的帮助。
非技术
1. Duet
一款很不错的分屏工具,可以将电脑屏幕通过数据线分屏到iphone,pad等设备,看到有人使用以后觉得真的这个十分赞, Duet
动漫推荐
《钢制炼金术士》
本期是动漫推荐的第一期,《钢之炼金术师》简称《钢炼》,被bones(骨头社)制作了两个版本,分别是03版本
和FA版本
,其中的03版本
使用了原创结局,两个版本都很出色,b站最近限免中。
世界观
钢炼基于等价交换的这一偏哲学意味的主题,所有的一切都是围绕此展开的,在一个虚构的世界,描绘了生活在这里的形形色色的正/反双方的观点对冲。
剧情走向
从艾尔利克兄弟为了复活病逝的母亲,尝试了禁忌的人体炼成,兄弟两人分别被人体炼成夺取了等价之物,为了夺回弟弟的身体,两人踏上了探索世界的道路。
推荐理由
推荐钢炼的理由有三点:
1)完整的人物刻画
钢炼是我看过的对于人物刻画特别完整的动漫,这里的完整是指人物前后行为逻辑的自洽,每个角色的行为都是其性格的体现。
每一个人物的出场和退场,都让这个故事的整体主线变得丰满,没有不必要的人,每个人在某个时刻总会发挥着特别的作用。
正如我对社会的认识一样,人类社会就是复杂的人类综合体,每个人都在其中推进着世界的进步,你可能不是明面上的主角,但是你一定是你自己的主角。
2)没有无限膨胀的剧情和战力
大部分少年漫特点就是战力崩坏,大家也喜欢各种傲天的类型(我也喜欢),而钢炼很好的控制了这个问题,从始至终,战力就是很平衡的状态,主角从来没有经历什么变得强的一发不可收拾,相反,你可以看到其实两兄弟一直在吃瘪。
没有传统少年漫的爽快感,反而更能整个剧情的饱满,剧情的推进不是依赖打怪升级,而是对这个世界的探索,对“真理”的探索一步一步推进的,牛姨想要表达的世界观。
3)唯一的世界法则
等价交换的法则,在看完03版本以后,我自身把这一法则认为了是唯一真理。所有的行为,都会导致其结果,其结果必定是合理的等价,只是这里的等价并不是以个人的意识为转移,而是真正的等价,也就是说,艾尔利克兄弟付出代价复活了母亲,只是这里的母亲并不等于他们所以想要的母亲,但是从原则上来说辅助的代价和母亲确实是等价的,简单来说,等价交换的产物不一定是你想要的,但是一定是对等的。
关于钢炼的整体在动漫领域的分析,其实很多人都在吹,但是动漫和其他的文艺作品一样,更多的还是要自己去体会,也许对于你来说他很不好,也正是大家能有各自的意见而且也能容纳他人的看法,社会才一直在进步
尾声
五一期间除了休息,经历了很难受的事情,也有反思,反思自己的问题,处理事情的方式和方法。希望大家能假期快乐!