Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

开发基于Node.js的前端工具 #28

Open
creeperyang opened this issue Apr 5, 2017 · 1 comment
Open

开发基于Node.js的前端工具 #28

creeperyang opened this issue Apr 5, 2017 · 1 comment

Comments

@creeperyang
Copy link
Owner

creeperyang commented Apr 5, 2017

开发基于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之类一样?

其实就是因为:

  1. 全局安装的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

  1. /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.jsonscripts中的相应命令。

// 来自 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"
  }
}

上面是从Reactpackage.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

2017-04-05 6 42 01

注: 使用通配符时,你获取的参数是通配符匹配的文件列表(如前面代码所示),如果你想获取原字符串,
请用引号,如node processImg.js "images/**/*.jpg"

2. git仓库更新后重新编译静态网站--githook + npm script

http://guide.cui.design/

这个是某种程度的css组件开发(专注CSS),一些我们机票部门的公用组件,比如paybar这些,可以通过这个项目有个
公共的最优实现,并在各个应用中保持一致。

以上并没有难点,难点主要在部署:即我们希望每次提交后(gitlab),可以在我们组的服务器同步最新的代码,
有最新的预览,并且这些应该完全自动化的。

代码的同步我们用了gitlab的API(这块是我同事在做),但预览呢?

  1. watch文件,有变动自动build。
  2. 使用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文件的权限,
没有执行权限会导致脚本执行失败。

wechatimg243

3. sugar-cli--npm package

sugar-cli是我们组原型开发(除RN外)的工具,主要提供模版和css编译的功能。

http://cui.design/

背景和需求: 原来还是基于PHP那一套开发原型,比较笨重;新的开发环境希望基于node.js,有简单
但足够的模板语法,支持一种(或多种)css预处理语言,易于部署(预览)等等。

结合这些需求,最终开发了一个npm包sugar-cli,只要全局安装后,一个命令即可快速开始开发:

  1. 开箱即用,不需要其它依赖,基本不需要额外的配置(这也是为什么选择发布一个cli工具)。
  2. 类似handlebars的完善模板。
  3. postcss/sass/less全支持,完善的sourcemap。
  4. 支持livereload和dev server,改善开发体验。

这里就不具体描述功能了,下面主要讲讲怎么开发一个cli工具。

核心很简单:代码(含shebang line) + package.json配置。

下面是sugar-clisugar 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,然后请同学们试用即可。

2017-04-06 12 24 03

三. Thanks

Thanks

有问题直接问我即可。

@kashtian
Copy link

讲的很详细,学习了,谢谢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants