简单理解前端打包amd cjs iife umd

4,829 阅读5分钟

序言

最近在改公司静态资源注入的问题,想由直接加载静态资源,改成先加载资源注入器,然后再去加载静态资源,就像所有的事一样,开始总是美好的,而过程中是坎坷的,结果总是不如意的,回味总是无穷的一样。(对不起,我的文采压不住了)。原本一马平川的道路,因为很多历史原因变得崎岖泥泞。

动态注入

简单的说通过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(传入函数) :
    传入函数();
})(传入函数)

从代码可以看出来

  1. 首先创建了一个自执行函数,传入用函数包裹的功能代码
  2. 判断当前全局变量上是否有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打包模式和其他的模式不一样