前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端模块系统

前端模块系统

作者头像
奋飛
发布2019-08-15 09:50:16
7930
发布2019-08-15 09:50:16
举报
文章被收录于专栏:Super 前端Super 前端

伴随着移动互联的大潮,当今越来越多的网站已经从网页模式进化到了 Webapp 模式。它们运行在现代的高级浏览器里,使用 HTML5、 CSS3、 ES6等更新的技术来开发丰富的功能,网页已经不仅仅是完成浏览的基本需求,并且webapp通常是一个单页面应用,每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的JavaScript 代码,这给前端开发的流程和资源组织带来了巨大的挑战。 前端开发和其他开发工作的主要区别,首先是前端是基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。 前端模块要在客户端中执行,所以他们需要加载到浏览器中。模块的加载和传输,我们首先能想到两种极端的方式,一种是每个模块文件都单独请求,另一种是把所有模块打包成一个文件然后只请求一次。显而易见,每个模块都发起单独的请求造成了请求次数过多,导致应用启动速度慢;一次请求加载所有模块导致流量浪费、初始化过程慢。这两种方式都不是好的解决方案,它们过于简单粗暴。分块传输,按需进行懒加载,在实际用到某些模块的时候再增量更新,才是较为合理的模块加载方案。

一、script标签

代码语言:javascript
复制
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="module3.js"></script>

这是最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中,不同模块的接口调用都是一个作用域中,一些复杂的框架,会使用命名空间的概念来组织这些模块的接口,典型的例子如 YUI 库。

缺点

  • 全局作用域下容易造成变量冲突
  • 文件只能按照 <script> 的书写顺序进行加载
  • 开发人员必须主观解决模块和代码库的依赖关系

二、CommonJS

服务器端的 Node.js 遵循 CommonJS规范,该规范的核心思想是允许模块通过 require 方法来同步加载所要依赖的其他模块,然后通过 exports 或 module.exports 来导出需要暴露的接口。CommonJS规范

代码语言:javascript
复制
// moduleA.js
module.exports = function( value ){
    return value * 2;
}

// moduleB.js
var multiplyBy2 = require('./moduleA');
var result = multiplyBy2(4);

优点:

  • 解决 JavaScript 的作用域问题而定义的模块形式,可以使每个模块在它自身的命名空间中执行

缺点:

  • 同步加载模块,不能非阻塞的并行加载多个模块

实现:

  • 服务器端的 Node.js
  • Browserify

补充,exports与module.exports区别: 为了方便,Node为每个模块提供一个exports变量,指向module.exports。var exports = module.exports;造成的结果是,在对外输出模块接口时,可以向exports对象添加方法。

代码语言:javascript
复制
 exports.area = function (r) {
  return Math.PI * r * r;
 };

注意,不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。

代码语言:javascript
复制
exports = function(x) {console.log(x)};

下面的写法也是无效的。

代码语言:javascript
复制
exports.hello = function() {
  return 'hello';
};

module.exports = 'Hello world';

上面代码中,hello函数是无法对外输出的,因为module.exports被重新赋值了。这意味着,如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。

代码语言:javascript
复制
module.exports = function (x){ console.log(x);};

如果你觉得,exports与module.exports之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。

三、AMD

AMD 定义了一套 JavaScript 模块依赖异步加载标准,来解决同步加载的问题。https://github.com/amdjs/amdjs-api/wiki/AMD

代码语言:javascript
复制
define(id?: String, dependencies?: String[], factory: Function|Object);
  • id 是模块的名字,它是可选的参数
  • dependencies 指定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输出将作为参数一次传入 factory 中。如果没有指定dependencies,那么它的默认值是 [“require”, “exports”, “module”]。
代码语言:javascript
复制
define(function(require, exports, module) {})
  • factory 是最后一个参数,它包裹了模块的具体实现,它是一个函数或者对象。如果是函数,那么它的返回值就是模块的输出接口或值。
代码语言:javascript
复制
define("module", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue;
});

注意:主模块module是在dep1、dep2加载完成并执行结束后才执行,即使处理函数中没有用到这两个模块,dep1、dep2一旦被依赖引用就会被加载执行!

代码语言:javascript
复制
require(["module", "../file"], function(module, file) { /* ... */ });

在模块定义内部引用依赖:

代码语言:javascript
复制
define(function(require) {
    var $ = require('jquery');
    $('body').text('hello world');
});

优点

  • 适合在浏览器环境中异步加载模块,可以并行加载多个模块

缺点

  • 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅,不符合通用的模块化思维方式

实现

  • RequireJS
  • curl

四、CMD

CMD规范和AMD很相似,尽量把持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。遵循按需执行依赖的原则,只有在用到某个模块的时候才会执行模块内部的require语句,同时加载完某个依赖文件后并不立即执行,在所有依赖模块加载完成后进入主模块逻辑,遇到模块运行语句的时候才执行对应的模块https://github.com/seajs/seajs/issues/242

代码语言:javascript
复制
define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');
  exports.doSomething = ...
  module.exports = ...
})

优点:

  • 依赖就近,延迟执行
  • 可以很容易在 Node.js 中运行

缺点:

  • 依赖 SPM 打包,模块的加载逻辑偏重

实现:

  • Sea.js
  • coolie

五、ES6模块

EcmaScript6 标准增加了 JavaScript 语言层面的模块体系定义。ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。

代码语言:javascript
复制
import "jquery";
export function doStuff() {}
module "localModule" {}

优点:

  • 容易进行静态分析
  • 面向未来的 EcmaScript 标准

缺点

  • 原生浏览器端还没有实现该标准
  • 全新的命令字,新版的 Node.js才支持

实现:

  • Babel

补充:

Webpack中提出了tree-shaking,其依赖ES6 modules的静态特性得以实现,ES6 modules 的 import 和 export statements相比完全动态的CommonJS、require,有着本质的区别:

  • 只能作为模块顶层的语句出现,不能出现在 function 里面或是 if 里面。(ECMA-262 15.2)
  • import 的模块名只能是字符串常量。(ECMA-262 15.2.2)
  • 不管 import 的语句出现的位置在哪里,在模块初始化的时候所有的 import 都必须已经导入完成。换句话说,ES6 imports are hoisted。(ECMA-262 15.2.1.16.4 - 8.a)
  • import binding 是 immutable 的,类似 const。比如说你不能 import { a } from ‘./a’ 然后给 a 赋值个其他什么东西。(ECMA-262 15.2.1.16.4 - 12.c.3)

注意:Babel 6的规范选择是使用预设es2015

代码语言:javascript
复制
{
    presets: ['es2015'],
}

但是,该预设包含插件transform-es2015-modules-commonjs,这意味着Babel会输出CommonJS模块,而webpack将无法进行tree-shaking。想要的是Babel的es2015,但没有插件transform-es2015-modules-commonjs。目前,唯一的办法就是提到配置数据中的所有预置插件,除了我们要排除的插件。预设的来源是GitHub,所以基本上是复制和粘贴的情况:

代码语言:javascript
复制
{
    plugins: [
        'transform-es2015-template-literals',
        'transform-es2015-literals',
        'transform-es2015-function-name',
        'transform-es2015-arrow-functions',
        'transform-es2015-block-scoped-functions',
        'transform-es2015-classes',
        'transform-es2015-object-super',
        'transform-es2015-shorthand-properties',
        'transform-es2015-computed-properties',
        'transform-es2015-for-of',
        'transform-es2015-sticky-regex',
        'transform-es2015-unicode-regex',
        'check-es2015-constants',
        'transform-es2015-spread',
        'transform-es2015-parameters',
        'transform-es2015-destructuring',
        'transform-es2015-block-scoping',
        'transform-es2015-typeof-symbol',
        ['transform-regenerator', { async: false, asyncGenerators: false }],
    ],
}

六、Webpack

Webpack 是一个前端资源模块化管理和打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader 的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。

优点

  • 将依赖树拆分成按需加载的块
  • 初始化加载的耗时尽量少
  • 各种静态资源都可以视作模块
  • 将第三方库整合成模块的能力
  • 可以自定义打包逻辑的能力
  • 适合大项目,无论是单页还是多页的 Web 应用
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年08月30日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、script标签
  • 二、CommonJS
  • 三、AMD
  • 四、CMD
  • 五、ES6模块
  • 六、Webpack
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档