浅谈模块化开发

如今的前端开发

如今的前端开发, 不只仅是完成浏览的基本需求,而且一般是一个单页面应用,每个视图经过异步的方式加载,这致使页面初始化和使用过程当中会加载愈来愈多的 JavaScript 代码. 如何在开发环境组织好这些碎片化的代码和资源,而且保证他们在浏览器端快速、优雅的加载和更新,就须要一个模块化系统css

什么是模块化

把函数做为模块 缺陷: 污染全局变量 模块成员之间没什么关系 面向对象思想 并使用当即执行函数 实现闭包 避免了变量污染 同时同一模块内的成员也有了关系 在模块外部没法修改咱们没有暴露出来的变量、函数 这就是简单的模块前端

指望的模块系统

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

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

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

模块系统

script

  • 全局做用域下容易形成变量冲突
  • 文件只能按照 <script> 的书写顺序进行加载
  • 开发人员必须主观解决模块和代码库的依赖关系
  • 在大型项目中各类资源难以管理,长期积累的问题致使代码库混乱不堪

CommonJS

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

require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;
复制代码

web

// moduleA.js
module.exports = function( value ){
    return value * 2;
}
复制代码
// moduleB.js
var multiplyBy2 = require('./moduleA');
var result = multiplyBy2(4);
复制代码

优势:npm

  • 服务器端模块便于重用
  • NPM 中已经有将近20万个可使用模块包
  • 简单并容易使用

缺点:gulp

  • 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的
  • 不能非阻塞的并行加载多个模块

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');
});
复制代码

优势:

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

缺点:

  • 提升了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不畅
  • 不符合通用的模块化思惟方式,是一种妥协的实现

4.CMD

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

优势:

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

缺点:

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

5.UMD

6.ES6模块

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

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

优势:

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

缺点:

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

实现:

AMD、CMD、webpack的区别:

从前有两个规范,一个是AMD 一个是CMD  RequireJS是AMD规范的实现,SeaJS是CMD规范的实现,  一个主张提早加载依赖,一个主张延迟加载依赖 后来出现了 commonjs规范  webpack就是支持commonjs规范的  目前能够说是主导了前端构建格局。

CommomJS是服务端规范,node就是采用这个规范,他是同步加载,毕竟服务端不用考虑异步。  它是对通用的JavaScript模式的标准化尝试,它包含有 AMD 定义  AMD是异步加载模块的缩写,使用require引入模块,提倡依賴前置。  CMD与AMD其实挺接近的,还由于有sea.js,中文资料仍是比较亲切的。  还有就是AMD和CommomJS的中间者UMD  Webpack其实就是一个打包工具,他的思想就是一切皆模块,css是模块,js是模块,图片是模块。而且提供了一些列模块加载(各类-loader)来编译模块。官方推荐使用commonJS规范,可是也支持CMD和AMD

不管是node应用模块,仍是webpack 配置 ,均是采用CommonJS模块化规范

CommonJS 与 AMD 支持

Webpack 对 CommonJS 的 AMD 的语法作了兼容, 方便迁移代码

不过实际上, 引用模块的规则是依据 CommonJS 来的

require('lodash') // 从模块目录查找
require('./file') // 按相对路径查找
复制代码

AMD 语法中, 也要注意, 是按 CommonJS 的方案查找的

define (require, exports. module) ->
  require('lodash') # commonjs 当中这样是查找模块的 

复制代码

CommonJs与AMD

在一开始,咱们先讲一下它和以往咱们所用的模块管理工具备什么不同。在最开始的阶段,Js并无这些模块机制,各类Js处处飞,得不到有效妥善的管理。后来前端圈开始制定规范,最耳熟能详的是CommonJs和AMD。

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

var firstModule = require("firstModule");
//your code...
module.export = anotherModule
复制代码

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

define(['firstModule'], function(module){
   //your code...
   return anotherModule
})
复制代码

其实咱们单比较写法,就知道CommonJs是更为优秀的。它是一种同步的写法,对Human友好,并且代码也不会繁琐臃肿。但更重要的缘由是,随着npm成为主流的JavaScript组件发布平台,愈来愈多的前端项目也依赖于npm上的项目,或者自身就会发布到npm平台。因此咱们对如何可使用npm包中的模块是咱们的一大需求。因此browserify工具就出现了,它支持咱们直接使用require()的同步语法去加载npm模块。

固然咱们这里不得不说的是,ES2015(ES6)里也有了本身的模块机制,也就是说ES6的模块机制是官方规定的,咱们经过babel(一种6to5的编译器)可使用比较多的新特性了,包括咱们提到的模块机制,而它的写法大体以下:

import {someModule} from "someModule";
// your codes...
export anotherModule;
复制代码

固然上面的写法只是最基本的,还有其余的不一样加载模块的写法,能够看一下阮一峰老师的ECMAScript 6 入门或者babel的相关文档Learn ES2015。

功能特性

browserify的出现很是棒,但webpack更胜一筹!

咱们来看看webpack支持哪些功能特性:

  • 支持CommonJs和AMD模块,意思也就是咱们基本能够无痛迁移旧项目。
  • 支持模块加载器和插件机制,可对模块灵活定制。特别是我最爱的babel-loader,有效支持ES6。
  • 能够经过配置,打包成多个文件。有效利用浏览器的缓存功能提高性能。
  • 将样式文件和图片等静态资源也可视为模块进行打包。配合loader加载器,能够支持sass,less等CSS预处理器。
  • 内置有source map,即便打包在一块儿依旧方便调试。
  • 看完上面这些,能够想象它就是一个前端工具,可让咱们进行各类模块加载,预处理后,再打包。以前咱们对这些的处理是放在grunt或gulp等前端自动化工具中。有了webpack,咱们无需借助自动化工具对模块进行各类处理,让咱们工具的任务分的更加清晰。

回顾ES6模块

对象的导出

1. export default{
        add(){}
 }
2. export fucntion add(){} 至关于 将add方法当作一个属性挂在到exports对象
复制代码

对象的导入

若是导出的是: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'
复制代码

回顾Node模块

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

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

前端标准的模块化规范

  1. AMD - requirejs
  2. CMD - seajs

服务器端的模块化规范

CommonJS - Node.js

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);
复制代码

##回顾webpack

模块打包器

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

  • 将依赖树拆分红按需加载的块
  • 初始化加载的耗时尽可能少
  • 各类静态资源均可以视做模块
  • 将第三方库整合成模块的能力
  • 能够自定义打包逻辑的能力
  • 适合大项目,不管是单页仍是多页的 Web 应用

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 可以以使人难以置信的速度快速增量编译。

webpack是什么?

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

为何使用 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 很快且在增量编译上更加快

相关文章
相关标签/搜索