前端自动化 模块化开发
Grunt
为什么Grunt
在前端开发的过程中,我们经常会做一些跟功能、代码无关的重复的事情,比如复制、粘贴、压缩等等,我们需要有一个前端自动化的工具。
什么是Grunt
免费的小秘书 Grunt官网
怎么用Grunt
- 前提:node npm
- 安装grunt命令行工具 npm install -g grunt-cli
- 在工作目录安装grunt npm install grunt --save-dev
- 编辑Gruntfile.js文件
- 在命令行执行grunt命令
关于Gruntfile.js的内容
格式如下,三步上篮:
module.exports=function(grunt){//包裹函数,所有代码都在这里
grunt.initConfig({});//1配置任务内容
grunt.loadNpmTasks();//2加载插件
grunt.registerTask();//3注册任务
}
initconfig——配置
可以有多个插件名,每个的插件名-加载插件名-注册任务中执行的插件名字要一致。
每个插件名可以有多个任务描述,默认全部执行,也可以指定执行某些任务。
grunt.initConfig({
插件名1: {
任务描述1: {
//描述xxx
},
任务描述2: {
//描述xxx2
}
},
插件名2: {
任务描述3: {
//描述xxx3
}
}
});
比如:
uglify: {
main: {
//描述
src: '1.js',//源文件
dest: '1.min.js'//目标文件
}
}
loadNpmTasks——引入插件
grunt.loadNpmTasks('grunt-contrib-uglify');
关于grunt的插件。。。
registerTask——注册任务
grunt.registerTask('default', ['uglify']);
关于执行grunt命令
执行的是registerTask注册的任务
Tips:
- 插件基本都能自动创建文件夹
- 重复执行grunt的话,已经存在的文件会被覆盖,不报错,不在后面添加。
- 通配符 *号 代指所有
- 通配符 **号 代指所有,包括没有
- cwd 描述想要作为当前文件夹的文件夹路径
- expand,分开成一个个的文件,uglify可以压缩文件到一个文件,cssmin不可以
- ext参数,设置后缀名
- grunt中的模板
<%= grunt.template.today("yyyy-mm-dd HH-MM-ss") %>
, 也可以放一个常量json进来可以在模板中使用,避免重复写,也便于更改
结合<%= %>使用模板- grunt输出
grunt > log.txt
- concat 连接起来,不压缩,生成的是一个文件【注意: 顺序未知】
- grunt.file上面有基于nodeJs的fs包装好的操作文件的方法,比如:
grunt.file.readJSON()
通常用来读取json文件,直接转化为可用的json- copy 带文件夹层级复制,复制多个文件时,要有expand,否则会报错
- watch —— 监测,(自动化)
AMD和CMD
WHY——模块化编程 vs 原始代码
转自阮一峰的博客:最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过。
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>
这段代码依次加载多个js文件。随着代码越来越多,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。就算是代码作者本人,在后期维护起来也会越来越吃力,更不敢想多人合作了。
在前端开发过程中,经常会出现一个单文件几千行甚至近万行的情况。这种大文件,对协作开发、后续维护、性能调优等都不利。模块化开发初衷是帮助前端开发工程师将大文件拆分成小文件,能保持小颗粒度的模块化开发,同时不需要过多考虑依赖关系,让依赖管理轻松自如,将更多精力聚焦在代码本身的逻辑上。
HOW——AMD和CMD之争
先分开介绍,再对比
CMD seaJs 玉伯
玉伯(王保平),淘宝前端类库 KISSY、前端模块化开发框架SeaJS、前端基础类库Arale的创始人。
CMD规范的定义
- 懒加载——Execution must be lazy.
- 一个模块就是一个文件,代码的书写格式如下:define(factory);
- define 是一个全局函数,用来定义模块。
- define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。
- factory 为对象、字符串时,表示模块的接口就是该对象、字符串
- factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory 方法在执行时,默认会传入三个参数:require、exports 和 module:
```
define(function(require, exports, module) {//参数的顺序必须是这样的
// 模块代码
//require是获取其他模块的函数
//exports用来输出当前模块
//module用来输出当前模块
//exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 exports 赋值是无效的,不能用来更改模块接口。
});
```
- 经常使用的 API 只有 define, require, exports, module.exports而已,简单明了
seaJs的用法
最简单的seaJs案例
//主html
<!doctype html>
<html>
<head>
<script src="../sea-modules/seajs/seajs/2.2.0/sea.js"></script>
<script>
seajs.use("main");//main是main.js,是用define定义的模块
</script>
</head>
<body>
页面内容
</body>
</html>
//main.js
define(function(require) {
alert(1);
});
- 主文件配置参数 seajs.config(); 官方介绍
seajs.config({//paths 配置可以结合 alias 配置一起使用,让模块引用非常方便。 // 设置路径,方便跨目录调用 paths: { 'arale': 'https://a.alipayobjects.com/arale', 'jquery': 'https://a.alipayobjects.com/jquery' }, // 设置别名,方便调用 alias: { 'class': 'arale/class/1.0.0/class', 'jquery': 'jquery/jquery/1.10.1/jquery' } });
- 主文件使用模块:seajs.use();——头(or衣领)API介绍
// 加载一个模块 seajs.use('./a'); // 加载一个模块,在加载完成时,执行回调 seajs.use('./a', function(a) { a.doSomething(); }); // 加载多个模块,在加载完成时,执行回调 seajs.use(['./a', './b'], function(a, b) { a.doSomething(); b.doSomething(); });
- 如何定义模块
用define来定义模块。Sea.js 推崇一个模块一个文件,遵循统一的写法:
define(function(require, exports, module) { // 模块代码 // exports 被require的对象上的属性或方法 // 对外提供 foo 属性 // 在外部使用:require(xx).foo exports.foo = 'bar'; // 对外提供 doSomething 方法 // 在外部使用:require(xx).dosomething exports.doSomething = function() {}; //********************// // module.exports 就是被require的对象 module.exports = { name: 'a', doSomething: function() {}; }; });
- 如何引用模块
require 用来获取指定模块的接口。
define(function(require, exports, module) { // 获取模块 a 的接口 var a = require('./a'); // 调用模块 a 的方法 a.doSomething(); });
- 总结:用define来定义模块,一个模块就是一个文件,在模块里用require来引用其他模块,在主html文件里引入seajs后用seajs.use()来开启js文件。
AMD requireJs
AMD规范的定义
- 全称 Asynchronous Module Definition——异步模块定义规范。
- 模块和模块的依赖可以被异步加载。
- 模块的依赖被异步加载完成以后,工厂函数才会执行。
- define 是一个全局函数,用来定义模块。
- define 可以接受多种格式的参数,不过通常可以是:
- define(依赖的数组,工厂函数);
- define(工厂函数);
- define(一个对象);
- 一个模块就是一个文件
requireJs的用法
最简单的requireJs案例
//主html
<!doctype html>
<html>
<head>
<script src="js/require.js"></script>
<script>
require(['jquery'], function($){
alert('jquery loaded');
});
</script>
</head>
<body>
页面内容
</body>
</html>
or:
//主html
<!doctype html>
<html>
<head>
<script src="js/require.js" data-main="js/main.js"></script>
</head>
<body>
// 这样写,会异步加载data-main指定的js文件,并把data-main所在的路径作为config配置的baseUrl。如果不想异步,就像上面一样写。
页面内容
</body>
</html>
//main.js
require(['jquery'], function($){//会在jquery模块执行完成后,把jquery的输出做为参数传入后面的工厂函数,并执行工厂函数
alert('jquery loaded');
});
- 配置: 可以使用require.config来设置依赖的模块的名称和对应的路径,也可以在引入require.js之前定义一个名字为require的json。两种方式都可以进行配置。; requireJs配置官方介绍
<script src="scripts/require.js"></script> <script> require.config({ baseUrl: "/another/path", paths: { "some": "some/v1.0" } }); require( ["some/module", "my/module"], function(someModule, myModule) { //..... } ); </script> 或者是: <script> var require = { baseUrl: "/another/path", paths: { "some": "some/v1.0" } }; </script> <script src="scripts/require.js"></script>
- 主文件如何使用模块:从上面已经可以看到,不同于seaJs的使用seajs.use()函数,requireJs使用require()函数来开始执行。
- 如何定义模块
用define来定义模块:常见的3种情况
- define(依赖的数组,工厂函数);
```
define(['aModule','bModule'],function(a,b) {
// 模块代码
// 这个定义的模块在被调用时,aModule和bModule模块加载完成之后,才会以他们的返回值作为参数执行工厂函数
});
```
>> * define(工厂函数); [语法糖](http://requirejs.org/docs/whyamd.html#sugar)
```
define(function(require, exports, module) {
// 模块代码
// 这种方式其实是requireJs的语法糖,可以在函数里面用变量接受require过来的模块(就像seaJs做的那样),并不真的是同步require那个代码,而是用的Function.prototype.toString()转换成数组里面的格式(怎么转换的咱们不管)后异步下载后执行回调。
});
```
>> * define(一个对象);
```
define({
color: "black",
size: "12"
});
```
- 如何引用模块
用require来获取指定模块的接口(常见有2种):
- require(依赖的数组,工厂函数);
```
require(['aModule','bModule'],function(a,b) {
// 模块代码
// aModule和bModule模块加载完成之后,才会以他们的返回值作为参数执行工厂函数
});
```
>> * require(依赖的数组);
```
//只是用来做一个头,异步引入js后自己不用再做其他事情
require(['aModule','bModule']) ;
```
- 总结:
用define来定义模块,一个模块就是一个文件。
define新模块时用数组提前声明依赖的模块。
在主html文件里引入requirejs后用require()来开启js文件。
RequireJS 与 SeaJs 的异同
共同点:
- 都是模块加载器,倡导模块化开发理念
- 核心价值是让 JavaScript 的模块化开发变得简单自然
不同点:
- RequireJS是异步加载模块,SeaJs是同步加载模块
- RequireJS是提前声明并加载依赖,SeaJs是按需加载依赖模块
我们目前使用的是 RequireJS。