使用 Pug 和 Stylus 开发小程序的 watch 脚本

小程序开发有两点比较蛋痛:1. 每个页面必须有3个文件,wxml,js,wxss;2. 使用 wxml 替代 html,使用 wxss 替代 css,使得默认的编译失效。这个脚本用来帮助我们使用 pug 和 stylus 进行开发。

首先,我试用了 Wepy,丑的一逼,遂放弃。

小程序开发有两点比较蛋痛:

  1. 每个页面必须有3个文件,wxml,js,wxss
  2. 使用 wxml 替代 html,使用 wxss 替代 css,使得默认的编译失效

Webpack 在这里不太适用,因为 1,我们并非要把所有代码打包到一起。WebStorm 的 File Watcher 也不适用,因为它输出的文件扩展名是固定的(跟 pug 和 stylus 源程序有关)。于是我经过摸索了,使用 gulp 脚本解决了这个问题,代码如下:

import gulp from 'gulp';
import pug from 'gulp-pug';
import rename from 'gulp-rename';

const pugFiles = 'pages/**/*.pug';

// 通用的 pug 处理,可以把 pug 转译并改名为 .wxml 文件
async function doPUG(path) {
  gulp.src(path)
    .pipe(pug())
    .pipe(rename({
      extname: '.wxml',
    }))
    .pipe(gulp.dest('./pages'));
}

// 将所有 pug 进行转译
gulp.task('pug', async () => {
  return doPUG(pugFiles);
});

// 侦听 .pug 文件的变化,并转译被修改的
gulp.task('watch', ['pug'], () => {
  gulp.watch(pugFiles, ({path}) => {
    doPUG(path);
  });
});

接下来,开发的时候,只要运行 gulp watch 即可。

用类似的方式,我们还能处理 stylus -> css,这里就不详述了,大家可以自己试一试。

完整的 gulpfile 在 GitHub 仓库里,请自行取用。

Gulp 中顺序执行任务

Gulp 顺序执行支持3中模式:callback、返回 stream、返回 Promise 对象。

书接上文,Chrome 插件中无法直接使用 Handlebars 处理模板。两种方案,一是利用沙箱,将 .eval() 放在独立的环境中执行,好处是其它跨域的操作也能这样处理,坏处是写起来麻烦。另一种则是利用 Handlebars 的“预编译”功能,将模板提前编译好,直接在代码中引用。好处是写起来更顺畅,并且从发布插件的角度来看,早晚都要这样做。

我决定选用后者,于是我需要把模板提取出来进行预编译,然后我准备写个 Gulp 任务来搞定这个。

从页面中提取模板不算太难,写个正则就好,这里提前约定,模板使用 <script type="x-handlebars-template"> 标签包裹。

gulp.task('template', function () {
  let promise = new Promise((resolve, reject) => {
    fs.readFile('popup.html', (err, content) => {
      if (err) reject(err);
      resolve(content);
    }
  });
  promise.then((content) => {
    content.replace(/<script([^>]*)>([\S\s]+?)<\/script>/g, (match, attr, template) {
      // 具体处理模板,略掉了
    });
  })
});

模板编译之后,还需要用 Webpack 打包才能使用,于是要增加 Webpack 的任务。这个比较简单,参考它的 文档 即可。不过很明显,必须等全部模板预编译完成才能打包。之前我已经试过用 run sequence 顺序执行任务,不过这里我用到 Promise,情况似乎有变。

稍微 Google 一下,得知要能顺序执行任务有三种选择:

使用 callback

gulp.task('task', function (callback) {
  setTimeout(function () {
    // 任务执行完成后,调用 callback
    callback();
  }, 5000);
});

返回 stream

这个好像是标准做法,也是我之前做的。

gulp.task('task', function (callback) {
   returen gulp.src('*.js')
     .pipe(uglify())
     .pipe(gulp.desc('dist/');
});

返回 Promise 对象

gulp.task('task', function () {
  return new Promise(resolve, reject) => {
    resolve();
  });
});

既然支持 Promise 那就好办了,在 .then() 前面加一个 return 就好。


完整代码

参考文章

Handling Sync Tasks with Gulp JS

初尝Gulp

本文从4个方面对比了Grunt和Gulp的优劣,根据我个人的状态,最终还是选择Grunt。不过将来,说不定我也会转投Gulp。

Gulp自出世起,热度就很高。它利用了Node的Stream机制,速度很快;函数式的写法也很便于阅读。这两大优势让它一跃成为最受欢迎的批处理工具,几乎占据了半壁江山(2015前端工具普查)。

我之前一直用Grunt,为了不落后于时代,前几天在一个业余项目中试用了一下Gulp,以下是我的感受。

确实好读

虽然无法得到证实,不过我相信Grunt的设计中有很多都是借鉴Ant得来的——在没有Grunt之前,我曾经用Ant脚本 + Google Closer Compiler + Google Closer Stylesheets 压缩代码——比如,它的配置是JSON格式的,跟XML非常相像。Grunt的问题也在这里:声明任务需要写JSON配置文件,不同的任务需要写在一起,然后通过registerTask('task', ['task1', 'task2:js', 'task3', ....])来组织。这样前后脱节,确实既不好写,也不好读。个把月回来完全看不懂也不算意外……

而Gulp的设计则更加自由,它充分利用JavaScript的函数式写法,一个任务就处理一批文件,从上往下看,结构非常清晰:

    gulp.task('js', function () {
      gulp.src(['js/'])
        .pipe(concat())
        .pipe(replace('@version@', version))
        .pipe(uglify())
        .pipe(gulp.dest('dist/js/app.js'));
    });

胜者:Gulp

速度快,然无卵

Gulp受推崇的另一大原因就是速度快,一般的任务都是毫秒级。

不过,这对于我来说意义不大。作为一名人民币玩家,哥的惯用IDE是WebStorm和PHPStorm,均支持文件实时编译和自动刷新等常用功能。所以我只需要批处理工具帮我压缩文件和打包即可。

这个回合:打平。

Stream VS Native,异步 VS 同步

前面提到,Gulp利用了Node的Stream机制,它的内部是处理文档流。这方面Grunt比较保守,仍然是把文件读进内存,然后进行字符串操作。二者的机制不同不好对比,不过作为从页面仔成长起来的前端工程师,不得不承认我对的理解并不深刻,对文件读取和二进制操作也不甚了了,所以在理解二者和开发业务插件的时候,我宁可选择Grunt。

另外,Gulp的任务是异步的,所以在我的使用场景中——先clean目录,然后各种压缩,最后打包成zip——就很难搞(所以我觉得很多人用Gulp只是为了实时编译预处理文件)。后来用了sequence插件才搞定。

这方面我觉得Grunt虽然保守,但是更接近前端开发者的知识结构,学习成本更低。

胜者:Grunt

杂项

Gulp的团队应该是有代码洁癖的,比如,他们有一个官方插件仓库,这很正常;但是他们竟然还有一个Gulp插件黑名单……简直匪夷所思……

所以Gulp团队显然不愿意重复发明轮子,比如简单的clean目录,他们就不愿意做,而且也不让别人做,因为已经有很多工具可以实现这个功能了……但是对于初学者比如我来说,就会在插件引擎中找半天,最后还是Google到相关指引才得到正确的做法。

这方面Grunt团队做的很好。Grunt插件中有一组是官方提供的,以grunt-contrib为命名空间,基本上,使用这套组件就可以完成我所有的日常工作了。

胜者:Grunt


总结

大趋势上,Gulp的确势头迅猛。但是从实际体验中,我觉得Grunt目前更成熟,也能满足工作所需。

我并不想说Gulp不好,只是现在的我用起来不太顺手。如果哪天我有时间好好搞搞后端,搞搞Node开发,把Stream概念搞清楚,也许我也会转投Gulp。不过目前,还是继续用Grunt吧。

近期要学习实践的新技术

接下来的一段时间里主动学习,更新一下知识。

众所周知,程序员是一个必须随时更新知识技能的职业。这其中,前端程序员尤甚。虽然我自称全栈,多半也要依赖前端技术作为小无相功驱使内力,方能打出少林寺七十二绝技写出其它平台的产品。所以,主动学习,更新知识技能是我必须坚持的。

同时,在前端这个充满变革的领域,我不能把各种新奇的东西都放到实际生产中去。所以,不定期的集中学习就不可避免。

那么,近期要学习实践的新技术有哪些呢?

  1. Bootstrap 4
  2. gulp
  3. Angular 2 直接从2开始吧,计划做个Todo
  4. React + React Native 要不做鸡血君?
  5. Polymer
  6. Material Design Lite
  7. Phonegap 5 自然是迁移游戏泡泡
  8. Eletron 结合别的技术做个写博客的工具。
  9. socket.io
  10. browserify

希望能在年底前搞定。