【问题标题】:Grunt Environment Variables Don't Get Set Until All Tasks Loaded在加载所有任务之前不会设置 Grunt 环境变量
【发布时间】:2014-10-09 13:07:46
【问题描述】:

我在我的项目中使用npm 模块grunt envload-grunt-configgrunt env 为您处理环境变量,而load-grunt-config 处理,好吧,为您加载grunt 配置。您可以将您的任务放入其他文件,然后load-grunt-config 将它们捆绑起来并让grunt 为您加载和使用它们。您还可以创建一个aliases.js 文件,其中包含您想要组合成一个任务的任务,一个接一个地运行。它类似于原始Gruntfile.js 中的grunt.registerTask 任务。正如 Github 上的 load-grunt-config README.md 所建议的,我将所有 grunt 任务放在根文件夹下的一个单独的 grunt/ 文件夹中,主文件夹 Gruntfile 没有额外的子文件夹。这是我瘦身后的Gruntfile

module.exports = function(grunt) {

    'use strict';

    require('time-grunt')(grunt);

    // function & property declarations
    grunt.initConfig({

        pkg: grunt.file.readJSON('package.json')

    });

    require('load-grunt-config')(grunt, {
        init: true,
        loadGruntConfig: {
            scope: 'devDependencies', 
            pattern: ['grunt-*', 'time-grunt']
        }
    });

};

理论上,将所有这些文件设置为load-grunt-config 加载的正确方式应该与仅使用Gruntfile.js 完全相同。然而,我似乎遇到了一些障碍。在env 任务下设置的环境变量似乎不会为后续的grunt 任务设置,而是在node 处理其任务时设置,在本例中是express 服务器。

grunt env任务:

module.exports = {

    // environment variable values for developers
    // creating/maintaining site
    dev: {
        options: {
            add: {
                NODE_ENV: 'dev',
                MONGO_PORT: 27017,
                SERVER_PORT: 3000
            }
        }
    }
};

grunt-shell-spawn任务:

// shell command tasks
module.exports = {

    // starts up MongoDB server/daemon
    mongod: {
        command: 'mongod --bind_ip konneka.org --port ' + (process.env.MONGO_PORT || 27017) + ' --dbpath C:/MongoDB/data/db --ipv6',
        options: {
            async: true, // makes this command asynchronous
            stdout: false, // does not print to the console
            stderr: true, // prints errors to the console
            failOnError: true, // fails this task when it encounters errors
            execOptions: {
                cwd: '.'
            }
        }
    }
};

grunt express任务:

module.exports = {

    // default options
    options: {
        hostname: '127.0.0.1', // allow connections from localhost
        port: (process.env.SERVER_PORT || 3000), // default port

    },

    prod: {
        options: {
            livereload: true, // automatically reload server when express pages change
            // serverreload: true, // run forever-running server (do not close when finished)
            server: path.resolve(__dirname, '../backend/page.js'), // express server file
            bases: 'dist/' // watch files in app folder for changes
        }
    }
};

aliases.js 文件(grunt-load-config 组合任务的方式,使它们一个接一个地运行):

module.exports = {
    // starts forever-running server with "production" environment
    server: ['env:prod', 'shell:mongod', 'express:prod', 'express-keepalive']
};

backend/env/prod.js 的一部分(特定于环境的 Express 配置,如果 NODE_ENV 设置为“prod”则加载,模仿 MEAN.JS):

'use strict';

module.exports = {
    port: process.env.SERVER_PORT || 3001,
    dbUrl: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://konneka.org:' + (process.env.MONGO_PORT || 27018) + '/mean'
};

backend/env/dev.js 的一部分(dev 环境的特定环境 Express 配置,如果 `NODE_ENV 变量未设置或设置为“dev”,则加载):

module.exports = {
    port: process.env.SERVER_PORT || 3000,
    dbUrl: 'mongodb://konneka.org:' + (process.env.MONGO_PORT || 27017) + '/mean-dev'
};

backend/page.js 的一部分(我的 Express 配置页面,也是仿照 MEAN.JS):

'use strict';
var session = require('express-session');
var mongoStore = require('connect-mongo')(session);
var express = require('express');
var server = express();

...

// create the database object
var monServer = mongoose.connect(environ.dbUrl);

// create a client-server session, using a MongoDB collection/table to store its info
server.use(session({
    resave: true,
    saveUninitialized: true,
    secret: environ.sessionSecret,
    store: new mongoStore({
        db: monServer.connections[0].db, // specify the database these sessions will be saved into
        auto_reconnect: true
    })
}));

...

// listen on port related to environment variable
server.listen(process.env.SERVER_PORT || 3000);

module.exports = server;

当我运行grunt server 时,我得到:

$ cd /c/repos/konneka/ && grunt server
Running "env:prod" (env) task

Running "shell:mongod" (shell) task

Running "express:prod" (express) task

Running "express-server:prod" (express-server) task
Web server started on port:3000, hostname: 127.0.0.1 [pid: 3996]

Running "express-keepalive" task
Fatal error: failed to connect to [konneka.org:27018]


Execution Time (2014-08-15 18:05:31 UTC)
loading tasks        38.3s  █████████████████████████████████ 79%
express-server:prod   8.7s  ████████ 18%
express-keepalive     1.2s  ██ 2%
Total 48.3s

现在,我一开始似乎无法连接数据库,但暂时忽略它。请注意,服务器在端口 3000 上启动,这意味着在执行 grunt express:prod 任务期间,未设置 SERVER_PORT,因此端口设置为 3000。还有许多其他类似的示例,其中未设置环境变量所以我的应用程序使用默认值。但是,请注意 session 尝试连接到端口 27018 上的数据库(并且失败),因此 MONGO_PORT 最终会被设置。

如果我刚刚尝试了 grunt server 任务,我可以将其归结为 load-grunt-config 并行运行任务,而不是一个接一个地运行任务或其他错误,但即使我一个接一个地尝试任务一个,例如运行grunt env:prod shell:mongod express-server:prod express-keepalive,我得到类似(不正确)的结果,所以gruntgrunt env 也并行运行任务,或者正在发生其他事情。

这里发生了什么?为什么没有为后面的grunt 任务正确设置环境变量?它们最终是在什么时候设置的,为什么然后而不是其他时间?假设甚至有办法,我怎样才能让他们自己为grunt 任务而不是之后设置?

【问题讨论】:

  • 我正在为此编写一个完整的测试用例,但我最初的想法是grunt env 只是在 grunt 执行完所有任务后才设置环境变量。可能的解决方案是将 env 任务合并到其他任务中。
  • 我正在尝试合并这些任务。这就是为什么我使用load-grunt-configgrunt 自己的方法,如registerTask。但我知道你的意思,我想我稍后会尝试。
  • 我们能否得到tree 的输出(或了解图片中的文件和文件夹)?
  • 我在 Windows 上使用 mSysGit。还有其他方法吗?根据load-grunt-config 的建议,我将grunt 任务放入单独的grunt/ 文件夹中。没有子文件夹,只有 grunt/ 文件夹。
  • @Whymarrh,我刚刚将它添加到问题中。我道歉,我想我认为这对这个问题并不重要,或者“每个人都这样做”。我会学习吗?我还认为这会给问题增加太多内容。

标签: javascript node.js express gruntjs environment-variables


【解决方案1】:

解决方案一搞定就很明显了,让我们从头开始吧:

问题

您正在使用load-grunt-config 加载一组模块(定义任务的对象)并将它们组合成一个模块(对象)并将其传递给 Grunt。为了更好地了解load-grunt-config 在做什么,请花点时间联系read through the source (it's just three files)。所以,不要写:

// filename: Gruntfile.js
grunt.initConfig({
    foo: {
        a: {
            options: {},
        }
    },
    bar: {
        b: {
            options: {},
        }
    }
});

你可以这样写:

// filename: grunt/foo.js
module.exports = {
    a: {
        options: {},
    }
}

// filename: grunt/bar.js
module.exports = {
    b: {
        options: {},
    }
}

// filename: Gruntfile.js
require('load-grunt-config')(grunt);

基本上,通过这种方式,您可以将 Grunt 配置拆分为多个文件,并使其更“可维护”。但是您需要意识到这两种方法在语义上是等价的。也就是说,您可以期望它们的行为方式相同。

因此,当您编写以下内容时*:

(* 我已经减少了问题,试图使这个答案更笼统并减少噪音。我已经排除了加载任务和无关选项传递等内容,但错误应该仍然相同。 另外请注意,我已经更改了环境变量的值,因为默认值与设置的值相同。)

// filename: grunt/env.js
module.exports = {
    dev: {
        options: {
            add: {
                // These values are different for demo purposes
                NODE_ENV: 'dev',
                MONGO_PORT: 'dev_mongo_port',
                SERVER_PORT: 'dev_server_port'
            }
        }
    }
};

// filename: grunt/shell.js
module.exports = {
    mongod: {
        command: 'mongod --port ' + (process.env.MONGO_PORT || 27017)
    }
};

// filename: grunt/aliases.js
module.exports = {
    server: ['env:prod', 'shell:mongod']
};

// filename: Gruntfile.js
module.exports = function (grunt) {
    require('load-grunt-config')(grunt);
};

你可以认为上面和下面一样:

module.exports = function (grunt) {
    grunt.initConfig({
        env: {
            dev: {
                options: {
                    add: {
                        NODE_ENV: 'dev',
                        MONGO_PORT: 'dev_mongo_port',
                        SERVER_PORT: 'dev_server_port'
                    }
                }
            }
        },
        shell: {
            mongod: {
                command: 'mongod --port ' + (process.env.MONGO_PORT || 27017)
            }
        }
    });
    grunt.registerTask('server', ['env:dev', 'shell:mongod']);
};

现在你看到问题了吗?你希望shell:mongod 运行什么命令?正确答案是:

mongod --port 27017

你想被执行的地方是:

mongo --port dev_mongo_port

问题在于,在评估 (process.env.MONGO_PORT || 27017) 时,尚未设置环境变量(即在运行 env:dev 任务之前)。

解决方案

在将 Grunt 配置拆分为多个文件之前,让我们先看看它的工作情况:

module.exports = function (grunt) {
    grunt.initConfig({
        env: {
            dev: {
                options: {
                    add: {
                        NODE_ENV: 'dev',
                        MONGO_PORT: 'dev_mongo_port',
                        SERVER_PORT: 'dev_server_port'
                    }
                }
            }
        },
        shell: {
            mongod: {
                command: 'mongod --port ${MONGO_PORT:-27017}'
            }
        }
    });
    grunt.registerTask('server', ['env:dev', 'shell:mongod']);
};

现在,当您运行 shell:mongod 时,该命令将包含 ${MONGO_PORT:-27017},并且 Bash(或只是 sh)将查找您在之前的任务中设置的环境变量(即 env:dev)。

好的,对于shell:mongod 任务来说这一切都很好,但是其他任务呢,例如 Express?

您需要远离环境变量(除非您想在调用 Grunt 之前设置它们。为什么?以这个 Grunt 配置为例:

module.exports = function (grunt) {
    grunt.initConfig({
        env: {
            dev: {
                options: {
                    add: {
                        NODE_ENV: 'dev',
                        MONGO_PORT: 'dev_mongo_port',
                        SERVER_PORT: 'dev_server_port'
                    }
                }
            }
        },
        express: {
            options: {
                hostname: '127.0.0.1'
                port: (process.env.SERVER_PORT || 3000)
            },
            prod: {
                options: {
                    livereload: true
                    server: path.resolve(__dirname, '../backend/page.js'),
                    bases: 'dist/'
                }
            }
        }
    });
    grunt.registerTask('server', ['env:dev', 'express:prod']);
};

express:prod 任务配置将包含什么端口? 3000。您需要的是它引用您在上述任务中定义的值。你如何做到这一点取决于你。你可以:

  • 分离env 配置并引用其值

    module.exports = function (grunt) {
        grunt.config('env', {
            dev: {
                options: {
                    add: {
                        NODE_ENV: 'dev',
                        MONGO_PORT: 'dev_mongo_port',
                        SERVER_PORT: 'dev_server_port'
                    }
                }
            }
        });
        grunt.config('express', {
            options: {
                hostname: '127.0.0.1'
                port: '<%= env.dev.options.add.SERVER_PORT %>'
            }
        });
        grunt.registerTask('server', ['env:dev', 'express:prod']);
    };
    

    但是您会注意到 env 任务的语义在这里不成立,因为它不再代表任务的配置。您可以使用自己设计的对象:

    module.exports = function (grunt) {
        grunt.config('env', {
            dev: {
                NODE_ENV: 'dev',
                MONGO_PORT: 'dev_mongo_port',
                SERVER_PORT: 'dev_server_port'
            }
        });
        grunt.config('express', {
            options: {
                hostname: '127.0.0.1'
                port: '<%= env.dev.SERVER_PORT %>'
            }
        });
        grunt.registerTask('server', ['env:dev', 'express:prod']);
    };
    
  • 传递grunt 一个参数来指定它应该使用什么配置

  • 拥有多个配置文件(例如Gruntfile.js.devGruntfile.js.prod)并根据需要重命名它们
  • 如果存在则读取开发配置文件(例如grunt.file.readJSON('config.development.json')),如果不存在则回退到生产配置文件
  • 这里没有列出更好的方法

但以上所有方法都应该达到相同的最终结果。

【讨论】:

  • 谢谢谢谢谢谢!你绝对应该得到赏金。我不敢相信我没想过首先在一个普通的旧 Gruntfile 上测试这个配置,这样可以省去几个月的麻烦。哦!我想我可能会采用您的“配置文件”方法。 grunt/env.js 文件几乎就是我正在做的事情,但现在我将拥有不止一个。
  • 所以您是说grunt 在运行任何任务之前评估变量?这似乎很愚蠢,考虑到您可以想到多少次您希望grunt 在任务期间评估变量,例如这个问题。
  • Grunt 不会评估变量,Node(或 JavaScript)会(有点,我不知道如何最好地解释这一点)。它归结为变量的使用位置——如果变量在任务中使用,那么它会在任务运行时进行评估。如果在定义任务时使用变量,则在定义任务时对其进行评估。如果您想使用环境变量,grunt env 任务很有用。
  • 那么在这种情况下,变量是在定义任务时使用的,而不是在使用时使用?有趣...我想我知道如何使用您的“自定义对象”想法解决我的问题。有机会时,我会将其添加到我的问题中。
  • @trysis 如果您有新问题,请随时提出新问题。如果有助于提供上下文,请包含指向此问题的链接。
【解决方案2】:

这似乎是你想要做的事情的本质,它对我有用。重要的部分是我在评论中提到的——在运行其他任务之前链接环境任务。

Gruntfile.js

module.exports = function(grunt) {
  // Do grunt-related things in here
  grunt.loadNpmTasks('grunt-env');

  grunt.initConfig({
      env: {
          dev: {
              PROD : 'http://production.server'
           }
      }
  });

  grunt.registerTask('printEnv', 'prints a message with an env var', function() { console.log('Env var in subsequent grunt task: ' + process.env.PROD) } );

  grunt.registerTask('prod', ['env:dev', 'printEnv']);

};

grunt prod的输出

Running "env:dev" (env) task

Running "printEnv" task
Env var in subsequent grunt task: http://production.server

Done, without errors.

【讨论】:

  • 所以您是说不要使用load-grunt-config 进行环境设置,而是使用常规的grunt.registerTask
  • 因为这应该和load-grunt-config 所做的完全一样。与我相比,您不再将 env 任务与其他任务“组合”或“链接”。
  • @trysis 我不确定 load-grunt-config 在做什么,但我认为这会帮助你做你想做的事情。我会看看我是否可以让它与 load-grunt-config 一起工作。
  • 因为我告诉过你这没有帮助,而且你没有回复或编辑。不过,我的行动可能有点过早。
  • 很遗憾,@ctlacko,在您编辑问题之前,我无法撤消我的反对意见。再次抱歉。
猜你喜欢
  • 1970-01-01
  • 2019-01-01
  • 1970-01-01
  • 2021-05-28
  • 2014-07-15
  • 2022-01-12
  • 2018-01-04
  • 2019-11-26
  • 1970-01-01
相关资源
最近更新 更多