UNPKG

5.28 kBJavaScriptView Raw
1var path = require('path');
2var loadModules = require('./path_helpers').loadModules;
3var EventEmitter = require('events').EventEmitter;
4
5/**
6 * Application extensions mixin.
7 * Extends an Express app with more functions.
8 */
9
10var app = module.exports = function(app) {
11
12 /**
13 * Event emitter. Used by `emit()` and such.
14 * @api private
15 */
16
17 var events = new EventEmitter();
18
19 /**
20 * The root path. To be overriden later.
21 */
22
23 app.root = '';
24
25 /**
26 * Gets a path relative to the root.
27 *
28 * app.path('public')
29 * app.path('app/migrations')
30 */
31
32 app.path = function() {
33 return path.resolve.apply(this, [this.root].concat([].slice.call(arguments)));
34 };
35
36 var loaded = false;
37
38 /**
39 * Loads all files needed to run an Express server.
40 *
41 * app.load();
42 * app.listen(3000);
43 *
44 * Also emits the following events in this sequence:
45 *
46 * - load:before
47 * - initializers:before, initializers:after
48 * - helpers:before, helpers:after
49 * - routes:before, routes:after
50 * - load:after
51 */
52
53 app.load = function(env) {
54 if (!loaded) {
55 process.chdir(app.root);
56
57 // Set environment if asked (usually test).
58 if (env) {
59 process.env.NODE_ENV = env;
60 app.set('env', env);
61 }
62
63 env = app.get('env');
64 if (env !== 'development') app.log = require('./logger')(env);
65
66 // Hooks: do pre-load hooks that extensions may listen for.
67 app.emit('load:before');
68 if (env === 'test') app.emit('load:test:before');
69
70 // Load initializers of the application.
71 loadAppPath('initializers', 'function', function(mixin) { mixin(app); });
72
73 // Middleware for 'file not found' and 'server error' -- must always be
74 // the last
75 app.use(notFound);
76 app.use(serverError);
77
78 // Apply the helpers using `.local()` to make them available to all views.
79 loadAppPath('helpers', 'object', function(helpers) { app.locals(helpers); });
80
81 // Load routes of the application.
82 loadAppPath('routes', 'function', function(mixin) { mixin(app); });
83
84 // After hooks
85 if (env === 'test') app.emit('load:test:after');
86 app.emit('load:after');
87
88 loaded = true;
89 }
90
91 return this;
92 };
93
94 /**
95 * Loads the app in a console-friendly way. Environment optional.
96 */
97
98 app.loadConsole = function(env) {
99 app.load(env);
100 app.emit('console');
101 global.app = app;
102 };
103
104 var command;
105
106 /**
107 * Returns the commander command line parser.
108 *
109 * app.cli()
110 * app.cli().parse(...)
111 */
112
113 app.cli = function() {
114 if (!command) {
115 command = require('commander');
116
117 // Import [1] default tasks, [2] extensions tasks, [3] app tasks.
118 require('./cli')(app, command);
119 app.emit('cli', command);
120 loadModules(app.path('app/tasks'), 'function', function(mixin) { mixin(app, command); });
121 }
122
123 return command;
124 };
125
126 // Logger
127 // ------
128
129 /**
130 * Simple logging facility.
131 *
132 * app.log.debug('Loading models');
133 * app.log.info('Loading models');
134 */
135
136 app.log = require('./logger')(process.env.NODE_ENV || 'development');
137
138 // Events
139 // ------
140
141 /**
142 * The events.
143 * You may emit events here.
144 */
145
146 app.events = events;
147
148 /**
149 * Listens to a given event.
150 *
151 * app.on('load:before', function() { ... })
152 *
153 * This emits events during the load process, allowing extensions to hook
154 * onto certain parts of the load process.
155 *
156 * This is just shorthand for `app.events.on(x, ...)`.
157 *
158 * See `app.load()` for a catalog of events that it emits.
159 */
160
161 app.on = function(eventName, listener) {
162 events.on(eventName, listener);
163 return this;
164 };
165
166 /**
167 * Emits an event.
168 *
169 * app.emit('load:before');
170 * app.emit('cli', app.cli);
171 */
172
173 app.emit = function(eventName, arg) {
174 events.emit(eventName, app, arg);
175 return this;
176 };
177
178 // Config
179 // ------
180
181 var configData = {};
182
183 /**
184 * Loads configuration from a file. Yaml, JSON and JS are supported.
185 *
186 * // Reads `config/secret_token.yml`
187 * // (or .json, or .js, or .coffee)
188 * app.conf('secret_token');
189 */
190
191 app.conf = function(file) {
192 if (!(file in configData)) {
193 var env = app.get('env');
194 var load = require('./conf');
195
196 var data = load(file, [
197 app.path('config'),
198 path.join(process.cwd(), 'config')
199 ]);
200
201 configData[file] = (env in data ? data[env] : data['default']);
202 }
203
204 return configData[file];
205 };
206
207 // Defaults
208 // --------
209
210 /**
211 * Set some defaults
212 */
213
214 app.set('views', app.path('app/views'));
215
216 // Private helpers
217 // ---------------
218
219 /**
220 * Loads mixins in a given path.
221 * @api private
222 */
223
224 function loadAppPath(path, type, callback) {
225 events.emit(path+':before', app);
226 loadModules(app.path('app', path), type, callback);
227 events.emit(path+':after', app);
228 }
229
230 function notFound(req, res, next) {
231 next(404);
232 }
233
234 function serverError(err, req, res, next) {
235 if (err === 404) {
236 res.render('errors/404', { status: 404, url: req.url });
237 return;
238 }
239
240 if (!app.get('throw errors')) {
241 res.render('errors/500', { status: 500, error: err });
242 }
243 }
244};