网站开发正变得越来越专业,涉及到各种各样的工具和流程,迫切需要构建自动化。
所谓"构建自动化",就是指使用构建工具,自动实现"从源码到网页"的开发流程。这有利于提高开发效率、改善代码质量。
本文介绍如何使用make命令,作为网站的构建工具。以下内容既是make语法的实例,也是网站构建的实战教程。你完全可以将代码略作修改,拷贝到自己的项目。
(题图:国家考古博物馆,西班牙,摄于2014年8月)
一、Make的优点
首先解释一下,为什么要用Make。
目前,网站项目(尤其是Node.js项目)有三种构建方案。
我觉得,make是大型项目的首选方案。npm run可以认为是make的简化形式,只适用于简单项目,而Grunt、Gulp那样的工具,有很多问题。
(1)插件问题
Grunt和Gulp的操作,都由插件完成。即使是文件改名这样简单的任务,都要写插件,相当麻烦。而Make是直接调用命令行,根本不用担心找不到插件。
(2)兼容性问题
插件的版本,必须与Grunt和Gulp的版本匹配,还必须与对应的命令行程序匹配。比如,grunt-contrib-jshint插件现在是0.11.0版,对应Grunt 0.4.5版和JSHint 2.6.0版。万一Grunt和JSHint升级,而插件没有升级,就有可能出现兼容性问题。Make是直接调用JSHint,不存在这个问题。
(3)语法问题
Grunt和Gulp都有自己的语法,并不容易学,尤其是Grunt,语法很罗嗦,很难一眼看出来代码的意图。当然,make也不容易学,但它有复用性,学会了还可以用在其他场合。
(4)功能问题
make已经使用了几十年,全世界无数的大项目都用它构建,早就证明非常可靠,各种情况都有办法解决,前人累积的经验和资料也非常丰富。相比之下,Grunt和Gulp的历史都不长,使用范围有限,目前还没有出现它们能做、而make做不到的任务。
基于以上理由,我看好make。
二、常见的构建任务
下面是一些常见的网站构建任务。
- 检查语法
- 编译模板
- 转码
- 合并
- 压缩
- 测试
- 删除
这些任务用到 JSHint、handlebars、CoffeeScript、uglifyjs、mocha 等工具。对应的package.json文件如下。
"devDependencies": { "coffee-script": "~1.9.1", "handlebars": "~3.0.0", "jshint": "^2.6.3", "mocha": "~2.2.1", "uglify-js": "~2.4.17" }
我们来看看,Make 命令怎么完成这些构建任务。
三、Makefile的通用配置
开始构建之前,要编写Makefile文件。它是make命令的配置文件。所有任务的构建规则,都写在这个文件(参见《Make 命令教程》)。
首先,写入两行通用配置。
PATH := node_modules/.bin:$(PATH) SHELL := /bin/bash
上面代码的PATH和SHELL都是BASH变量。它们被重新赋值。
PATH变量重新赋值为,优先在 nodemodules/.bin 目录寻找命令。这是因为(当前项目的)node模块,会在 nodemodules/.bin 目录设置一个符号链接。PATH变量指向这个目录以后,调用各种命令就不用写路径了。比如,调用JSHint,就不用写 ~/node_modules/.bin/jshint ,只写 jshint 就行了。
SHELL变量指定构建环境使用BASH。
四、检查语法错误
第一个任务是,检查源码有没有语法错误。
js_files = $(shell find ./lib -name '*.js') lint: $(js_files) jshint $?
上面代码中,shell函数调用find命令,找出lib目录下所有js文件,保存在变量js_files。然后,就可以用jshint检查这些文件。
使用时调用下面的命令。
$ make lint
五、模板编译
第二个任务是编译模板。假定模板都在templates目录,需要编译为build目录下的templates.js文件。
build/templates.js: templates/*.handlebars mkdir -p $(dir $@) handlebars templates/*.handlebars > $@ template: build/templates.js
上面代码查看build目录是否存在,如果不存在就新建一个。dir函数用于取出构建目标的路径名(build),内置变量$@代表构建目标(build/templates.js)。
使用时调用下面的命令。
$ make template
六、Coffee脚本转码
第三个任务是,将CofferScript脚本转为JavaScript脚本。
source_files := $(wildcard lib/*.coffee) build_files := $(source_files:lib/%.coffee=build/%.js) build/%.js: lib/%.coffee coffee -co $(dir $@) $< coffee: $(build_files)
上面代码中,首先获取所有的Coffee脚本文件,存放在变量sourcefiles,函数wildcard用来扩展通配符。然后,将变量sourcefiles中的coffee文件名,替换成js文件名,即 lib/x.coffee 替换成 build/x.js 。
使用时调用下面的命令。
$ make coffee
七、合并文件
使用cat命令,合并多个文件。
JS_FILES := $(wildcard build/*.js) OUTPUT := build/bundle.js concat: $(JS_FILES) cat $^ > $(OUTPUT)
使用时调用下面的命令。
$ make concat
八、压缩JavaScript脚本
将所有JavaScript脚本,压缩为build目录下的app.js。
app_bundle := build/app.js $(app_bundle): $(build_files) $(template_js) uglifyjs -cmo $@ $^ min: $(app_bundle)
使用时调用下面的命令。
$ make min
还有另一种写法,可以另行指定压缩工具。
UGLIFY ?= uglify $(app_bundle): $(build_files) $(template_js) $(UGLIFY) -cmo $@ $^
上面代码将压缩工具uglify放在变量UGLIFY。注意,变量的赋值符是 ?= ,表示这个变量可以被命令行参数覆盖。
调用时这样写。
$ make UGLIFY=node_modules/.bin/jsmin min
上面代码,将jsmin命令给变量UGLIFY,压缩时就会使用jsmin命令。
九、删除临时文件
构建结束前,删除所有临时文件。
clean: rm -rf build
使用时调用下面的命令。
$ make clean
十、测试
假定测试工具是mocha,所有测试用例放在test目录下。
test: $(app_bundle) $(test_js) mocha
当脚本和测试用例都存在,上面代码就会执行mocha。
使用时调用下面的命令。
$ make test
十一、多任务执行
构建过程需要一次性执行多个任务,可以指定一个多任务目标。
build: template concat min clean
上面代码将build指定为执行模板编译、文件合并、脚本压缩、删除临时文件四个任务。
使用时调用下面的命令。
$ make build
如果这行规则在Makefile的最前面,执行时可以省略目标名。
$ make
通常情况下,make一次执行一个任务。如果任务都是独立的,互相没有依赖关系,可以用参数 -j 指定同时执行多个任务。
$ make -j build
十二、声明伪文件
最后,为了防止目标名与现有文件冲突,显式声明哪些目标是伪文件。
.PHONY: lint template coffee concat min test clean build
十三、Makefile文件示例
下面是两个简单的Makefile文件,用来补充make命令的其他构建任务。
实例一。
PROJECT = "My Fancy Node.js project" all: install test server test: ;@echo "Testing ${PROJECT}....."; \ export NODE_PATH=.; \ ./node_modules/mocha/bin/mocha; install: ;@echo "Installing ${PROJECT}....."; \ npm install update: ;@echo "Updating ${PROJECT}....."; \ git pull --rebase; \ npm install clean : ; rm -rf node_modules .PHONY: test server install clean update
实例二。
all: build-js build-css build-js: browserify -t brfs src/app.js > site/app.js build-css: stylus src/style.styl > site/style.css .PHONY build-js build-css
十四、参考链接
- Jess Telford, Example using Makefile for cloverfield
- Oskar Schöldström, How to use Makefiles in your web projects
- James Coglan, Building JavaScript projects with Make
- Rob Ashton, The joy of make
(完)
Clunt 说:
先马再看,最近正急需相关资料
2015年3月13日 17:01 | # | 引用
darasion 说:
人们为了解决一个问题,然后发明了一个工具,然后这个工具产生了N个问题;
为解决工具产生的N个问题,又发明了N个不同的工具;
....
然后人们发现,呃,这么多工具的使用,永远没办法学完,但却不得不继续学下去!
2015年3月13日 17:17 | # | 引用
问心 说:
明明提倡不要重复造轮子,但大牛们还是喜欢重复的造轮子。
苦了码农了。
2015年3月13日 18:14 | # | 引用
Juanito 说:
Rake 也不錯呦!
2015年3月14日 00:18 | # | 引用
独自流浪 说:
make 不是跨平台,在Win下不可用
2015年3月15日 12:30 | # | 引用
陈志远 说:
您的《ECMAScript 6入门》已经出版。但是作为一个js入门以及想要深入的中文读者,更加期待您的《javascript参考教程》。这几天看您的网页教程,但是更加想看看数。特别特别期待。
2015年3月15日 20:31 | # | 引用
rexdf 说:
grunt和node是配合的,可以加watch实时修改并构建,make好像没法自己一直跑着,不过真要搞一个npm包来似乎也可以。
2015年3月16日 02:56 | # | 引用
ran 说:
Linuxer首选make啊, 基本上就是Shell语法. 个人觉得比Node的构建工具方便.
2015年3月17日 21:18 | # | 引用
marcus 说:
gyp呢?现在node-gyp、nw-gyp用着也挺好的。
阮兄,能一起说一说吗?谢谢
2015年3月18日 09:15 | # | 引用
yicone 说:
会写 shell 的用习惯make 的可以用 make,会 javascript/nodejs 的用 grunt/gulp。
“构建网站”这个事,多数是前端人员在做,我相信后者的比例会大很多。
这个角度看,用 make 来做更像是全栈的风格,但不利于团队协作和人员变动。
另外 gulp 不并不是做什么都一定要找插件,每个 gulpfile 就是一个普通的 js,可以直接编写 js 代码,利用现有庞大数量的 nodejs 库。虽说对纯前端人员,nodejs 也有些跨界了,但他们肯能更愿意跨到 nodejs,而不是 make/shell。
大部分常用的插件找起来也很容易,npmjs.org 里搜索 gulp,前几页就是了。
https://www.npmjs.com/search?q=gulp
2015年3月19日 23:12 | # | 引用
chm 说:
想了解一下自己在国内建立一个网站的流程,不是编码方面,主要是想知道服务器托管,域名注册,公安局备案等等流程,请阮老师赐教,谢谢~~
2015年3月21日 22:14 | # | 引用
沈嵘 说:
我一般混合使用 gulp(一年前是 grunt) 和npm run。使用 gulp 一个巨大的好处是性能。由于要处理的文件几乎都是在内存中,而且可以并行处理,因此对于那些有很多模板文件的项目(例如:Jade + Stylus),构建时的性能要比其他方案快很多倍。这还只是直接的性能增益,没有引入其他cache或增量构建的前提下,这在 make 下是很难达到的。如果有较为复杂的任务依赖,也很难用 make 来维护。
2015年3月23日 12:14 | # | 引用
陈计节 说:
现在明显地更代码了啊,比以前纯粹的科普作家更细节了。
2015年3月23日 15:09 | # | 引用
cccc 说:
有那复杂直接,go build
2015年3月24日 09:59 | # | 引用
taocp 说:
$ make -j build
对于大的项目, -j不带参数很容易死机。
2015年3月25日 15:09 | # | 引用
leo 说:
阮哥,能不能把你的文章使用微信公众号推送下,这样方便大家订阅,手机党阅读方便。
2015年3月27日 10:37 | # | 引用
粉丝一 说:
阮兄的文章非常适合阅读,可读性,渐进性。我打印了一些留在手边作为存档。 在打印的时候遇到一个问题,就是在页面直接打印,会把用户评论,相关文章等一些内容也全部打印出来,影响阅读,也很浪费。建议在内容中增加print.css,在打印的时候隐藏掉一些无用的东西。谢谢
2015年3月27日 13:47 | # | 引用
土木坛子 说:
这种方式只能适合极客使用,一般用户还是喜欢图形化界面。
2015年3月28日 14:25 | # | 引用
liyang 说:
这几天怎么打不开您写的JS教程呢。http://javascript.ruanyifeng.com/(觉得你这教程写得特好,一直把之作为我的入门教程不二之选)
2015年3月29日 17:17 | # | 引用
Sunday 说:
的确,前端生态太乱了。。。node,grunt,glup,npm run,还有国内每家公司。。。BAT人手一份的FIS什么的。。。
比起来,make简单太多了。
2015年3月29日 21:09 | # | 引用
bigzhu 说:
如果能解决 watch 的问题我马上就换成 make
2015年3月30日 23:52 | # | 引用
阮一峰 说:
@bigzhu:
watchify https://www.npmjs.com/package/watchify
2015年3月31日 06:24 | # | 引用
tolerious 说:
以前写C的时候是经常要用到Make的,现在做Web开发了,没想到也能用到的哇~
2015年4月 6日 22:10 | # | 引用
tolerious 说:
我公司一个面试前端的人,给他出了个题目,他用的Grunt构建的,尼玛,我按照TA的Readme搞了半天都没跑出来结果。
2015年4月 6日 22:12 | # | 引用
gogu 说:
需要引用的工具都支持命令行方式。grunt/gulp 倒是不需要。
但是,grunt/gulp 各种插件又有各种引用,即使缓存了,线上构建还是很慢。
2015年4月15日 15:56 | # | 引用
青豆 说:
原位置压缩应该怎么写呢?要循环吗?
例如:assets/js/很多目录/*.js > (相对应的) dest/js/目录/*.min.js
2015年4月23日 21:22 | # | 引用
John Doe 说:
请问方便透露下贵公司是哪家吗?
2015年5月12日 17:24 | # | 引用
Roy 说:
也就是make不能watch?
2015年6月 6日 16:09 | # | 引用
北河 说:
这个插件提供了命令行的调用形式,即可以被cmkae调用.如果人员熟悉linux环境,转make吧.
2015年7月27日 15:49 | # | 引用
传说 说:
想到一块去了, 最近再看Gunt的构建, 感觉其实并不是很好,主要是没有DSL.
其实使用make完全可以构建web应用. 只不过不见得每个人都熟悉make.
所以我个人的观点是用你熟悉的工具,你觉得好就好.
2015年10月16日 09:38 | # | 引用
Super Woods 说:
我还是觉得多学点东西没有坏处,技术没有好坏,只有那个更适用而已,所以没办法单纯的比较grunt和make的构建方式到底那个更好。。。。
其实我是都在用的。。。虽然很不专业,不过确实能解决我手头很多项目的开发问题
2016年3月14日 16:41 | # | 引用
Zombie110year 说:
emmm, 可以在 GNU 的 ftp 服务器下载源码, 里面有一个 .sln 文件, 可以用 Visual Studio 打开这个项目, 自己编译.
编译过程很简单, 使用 VS2017 的体验就是, 打开之后, 弹窗询问是否迁移项目 2003 -> 2017. 无脑同意, 就能无错迁移. 之后傻瓜式编译就可以用了.
2018年11月 5日 17:25 | # | 引用