A speed oriented javascript browser web framework with nodejs like syntax
// cwd
const { build } = require('jsnode');
build()
- all default vals/functions are fully customizable
// ./app/defaults.mjs
import { x, xrender } from './modules/xscript.mjs';
import { xutils } from './modules/xutils.mjs';
import { xviews } from './views/xviews.mjs';
import { xdata } from './data/xdata.mjs';
//cached reference to app-main object
let app_main = x('div');
// app default functions
let defaults = Object.assign(xdata.default, {
app_main: app_main,
each: {
before: function(dest) { // tasks to be carried out prior to each rout
// return false; cancel rout
return true // continue to rout
},
after: function(dest) { // tasks to be carried out after each rout
document.title = dest.slice(1)
}
},
init: function(){ // tasks to be carried out on init
xutils.build(xdata, xviews['build'](app_main));
return this;
},
render: function(stream, path, data, cb){ // tasks to be carried out on render
xrender(stream, xviews[path], data, xdata[path], cb);
return this;
}
})
export { defaults, app_main }
// app/data/xdata.mjs
const xdata = {
default:{
version: '1.0.0', // app version
origin: 'http://localhost:8000', // app origin
params: true, // parse rout params
error: 'error', // error handler listener
base_path: '/index', // app base path
delete_meta: 10000, // automatically remove meta timeout in ms || 0/false
webmanifest: './manifest.webmanifest', // webmanifest path || false
base_script_name: 'main', //main script name attr
styles:[{ // styles to be added
href: 'app/css/app.css',
rel: 'stylesheet'
}],
js_head:[], // js to be added to head
js_body:[], // js to be added to body
storage: { // state storage
max_age: 9999999999, // state storage max-age ms
prefix: 'rt' // state storage key prefix
},
stream: { // stream method defaults/fallbacks
download: { //download fallbacks
type: 'text/plain',
charset: 'utf-8'
},
fetch: { //fetch fallbacks
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
}
},
// rout default data below
index: {
msg: 'Big things have small beginnings.'
},
home: {
msg: 'welcome message 2'
}
}
export { xdata }
import { router, x } from './app/modules/jsnode.mjs';
router.on('/', function(request, stream) {
stream.render('index', request.data, function(err){
if(err){return console.error(err)}
})
})
.on('/home', function(request, stream) {
stream.render('home', request.data, function(err){
if(err){return console.error(err)}
})
})
.on('error', function(err) { // router error handler
console.log(err)
})
.init() // build app base defaults.init function ~ optional ~ called first
.listen() // initialize router and start listening for rout calls ~ called second
.validate() // check/remove stale cache entries ~ optional ~ called third
import { router } from './jsnode.mjs';
router.on('/', function(req, res) {
console.log(req) // request object
console.log(res) // stream object
})
import { router } from './jsnode.mjs';
router.on('/delete_rout', function(request,stream){
})
.off('/delete_rout')// delete rout '/delete_rout'
router.on('/test_basic', function(request, stream) {
console.log(request.data) // {test:'basic'}
})
.on('/test_params', function(request, stream) {
if(request.params){
console.log(request.params.get('test')) // ok
}
})
// navigate with data
router.rout('/test_basic', {
test:'basic'
})
// navigate with data and params
router.rout('/test_params?test=ok', {
'test':'sdfsdfsd'
})
router.on('/', function(request, stream) {
stream.setCookie('name', 'value', { // add cookie
'path': '/',
'secure': true,
'max-age': 999999
})
stream.render('index', function(err){
if(err){return console.error(err)}
//do something
})
})
.on('/about', function(request, stream) {
stream.render('about', {some: 'data'}, function(err){
if(err){return console.error(err)}
//do something
})
})
redirect path
router.on('/path1', function(request, stream) {
// navigate away from path1 to path2 within the site
// history state is added
stream.redirect('/path2', {data: 'redirecting'})
})
replace state
router.on('/path1', function(request, stream) {
// load path2 into view without navigating away from path1
// no history state is added
stream.replace('/path2', {data: 'redirecting'})
})
router.on('/download', function(request, stream) {
let data = JSON.stringify({"test":"!@#$<}(*&^%$ok"});
stream.download(
"test.json", // filename ~ required
data, // file data ~ required
'application/json', // content-type ~ optional
'utf8' // content-encoding ~ optional
);
})
router.on('/fetch_default', function(request, stream) {
//fallback to default.fetch
stream.fetch('./app/data/index.json', function(err, data){
if(err){return console.error(err)}
console.log(data.headers) // return response headers object
console.log(data.json) // return json data
console.log(data.body) // return body response
})
})
.on('/fetch_post', function(request, stream) {
// cors post example
let obj = { // optional || fallback to xdata.default.stream.fetch
method: 'post',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
'Sec-Fetch-Dest': 'object',
'Sec-Fetch-mode': 'cors',
'Sec-Fetch-Site': 'cross-site'
},
body: stream.js({example: 'working'})
}
//fallback to default.fetch
stream.fetch('https/someurl.com/api', obj, function(err, data){
if(err){return console.error(err)}
console.log(data.headers) // return response headers object
console.log(data.json) // return json data
console.log(data.body) // return body response
})
})
router.on('/', function(request, stream) {
console.log(request)
if(request.params){
console.log(request.params.get('test')) // get params by key
console.log(params.getAll('foo')) // get all of key ["1","4"].
console.log(params.has('bar') === true); // true/false param exists
console.log(params.keys()) // return params keys
console.log(params.values()) // return params values
params.set('baz', 3) // set params
params.append('foo', 4); // add to params
params.toString() // return params as string
params.sort(); // sort params
params.delete('foo') // delete a param
params.entries()
params.forEach()
}
})
router.on('/', function(r, s) {
let data = {
test: 'json.stringify'
}
console.log(s.js(data)) // '{"test":"json.stringify"}'
console.log(s.js(data,0,2))
/*
{
"test":"json.stringify"
}
*/
})
router.on('/', function(r, s) {
let data = '{"test":"json.parse"}'
console.log(s.jp(data)) // {test: "json.parse"}
})
router.on('/', function(request, stream) {
stream.empty() // remove app_main childNodes
stream.empty(document.body) // remove any elements childNodes
})
router.on('/', function(request, stream) {
// append elements to app-main
stream.append(x('p', 'text appended to app-main'))
})
router.on('/example', function(request, stream) {
// return parsed path details
console.log(
stream.path(request.data.filename)
)
//{fileName: "file.js", baseName: "file", ext: "js", dirName: "/path/to"}
})
router.rout('/example', {filename: '/path/to/file.js'})
router.on('/example', function(request, stream) {
// create blob object from data
console.log(
stream.blob(...request.data.blob)
)
//Blob {size: 9, type: "plain/text;utf-8"}
})
router.rout('/example', {blob: ['some test', 'plain/text','utf-8']})
router.on('/example', function(request, stream) {
// return parsed url object
console.log(
stream.url.parse(request.data.path)
)
/*
{
hash: ""
host: "www.someurl.com"
hostname: "www.someurl.com"
href: "https://www.someurl.com/index.js"
origin: "https://www.someurl.com"
password: ""
pathname: "/index.js"
port: ""
protocol: "https:"
search: ""
searchParams: URLSearchParams {}
username: ""
}
*/
})
router.rout('/example', {path: 'https://www.someurl.com/index.js'})
router.on('/example', function(request, stream) {
// create new url from blob object
let newUrl = stream.url.add(...request.data.url);
document.body.append(
x('a', {href: newUrl}, 'test-link')
)
})
router.rout('/example', {url: ['some test', 'plain/text','utf-8']})
router.on('/example', function(request, stream) {
// create new url from blob object
let newUrl = stream.url.add(...request.data.url),
lnk = x('a', {
href: newUrl,
onclick: function(){
lnk.remove();
setTimeout(function(){
// delete new url link
stream.url.del(newUrl);
},3000)
}
}, 'test-link');
document.body.append(lnk)
})
router.rout('/example', {url: ['some test', 'plain/text','utf-8']})
- stream.setCookie
- stream.getCookie
- stream.delCookie
router.on('/', function(request, stream) { // add cookie
stream.setCookie('name', 'value', {
'path': '/',
'secure': true,
'max-age': 999999
})
.delCookie('name') // delete cookie
console.log(stream.getCookie('name')) // get a cookie
})
- stream.ssSet
- stream.ssGet
- stream.ssDel
router.on('/', function(request, stream) {
stream.ssSet('key', {test: 'working'}) // set stringified session storage
.ssDel('key') // delete session storage item
console.log(stream.ssGet('key')) // get parsed session storage
})
- stream.lsSet
- stream.lsGet
- stream.lsDel
router.on('/', function(request, stream) {
stream.lsSet('key', {test: 'working'}) // set stringified local storage
.lsDel('key') // delete local storage item
console.log(stream.lsGet('key')) // get parsed local storage
})
xscript is the pure javascript template engine used for jsnode
- xscript works directly with the dom
- xscript uses no regex/unsafe functions
- xscript is about as close to using vanilla js speed as it gets.
/**
* @x(tag, ...arguments)
* @param {string} tag ~ html tag
* @param {object|string|function} arguments
**/
// create a basic element with text
let item = x('p', 'example plain text');
console.log(item)
// <p>example plain text</p>
document.body.append(item)
// create an element with multiple attributes
let item = x('input', {
id: 'testid',
class: 'class1 class2 class3',
type: 'text',
placeHolder: 'example attributes',
style: 'color:red;background:black'
})
console.log(item)
// <input id="testid" class="class1 class2 class3" type="text" style="color:red;background:black" placeholder="example attributes">
let item = x('p', {
id: 'testid',
class: 'class1 class2 class3',
onclick: function(){
console.log('item clicked!');
},
onmouseover: function(){
console.log('item mouseover!');
}
})
document.body.append(item);
item.click()
// item clicked!
item.onmouseover()
// item mouseover!
let items = x('p',
x('p', 'level 2.1'),
"some text",
x('p', 'level 2.2',
x('p', 'level 3',
x('p', 'level 4.1'),
function(){
// some function element
return x('p', 'level 4.2')
},
x('p', 'level 4.3'),
function(){
return 'some function text node'
}
)
),
'level 1'
)
document.body.append(items);
once created, an node can be referenced like any other js node.
let item = x('p')
item.id = 'testid';
item.textContent = 'click me';
let somefunction = function(){
item.removeEventListener('click', somefunction);
item.textContent = 'clicked'
somefunction = item = null;
console.log(somefunction, item)
}
item.addEventListener('click', somefunction, false)
document.body.append(item);
let items = x('p',
'prepended text',
x('p', 'basic 1', 'basic 2', () => 'basic 3'),
'nested text',
x('p', 'basic 4', 'basic 5', () => 'basic 6'),
'appended text text'
);
console.log(items);
/*
<p>
"prepended text"
<p>"basic 1" "basic 2" "basic 3"</p>
"nested text"
<p>"basic 4" "basic 5" "basic 6"</p>
"appended text text"
</p>
*/
- functions should only return element or text nodes.
let items = x('p',
() => 'you',
() => 'can',
() => 'add',
() => 'as',
() => 'many',
() => x('p','as'),
() => 'you',
() => 'want',
() => 'wherever',
() => 'you',
() => 'want',
);
console.log(items);