序言
最近在改公司静态资源注入的问题,想由直接加载静态资源,改成先加载资源注入器,然后再去加载静态资源,就像所有的事一样,开始总是美好的,而过程中是坎坷的,结果总是不如意的,回味总是无穷的一样。(对不起,我的文采压不住了)。原本一马平川的道路,因为很多历史原因变得崎岖泥泞。
动态注入
简单的说通过js向html里面插入script标签,动态执行js
静态注入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 一般我们引入静态资源是这样的 -->
<script src='静态支援'></script>
</body>
</html>
这样的引入方式的优势就是,资源的版本固定,不会有因为资源版本迭代升级。就像它的优势一样明显的就是它的劣势,就是我们无法在不从新构建打包项目的情况下,迭代升级资源。特别是一些很稳定的项目,短时间内无迭代版本要发布。
动态注入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 动态注入 -->
<script src='./injector.js'></script>
</body>
</html>
静态注入的是一个注入器
// ./injector
var script = document.createElement('script');
script.src = '静态支援';
script.async = true;
document.querySelector('body').appendChild(script);
注入器创建一个script标签加入html标签里面
至于script的 async 属性,可以参看详情async
打包
现在项目,都会通过各种打包工具构建打包。 对应的前端有各种各样的打包格式。 打包格式有,amd,cmd,iife,umd
AMD(Asynchronous Module Definition)(异步模块定义,用于像RequireJS这样的模块加载器)
AMD始于前端模块化的初期,那是“天地初开的时候,那已经盛放的玫瑰”(不好意思串台了)。
因为是最早的探索者,AMD借鉴了ComminJS的require()语句加载模块,同时做了改进,它要求两个参数
require([module], callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:
require(['injector'], function (injector) {
// injector是模块执行返回的结果
injector;
});
做这样的改进,有一个好处,就是异步加载模块
改造原项目->AMD
我们使用rollup来作为构建打包的工具
rollup injector.js --file injector.amd.js --format amd
打包之后的样子:
// ./injectot.amd.js
define(function () { 'use strict';
var script = document.createElement('script');
script.src = '静态支援';
script.async = true;
document.querySelector('body').appendChild(script);
});
我们可以看出,它是用全局的一个define方法包裹了我们的注入器,但是并没有执行
AMD我们使用非常流行的库require.js 我们来改造一下我们的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 从cdn加载require.js -->
<script src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script>
require(["./injector.amd.js"], function (injector){
console.log(injector);//undefined
});
</script>
</body>
</html>
现在来思考一下,打印injector为什么为undefined?
因为,这个injector是
function () { 'use strict';
var script = document.createElement('script');
script.src = '静态支援';
script.async = true;
document.querySelector('body').appendChild(script);
}//执行之后的结果
使用require加载使用define定义之后的模块,才会执行模块 执行之后的返回(重点)
总结,如果一个项目使用require.js,会在window对象上留下什么
可以看到使用require.js, 会在window对象上绑定,一个define,require,requirejs方法
CJS(CommonJS,适用于 Node 和 Browserify/Webpack)
我们来看看cjs的打包
rollup injector.js --file injector.cjs.js --format cjs
'use strict';
var script = document.createElement('script');
script.src = '静态支援';
script.async = true;
document.querySelector('body').appendChild(script);
可以看出来,cjs基本没有什么变化,但是重点,当加载cjs模块式,会自动执行js。
IIFE(Immediately-invoked function expression)(一个自动执行的功能,适合作为<script>标签)
这个也是非常常见的一中打包模式
rollup injector.js --file injector.iife.js --format iife
打包出来之后样子
(function () {
'use strict';
var script = document.createElement('script');
script.src = '静态支援';
script.async = true;
document.querySelector('body').appendChild(script);
}());
这个不必多说
UMD(Universal Module Definition)(建议作为通用的模块化系统,它与 AMD 和 CommonJS 都是兼容的。)
作为我们现在最常用的打包
rollup injector.js --file injector.umd.js --format umd
我们重点看看它打包之后的代码
(function (factory) {
typeof define === 'function' && define.amd ? define(factory) :
factory();
}((function () { 'use strict';
var script = document.createElement('script');
script.src = '静态支援';
script.async = true;
document.querySelector('body').appendChild(script);
})));
这段代码可以分为几个部分 看下面的伪代码
(function (传入函数) {
//这里通过判断当前是否符合amd打包规则
typeof define === 'function' && define.amd ? define(传入函数) :
传入函数();
})(传入函数)
从代码可以看出来
- 首先创建了一个自执行函数,传入用函数包裹的功能代码
- 判断当前全局变量上是否有define方法,如果有,认为是amd打包格式
可以发现,使用umd打包之后的代码,在amd环境之下,传入函数是没有执行的
说了这么多,才说道问题的关键
问题:因为不同的业务线模块化的方式各有不同,打包的方式也不同。引入的方式也不同
我们所有的资源包基本都是umd格式的
引入方式如下:
| 引入方式 | 是否正常使用 |
|---|---|
| requirejs(资源) | √ |
| requirejs(注入器) | × |
| import(资源) | √ |
| import(注入器) | √ |
| script标签(资源) | √ |
| script标签(注入器) | √ |
requirejs 模式下,采用注入是有问题
原因:这是umd打包模式兼容amd模式带来的
因为是通过window对象上是否有define函数,来判断当前是否是按照amd格式来使用,所以当我们采用
资源只是加载但不会不会执行
解决办法
我们看看逻辑伪代码
(function () {
'use strict';
function injector(params) {
//这里返回已经构建的实例
if(window.injector) {
return window.injector
}
var script = document.createElement('script');
script.src = '静态支援';
script.async = true;
document.querySelector('body').appendChild(script);
window.injector = '已经注入了';
// 这里会在严格模式下面报错的,这是方便理解
return arguments.callee;
}
injector();
}());
后记
这次问题有惊无险,谨记amd打包模式和其他的模式不一样。