编写gulp、webpack与fis3插件

在工作中接触了不少打包和环境构建工具,如gulpwebpack以及现在公司使用的fis3等,每个工具都有自己的社区和生态,并提供相关的功能插件,如编译sass、babel转移、压缩代码等。

在某些特定的需求和开发环境下,社区的插件并不能很好地满足开发生产需求(比如需要自定义打包过程的控制台输出、在编译scss文件前注入公共的scss变量代码等),因此需要了解对应工具的插件编写,这里整理一下。

<!--more-->

参考

1. 编写gulp插件

我们知道gulp是基于文件流的任务管理工具,编写gulp插件可以通过through2来实现。

// gulp-demo.js
let through2 = require('through2');
module.exports = function () {
    return through2.obj(function (file, encoding, cb) {
        // 这里可以做一些内容校验
        // ..
        // 获取文件内容
        let content = file.contents.toString();
        // 处理内容
        let res = demoHandler(content);
        // 完成后需要转换回buffer类型
        file.contents = new Buffer(res);

        //下面这两句基本是标配,可参考through2的API
        this.push(file);
        cb();
    });
}
function demoHandler(data) {
    return data.replace(/{{.*?}}/, "xxx");
}

ran'ho

然后编写gulp任务,在pipe中调用插件处理文档内容即可

// gulpfile.js
let gulpDemo = require('./dev/gulp-demo');
gulp.task('demo', function(){
    gulp.src('src/test.txt')
        .pipe(gulpDemo())
        .pipe(gulp.dest('./dist'));
})

2. 编写webpack loader

loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文访问。

从描述可见webpack的loader与gulp的插件工作编写比较相同。下面是实例代码,编写对应的模块

// rem-loader.js
module.exports = function (source) {
    // 可以通过this访问到webpack上下文
    // 这里对文件内容进行一些处理
    let remScss = ` 
        @function rem($px) {
            @if (unitless($px)) {
                @return 1rem * ($px/100);
            } @else {
                @return 1rem * ($px/100px);
            }
        }
    `;
    source = `${remScss} \n ${source}`;
    // 将内容导出到下一个loader
    return source;
}

然后在配置文件引入对应loader即可

module.exports = {
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: page1ExtractTextPlugin.extract({
                    use: [
                        "css-loader",
                        "autoprefixer-loader",
                        "sass-loader",
                        path.resolve("./dev/rem-loader.js")
                    ]
                })
            }
        ];
    }
}

3. 编写webpack plugin

webpack插件可以在webpack打包过程中调用相关的钩子函数,并进行相关逻辑处理。

下面插件是官方demo里面的代码,会在webpack指令执行完毕后在控制台输出文案

class HelloWorldPlugin {
    constructor(options) {
        this.options = options;
    }

    apply(compiler) {
        compiler.hooks.done.tap('HelloWorldPlugin', () => {
            console.log('Hello World!');
            console.log(this.options);
        });
    }
}

module.exports = HelloWorldPlugin;

然后在配置文件调用即可

let HelloWorldPlugin = require('./dev/hello-world-plugin.js')

module.exports = {
    // ...
    plugins: [
        new HelloWorldPlugin()
    ]
}

4. 编写fis3插件

fis3中,每一个File对象都要经过编译、打包、发布三个阶段,对应的插件基本使用方式为

  fis.match('xx.xx', {
      <type>: fis.plugin('<name>', {
          //conf
      })
  })

其中的type类型有

  • 编译阶段插件,包括lintparserpreprocessorpostprocessoroptimizer 等插件扩展点

    // 编译阶段插件的基本形式
    module.exports = function (content, file, settings) {}
  • 打包阶段插件,包括prepackagerpackagerspriterpostpackager等插件扩展点

    // 打包阶段插件的基本形式
    module.exports = function (ret, conf, settings, opt) {}
  • 部署阶段插件,包含deploy插件扩展点

    // 部署阶段插件的基本形式
    module.exports = function(options, modified, total, next) {}

fis3是一套完整的前端工程化开发方案,如果仅仅是为了处理开发环境的需求,一般只需要关注编译阶段插件即可。

以对js文件进行babel处理为例,编写fis3-parser-translate-es6插件

// node_modules/fis3-parser-translate-es6/index.js
var babel = require('babel-core');
module.exports = function (content, file, options) {
  var result = babel.transform(content, options);
  return result.code; // 处理后的文件内容
}

然后在fis-conf.js文件中进行声明,由于该插件是编译类型插件,在parser扩展点调用该插件即可

fis.media('prod').match("*.es6.js", {
    parser: fis.plugin("translate-es6", {
        presets: ["env"]
    }),
});

5. 小结

这里整理了我在工作中用到的几种流程工具的插件开发,虽然目前还在维护一些grunt构建的项目,但是更新频率比较低,因此这里就没有整理grunt相关的插件开发方案。

除此之外,现在各个主流框架基本都内置了脚手架工具,如react-create-appvue-cli3等,绕开了基础的开发环境配置。

但是,了解开发环境的搭建,还是前端的基本功,因此了解插件的开发还是很有必要的,整理在这里,方便后面自己查阅。