blog
blog copied to clipboard
开发基于Node.js的前端工具
开发基于Node.js的前端工具
知乎上有这样一个问题:为什么node出现之后,各种前端构建工具和手段才如雨后春笋般层出不穷?,里面的答案挺有意思的。其实自从有了Node.js,
Jser们可以脱离浏览器做各种各样有趣的事,在开发中,各种JS库帮助我们改善开发流程,提高开发效率,
比如webpack/babel等等。
今天这里我们就主要讲讲怎么基于Node.js来开发(小)工具,提高我们的工作效率,满足各种实际需要。
一. 相关前置知识
1. Environments
The environment is an area that the shell builds every time that it starts a session that contains variables that define system properties.
每当shell新开启一个会话时,shell都会生成environment,environment里都是些定义系统属性的变量。
$ env
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/zsh
USER=creeper
PATH=/Users/creeper/git/depot_tools:/usr/local/sbin:/Users/creeper/.nvm/versions/node/v6.9.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/dist
...
MANPATH=/Users/creeper/.nvm/versions/node/v6.9.0/share/man:/usr/local/share/man:/usr/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man
NVM_PATH=/Users/creeper/.nvm/versions/node/v6.9.0/lib/node
NVM_BIN=/Users/creeper/.nvm/versions/node/v6.9.0/bin
_=/usr/bin/env
很多程序都会用到这里的变量,比如nvm会用这里的NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/dist
作为代理服务器地址,去淘宝的源下载node来安装,提高速度。
PATH
在environment这么多变量里,有一个变量需要特别注意,就是PATH=/Users/creeper/git/depot_tools:/usr/local/sbin:/Users/creeper/.nvm/versions/node/v6.9.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin。
In UNIX / Linux file systems, the human-readable address of a resource is defined by PATH. It is an environmental variable that tells the shell which directories to search for executable files (i.e., ready-to-run programs) in response to commands issued by a user.
PATH指定了Shell从哪些目录去查找执行文件(PATH在windows里可以通过环境变量去设置)。
当我们在shell里输入比如ls时,其实shell会找到/bin/ls来执行。
2. shebang line(#!)与可执行文件
上面一段提到了可执行文件,在这里我们只讲其中的一块——script文件。任何以shebang line(#!)
开头的文件即可执行的脚本,其中shebang line指定了用什么(解释器)来解释执行脚本。
比如常见的python脚本,你可以看到第一行是这样的:
#!/usr/bin/env python
对node.js来说,shebang line通常这么写:
#!/usr/bin/env node
对有这行的文件,当你shell里执行./my_script时,系统会调用node来解释执行my_script文件。
注意,shebang line是可以加上参数的,如#!/usr/bin/env node --harmony。
以常见的webpack为例,当你npm i webpack之后,你可以找到这样一个文件node_modules/.bin/webpack,
它即webpack的可执行文件。你可以shell里执行node_modules/.bin/webpack,那么会输出help信息。
那么我们稍微看下node_modules/.bin/webpack这个文件:
#!/usr/bin/env node
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var path = require("path");
果然是shebang line加上js代码。
3. 全局安装的npm包为什么可以在shell里直接使用?
接着上面两段,我们差不多明白用node.js写工具的原理了,和python没什么不同。
但是,我们全局安装的一些npm包,比如grunt/gulp/webpack,为什么可以直接在shell里执行呢,和ls之类一样?
其实就是因为:
- 全局安装的npm包安装的位置是固定的,可执行文件存放的位置也是固定的。比如我这里,全局包放在
/Users/creeper/.nvm/versions/node/v6.9.0/lib/node_modules,可执行文件放在/Users/creeper/.nvm/versions/node/v6.9.0/bin:
$ ls -al /Users/creeper/.nvm/versions/node/v6.9.0/bin
drwxr-xr-x 19 creeper staff 646 3 16 16:37 .
drwxr-xr-x 10 creeper staff 340 10 27 20:01 ..
lrwxr-xr-x 1 creeper staff 33 10 27 20:06 cnpm -> ../lib/node_modules/cnpm/bin/cnpm
lrwxr-xr-x 1 creeper staff 43 3 16 16:37 crn-cli -> ../lib/node_modules/@ctrip/crn-cli/index.js
当然,这里的路径都是我本机的,不同机器会有不一样的路径。另外,可执行文件用放在 这个词描述可能不准确,这里其实是 软链接(symlink)。
/Users/creeper/.nvm/versions/node/v6.9.0/bin这个路径是在环境变量PATH里的,所以当你 执行crn-cli时,shell可以正确找到这个命令。
PATH=/Users/creeper/git/depot_tools:/usr/local/sbin:/Users/creeper/.nvm/versions/node/v6.9.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
4. 当npm run command时,我们是在做什么?
我们一般的开发中,经常会用到npm run command,比如npm test等等,这里简单补充下。
npm run-script <command> [-- <args>...]可以执行在package.json的scripts中的相应命令。
// 来自 React repo
{
"scripts": {
"build": "grunt build",
"linc": "git diff --name-only --diff-filter=ACMRTUB `git merge-base HEAD master` | grep '\\.js$' | xargs eslint --",
"lint": "grunt lint",
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
"test": "jest",
"flow": "flow"
}
}
上面是从React的package.json截出的,当我们在shell里执行npm run build时,其实执行的就是
grunt build。
注意:除了shell已经存在的PATH,npm run会添加node_modules/.bin到PATH里。也就是说,
node_modules/.bin的执行文件是可以直接执行的,不用node_modules/.bin/grunt build这种。
二. 结合实例来具体阐述工具编写
前面讲完了一些前置知识,下面结合实例来讲工具编写,主要是我们组实际用到的 :)
1. 图片处理--直接写JS
虽然前面讲了一大堆可执行文件相关的,但对一些一次性工作,我们其实可以直接写JS,然后node执行就好了。 这是最简单快捷的。
当我们shell里执行node x.js --args,args可以通过process.argv来访问:
$ node run.js *.png
[ '/Users/creeper/.nvm/versions/node/v6.9.0/bin/node',
'/Users/creeper/Downloads/切图/run.js',
'*.png' ]
$ node run.js test/*.jpg
[ '/Users/creeper/.nvm/versions/node/v6.9.0/bin/node',
'/Users/creeper/Downloads/切图/run.js',
'test/00010_西安_SIA_12_中国.jpg',
'test/00012_南京_NKG_15_中国.jpg',
'test/00013_无锡_WUX_15_中国.jpg',
'test/00015_扬州_YTY_15_中国.jpg',
'test/00017_杭州_HGH_16_中国.jpg',
'test/00019_舟山_HSN_16_中国.jpg' ]
可以看到process.argv[0]固定是node本身路径,process.argv[1]是文件路径,process.argv.slice(2)
才是我们输入的参数。
具体到我们这里图片处理(UED有很多图片处理工作):
需求: 有一大堆大图(几百张),请导出640x420, 582×178, 284x178, 178x178, 268x106五种尺寸(中心缩放/切割),
且每种尺寸有高斯模糊和正常两种。
方案: 手动PS处理肯定不行,所以 imagemagick(负责图片处理)+ JS(负责参数和一些额外工作,比如图片分类/改名)。
核心代码:
// ...
const GaussianBlur = `20x8`
const getCmd = (file, size, destFile, gaus) => {
return `convert ${file} -resize "${size}^"${
gaus ? (' -gaussian-blur ' + GaussianBlur) : ''
} -gravity center -crop ${size}+0+0 +repage ${destFile}`
}
// ...
使用:
node processImg.js images/**/*.jpg
注: 使用通配符时,你获取的参数是通配符匹配的文件列表(如前面代码所示),如果你想获取原字符串,
请用引号,如node processImg.js "images/**/*.jpg"。
2. git仓库更新后重新编译静态网站--githook + npm script
这个是某种程度的css组件开发(专注CSS),一些我们机票部门的公用组件,比如paybar这些,可以通过这个项目有个 公共的最优实现,并在各个应用中保持一致。
以上并没有难点,难点主要在部署:即我们希望每次提交后(gitlab),可以在我们组的服务器同步最新的代码, 有最新的预览,并且这些应该完全自动化的。
代码的同步我们用了gitlab的API(这块是我同事在做),但预览呢?
- watch文件,有变动自动build。
- 使用githook,每次在服务器上仓库同步后自动build。
基于性能原因,第2种当然更好,所以我们选择hook仓库的post-merge(每册服务器本地仓库更新后调用)。
稍微看下post-merge的内容:
#!/bin/sh
cd /usr/share/nginx/html/repos/Dolphin-UI/
npm run build
不能更简单了,但的确做到了自动化和高效。
注: 一个坑,需要注意/usr/share/nginx/html/repos/Dolphin-UI/.git/hooks/post-merge文件的权限,
没有执行权限会导致脚本执行失败。

3. sugar-cli--npm package
sugar-cli是我们组原型开发(除RN外)的工具,主要提供模版和css编译的功能。
背景和需求: 原来还是基于PHP那一套开发原型,比较笨重;新的开发环境希望基于node.js,有简单 但足够的模板语法,支持一种(或多种)css预处理语言,易于部署(预览)等等。
结合这些需求,最终开发了一个npm包sugar-cli,只要全局安装后,一个命令即可快速开始开发:
- 开箱即用,不需要其它依赖,基本不需要额外的配置(这也是为什么选择发布一个cli工具)。
- 类似handlebars的完善模板。
postcss/sass/less全支持,完善的sourcemap。- 支持livereload和dev server,改善开发体验。
这里就不具体描述功能了,下面主要讲讲怎么开发一个cli工具。
核心很简单:代码(含shebang line) + package.json配置。
下面是sugar-cli中sugar static(运行一个静态文件服务器)的实现:
#!/usr/bin/env node
const program = require('commander')
program
.option('-a, --host <host>', 'server host, default to "0.0.0.0"')
.option('-p, --port <port>', 'server port, default to 2333')
.on('--help', () => {
console.log(colors.green(' Examples:'))
console.log()
console.log(colors.gray(' $ sugar static'))
})
.parse(process.argv)
const root = program.args[0]
serveStatic(root, program.host, program.port)
// 这里省略 serveStatic 具体实现
然后,我们需要在package.json中配置:
"bin": {
"sugar": "bin/sugar.js"
},
bin是个map,其中key是command,value
是对应可执行文件。当全局安装时,npm会 symlink 这个可执行文件到 prefix/bin;本地安装时,
则 symlink 这个可执行文件到 ./node_modules/.bin/。 (这一段可配合上面 全局安装的npm包为什么可以在shell里直接使用? 一起食用)。
结合这两个,我们即可轻松开发一个前端工具。剩下的我们可以发布到npm,然后请同学们试用即可。
三. Thanks
Thanks
有问题直接问我即可。
讲的很详细,学习了,谢谢