【问题标题】:Odd behavior from grunt-contrib-concat when using globbing pattern使用 globbing 模式时 grunt-contrib-concat 的奇怪行为
【发布时间】:2026-01-12 15:20:08
【问题描述】:

我正在使用 grunt,我想按特定顺序连接某个目录中的所有 js 文件(这是一个 Angular js 应用程序,所以我想先定义我的模块,然后再进行其他所有操作)。我的 grunt concat 目标看起来像:

concat: {

    mobile: {
        expand: true,
        cwd: "static/javascript/mobile/app/",
        src: ["main-module.js", "**/*-module.js", "**/*.js", "!static/javascript/mobile/dist/*"],
        dest: "static/javascript/mobile/app/dist/ngmobile.concat.js"
    }
}

上面的配置似乎应该连接 main-module.js,然后是所有其他 module.js 文件,然后是其他所有文件,省略 dist 文件夹中的所有内容。但是,当使用 --verbose 运行 grunt 时,结果类似于:

Running "concat:mobile" (concat) task
Verifying property concat.mobile exists in config...OK
Files: static/javascript/mobile/app/main-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/main-module.js
Files: static/javascript/mobile/app/clients/clients-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/clients-module.js
Files: static/javascript/mobile/app/reports/reports-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/reports/reports-module.js
Files: static/javascript/mobile/app/schedules/schedules-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/schedules/schedules-module.js
Files: static/javascript/mobile/app/services/services-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/services-module.js
Files: static/javascript/mobile/app/clients/addclient-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/addclient-ctrl.js
Files: static/javascript/mobile/app/clients/clientprofile-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/clientprofile-ctrl.js
Files: static/javascript/mobile/app/clients/clients-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/clients-ctrl.js
Files: static/javascript/mobile/app/clients/clients-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/clients-service.js
Files: static/javascript/mobile/app/common/directives.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/common/directives.js
Files: static/javascript/mobile/app/common/filters.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/common/filters.js
Files: static/javascript/mobile/app/common/header-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/common/header-ctrl.js
Files: static/javascript/mobile/app/common/navbarcollapse-directive.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/common/navbarcollapse-directive.js
Files: static/javascript/mobile/app/dist/ngmobile.concat.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/dist/ngmobile.concat.js
Files: static/javascript/mobile/app/main-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/main-ctrl.js
Files: static/javascript/mobile/app/main-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/main-service.js
Files: static/javascript/mobile/app/reports/reports-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/reports/reports-ctrl.js
Files: static/javascript/mobile/app/schedules/schedules-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/schedules/schedules-ctrl.js
Files: static/javascript/mobile/app/schedules/schedules-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/schedules/schedules-service.js
Files: static/javascript/mobile/app/services/addservice-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/addservice-ctrl.js
Files: static/javascript/mobile/app/services/serviceprofile-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/serviceprofile-ctrl.js
Files: static/javascript/mobile/app/services/services-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/services-ctrl.js
Files: static/javascript/mobile/app/services/services-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/services-service.js
Options: separator="\n", banner="", footer="", stripBanners=false, process=false
Reading static/javascript/mobile/app/main-module.js...OK
Writing static/javascript/mobile/app/dist/ngmobile.concat.js/main-module.js...ERROR
Warning: Unable to write "static/javascript/mobile/app/dist/ngmobile.concat.js/main-module.js" file (Error code: ENOTDIR). Use --force to continue.

Aborted due to warnings.

这告诉我它正在查找我希望它找到的文件,然后尝试将它们写入(复制?)到 dest 中指定的文件路径。我怎么弄错了这么可怕的东西? :) 如果有人足够关心我头上的最后几根头发,我将非常感谢一些关于我在这里做错了什么的意见。我想将 src 中的所有文件合并到 dest 文件中。

编辑

如果我删除 expand 属性,输出如下所示:

Running "concat:mobile" (concat) task
Verifying property concat.mobile exists in config...OK
Files: main-module.js, clients/clients-module.js, reports/reports-module.js, schedules/schedules-module.js, services/services-module.js, clients/addclient-ctrl.js, clients/clientprofile-ctrl.js, clients/clients-ctrl.js, clients/clients-service.js, common/directives.js, common/filters.js, common/header-ctrl.js, common/navbarcollapse-directive.js, dist/ngmobile.concat.js, main-ctrl.js, main-service.js, reports/reports-ctrl.js, schedules/schedules-ctrl.js, schedules/schedules-service.js, services/addservice-ctrl.js, services/serviceprofile-ctrl.js, services/services-ctrl.js, services/services-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js
Options: separator="\n", banner="", footer="", stripBanners=false, process=false
>> Source file "main-module.js" not found.
>> Source file "clients/clients-module.js" not found.
>> Source file "reports/reports-module.js" not found.
>> Source file "schedules/schedules-module.js" not found.
>> Source file "services/services-module.js" not found.
>> Source file "clients/addclient-ctrl.js" not found.
>> Source file "clients/clientprofile-ctrl.js" not found.
>> Source file "clients/clients-ctrl.js" not found.
>> Source file "clients/clients-service.js" not found.
>> Source file "common/directives.js" not found.
>> Source file "common/filters.js" not found.
>> Source file "common/header-ctrl.js" not found.
>> Source file "common/navbarcollapse-directive.js" not found.
>> Source file "dist/ngmobile.concat.js" not found.
>> Source file "main-ctrl.js" not found.
>> Source file "main-service.js" not found.
>> Source file "reports/reports-ctrl.js" not found.
>> Source file "schedules/schedules-ctrl.js" not found.
>> Source file "schedules/schedules-service.js" not found.
>> Source file "services/addservice-ctrl.js" not found.
>> Source file "services/serviceprofile-ctrl.js" not found.
>> Source file "services/services-ctrl.js" not found.
>> Source file "services/services-service.js" not found.
Writing static/javascript/mobile/app/dist/ngmobile.concat.js...OK
File "static/javascript/mobile/app/dist/ngmobile.concat.js" created.

请注意,上面“文件:...”片段中的第三行列出了我希望它找到的所有文件,但随后说找不到源文件。

编辑 2

下面的马特解决方案解决了这个问题,下面是我更新的代码:

concat: {
    mobile: {
        dest: "static/javascript/mobile/app/dist/ngmobile-concat.js",
        src: (function () {
            var cwd = "static/javascript/mobile/app/";
            var files = ["*-module.js", "**/*-module.js", "**/*.js"];

            files = files.map(function (file) {
                return cwd + file;
            });

            files.push("! static/javascript/mobile/app/dist");

            return files;
        }())
    }
}

【问题讨论】:

    标签: javascript angularjs gruntjs grunt-contrib-watch


    【解决方案1】:

    我自己使用 Angular 应用程序遇到了这个问题,我考虑了三个选项:

    1) 遵循项目github线程上的建议

    你并不孤单。在 grunt-contrib-concat github 项目中有一个 long thread 关于这个问题。这与cwddest 的工作方式有关。

    A suggested workaround张贴在那里将cwd属性移动到一个函数中,然后产生预期的结果而不是ENOTDIR错误。

    src: (function() {
      var cwd = 'src/js/';
      var arr = [];
      // determine file order here and concat to arr
      return arr.map(function(file) { return cwd + file; });
    }())
    

    我认为缺点是在 gruntfile 中添加了一个函数,这通常非常简单。如果您不是唯一维护应用程序的人,这可能会令人困惑。

    2) 抛弃 cwd 并使用具有 变量语法的静态方法

    如果您的 srcdest 目录在整个 gruntfile 中是一致的,您可以定义 srcdest 目录执行以下操作:

    myapp: {
      // configurable paths
      app: "static/javascript/mobile/app/",
      dist: "static/javascript/mobile/app/dist/"
    }
    

    ...然后您可以删除 cwd 属性并将其配置为看起来更“静态”:

    concat: {
    
        mobile: {
            expand: true,
            src: ["<%= myapp.app %>/main-module.js", "<%= myapp.app %>/**/*-module.js", "<%= myapp.app %>/**/*.js", "!<%= myapp.dist %>/*"],
            dest: "<%= myapp.dist %>/ngmobile.concat.js"
        }
    }
    

    对于我的大多数项目,这已经足够了。当我稍后回来并需要进行更改时,它很简单、干燥且易于理解。

    当然:配置属性一开始会因为 的混乱而变得有点难以阅读,但我的编辑器中的一些格式可以改进。此外,其他程序员很容易阅读并了解您的意图。

    3) 使用像 usemin 这样的插件来为您完成这项工作

    如果你也在做缩小,你可以考虑像grunt-usemin这样的插件。

    此选项的缺点很明显:usemin 增加了复杂性,因为您现在有另一个插件来管理和配置 并且您需要格式化 HTML 以使用 usemin 功能。 Usemin 配置一开始可能会令人困惑,如果您与团队合作,修改 usemin 配置或 HTML cmets usemin 需要的程序员或设计人员很容易破坏事情。

    好处是 usemin 将直接从 HTML 为您生成 grunt 的复制、连接和缩小配置。因此,如果您在 HTML 中添加/删除脚本或更改加载顺序,您无需在 grunt 配置中执行任何操作,因为 usemin 会选择它。

    【讨论】:

    • 马特!非常感谢您的帮助。我现在使用返回文件列表的自调用匿名函数使用 globbing 使其工作。我会用确切的代码更新我原来的问题,但非常感谢您的详细回复。
    • 感谢您的建议。我更喜欢第一个并写在一行中:['main-module.js', ...].map(function(file) {return 'src/js/'+file})
    【解决方案2】:

    如果设置了 'cwd',grunt-contrib-concat 只会复制文件,而不是 concat。 这个问题有一个棘手的解决方案。只需添加 'rename' 属性并返回 'dest' 名称,如下所示。

    rename: function (dest) {
                  return dest;
            },
    

    【讨论】: