我爱编程

前端模块化开发

2018-03-20  本文已影响0人  majun00

1. 前言

现在的前端开发, 通常是一个单页面应用,每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的 JS 代码,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统。

1.1 最简单的模块

其实我们曾把函数作为模块,但会污染全局变量,并且模块成员之间没什么关系。这个时候我们可以运用面向对象思想,使用立即执行函数实现闭包,可以避免变量污染,同时同一模块内的成员也有了关系,在模块外部无法修改我们没有暴露出来的变量、函数,这就是简单的模块。但是这样处理起来麻烦,并且远远不够。

1.2 期望的模块系统

模块的加载和传输,我们首先能想到两种极端的方式,一种是每个模块文件都单独请求,另一种是把所有模块打包成一个文件然后只请求一次。显而易见,每个模块都发起单独的请求造成了请求次数过多,导致应用启动速度慢;一次请求加载所有模块导致流量浪费、初始化过程慢。这两种方式都不是好的解决方案,它们过于简单粗暴。

分块传输,按需进行懒加载,在实际用到某些模块的时候再增量更新,才是较为合理的模块加载方案。要实现模块的按需加载,就需要一个对整个代码库中的模块进行静态分析、编译打包的过程。

在上面的分析过程中,我们提到的模块仅仅是指 JS 模块文件。然而,在前端开发过程中还涉及到样式、图片、字体、HTML 模板等等众多的资源。如果他们都可以视作模块,并且都可以通过require的方式来加载,将带来优雅的开发体验,那么如何做到让 require 能加载各种资源呢?在编译的时候,要对整个代码进行静态分析,分析出各个模块的类型和它们依赖关系,然后将不同类型的模块提交给适配的加载器来处理。Webpack 就是在这样的需求中应运而生。

2. 模块系统

2.1 script

2.2 CommonJS

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

require('module');
require('../file.js');
exports.doStuff = function() {};
module.exports = someValue;

// moduleA.js
module.exports = function(value) {
  return value * 2;
};

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

优点:

缺点:

2.3 AMD

define(id?, dependencies?, factory),它要在声明模块的时候指定所有的依赖 dependencies,并且还要当做形参传到 factory 中,对于依赖的模块提前执行,依赖前置。

define('module', ['dep1', 'dep2'], function(d1, d2) {
  return someExportedValue;
});
require(['module', '../file'], function(module, file) {
  /* ... */
});

一些用例:
定义一个名为 myModule 的模块,它依赖 jQuery 模块:

define('myModule', ['jquery'], function($) {
  // $ 是 jquery 模块的输出
  $('body').text('hello world');
});
// 使用
define(['myModule'], function(myModule) {});

注意:在 webpack 中,模块名只有局部作用域,在 Require.js 中模块名是全局作用域,可以在全局引用。
定义一个没有 id 值的匿名模块,通常作为应用的启动函数:

define(['jquery'], function($) {
  $('body').text('hello world');
});

依赖多个模块的定义:

define(['jquery', './math.js'], function($, math) {
  // $ 和 math 一次传入 factory
  $('body').text('hello world');
});

模块输出:

define(['jquery'], function($) {
  var HelloWorldize = function(selector) {
    $(selector).text('hello world');
  };

  // HelloWorldize 是该模块输出的对外接口
  return HelloWorldize;
});

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

define(function(require) {
  var $ = require('jquery');
  $('body').text('hello world');
});

优点:

缺点:

2.4 CMD

define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');
  exports.doSomething = ...
  module.exports = ...
})

优点:

缺点:

2.5 UMD (暂未接触)

2.6 ES6 模块

ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。

import "jquery";
export function doStuff() {}
module "localModule" {}

优点:

缺点:

实现:

3. 模块系统/规范对比

3.1 AMD 与 CMD

从前有两个规范,一个是 AMD,一个是 CMD。RequireJS 是 AMD 规范的实现,SeaJS 是 CMD 规范的实现。一个主张提前加载依赖,一个主张延迟加载依赖。后来出现了 CommomJS 规范,CommomJS 是服务端规范,node 就是采用这个规范,他是同步加载,毕竟服务端不用考虑异步。

3.2 AMD 与 CommonJs

AMD 的应用场景则是浏览器,异步加载的模块机制。require.js 的写法大致如下:

define(['firstModule'], function(module) {
  //your code...
  return anotherModule;
});

CommonJs 是应用在 NodeJs,是一种同步的模块机制。它的写法大致如下:

var firstModule = require('firstModule');
//your code...
module.export = anotherModule;

其实我们单比较写法,就知道 CommonJs 是更为优秀的。它是一种同步的写法,友好而且代码也不会繁琐臃肿。但更重要的原因是,随着 npm 成为主流的 JS 组件发布平台,越来越多的前端项目也依赖于 npm 上的项目,或者自身就会发布到 npm 平台。所以我们对如何可以使用 npm 包中的模块是我们的一大需求。

3.3 browserify 与 webpack

browserify 工具支持我们直接使用 require()的同步语法去加载 npm 模块,使用不多这里就不做介绍。

  1. Webpack 其实就是一个打包工具,他的思想就是一切皆模块,css 是模块,js 是模块,图片是模块。并且提供了一些列模块加载(各种-loader)来编译模块。官方推荐使用 commonJS 规范,但是也支持 CMD 和 AMD。无论是node应用模块,还是webpack配置 ,均是采用CommonJS模块化规范。

  2. webpack 支持哪些功能特性:

4. 相关知识

4.1 ES6 模块

4.1.1 对象的导出

1. export default{
        add(){}
 }
2. export fucntion add(){} 相当于 将add方法当做一个属性挂在到exports对象

4.1.2 对象的导入

如果导出的是:export default{ add(){}}
那么可以通过  import obj from './calc.js'
如果导出的是:
export fucntion add(){} 
export fucntion substrict(){} 
export const PI=3.14
那么可以通过按需加载 import {add,substrict,PI} from './calc.js'

4.2 Node 模块

4.2.1 传统非模块化开发有如下的缺点

  1. 命名冲突
  2. 文件依赖

4.2.2 前端标准的模块化规范

  1. AMD - requirejs
  2. CMD - seajs

4.2.3 服务器端的模块化规范

CommonJS - Node.js

4.2.4 Node 模块化相关的规则

  1. 如何定义模块:一个 js 文件就是一个模块,模块内部的成员都是相互独立
  2. 模块成员的导出和引入:
    exports 与 module 的关系:module.exports = exports = {};
    模块成员的导出最终以 module.exports 为准
    如果要导出单个的成员或者比较少的成员,一般我们使用 exports 导出;如果要导出的成员比较多,一般我们使用 module.exports 的方式;这两种方式不能同时使用
var sum = function(a, b) {
  return parseInt(a) + parseInt(b);
};
// 方法1
// 导出模块成员
exports.sum = sum;
//引入模块
var module = require('./xx.js');
var ret = module.sum(12, 13);

// 方法2
// 导出模块成员
module.exports = sum;
//引入模块
var module = require('./xx.js');
module();

// // 方法1
// exports.sum = sum;
// exports.subtract = subtract;
//
// var m = require('./05.js');
// var ret = m.sum(1,2);
// var ret1 = m.subtract(1,2);
// console.log(ret,ret1);
//
// // 方法2
// module.exports = {
//     sum : sum,
//     subtract : subtract,
//     multiply : multiply,
//     divide : divide
// }
//
// var m = require('./05.js');
// console.log(m);

4.3 webpack

4.3.1 模块打包器

根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。如何在一个大规模的代码库中,维护各种模块资源的分割和存放,维护它们之间的依赖关系,并且无缝的将它们整合到一起生成适合浏览器端请求加载的静态资源。市面上已经存在的模块管理和打包工具并不适合大型的项目,尤其单页面 Web 应用程序。最紧迫的原因是如何在一个大规模的代码库中,维护各种模块资源的分割和存放,维护它们之间的依赖关系,并且无缝的将它们整合到一起生成适合浏览器端请求加载的静态资源。

这些已有的模块化工具并不能很好的完成如下的目标:

4.3.2 Webpack 的特点

Webapck 和其他模块化工具有什么区别呢?

  1. 代码拆分
    Webpack 有两种组织模块依赖的方式,同步和异步。异步依赖作为分割点,形成一个新的块。在优化了依赖树后,每一个异步区块都作为一个文件被打包。
  2. Loader
    Webpack 本身只能处理原生的 JavaScript 模块,但是 loader 转换器可以将各种类型的资源转换成 JavaScript 模块。这样,任何资源都可以成为 Webpack 可以处理的模块。
  3. 智能解析
    Webpack 有一个智能解析器,几乎可以处理任何第三方库,无论它们的模块形式是 CommonJS、 AMD 还是普通的 JS 文件。甚至在加载依赖的时候,允许使用动态表达式 require("./templates/" + name + ".jade")
  4. 插件系统
    Webpack 还有一个功能丰富的插件系统。大多数内容功能都是基于这个插件系统运行的,还可以开发和使用开源的 Webpack 插件,来满足各式各样的需求。
  5. 快速运行
    Webpack 使用异步 I/O 和多级缓存提高运行效率,这使得 Webpack 能够以令人难以置信的速度快速增量编译。

4.3.3 webpack 是什么?

CommonJS 和 AMD 是用于 JavaScript 模块管理的两大规范,前者定义的是模块的同步加载,主要用于 NodeJS;而后者则是异步加载,通过 requirejs 等工具适用于前端。随着 npm 成为主流的 JavaScript 组件发布平台,越来越多的前端项目也依赖于 npm 上的项目,或者 自身就会发布到 npm 平台。因此,让前端项目更方便的使用 npm 上的资源成为一大需求。
web 开发中常用到的静态资源主要有 JavaScript、CSS、图片、Jade 等文件,webpack 中将静态资源文件称之为模块。 webpack 是一个 module bundler(模块打包工具),其可以兼容多种 js 书写规范,且可以处理模块间的依赖关系,具有更强大的 js 模块化的功能。Webpack 对它们进行统 一的管理以及打包发布

4.3.4 为什么使用 webpack?

1. 对 CommonJS 、 AMD 、ES6 的语法做了兼容
2. 对 js、css、图片等资源文件都支持打包
3. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对 CoffeeScript、ES6 的支持
4. 有独立的配置文件 webpack.config.js
5. 可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间
6. 支持 SourceUrls 和 SourceMaps,易于调试
7. 具有强大的 Plugin 接口,大多是内部插件,使用起来比较灵活
8.webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快

上一篇 下一篇

猜你喜欢

热点阅读