在使用 stylelint 的时候,发现了一个有趣的问题:如果直接使用 stylelint 的 bin
文件对批量 LESS 文件进行检查,程序可以如预期的运行;但是如果把同样的命令写到 package.json
中,以 npm script 的方式进行运行,最终被检查的文件就少了很多,实际只有一个文件参与了检查。
具体来说,./node_modules/.bin/stylelint src/**/*.less
这个命令可以检查所有的 LESS 文件,但是把 stylelint src/**/*.less
写到 package.josn
中之后,再运行却只检查了一个文件。
通过检查 stylelint 的文档,发现官方在写命令的时候,写法和上述略有不同,为:stylelint "src/**/*.less"
。
经过排查问题,发现根源在于:npm
使用了 sh
来执行代码,而 sh
和 zsh
在解析 Glob 的时候,行为是不同的。
npm
,包括其他 Linux 进程,在使用 shell 的时候,默认使用的都是 sh
,除非有其他明确的指定。这意味着,即使当前正在使用的 shell 是 zsh
,在运行 npm
命令的时候,还是默认使用了 sh
对脚本进行执行。也就是说,./node_modules/.bin/stylelint src/**/*.less
这个命令执行,使用的是当前打开的 shell 程序(比如 zsh
);而当这个命令写到 package.json
中,并以 npm script 的方式进行运行的时候,执行 shell 的就是 sh
了。
使用不同的 shell 程序,难免就会在行为上造成不一致。这里的 Glob 解析就是一个例子。在 zsh
里面可以简单的做一个实验。执行如下的命令:
ls src/**/*.less
可以看到,zsh
给出了当前 src
目录下所有的 LESS 文件, 不管这个文件是在多深的子目录下;而如果先在 zsh
中执行 sh
或 bash
进入到 sh
或 bash
的工作环境中,再执行同样的命令,可以看到输出的结果可能就是不同的。实际上,对于 sh
来说,它本身并不识别 **
这个语法,这个表示在 sh
中会被简单的识别为 *
,src/**/*.less
在 sh
中等价于 src/*/*.less
。换句话说,在 sh
的环境中,上述命令只会寻找所有在 src
目录下一级子目录中的 LESS 文件,一旦层级大于一层,就不会被找到了。
这也是为什么同样的命令,直接执行和在 npm 中执行会有差异的原因。
最后,加上双引号 stylelint "src/**/*.less"
就可以解决这一问题的原因在于:一旦加上了双引号,这一个 Glob 就不会被 shell 直接解析,而是会以字符串的形式直接传递给 stylelint。(具体来说,如果不加双引号,shell 会先将 Glob 解析成一组具体的文件,stylelint 实际拿到的 process.env.argv
很可能会是一个很长的字符串数组,每一个元素都是一个具体的文件;而如果加上了双引号,stylelint 拿到的只有一个 Glob 表达式字符串。)有了这个 Glob 的字符串,stylelint 内部就可以使用相应的 package 来进行解析,从而得到一串具体的文件列表。因为使用了 stylelint 内部自带的 Glob 解析,就可以保证在不同的 shell 环境中都得到一致的结果了。