提问 发文

前端模块规范简介

赵炎飞

| 2023-09-02 15:35 177 0 0

前言

        日常开发中,我们都会用到一些第三方库,或者自己封装一些库,用来减少自己的开发量。在封装库的时候,我们会借助一些打包工具,来转换代码的语法(比如解析jsx语法,把 JavaScript 中 es2015/2016/2017 等新语法转化为 es5),使其能在目标浏览器上正常执行。而打包工具有很多,比如webpack,babel,rollup,每个打包工具都有自己的配置项和插件,且体系庞大,构成复杂。所以本文不介绍打包工具如何使用,而是让大家对不同的模块规范有一个简单的了解,以及普及一些打包知识。

        注:本文主要以babel为例。

模块规范

1.IIFE——Immediately Invoked Functions Expressions

        这是立即执行函数,它的代码结构如下所示:

(function(){
  ...
})()

        这种格式的文件主要通过<script>标签引用来使用的,所以可以直接在浏览器中执行使用,大部分的前端sdk会采用这种方式。

2.CJS——CommonJS

        是一种服务端的模块规范,常见于node.js,其引入和导出的方法如下所示:

 // 引入 
const myModule = require('./myModule.js'); 

// 导出
module.exports = function doSomething(n) { ... };
module.exports.a = 1;
//或者
exports = function otherThing(n) { ... }; 
exports.b = 2;

        引入CJS包的方法大家应该都懂,就是通过require把对应路径的模块作为一个对象赋值给某个变量。但是在导出时,需要注意的是我们用的是exports,而不是export,因为export是ESM的导出方式,这里需要区分一下。另外,导出时module.exports和exports的作用是一样的,module.exports只是对exports的引用。CJS模块有以下几个特点:

  • 1.模块在运行时加载和执行,并且只在首次加载时运行一次,然后将运行结果缓存,以备后续多次加载
  • 2.模块加载方式为同步加载,多个模块依次按顺序加载
  • 3.require 语句可以放在块级作用域或条件语句中动态加载

3.AMD——Asynchronous Module Definition

        AMD是主要用于浏览器的模块规范,其引入和导出的方法如下所示:

 // 引入
require(["./amd.js"], function (m) { 
    console.log(m); 
});

// 导出
define("a");
define(['dep1', 'dep2'], function (dep1, dep2) { 
    // Define the module value by returning a value. 
    return function () {}; 
});

        AMD的引入方式其实和CJS差不多,都是通过require来引入,不过它提供了两个参数,第一个是需要引入的模块的名称数组,第二个则是模块引入完成后执行的回调函数。所以当大家看到require时,不要简单的以为这个模块就是CJS规范的,主要还是要看它是同步加载还是异步加载的。

        另外,在导出AMD模块时,主要是用define方法,该方法也提供两个参数,第一个是依赖的模块数组,第二个则是一个匿名函数,该函数返回一个新的函数,返回的函数就是导出的模块,AMD会先加载依赖,然后再执行模块代码。

4.CMD——Common moudle definition

        CMD和AMD类似,只不过AMD需要先加载依赖,再执行代码,而CMD将依赖后置,在模块内部,在需要时才通过require引入依赖,其代码格式如下所示:

define(function(require, export, module){
 	require('./a.js')  // 引入 模块
 
  module.exports = {   // 导出模块
     a: 1,
  }
})

5.UMD——Universal Module Definition

        UMD是对多种模块格式(包括上面四种)都进行兼容的模块规范,所以它既能在服务端执行,又能在浏览器端执行,它打包后的模块格式如下:

 (function (root, factory) { 
    if (typeof define === "function" && define.amd) { 
    // 支持 AMD 规范
        define(["jquery", "underscore"], factory);
    } else if (typeof define === 'function' && define.cmd){ 
    // 支持 CMD 规范
        define(function(require, exports, module) { 
            module.exports = factory() 
        })
    } else if (typeof exports === "object") { 
    // 支持 CommonJS
        module.exports = factory(require("jquery"), require("underscore")); 
    } else { 
    // 支持全局引用
        root.Requester = factory(root.$, root._); 
    } 
}(this, function ($, _) { 
    var Requester = { ... }; 
    return Requester; 
}))

        根据上面代码的条件判断可知,上面的代码只兼容了四种模块规范,很显然,ESM不在其中,所以如果你用UMD打包完你的代码后,发现打包后的文件里没有export导出的模块,那么你可能没办法通过import来引入你的模块了。

6.ESM——ES Module

        ESM是我们最熟知的模块规范,日常的组件开发也是用的这种规范,简单的import和export即可,其格式如下所示:

// 引入
import {foo, bar} from './myLib'; 

// 导出
export default function() { 
	...
}

      ESM模块规范是ES6出现后才有的模块,它有以下几个特点;

  • 1.可能存在浏览器兼容性问题。
  • 2.可以在html文件中通过<script>标签直接引用,不过需要添加type="module"属性。
  • 3.ESM是编译时加载的,而其他几种模块规范都是在运行时加载的。
  • 4.ESM也是异步加载模块文件的。

        从上文我们可以看到不同模块规范下的模块格式,在实际开发过程中,我们也需要根据开发环境来选择合适的模块规范,不要试图用import去引入一个没有export的模块。

babel插件配置

        如果要使用babel插件来打包代码,需要在当前项目目录下添加.babelrc文件,下面是该文件的一个例子:

        .babelrc文件内存放的是一个对象,其中的presets和plugins都是插件数组,只不过presets里的插件是一整套的,而plugins里的插件都是单个的。

        此外,如果我们要对插件进行配置,就需要用数组进行包裹,以["@babel/preset-env",{"modules":false}]为例,数组中第一个元素是插件名称,第二个则是插件的配置对象,不同插件的配置对象内容是不同的。

        env这个插件是babel中很重要的一个插件,它的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。例如目标浏览器支持 es2015,那么 es2015 这个 preset 其实是不需要的,于是代码就可以小一点(一般转化后的代码总是更长),构建时间也可以缩短一些。

        另外,在env中,我们可以设置modules的值为 amd, umd, systemjs, commonjs,以及false,分别对应不同的模块规范,false则是不进行模块化处理,仅仅进行打包。

收藏 0
分享
分享方式
微信

评论

游客

全部 0条评论

19

文章

1.86K

人气

6

粉丝

1

关注

官方媒体

轻松设计高效搭建,减少3倍设计改稿与开发运维工作量

开始免费试用 预约演示

扫一扫关注公众号 扫一扫联系客服

©Copyrights 2016-2022 杭州易知微科技有限公司 浙ICP备2021017017号-3 浙公网安备33011002011932号

互联网信息服务业务 合字B2-20220090

400-8505-905 复制
免费试用
微信社区
易知微-数据可视化
微信扫一扫入群