neft
Version:
Universal Platform
294 lines (228 loc) • 8.77 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'
bootstrapRoute = try 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 = {}) ->
null
`//<development>`
log.ok "Welcome! Neft.io v#{pkg.version}; Feedback appreciated"
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 *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 *components* folder as the *Document* instances.
app.components = {}
app.resources = do ->
if opts.resources
Resources.fromJSON(opts.resources)
else
new Resources
Called when all modules, components, 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'
IS_CLIENT = not process.env.NEFT_SERVER
app.cookies = Dict()
onCookiesReady = (dict) ->
app.cookies = dict
if IS_CLIENT
dict.set 'sessionId', utils.uid(16)
db.get COOKIES_KEY, db.OBSERVABLE, (err, dict) ->
if dict
onCookiesReady dict
else
if IS_CLIENT
cookies = {clientId: utils.uid(16)}
else
cookies = {}
db.set COOKIES_KEY, cookies, (err) ->
db.get COOKIES_KEY, db.OBSERVABLE, (err, dict) ->
if dict
onCookiesReady dict
app.networking.onRequest (req, res) ->
if IS_CLIENT
utils.merge req.cookies, app.cookies
else
utils.merge res.cookies, app.cookies
req.onLoadEnd.listeners.unshift ->
if IS_CLIENT
for key, val of res.cookies
unless utils.isEqual(app.cookies[key], val)
app.cookies.set key, val
return
, null
return
Renderer.setResources app.resources
Renderer.setServerUrl app.networking.url
Renderer.onLinkUri (uri) ->
app.networking.createLocalRequest
method: Networking.Request.GET
type: Networking.Request.HTML_TYPE
uri: uri
app.documentScripts = utils.arrayToObject opts.scripts,
(index, elem) -> elem.name,
(index, elem) -> elem.file
Document.Scripts.initialize app.documentScripts
if opts.styles?
for style in opts.styles
if style.name in ['windowItem', '__windowItem__']
style.file._init windowItem: null
windowStyle = style.file._main document: null
break
app.windowItem = windowStyleItem = windowStyle?.item
assert.ok windowStyleItem, '__windowItem__ style must be defined'
Renderer.setWindowItem windowStyleItem
if opts.styles?
stylesInitObject =
windowItem: windowStyleItem
for style in opts.styles when style.name?
if style.name isnt 'windowItem'
style.file._init stylesInitObject
app.styles[style.name] = style.file
Styles
windowStyle: windowStyle
styles: app.styles
queries: opts.styleQueries
resources: app.resources
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 component in opts.components when component.name?
app.components[component.name] = Document.fromJSON component.file
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