Grunt 插件开发例子

简介:

插件的目的

最近项目前端用的是yeoman来进行压缩,合并等,使用yeoman的时候,需要静态页里配置构建块,yeoman根据配置好的内容动态生成合并,压缩用的配置文件.

比如下面这段代码 html <!-- build:js({.tmp,source}) js/json.js --> <script src="lib/json/json2.js"></script> <!-- endbuild -->

<!-- build<!-- endbuild -->之间的都是需要合并的文件,合并之后的文件名指定在build后面,这里是js/json.js.

现在我们组内觉的这种方式不是很灵活,因为这个静态页面需要经常更新,开发的时候,经常需要引用js,所以最后出了一个方案,静态页面只需要引用几个入口js文件,项目用到的js可以放到入口内,通过document.write方式引入,最后项目构建的时候,动态的合并,压缩这些js,生成跟入口js同名的js,不过为了处理浏览器端cache,所以会对文件进行md5处理生成唯一编号放到文件名中.像这样的a.123123.js.


插件的实现原理

  • 需要一个任务来找出入口js文件内包含的js,并生成合并,压缩需要的grunt 配置文件
  • 替换rev插件生成的新的文件名字,防止浏览器cache问题

开始实现插件

grunt 提供了一个插件生成模板,可以减少许多创建约定好的文件的时间,可以看这里,grunt插件模板,以下是一个常用的grunt插件的目录结构.

  • 根目录
    • tasks,里面存放插件核心文件
    • node-modules ,放插件依赖的node模块
    • Gruntfile.js, 存放插件运行的一些配置,一般测试用
    • package.json, 存放描述插件相关的json文件

以下是完整的插件代码

下面的代码,包括两个任务,一个用来生成合并,压缩的配置文件;一个用来替换静态页中的js文件名,目前只实现了js文件的处理,css的差不多,读者可自行实现.
'use strict';
var util = require('util');
var fs = require('fs');
var inspect = function (obj) {
  return util.inspect(obj, false, 4, true);
};

module.exports = function(grunt) {

    // 先获取动态js列表
    var srcList = [];

    var _ = grunt.util._;
    var reg = /<script src=\\?\"([^\"\\]+)\\?\">/gi;
    var regReplace = /(<script src=\\?\")([^\"\\]+)(\\?\">)/gi;
    // 获取准备前的配置文件
    var smartisan = grunt.config('buildpjPrepare');

    grunt.registerMultiTask('buildpjPrepare', 'build front-end program', function() {
        // Merge task-specific and/or target-specific options with these defaults.

        var options = this.options();
        // Iterate over all specified file groups.
        // 首先检查文件是否存在
        var src;
        var filepath = options.url;
        if (!grunt.file.exists(filepath)) {
            grunt.log.warn('Source file "' + filepath + '" not found.');
        } else {
            src = grunt.file.read(filepath);
        }

        src.replace(reg, function ($0, $1) {
            // $0: 某一模板, $1: key, $2: value
            srcList.push({
                name: $1,
                content: grunt.file.read(smartisan.app + '/' + $1)
            });
        });


        var concatFiles = [], uglifyFiles = [];

        _.each(srcList, function(v, k){

            console.log(v, k);
            var srcFiles = [];
            v.content.replace(reg, function ($0, $1) {
                // $0: 某一模板, $1: key, $2: value
                srcFiles.push(smartisan.app + '/' + $1);
            });

            // 增加合并配置文件
            concatFiles.push({
                src: srcFiles,
                dest: '.temp/' + v.name
            });


            // 增加压缩配置文件
            uglifyFiles.push({
                src: '.temp/' + v.name,
                dest: smartisan.dist + '/' + v.name
            });

        });

        grunt.config('concat', {
            foo: {
                files: concatFiles
            }
        });

        grunt.config('uglify', {
            foo: {
                files: uglifyFiles
            }
        })

        var cfgNames = ['concat', 'uglify'];

        grunt.log.subhead('Configuration is now:');

        _.each(cfgNames, function(name) {

            grunt.log.subhead('  ' + name + ':').writeln('  ' + inspect(grunt.config(name)));

        });

    });

    grunt.registerMultiTask('buildpj', 'build front-end program', function() {

        var options = this.options();

        // 首先检查文件是否存在
        var src;
        var filepath = options.url;
        if (!grunt.file.exists(filepath)) {
            grunt.log.warn('Source file "' + filepath + '" not found.');
        } else {
            src = grunt.file.read(filepath);
        }

        // 获取所有md5之后的名称与之前的对应关系
        _.each(srcList, function(v, k){
            var arg = v.name.split('/');
            var dir = smartisan.dist + '/' + arg[0];
            var result = fs.readdirSync(dir);
            _.each(result, function(_v, _k){
                var fileNameArgOld = arg[1].split('.');
                var fileNameArgNew = _v.split('.');
                fileNameArgOld.splice(fileNameArgOld.length-1, 1);
                fileNameArgOld.push(fileNameArgNew[fileNameArgNew.length-2]);
                fileNameArgOld.push(fileNameArgNew[fileNameArgNew.length-1]);
                if(fileNameArgOld.join('.') == _v){
                    // 则找到一个替换后的文件名了
                    srcList[k].newName = arg[0] + '/' + _v;
                }
            });
        });

        // 替换内容中的压缩之后的并md5处理的名称
        src = src.replace(regReplace, function ($0, $1, $2, $3) {
            $2 = _.filter(srcList, function(v, k){
                return v.name == $2;
            })[0].newName;
            return $1 + $2 + $3;
        });

        // 开始写新的文件
        var fd = fs.openSync(filepath, 'w');
        fs.writeSync(fd, src, 0);
        fs.closeSync(fd);

    });

};

附上一个插件配置

// 全局设置
smartisan: {
    app: 'source',
    dist: 'dist'
},
buildpjPrepare: {
    build: {
        options: {
            url: '<%= smartisan.app %>/index.html'
        }
    },
    app: '<%= smartisan.app %>',
    dist: '<%= smartisan.dist %>',
},
buildpj: {
    build: {
        options: {
            url: '<%= smartisan.dist %>/index.html'
        }
    }
}

总结

感觉grunt是一个非常不错的构建工具,可用的模块非常多,也许我现在写的插件,就有别的实现,权当练手用.


目录
相关文章
|
23天前
|
JavaScript 前端开发 编译器
js开发: 请解释什么是Babel,以及它在项目中的作用。
**Babel是JavaScript编译器,将ES6+代码转为向后兼容版本,确保在旧环境运行。它在前端构建中不可或缺,提供语法转换、插件机制、灵活配置及丰富的生态系统,支持代码兼容性和自定义编译任务。**
17 6
|
4月前
|
JavaScript 前端开发
Nodejs 第六章(npx)
Nodejs 第六章(npx)
42 0
|
4月前
|
SQL 资源调度 前端开发
VUE3(三十四)项目启动sass报错
我有个不是很好的习惯,每天启动前端项目的时候,都会把项目中使用到的组件更新到最新的版本。其实这样是非常不好的。为什么呢?新版本除了修复之前的问题,也有可能会带来新的问题。 正常的做法大概是,等新版本发布了一段时间之后,再去更新,这样就相对保险一丢丢。 而且,目前前端项目中组件依赖太多,各个组件之间,难免会有兼容性的问题。今天在将组件更新到最新版本之后,启动项目,就遇到了问题。 报错如下: bash 复制代码 ERROR in ./src/pages/porder/index.scss (./node_modules/css-loader/dist/cjs.js!./node_modules/s
58 1
|
8月前
|
资源调度 前端开发 JavaScript
ant-design-vue+vite主题切换详细步骤(简单案例)
ant-design-vue+vite主题切换详细步骤(简单案例)
571 0
|
9月前
|
缓存 JSON JavaScript
30分钟搞懂Rollup+Typescript工程构建(一)
最近在研究一个ngptcommit命令行工具,然后想通过Rollup+Typescript去编译的时候,发现对Rollup和Typescript的编译配置有点陌生,所以希望通过本文能够对其有个系统的认知。
141 0
|
9月前
|
JSON JavaScript 前端开发
30分钟搞懂Rollup+Typescript工程构建(二)
在本文中不讨论Typescript的具体用法,我们将学习如何将Typescript代码转为JavaScript。
328 0
|
10月前
|
JavaScript 前端开发
从0搭建Vue3组件库(十一): 集成项目的编程规范工具链(ESlint+Prettier+Stylelint)
从0搭建Vue3组件库(十一): 集成项目的编程规范工具链(ESlint+Prettier+Stylelint)
152 0
|
前端开发
Egg.js 项目中怎么使用前端模板
Egg.js 项目中怎么使用前端模板
184 0
Egg.js 项目中怎么使用前端模板
|
前端开发
前端学习案例18-使用babel-polyfill补充ES6代码实现
前端学习案例18-使用babel-polyfill补充ES6代码实现
55 0
前端学习案例18-使用babel-polyfill补充ES6代码实现
|
前端开发 Go API
如何编写 esbuild 插件
如何编写 esbuild 插件
349 0
如何编写 esbuild 插件