neft
Version:
Universal Platform
320 lines (249 loc) • 9.22 kB
text/coffeescript
'use strict'
utils = require 'src/utils'
log = require 'src/log'
signal = require 'src/signal'
db = require 'src/db'
assert = require 'src/assert'
Schema = require 'src/schema'
Networking = require 'src/networking'
Document = require 'src/document'
Renderer = require 'src/renderer'
Styles = require 'src/styles'
Resources = require 'src/resources'
Dict = require 'src/dict'
AppRoute = require './route'
if utils.isNode
bootstrapRoute = require './bootstrap/route.node'
`//<development>`
pkg = require 'package.json'
`//</development>`
BASE_FILE_NAME_RE = /(.+)\.(?:node|server|client|browser|ios|android|native)/
DEFAULT_CONFIG =
title: 'Neft.io Application'
protocol: 'http'
port: 3000
host: 'localhost'
language: 'en'
type: 'app'
exports = module.exports = (opts = {}, extraOpts = {}) ->
log.ok "Welcome! Neft.io v#{pkg.version}; Feedback appreciated"
`//<development>`
log.warn "Use this bundle only in development; type --release when it's ready"
`//</development>`
config = utils.clone DEFAULT_CONFIG
config = utils.mergeAll config, opts.config, extraOpts
app = new Dict
Config object from the *package.json* file.
Can be overriden in the *init.js* file.
The `app` type (the default one) uses renderer on the client side.
The `game` type uses special renderer (if exists) focused on more performance goals.
The `text` type always return HTML document with no renderer on the client side.
It's used for the crawlers (e.g. GoogleBot) or browsers with no javascript support.
```javascript
// package.json
{
"name": "neft.io app",
"version": "0.1.0",
"config": {
"title": "My first application!",
"protocol": "http",
"port": 3000,
"host": "localhost",
"language": "en",
"type": "app"
}
}
// init.js
module.exports = function(NeftApp) {
var app = NeftApp({ title: "Overridden title" });
console.log(app.config);
// {title: "My first application!", protocol: "http", port: ....}
};
```
app.config = config
Standard Networking instance used to communicate
with the server and to create local requests.
All routes created by the *App.Route* uses this networking.
HTTP protocol is used by default with the data specified in the *package.json*.
app.networking = new Networking
type: Networking.HTTP
protocol: config.protocol
port: parseInt(config.port, 10)
host: config.host
url: config.url
language: config.language
Files from the *models* folder with objects returned by their exported functions.
```javascript
// models/user/permission.js
module.exports = function(app) {
return {
getPermission: function(id){}
};
};
// controllers/user.js
module.exports = function(app) {
return {
get: function(req, res, callback) {
var data = app.models['user/permission'].getPermission(req.params.userId);
callback(null, data);
}
}
};
```
app.models = {}
Files from the *routes* folder with objects returned by their exported functions.
app.routes = {}
Files from the *styles* folder as *Function*s
ready to create new *Item*s.
app.styles = {}
Files from the *views* folder as the *Document* instances.
app.views = {}
app.resources = do ->
if opts.resources
Resources.fromJSON(opts.resources)
else
new Resources
Called when all modules, views, styled etc. have been loaded.
signal.create app, 'onReady'
config.type ?= 'app'
assert.ok utils.has(['app', 'game', 'text'], config.type), "Unexpected app.config.type value. Accepted app/game/text, but '#{config.type}' got."
app.Route = AppRoute app
On the client side, this object refers to the last received cookies
from the networking request.
On the server side, this cookies object are added into the each networking response.
By default, client has *clientId* and *sessionId* hashes.
```javascript
app.cookies.onChange(function(key){
console.log('cookie changed', key, this[key]);
});
```
```xml
<h1>Your clientId</h1>
<em>${context.app.cookies.clientId}</em>
```
COOKIES_KEY = '__neft_cookies'
app.cookies = null
onCookiesReady = (dict) ->
app.cookies = dict
if utils.isClient
dict.set 'sessionId', utils.uid(16)
db.get COOKIES_KEY, db.OBSERVABLE, (err, dict) ->
if dict
onCookiesReady dict
else
if utils.isClient
cookies = {clientId: utils.uid(16)}
else
cookies = {}
db.set COOKIES_KEY, cookies, (err) ->
db.get COOKIES_KEY, db.OBSERVABLE, (err, dict) ->
onCookiesReady dict
app.networking.onRequest (req, res) ->
if utils.isClient
utils.merge req.cookies, app.cookies
else
utils.merge res.cookies, app.cookies
req.onLoadEnd.listeners.unshift ->
if utils.isClient
for key, val of res.cookies
unless utils.isEqual(app.cookies[key], val)
app.cookies.set key, val
return
, null
return
Renderer.resources = app.resources
Renderer.serverUrl = app.networking.url
Renderer.onLinkUri (uri) ->
app.networking.createLocalRequest
method: Networking.Request.GET
type: Networking.Request.HTML_TYPE
uri: uri
Document.Scripts.scripts = utils.arrayToObject opts.scripts,
(index, elem) -> elem.name,
(index, elem) -> elem.file
if opts.styles?
for style in opts.styles
if style.name in ['view', '__view__']
style.file._init app: app, view: null
windowStyle = style.file._main.getComponent()
break
windowStyleItem = windowStyle?.item
assert.ok windowStyleItem, '__view__ style must be defined'
Renderer.window = windowStyleItem
if opts.styles?
stylesInitObject =
app: app
view: windowStyleItem
for style in opts.styles when style.name?
if style.name isnt 'view'
style.file._init stylesInitObject
app.styles[style.name] = style.file
Styles
windowStyle: windowStyle
styles: app.styles
queries: opts.styleQueries
resources: app.resources
if utils.isNode
bootstrapRoute app
init = (files, target) ->
for file in files when file.name?
if typeof file.file isnt 'function'
continue
fileObj = file.file app
target[file.name] = fileObj
if baseNameMatch = BASE_FILE_NAME_RE.exec(file.name)
[] = baseNameMatch
if target[baseName]?
if utils.isPlainObject(target[baseName]) and utils.isPlainObject(fileObj)
fileObj = utils.merge Object.create(target[baseName]), fileObj
target[baseName] = fileObj
return
if utils.isObject(opts.extensions)
for ext in opts.extensions
ext app
exports.app =
Route: app.Route
for view in opts.views when view.name?
app.views[view.name] = Document.fromJSON view.file
init opts.models, app.models
init opts.routes, app.routes
for path, obj of app.routes
r = {}
if utils.isObject(obj) and not (obj instanceof app.Route)
for method, opts of obj
if utils.isObject(opts)
route = new app.Route method, opts
r[route.name] = route
else
r[method] = opts
app.routes[path] = r
unless app.routes.index
app.routes.index = new app.Route 'get /', {}
app.onReady.emit()
app