Grunt:跨多个目录的项目缩小 js,以便每个目录的内容扁平化为单个缩小文件

Grunt: minify js across project of multiple dirs, so that the contents of each dir flattens to a single minified file

我有一个项目,javascript 个文件结构如下:

我的目标:我想 minify/uglify 这些 javascript 文件 在构建输出中,例如有 1 个缩小每个目录的文件 。因此,例如,如果我将 js 存储在 bld/js 中的构建输出中,那么在 bld/js 中,我最终会得到文件:dir1.min.js(连接和缩小 [= 的结果56=]、fileB.js 和 /src/js/dir1/ 中的所有其他 js 文件)、dir2.min.js(连接和缩小 fileG.js 和 fileN.js 的结果,以及所有/src/dir2/) 中的其他 js 文件,依此类推。

现在 - 我正在使用 grunt 框架生成构建,并且必须在 grunt 文件中执行此操作。我是 grunt、node.js 和 javascript 的新手。 我想解释一下我解决这个问题的思路,想知道是否有更熟悉这些框架的人可以告诉我我的 approach/thinking 是否有问题。

据我了解,在 grunt 中做事的主要方式是使用插件。所以我的第一个想法是使用一个插件来完成这个任务。有一个名为'npm-contrib-uglify'的插件,专门用于缩小。 https://github.com/gruntjs/grunt-contrib-uglify 问题是 - 我的理解是,要执行我希望在此处执行的操作(每个目录 1 min.js 个文件),我需要为每个目录创建特定任务。所以初始化看起来像这样:

 grunt.initConfig({
      uglify: {
        dir1: {
          files: {
            'dest/js/dir1.min.js': ['src/js/dir1/**.js']
          }
        },
        dir2: {
          files: {
            'dest/js/dir2.min.js': ['src/js/dir2/**.js']
          }
        },
...
        dirN: {
          files: {
            'dest/js/dirN.min.js': ['src/js/dirN/**.js']
          }
        },
      }
    });

如果我只有一个或两个目录,并且项目格式将保持相对不变 - 我想我可以接受。但是这个项目中有很多目录,更重要的是,项目结构会发生变化;我希望能够将目录添加到我的项目中,而不必每次都通过添加新目标来更新 grunt 文件。我希望有一种方法来参数化任务 - 意思是,有一个通用目标说 'for some dir, minify all the .js files in that dir to some .min.js file' 然后我可以为每个目录以编程方式调用目标 - 但这似乎不可能。

所以我决定避开插件,只依赖node.js。有一个名为“npm-uglify”的 npm 模块。 https://www.npmjs.com/package/uglify-js。我想我可以制作自己的自定义 grunt 任务,它循环遍历源目录,并在每个嵌套目录上调用 uglify-js。这是一个示例(请注意,我在几个地方使用了伪代码,特别是为了获取目录列表,因为我无法访问机器,也没有提交给内存的语法。但这应该给出这样的想法我的意思是:)

var fs = require('fs');
var uglifyJs = require('uglify-js');

...

grunt.registerTask('minifyMyJs', function(myJsSrcDir) { // myJsSrcDir is an abs. path to the src dir

    for (dir in myJsSrcDir): // go through all the dirs.. realize I should use fs.readdir, but just use pseudocode here to give the idea
        var minifiedOutputFileName = fs.basename(dir); // to get dir1 from /src/js/dir1
        var minified = uglifyJs.Minify(["**.js"]);
        fs.writeFile(minifiedOutputFileNam, result.code, function(err)) {
           if(err) {
              ... error checking...
           }
        }
    }
}

现在 - 我不是要你为我解决这个问题,而且我知道上面的代码不会编译。相反,我的问题是——我只是在以完全错误的方式思考这个问题吗? grunt 的全部意义在于避免在 node.js 中编写这样的代码(所以我这样做违背了目的吗?) 你真的可以参数化grunt 插件的目标,例如 grunt-contrib-uglify?我 运行 遇到许多插件的相同问题,我有一些任务需要在许多不同的 dirs/files 中一遍又一遍地重复,但以完全相同的方式,所以我想能够配置一个通用目标,我可以为不同的值以编程方式调用(而不是为特定目录或文件名硬编码大量目标,这些目标可能经常更改)。但这似乎不可能,所以我继续在 node.js 中实施。 我处理 grunt 的方式是否存在根本性缺陷?如果是这样 - 你能提供一些不同的观点吗?

想法

am I just thinking about this in the completely wrong way?

不,我不认为你以错误的方式思考这个问题。质疑 why/how 对于任何发展 [period] 总是一件好事。这就是让我们长期成为更好的开发人员的原因。

无论您选择将哪个 solution/tools 用于您的构建过程,无论它是一个专门的任务 运行ner 例如 grunt or gulp, or a higher level solution such as nodejs perhaps combined with npm-scripts,您仍然需要编写一些代码。当然,上述所有方法的共同点是您必须使用 JavaScript.

进行编码

Is the whole point of grunt to avoid coding things like this in node.js (and so I'm defeating the purpose by doing this?

我不能说哪个 solution/tool 比另一个 更好用(那太自以为是了) 我个人有开发构建过程的经验使用提到的所有这些工具。通常,尽管我经常发现使用专用任务 运行ner (g运行tgulp) 比使用更自定义的方法(例如 npm-scripts 结合 nodejs.

通常,我也会为 g运行t(例如 grunt-contrib-uglify)找到一个非常棒的插件,然后在进行一些修补后发现它满足了我 90% 的要求,但剩下的 10% 却惨遭失败。然而,当 utilizing/integrating 任何开源 package/module 时,情况往往如此......可以这么说,你仍然需要填补空白!


G运行ts 鲜为人知的宝石

在放弃 gruntjs 并进入 nodejs 仅开发模式之前,(再次重申我对任何方法都没有偏见),我建议您查看一些 g运行ts 功能,例如:

在使用 g运行t 时,我发现利用上面列出的功能可以帮助我解决剩余的 10%我之前提到的要求。特别是自定义任务功能已多次证明非常有用。


解决方案

值得注意的是 grunt-contrib-uglify is a multi-task plugin. I.e. It allows you to configure multiple Targets 正如您在问题的 uglify 配置中所展示的那样。您包含了三个目标,名称为 dir1dir2dirN.

由于 grunt-contrib-uglify 多任务 并牢记我之前列出的 g运行t 功能......一种方法使用 g运行t 满足您的要求是:

  1. 使用自定义任务动态配置和运行 uglify 任务。
  2. 利用grunt.file.expand获取每个.js文件的父文件夹路径。
  3. filter paths 数组到唯一的目录路径。
  4. uglify 任务的每个目标动态创建 files 配置。
  5. 使用 grunt.config 配置具有多个目标的 uglify 任务,然后使用 grunt-task.run 配置 运行 任务。

Grunfile.js

module.exports = function (grunt) {

  'use strict';

  grunt.initConfig({
    uglify: {
      // <-- Intentionally blank, will be dynamically generated.
    }
  });

  /**
   * 1. Helper custom Task to dynamically configure and run the uglify task.
   */
  grunt.registerTask('myUglify', 'Configures uglify task', function () {
    var srcDirGlob = 'src/js/**/*.js';
    var destDir = 'dist/js/';
    var config = {};

    // 2. Get the paths to the parent diretory of each .js file.
    var paths = grunt.file.expand({ filter: 'isFile' }, srcDirGlob)
        .map(function (_path) {
          return _path.substring(0, _path.lastIndexOf("/"));
        });

    // 3. Filter paths Array to only unique directory paths.
    paths.filter(function(_path, pos){
          return paths.indexOf(_path) === pos;
        })

        // 4. Dynamically create the `files` configuration for each Target.
        .forEach(function(_path, index) {
          var dirName = _path.substring(_path.lastIndexOf('/') + 1);
          var destPath = destDir + dirName + '.min.js';
          var srcGlob = _path + '/*.js';

          config[index] = {
            files: {}
          }

          config[index].files[destPath] = srcGlob;
        });

    // NOTE: The dynamically created `config` object is now something like this:
    //
    //    config: {
    //      0: {
    //        files: {
    //          'dist/js/dir1.min.js': 'src/js/dir1/**.js'
    //        }
    //      },
    //      1: {
    //        files: {
    //          'dist/js/dir2.min.js': 'src/js/dir2/**.js'
    //        }
    //      },
    //      ...
    //    }

    // 5. Configure the uglify task with multiple Targets
    //    (i.e. the `config` object) and then run the Task.
    grunt.config('uglify', config);
    grunt.task.run(['uglify']);
  });

  grunt.loadNpmTasks('grunt-contrib-uglify');

  grunt.registerTask('default', ["myUglify"]);
};

结果

运行 $ grunt 通过 CLI 使用上面的 Gruntfile.jssrc 目录,如下所示:

.
└── src
    └── js
        ├── dir1
        │   ├── fileA.js
        │   └── fileB.js
        ├── dir2
        │   ├── fileG.js
        │   └── fileN.js
        └── dir3
            ├── fileQ.js
            └── fileR.js`

.. 将连接 uglify .js 个文件,生成目录结构如下:

.
└── dist
    └── js
        ├── dir1.min.js
        ├── dir2.min.js
        └── dir3.min.js`

注意:源 .js 文件的父文件夹名称用于生成的 concatenated/uglified .js 文件的名称。


总结

  • Gruntfile.js(上文)中使用的 pattern/approach 可以根据需要针对许多可用插件进行修改。
  • 只要插件本身支持Multi-Tasks然后考虑使用g运行t Custom Task来填充在空白处。

希望对您有所帮助。