JavaScript模块化-require.js,r.js和打包发布
在JavaScript模块化和闭包和JavaScript-Module-Pattern-In-Depth这两篇文章中,提到了模块化的基本思想,但是在实际项目中模块化和项目人员的分工,组建化开发,打包发布,性能优化,工程化管理都有密切的关系,这么重要的事情,在JavaScript大行其道的今天,不可能没有成熟的解决方案,所以从我的实践经验出发,从模块化讲到工程化,分享一下自己的经验。
这篇文章主要是讲require.js和r.js在项目中的使用,不会涉及到工程化问题,对此熟悉的看官可以略过此文。对于require.js基本用法不熟悉的朋友,可以看看这个blog:asynchronous_module_definition
JavaScript的模块化
流行的模块化解决方案现在有很多,主要分为以下几种规范
- AMD:今天讨论的主题,AMD 规范是JavaScript开发的一次重要尝试,它以简单而优雅的方式统一了JavaScript的模块定义和加载机制,并迅速得到很多框架的认可和采纳。这对开发人员来说是一个好消息,通过AMD我们降低了学习和使用各种框架的门槛,能够以一种统一的方式去定义和使用模块,提高开发效率,降低了应用维护成本。
- CommonJS:node.js的方式,在前端需要打包工具配合使用。在后端比较好用。
- CMD & sea.js: 国内牛人搞的。LABjs、RequireJS、SeaJS 哪个最好用?为什么?
JavaScript的模块化需要解决下面几个问题
- 定义模块
- 管理模块依赖
- 加载模块
- 加载优化
- 代码调试支持
为了直观的理解一下流行了很久的require.js和r.js是如何解决这些问题的,我们从一个例子入手吧。下载example-multipage-shim
代码结构
我们看一下基于requirejs的多页面项目的一个基本结构:
下面我们看看如何解决js模块化的问题的。
定义模块
看一下base.js
define(function () {
function controllerBase(id) {
this.id = id;
}
controllerBase.prototype = {
setModel: function (model) {
this.model = model;
},
render: function (bodyDom) {
bodyDom.prepend('<h1>Controller ' + this.id + ' says "' +
this.model.getTitle() + '"</h2>');
}
};
return controllerBase;
});
使用define就可以定义了。不需要我们自己手动导出全局变量啦。
管理模块依赖
看一下c1.js
define(['./Base'], function (Base) {
var c1 = new Base('Controller 1');
return c1;
});
可以看到通过['./Base']注入依赖。
在看一下main1.js
define(function (require) {
var $ = require('jquery'),
lib = require('./lib'),
controller = require('./controller/c1'),
model = require('./model/m1'),
backbone = require('backbone'),
underscore = require('underscore');
//A fabricated API to show interaction of
//common and specific pieces.
controller.setModel(model);
$(function () {
controller.render(lib.getBody());
//Display backbone and underscore versions
$('body')
.append('<div>backbone version: ' + backbone.VERSION + '</div>')
.append('<div>underscore version: ' + underscore.VERSION + '</div>');
});
});
也可以通过require的方式(CommonJS风格)去加载依赖模块
加载模块
看一下如何启动,看看page1.html
<!DOCTYPE html>
<html>
<head>
<title>Page 1</title>
<script src="js/lib/require.js"></script>
<script>
//Load common code that includes config, then load the app
//logic for this page. Do the requirejs calls here instead of
//a separate file so after a build there are only 2 HTTP
//requests instead of three.
requirejs(['./js/common'], function (common) {
//js/common sets the baseUrl to be js/ so
//can just ask for 'app/main1' here instead
//of 'js/app/main1'
requirejs(['app/main1']);
});
</script>
</head>
<body>
<a href="page2.html">Go to Page 2</a>
</body>
</html>
我们看到首先用script标签引入require.js,然后使用requirejs加载模块,而这些模块本来也要用script标签引用的,所以说requirejs帮助我们管理文件加载的事情了。可以使用data-main属性去加载,详细说明可以看文档了。
我们看一下运行效果。
可以看到requirejs帮助我们家在了所有模块,我们可以更好的组织JavaScript代码了。
优化加载
我们模块化代码以后,并不想增加请求的次数,这样会使网页的性能降低(这里是异步加载,但是浏览器异步请求的过多,还是有问题的),所以我们想合并一下代码。
使用r.js:
node r.js -o build.js
看看结果:
构建后我们的代码都经过处理了。
看看运行效果。
可见可以通过r.js帮助我们优化请求(通过合并文件)。
如何配置
- requirejs如何配置,我们看看common.js
requirejs.config({
baseUrl: 'js/lib', 从这个位置加载模块
paths: {
app: '../app'
},
shim: {
backbone: {
deps: ['jquery', 'underscore'],
exports: 'Backbone'
},
underscore: {
exports: '_'
}
}
});
| 属性 | 意义 |
|---|---|
| baseUrl | 加载模块的位置 |
| app:'../app' | 像这样的'app/sub',在app目录下找sub模块 |
| shim | 全局导出的库,在这里包装 |
可以查看中文说明书看看更详细的说明。
- r.js如何配置,我们看看build.js
这里面有很全的配置说明example.build.js,过一下我们自己是怎么配置的。
{
appDir: '../www',
mainConfigFile: '../www/js/common.js',
dir: '../www-built',
modules: [
//First set up the common build layer.
{
//module names are relative to baseUrl
name: '../common',
//List common dependencies here. Only need to list
//top level dependencies, "include" will find
//nested dependencies.
include: ['jquery',
'app/lib',
'app/controller/Base',
'app/model/Base'
]
},
//Now set up a build layer for each main layer, but exclude
//the common one. "exclude" will exclude nested
//the nested, built dependencies from "common". Any
//"exclude" that includes built modules should be
//listed before the build layer that wants to exclude it.
//The "page1" and "page2" modules are **not** the targets of
//the optimization, because shim config is in play, and
//shimmed dependencies need to maintain their load order.
//In this example, common.js will hold jquery, so backbone
//needs to be delayed from loading until common.js finishes.
//That loading sequence is controlled in page1.html.
{
//module names are relative to baseUrl/paths config
name: 'app/main1',
exclude: ['../common']
},
{
//module names are relative to baseUrl
name: 'app/main2',
exclude: ['../common']
}
]
}
我们主要看modules下面定义的数组,实际上就是一个个文件的依赖关系,r.js会一用这里的关系,合并文件。详细的配置意义可以看文档
提示:r.js还可以优化css。
如何调试
前面代码被优化了以后,调试起来就痛苦了,这里我们可以使用sourcemap技术来调试优化后的代码。进行如下操作。
- 修改build.js,增加如下配置
generateSourceMaps: true,
preserveLicenseComments: false,
optimize: "uglify2", - 重新构建
node r.js -o build.js 打开浏览器支持
这里最好用firefox浏览器,chrome从本地文件打开html不能正常使用sourcemap。直接用firefox浏览就可以了。
firefox支持sourcemap可以看到可以加载非优化的代码,有人会问,这不要请求多次吗?优化一份,非优化一份,这样不是性能更差劲。其实只有你调试的时候,开启了这个功能才会请求对应的sourcemap文件,所以对用户来说并不浪费。
写一个server让chrome也支持
chrome本身是支持source map的,就是从硬盘直接打开文件的权限有特殊处理。以file://开头的路径很多事情做不了。所以我们做一个简单的server吧。
在tools目录下增加一个server.js文件
var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs'),
port = process.argv[2] || 8888,
types = {
'html': 'text/html',
'js': 'application/javascript'
};
http.createServer(function (request, response) {
var uri = url.parse(request.url).pathname,
filename = path.join(__dirname, '..', uri);
console.log(filename);
fs.exists(filename, function (exists) {
if (!exists) {
response.writeHead(404, {'Content-Type': 'text/plain'});
response.write('404 Not Found\n');
response.end();
return;
}
var type = filename.split('.');
type = type[type.length - 1];
response.writeHead(200, { 'Content-Type': types[type] + '; charset=utf-8' });
fs.createReadStream(filename).pipe(response);
});
}).listen(parseInt(port, 10));
console.log('Static file server running at\n => http://localhost:' + port + '/\nCTRL + C to shutdown');
开启chrome支持sourcemap
使用node启动server
浏览器中调试
发布
这篇文章是来讲模块化的,和发布没啥关系,但是都写到这里了,就把程序发布出去吧,后面借着这篇文章讨论工程化的时候,可以在看看这篇文章的流程如何提高。
发布的方法无非这么几种:
- windows server的话直接远程过去,copy一下就好。web deploy这种工具也很好用。
- linux使用ftp到远程,再去copy一下。
- 使用rsync。
我们看一下第三种吧。我们用r.js优化了以后怎么发布到服务器上呢。我们按照Deployment-Techniques这个文章推荐的方法说一说。这个发布方法是在这些考虑下提出的。
- 构建后的代码不提交到版本控制。理由主要是为了好维护,提交前build一下很容易忘记,而且提交优化后的代码如果冲突了很难diff,merge。
- 使用r.js在server上生成构建后的代码也不好,因为r.js会删除目录再重新创建,所以如果项目很大,有一段时间服务就会有很多404错误。
所以我们想到了用增量更新的方法去同步文件夹。主要依赖rsync这个命令了。
文章推荐使用grunt工具来打包,然后再跑一个命令去同步文件夹。我们看看代码。
/**
* Gruntfile.js
*/
module.exports = function(grunt) {
// Do grunt-related things in here
var requirejs = require("requirejs"),
exec = require("child_process").exec,
fatal = grunt.fail.fatal,
log = grunt.log,
verbose = grunt.verbose,
FS = require('fs'),
json5 = FS.readFileSync("./build.js", 'utf8'),
JSON5 = require('json5'),
// Your r.js build configuration
buildConfigMain = JSON5.parse(json5);
// Transfer the build folder to the right location on the server
grunt.registerTask(
"transfer",
"Transfer the build folder to ../website/www-built and remove it",
function() {
var done = this.async();
// Delete the build folder locally after transferring
exec("rsync -rlv --delete --delete-after ../www-built ../website && rm -rf ../www-built",
function(err, stdout, stderr) {
if (err) {
fatal("Problem with rsync: " + err + " " + stderr);
}
verbose.writeln(stdout);
log.ok("Rsync complete.");
done();
});
}
);
// Build static assets using r.js
grunt.registerTask(
"build",
"Run the r.js build script",
function() {
var done = this.async();
log.writeln("Running build...");
requirejs.optimize(buildConfigMain, function(output) {
log.writeln(output);
log.ok("Main build complete.");
done();
}, function(err) {
fatal("Main build failure: " + err);
});
// This is run after the build completes
grunt.task.run(["transfer"]);
}
);
};
运行结果
可以看到新建了一个website文件夹,并把构建的中间文件同步到此文件夹下面了,而website文件是可以在远程服务器的,是不是很方便呢?
上面的改动可以从这里下载到,大家可以把玩一下requirejs-deploy-demo
总结
可以看到,通过require.js,r.js可以很好的进行模块话的开发;使用grunt,rsync,我们可以完成构建和发布的功能。
作者:沈寅
链接:http://www.jianshu.com/p/7186e5f2f341
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
JavaScript模块化-require.js,r.js和打包发布的更多相关文章
- 【实践】require.js + r.js 代码打包压缩初体验
第二个分享的是学校项目所接触到的新知识,代码压缩 + 代码打包 这次的项目用了require.js 这个插件做模块化管理的工具,所谓模块化就是在开发的过程中将功能划分成一个独立的模块,使代码可读性更强 ...
- [javascript模块化]require.js简单使用
1.javascript模块规范 CommonJS 主要用于服务器端编程,比如node.js的模块系统,就是参照CommonJS规范实现的.在CommonJS中,有一个全局性方法require(),用 ...
- JavaScript模块化-require.js
http://www.cnblogs.com/duanhuajian/archive/2013/01/04/2844151.html 原文:http://www.ruanyifeng.com/blog ...
- Javascript模块化开发2——Gruntfile.js详解
一.grunt模块简介 grunt插件,是一种npm环境下的自动化工具.对于需要反复重复的任务,例如压缩.编译.单元测试.linting等,自动化工具可以减轻你的劳动,简化你的工作.grunt模块根据 ...
- r.js压缩打包(require + backbone)项目开发文件
最近项目稳定了一点,之前一直没空关注的开发文件压缩打包问题也有时间来解决了 AMD模块化开发中的代码压缩打包工具——r.js 环境搭建基于nodejs:用于AMD模块化开发中的项目文件压缩打包,不是A ...
- r.js合并实践 --项目中用到require.js做生产时模块开发 r.js build.js配置详解
本文所用源代码已上传,需要的朋友自行下载:点我下载 第一步: 全局安装 npm install -g requirejs 第二步: 1.以下例子主要实现功能, 1)引用jq库获取dom中元素文本, ...
- require.js 加载 vue组件 r.js 合并压缩
https://www.taoquns.com 自己搭的个人博客 require.js 参考阮一峰 Javascript模块化编程(三):require.js的用法 r.js 合并压缩 参考司徒正美 ...
- r.js压缩打包
AMD模块化开发中的代码压缩打包工具——r.js 环境搭建基于nodejs:用于AMD模块化开发中的项目文件压缩打包,不是AMD模式也是可以的 javascript部分 压缩javascript项目开 ...
- r.js 配置文件 build.js 不完整注释
-----------------------------------------------------------------------r.js 配置文件 example.build.js ...
- requirejs和r.js的心得
requirejs的GitHub:requirejs r.js的GitHub:r.js grunt-contrib-requirejs的GitHub:grunt-contrib-requirejs r ...
随机推荐
- 内网 LAN IPv6 环境配置 H3C S5500 Huawei S5700
# 使能IPv6报文转发功能. <Sysname> system-view [Sysname] ipv6 # 使能DHCPv6服务器功能. <Sysname> system-v ...
- section和div
section和div 一.DIV div即division(区块),把文档分割为独立的.不同的部份.作用,以下三种情况应该用div标签: 1.用于页面布局,且不是 header.footer 之类的 ...
- jquery基础 笔记二
动态创建元素 关于使用HTML DOM创建元素本文不做详细介绍, 下面举一个简单的例子: //使用Dom标准创建元素 var select = document.createElement(" ...
- LeetCode OJ:Burst Balloons(击破气球)
Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by ...
- CUDA Samples: ripple
以下CUDA sample是分别用C++和CUDA实现的生成的波纹图像,并对其中使用到的CUDA函数进行了解说,code参考了<GPU高性能编程CUDA实战>一书的第五章,各个文件内容如下 ...
- 在线机器学习FTRL(Follow-the-regularized-Leader)算法介绍
看到好文章,坚决转载!哈哈,学术目的~~ 最近几个同事在做推荐平台的项目,都问到怎么实现FTRL算法,要求协助帮忙实现FTRL的算法模块.今天也是有空,赶紧来做个整理.明天还要去上海参加天善智能组织的 ...
- 使用微软T4 template进行代码生成
使得软件工程高效开发的主要方法是复用.复用的宗旨是提高设计的内聚性,主要包括:函数,类,模式,组件,框架等等.而有些应用场景并都是可以直接拿来现成代码使用的,有时代码库的代码不是那么容易修改,或者根本 ...
- 高并发异步uwsgi+web.py+gevent
为什么用web.py? python的web框架有很多,比如webpy.flask.bottle等,但是为什么我们选了webpy呢?想了好久,未果,硬要给解释,我想可能原因有两个:第一个是兄弟项目组用 ...
- python sys.path.append()和sys.path.insert()
python程序中使用 import XXX 时,python解析器会在当前目录.已安装和第三方模块中搜索 xxx,如果都搜索不到就会报错. 使用sys.path.append()方法可以临时添加搜索 ...
- git重新配置所有
参考,防止以后忘记:https://www.jianshu.com/p/7fa6b2d81f19 git clone git://github.com/ZengsfOS/SecurityKeyBroa ...