【问题标题】:Grunt: minify js across project of multiple dirs, so that the contents of each dir flattens to a single minified fileGrunt:跨多个目录的项目缩小js,使每个目录的内容扁平化为单个缩小文件
【发布时间】:2018-04-29 15:16:17
【问题描述】:

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

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

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

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

 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']
          }
        },
      }
    });

如果我只有一两个目录,并且项目格式保持相对相同 - 我想我可以接受这个。但是这个项目有很多dirs,更重要的是项目结构会发生变化;我希望能够将目录添加到我的项目中,而不必每次都通过添加新目标来更新 grunt 文件。我希望有一种方法来参数化任务 - 意思是,有一个通用目标,上面写着“对于某个目录,将该目录中的所有 .js 文件缩小为某个 .min.js 文件”,然后我可以以编程方式调用目标每个目录 - 但这似乎不可能。

所以我决定避免使用插件,而只依赖 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?我在使用许多插件时遇到了同样的问题,我有一些任务需要在许多不同的目录/文件中一遍又一遍地重复,但方式完全相同,所以我希望能够配置一个通用的目标我可以以编程方式调用不同的值(而不是对可能经常更改的特定目录或文件名的大量目标进行硬编码)。但这似乎不可能,所以我继续在 node.js 中实现。 我接近咕噜声的方式是否存在根本缺陷?如果是这样 - 你能提供一些替代的观点吗?

【问题讨论】:

    标签: javascript node.js gruntjs minify


    【解决方案1】:

    想法

    我只是在以完全错误的方式思考这个问题吗?

    不,我认为您没有以错误的方式考虑这个问题。对于任何开发[时期],质疑为什么/如何总是一件好事。这就是使我们长期成为更好的开发人员的原因。

    无论您选择在构建过程中使用哪种解决方案/工具,无论是专用任务运行器,例如 gruntgulp,还是更高级别的解决方案,例如 nodejs,可能与 npm-scripts 结合使用,你仍然需要编写一些代码。当然,所有提到的方法之间的共同点是您必须使用 JavaScript 进行编码。

    避免在 node.js 中编写类似这样的代码(所以我这样做违背了目的?

    我不能说哪个解决方案/工具比另一个更好(那太固执了)我个人有使用所有这些工具开发构建过程的经验。通常,虽然我经常发现使用专用任务运行程序(gruntgulp)比使用更自定义的方法(例如 npm-scripts)减少了处理构建的开发时间结合nodejs

    通常,我也为 grunt 找到了一个非常棒的插件(例如 grunt-contrib-uglify),然后经过一些修改后发现它满足了我 90% 的要求,但对于剩余 10%。但是,在使用/集成任何开源包/模块时经常会出现这种情况……可以这么说,您仍然需要填补空白!


    咕噜声鲜为人知的宝石

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

    在使用 grunt 时,我发现使用上面列出的功能帮助我解决了我之前提到的剩余 10% 的需求。尤其是自定义任务功能多次被证明非常有用。


    解决方案

    值得注意的是grunt-contrib-uglify 是一个multi-task 插件。 IE。它允许您配置多个Targets,正如您在问题中的uglify 配置中所展示的那样。您已包含三个名为 dir1dir2dirN 的目标。

    考虑到grunt-contrib-uglify多任务,并记住我之前列出的 grunt 功能...使用 grunt 满足您的要求的一种方法是到:

    1. 使用自定义任务动态配置和运行 uglify 任务。
    2. 利用grunt.file.expand获取每个.js文件的父文件夹的路径。
    3. filterpaths 数组到唯一的目录路径。
    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"]);
    };
    

    结果

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

    .
    └── 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 文件的父文件夹名称用于连接/丑化的.js 文件的名称。


    总结

    • Gruntfile.js(上图)中使用的模式/方法可以根据许多可用插件的需要进行修改。
    • 只要插件本身支持多任务,那么考虑使用grunt Custom Task来填补空白。

    希望对你有帮助。

    【讨论】:

    • RobC - 您动态生成配置内容的方法正是我对 grunt 的理解所缺乏的。我会像这样玩弄它。我的印象是你只能设置一次配置,然后一旦你做了 grunt.loadNpmTasks(),无论在 initConfig 中设置什么配置,都会永远存在。您可以在自定义任务(或其他一些功能)中动态生成配置这一事实极大地改变了我如何处理这个(和其他 grunt 插件)。感谢您提供宝贵的见解。
    • 不客气。您也可以在 Gruntfile.js 中要求其他节点模块。例如目的;在forEach 函数中,我获得了dirNamedestPath 值,可能需要内置path 模块的节点并改用该API。记住 gruntjs 是建立在 nodejs 之上的。 (我尽量保持我的主旨)如果一段时间后你觉得我的回答是正确的,请考虑接受它。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    • 2021-02-28
    • 2021-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多