te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>javascript - Grunt Environment Variables Don't Get Set Until All Tasks Loaded - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Grunt Environment Variables Don't Get Set Until All Tasks Loaded - Stack Overflow

programmeradmin3浏览0评论

I am using the npm modules grunt env and load-grunt-config in my project. grunt env handles environment variables for you, while load-grunt-config handles, well, loads the grunt configuration for you. You can put your tasks into other files, then load-grunt-config will bundle them up and have grunt load & consume them for you. You can also make an aliases.js file, with tasks you want to bine together into one task, running one after another. It's similar to the grunt.registerTask task in the original Gruntfile.js. I put all my grunt tasks inside a separate grunt/ folder under the root folder with the main Gruntfile, with no extra subfolders, as suggested by the load-grunt-config README.md on Github. Here is my slimmed-down 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']
        }
    });

};

In theory, setting all these files up the correct way for load-grunt-config to load should be exactly the same as just having a Gruntfile.js. However, I seem to have run into a little snag. It seems the environment variables set under the env task do not get set for the subsequent grunt tasks, but are set by the time node processes its tasks, in this case an express server.

grunt env task:

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 task:

// shell mand tasks
module.exports = {

    // starts up MongoDB server/daemon
    mongod: {
        mand: 'mongod --bind_ip konneka --port ' + (process.env.MONGO_PORT || 27017) + ' --dbpath C:/MongoDB/data/db --ipv6',
        options: {
            async: true, // makes this mand 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 task:

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 file (grunt-load-config's way of bining tasks so they run one after the other):

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

part of backend/env/prod.js (environment-specific Express configuration, loaded if NODE_ENV is set to "prod", modeled after MEAN.JS):

'use strict';

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

part of backend/env/dev.js (environment-specific Express configuration for dev environment, loaded if the `NODE_ENV variable is not set or is set to "dev"):

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

part of backend/page.js (my Express configuration page, also modeled after 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;

When I run grunt server, I get:

$ 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: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

Now, I can't seem to get the database connected in the first place, but ignore that for now. Notice that the server is started on port 3000, meaning that during execution of the grunt express:prod task, SERVER_PORT is not set so the port gets set to 3000. There are numerous other examples like this, where an environment variable is not set so my app uses the default. However, notice that session tries to connect to the database on port 27018 (and fails), so MONGO_PORT does get set eventually.

If I had just tried the grunt server task, I could chalk it up to load-grunt-config running the tasks in parallel instead of one after the other or some other error, but even when I try the tasks one-by-one, such as running grunt env:prod shell:mongod express-server:prod express-keepalive, I get similar (incorrect) results, so either grunt or grunt env run the tasks in parallel, as well, or something else is going on.

What's going on here? Why are the environment variables not set correctly for later grunt tasks? When are they eventually set, and why then rather than some other time? How can I make them get set for grunt tasks themselves rather than after, assuming there even is a way?

I am using the npm modules grunt env and load-grunt-config in my project. grunt env handles environment variables for you, while load-grunt-config handles, well, loads the grunt configuration for you. You can put your tasks into other files, then load-grunt-config will bundle them up and have grunt load & consume them for you. You can also make an aliases.js file, with tasks you want to bine together into one task, running one after another. It's similar to the grunt.registerTask task in the original Gruntfile.js. I put all my grunt tasks inside a separate grunt/ folder under the root folder with the main Gruntfile, with no extra subfolders, as suggested by the load-grunt-config README.md on Github. Here is my slimmed-down 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']
        }
    });

};

In theory, setting all these files up the correct way for load-grunt-config to load should be exactly the same as just having a Gruntfile.js. However, I seem to have run into a little snag. It seems the environment variables set under the env task do not get set for the subsequent grunt tasks, but are set by the time node processes its tasks, in this case an express server.

grunt env task:

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 task:

// shell mand tasks
module.exports = {

    // starts up MongoDB server/daemon
    mongod: {
        mand: 'mongod --bind_ip konneka --port ' + (process.env.MONGO_PORT || 27017) + ' --dbpath C:/MongoDB/data/db --ipv6',
        options: {
            async: true, // makes this mand 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 task:

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 file (grunt-load-config's way of bining tasks so they run one after the other):

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

part of backend/env/prod.js (environment-specific Express configuration, loaded if NODE_ENV is set to "prod", modeled after MEAN.JS):

'use strict';

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

part of backend/env/dev.js (environment-specific Express configuration for dev environment, loaded if the `NODE_ENV variable is not set or is set to "dev"):

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

part of backend/page.js (my Express configuration page, also modeled after 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;

When I run grunt server, I get:

$ 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: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

Now, I can't seem to get the database connected in the first place, but ignore that for now. Notice that the server is started on port 3000, meaning that during execution of the grunt express:prod task, SERVER_PORT is not set so the port gets set to 3000. There are numerous other examples like this, where an environment variable is not set so my app uses the default. However, notice that session tries to connect to the database on port 27018 (and fails), so MONGO_PORT does get set eventually.

If I had just tried the grunt server task, I could chalk it up to load-grunt-config running the tasks in parallel instead of one after the other or some other error, but even when I try the tasks one-by-one, such as running grunt env:prod shell:mongod express-server:prod express-keepalive, I get similar (incorrect) results, so either grunt or grunt env run the tasks in parallel, as well, or something else is going on.

What's going on here? Why are the environment variables not set correctly for later grunt tasks? When are they eventually set, and why then rather than some other time? How can I make them get set for grunt tasks themselves rather than after, assuming there even is a way?

Share Improve this question edited Oct 19, 2014 at 23:04 trysis asked Aug 15, 2014 at 18:56 trysistrysis 8,41618 gold badges53 silver badges86 bronze badges 5
  • I'm working up a plete test case for this, but my initial thought is that grunt env is only setting the environment variables once grunt has executed all of its tasks. Possible solution is to bine the env task into the other tasks. – ctlacko Commented Aug 15, 2014 at 19:25
  • I'm trying to bine the tasks. That's why I'm using load-grunt-config and grunt's own methods like registerTask. But I know what you mean, and I suppose I'll try that later. – trysis Commented Aug 15, 2014 at 19:32
  • Could we get the output from tree (or some insight into what files and folders are in the picture)? – Whymarrh Commented Oct 19, 2014 at 1:44
  • I am on Windows with mSysGit. Is there any other way? I put the grunt tasks into a separate grunt/ folder, as suggested by load-grunt-config. No subfolders, just the grunt/ folder. – trysis Commented Oct 19, 2014 at 16:53
  • @Whymarrh, I just added it to the question. I apologize, I suppose I assumed it wasn't important to the question or that "everyone does it that way". Will I ever learn? I also thought it would add too much to the question. – trysis Commented Oct 19, 2014 at 16:58
Add a ment  | 

2 Answers 2

Reset to default 9 +50

The solution is rather obvious once you figure it out, so let's start at the beginning:

The problem

You're using load-grunt-config to load a set of modules (objects that define tasks) and bine them into one module (object) and pass it along to Grunt. To better understand what load-grunt-config is doing, take a moment to read through the source (it's just three files). So, instead of writing:

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

You can write this:

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

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

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

Basically, this way you can split up a Grunt configuration into multiple files and have it be more "maintainable". But what you'll need to realize is that these two approaches are semantically equivalent. That is, you can expect them to behave the same way.

Thus, when you write the following*:

(* I've reduced the problem in an attempt to make this answer a bit more general and to reduce noise. I've excluded things like loading the tasks and extraneous option passing, but the error should still be the same. Also note that I've changed the values of the environment variables because the default was the same as what was being set.)

// 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: {
        mand: '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);
};

You can consider the above the same as below:

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: {
                mand: 'mongod --port ' + (process.env.MONGO_PORT || 27017)
            }
        }
    });
    grunt.registerTask('server', ['env:dev', 'shell:mongod']);
};

Now do you see the problem? What mand do you expect shell:mongod to run? The correct answer is:

mongod --port 27017

Where what you want to be executed is:

mongo --port dev_mongo_port

The problem is that when (process.env.MONGO_PORT || 27017) is evaluated the environment variables have not yet been set (i.e. before the env:dev task has been run).

A solution

Well let's look at a working Grunt configuration before splitting it across multiple files:

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: {
                mand: 'mongod --port ${MONGO_PORT:-27017}'
            }
        }
    });
    grunt.registerTask('server', ['env:dev', 'shell:mongod']);
};

Now when you run shell:mongod, the mand will contain ${MONGO_PORT:-27017} and Bash (or just sh) will look for the environment variable you would have set in the task before it (i.e. env:dev).

Okay, that's all well and good for the shell:mongod task, but what about the other tasks, Express for example?

You'll need to move away from environment variables (unless you want to set them up before invoking Grunt. Why? Take this Grunt configuration for example:

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

What port will the express:prod task configuration contain? 3000. What you need is for it to reference the value you've defined in the above task. How you do this is up to you. You could:

  • Separate the env configuration and reference its values

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

    But you'll notice that the semantics of the env task don't hold up here due to it no longer representing a task's configuration. You could use an object of your own design:

    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']);
    };
    
  • Pass grunt an argument to specify what config it should use

  • Have multiple configuration files (e.g. Gruntfile.js.dev and Gruntfile.js.prod) and rename them as needed
  • Read a development configuration file (e.g. grunt.file.readJSON('config.development.json')) if it exists and fall back to a production configuration file if it doesn't exist
  • Some better way not listed here

But all of the above should achieve the same end result.

This seems to be the essence of what you are trying to do, and it works for me. The important part was what I mentioned in my ment -- chaining the environment task before running the other tasks.

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

};

Output of grunt prod

Running "env:dev" (env) task

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

Done, without errors.
发布评论

评论列表(0)

  1. 暂无评论