首页 > 代码库 > 前端模块化

前端模块化

前端模块化

一、为什么需要模块化?

   代码量骤增 => 分治管理的刚性需求

 

二、模块化方案需解决什么问题?

       模块化要实现两个东西:模块加载与模块封装。面临的具体问题包括:

       1、如何定义模块以确保模块的作用域独立,避免命名冲突?

       2、如何管理模块间的依赖关系,避免重复加载与循环引用?

       3、模块化的代码如何部署,以降低HTTP请求数?

       4、如何实现按需加载?

       5、如何在解决上述问题之后,保证性能且不影响debug?

 

三、原始的解决方案有何局限?

   命名空间 + 立即执行函数 + script标签

   局限性:

   1、全局空间污染

   2、需手动管理依赖,不具备可扩展性

   3、无法实现按需加载

 

四、新的解决方案

1、CommonJS

       CommonJS 起源于一个服务端项目 SeverJS,该项目意在通过模块化的开发模式, 解决 JS 作用域的问题。后来发展成了一个致力于构建 JS 生态圈的组织。

       CommonJS 提供了一套模块加载的规范,其核心语法是通过 module.exports 暴露接口,通过 require() 加载资源。

技术分享

   CommonJS 规范采用同步加载,适用于服务端,但并不适用于浏览器环境(网络延迟、异步特性),因此在浏览器端出现了各类模块加载器,以解决模块加载的问题。

   各类模块加载器提出了各自的模块封装的规范。其中 Sea.js 提出/实现的封装规范,就是 CMD 规范;RequireJS 提出/实现的封装规范,就是 AMD 规范。

 

2、Sea.js

  模块封装:

   define (function (require, exports, module) {

    var a = require(‘./a‘)  // 模块加载

    a.doSomething();

    // ……

    var b = require(‘./b‘)  // 依赖可以就近书写

    b.doSomething();

    // 通过 exports 对外提供接口

    exports.doSomething = ...

    // 或者通过 module.exports 提供整个接口

    module.exports = ...

   })

   模块加载:

   var $ = require(‘jquery‘);

 

3、RequireJS

       模块封装:

   define ([‘./a‘, ‘./b‘], function(a, b) {

    a.doSomething();

    // 此处略去 100 行

    b.doSomething();

    return function () { } //返回模块的值,可以是函数,也可以是对象

  })

  模块加载:

  require ([‘./a‘, ‘./b‘], function (a, b) {

        // do sth

  })  

      区别

      RequireJS:依赖前置,提前加载

      Sea.js:依赖就近,延迟加载

 

4、UMD

       一种兼容 CommonJS 和 AMD 的语法糖。事实上 RequireJS 和 Sea.js 就是相互支持的。

技术分享

图片来源:https://www.leanpanda.com/blog/2015/06/28/amd-requirejs-commonjs-browserify/

      

5、打包工具:Browserify&Webpack

       在模块化的开发方式下,模块加载器(Module loader)解决了模块的加载与依赖的自动管理,但是并没有解决 HTTP 请求数的问题。如何将分散的文件合并成一个或几个文件以减少 HTTP 请求,这就是打包工具(Module bundler)的作用。

       打包工具的核心能力是对 js 代码进行合并,扩展能力是对 js 代码进行优化、编译和压缩。webpack 的特色是扩展得比较狠,通过各类插件,可以打包任意类型的文件。

       打包工具的真正价值在哪里呢?仅仅是合并文件从而减少 HTTP 请求数吗?当然不止,打包工具的真正价值是在工程开发中,完成从开发状态到发布状态的自动化构建

技术分享

  减少 HTTP 请求和按需加载其实是相互矛盾的,都打包成一个文件了,自然不需要考虑模块加载的问题。但是把所有文件都打包成一个文件,又显得有些冗余。所以 webpack 也支持 code spliting,把文件打成多个包。

 

6、终结者:ES6 Module

      原生模块标准得到浏览器全面支持之时,就是所有模块封装方案灭亡之日。但打包工具(自动化构建工具)仍会继续存在。

 

五、模块打包的技术实现

技术分享

图片来源:https://www.leanpanda.com/blog/2015/06/28/amd-requirejs-commonjs-browserify/

  以上是 browserify 的实现,webpack 的实现也差不多,这里详细说明一下。

  首先,webpack 会将每个 js 文件编译成一个函数:

技术分享

   webpack 会将文件路径映射为一个数字 id,入口文件默认 id 为 0,在入口文件中遇到的第一个加载的模块,id 为 1……,总之按照加载的顺序依次赋予一个 id。

技术分享

    webpack 函数用于整个模块的加载,所有模块函数会按照 id 的顺序组成一个数组,传给 webpack 进行加载。

技术分享

       最后运行的结果是:

技术分享

 

前端模块化