利用webpack实现js/css模块化开发
什么是模块化开发?
对前端工程化的参考资料:https://github.com/fouber/blo...
前端的工程化构建开发不仅仅停留在‘压缩’、‘校验’、‘合并’,模块化构建可以使项目的扩展性、代码的复用性和可维护性大大提高。在解决基本的开发效率和运行效率之后,前端团队要思考维护效率。模块化是当前前端最流行的开发手段。
模块化是一种处理复杂系统分解为更好的管理模块的方式。它可以通过不同的组件设定不同的功能,把一个问题分解成多个小的独立的、相互作用的组件。感觉就像是在拼图。
模块化的好处:
提高代码的重用率
提高开发效率、减少沟通成本
降低耦合
更好实现代码的快速迭代
便于代码的维护
1、css模块化
css模块化开发基本都是在less、sass、stylus等预处理器的import/mixin特性支持下实现的。
2、js模块化
js模块化方案有AMD/CommonJS/ES6 Module等
使用webpack模块的各种方式的依赖关系:
1、ES2015 import语句
2、CommonJS require()语句
3、AMD define 和 require语句
4、css/sass/less文件的 @import 语句
5、样式 (url(...)) 或 HTML文件(<img src=...>) 中的图片链接(image url)
webpack的优势
1、支持CommonJS 和 AMD 模块。
2、支持模块加载器和插件机制,可对模块灵活定制。babel-loader支持ES6
3、可以通过配置,打包成多个文件。有效的利用浏览器的缓存。
4、将样式文件和图片等静态资源视为模块进行打包。配合loader加载器,对资源进行处理。
下面介绍javascript模块化编程:
一、原始写法
模块是实现特定功能的一组方法。
function m1(){ //'''' } function m2(){ //'''' }
上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就ok了。
这种做法的缺点就是污染了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接联系。
二、对象的写法
将模块写成一个对象,所有的模块成员都在这个对象里。
var module = new Object({ _count : 0, m1: function(){ console.log(this._count) }, m2:function(){ console.log(this) } }) module.m1() module._count = 5 module.m2()
上面的函数m1()和m2(),都封装在module对象里。使用的时候直接调用这个对象的属性。
module.m1(); 输出:0
但是这样的写法会暴露所有的模块成员,内部状态会被外部状态改写,比如:
module._count = 5 module.m1() 输出:5
三、立即执行函数
使用立即执行函数可以达到不暴露私有成员的目的。
var module_1 = (function(){ var _count = 1; var m1 = function(){ console.log(_count) }; var m2 = function(){ console.log(this) }; return { m1:m1, m2:m2 } })(); module_1.m1() module_1.m2()
使用上面的写法,外部代码无法读取内部的_count变量。
介绍如何规范的使用模块
JavaScript模块规范共有两种:CommonJS 和 AMD。
为什么要有模块?
有了模块,我们就可以方便的使用别人的代码,想要什么功能,就加载什么模块。
一、CommonJS
用于服务端模块化编程:
一个文件就是一个模块,require方法用来加载模块,该方法读取一个文件并执行,最后返回文件内部的module.exports对象;
require是默认读取 .js 文件,所以require(模块名)可以不写后缀;
同步加载,由于服务端加载的模块一般在本地,所以可以这样;但是客户端如果一个模块过大就会导致页面“假死”;
。
node.js的项目,将JavaScript语言用于服务器编程,在浏览器环境下,没有模块也是可以的。但在服务器端,一定要有模块,与操作系统和其他应用程序互动,否侧就没法编程。
node.js的模块系统,就是参考CommonJS规范实现的。在CommonJS中,有一个全局性的方法require(),用于加载模块。假定有一个数学math.js,就可以像下面这样加载。
var math = require('math');
然后,就可以调用模块提供的方法;
var math = require('math'); math.add(2,3); //5
module.exports属性表示当前模块对外输出的接口,其他模块文件加载该模块,实际上就是读取module.exports变量;为了方便用exports,exports指向module.exports;即exports = module.exports = {};
exports.xxx相当于在导出的对象上添加属性,该属性对调用模块可见;
exports = 相当于给exports重新赋值,这样就切断了和module.exports的关联,调用模块就不能访问exports的对象及其属性。
在浏览器环境:
CommonJS不使用于浏览器环境。如果在浏览器中运行,会有一个大问题;
var math = require('math'); math.add(2,3)
在第二行math.add(2,3),在第一行require('math')之后运行,因此必须等math.js加载完毕。如果加载时间很长,整个应用就会停在这里。
这对服务器不是问题,因为所有的模块都存在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,在浏览器,因为模块都在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于“假死”状态。
因此,浏览器端的模块,不能采用“同步加载”,只能采用“异步加载”。这就是AMD的产生的背景。
二、AMD
AMD是“Asynchronous Module Definition”的缩写,意思是“异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面的语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回到函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数,
require([module],callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块,第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面的形式:
require(['math'],function(math){ math.add(2,3); })
math.add()与math模块加载不是同步的,浏览器不会发生‘假死’。所以AMD比较适合浏览器环境。
三、require.js的用法:
require.js(前端模块化管理的工具库)实现js文件的异步加载,避免网页失去响应;管理模块之间的依赖性,便于代码的编写和维护。
主模块的写法:
main.js称为是“主模块”,意思是整个网页的入口代码。所有的代码都从这开始运行。主模块依赖于其他模块,这时就使用AMD规范定义的require()函数。
//main.js require(['moduleA','moduleB','moduleC'],function(moduleA, moduleB, moduleC){ //some code here }); require()函数接受两个参数。第一个参数是一个数组,表示所依赖的模块, 上例就是['moduleA','moduleB','moduleC'],即主模块依赖这三个模块; 第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。 加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。 require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖的问题。
假定主模块依赖jquery、underscore和backbone三个模块,main.js就可以这样写:
require(['jquery','underscore','backbone'],function($,_,Backbone){
//some code here }) requery.js是先加载jQuery、underscore和backbone,然后在运行回调函数。主模块的代码就写在回调函数中。
四、模块的加载
主模块的依赖模块是['jquery','underscore','backbone']。默认情况下,require.js假定这三个模块于main.js在同一个目录,文件名分别是jquery.js、
underscore和backbone.js,然后自动加载。
使用require.config()方法,我们可以对模块的加载行为进行自定义。
require.config()就写在主模块(main.js)的头部。参数就是一个对象,
这个对象的paths属性指定各个模块的加载路径。
require.config({ paths:{ "jquery":"jquery.min", "underscore":"underscore.min", "backbone":"backbone.min" } }); 上面的代码路径默认与main.js在同一个目录(js子目录)。如果这些模块在 其他目录,比如js/lib目录,则有两种写法: 一种是逐一指定路径。 require.config({ paths:{ "jquery":"lib/jquery.min", "underscore":"lib/underscore.min", "backbone":"lib/backbone.min" } }); 另一种则是直接改变基目录(baseUrl) require.config({ baseUrl:"js/lib", paths:{ "jquery":"jquery.min", "underscore":"underscore.min", "backbone":"backbone.min" } }); 如果某个模块在另一台主机上,也可以直接指定它的网址,比如, require.config({ path:{ "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min" } }) requiry.js要求,每一个模块是一个单独的js文件。这样的话,如果加载多个 模块,就会发出多次HTTP请求,会影响网页的加载速度。因此,require.js提供一个优化工具, 当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少http请求。