UNPKG

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