diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 00000000..d1154df3
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,33 @@
+{
+ "extends": "plugin:nullstack/recommended",
+ "plugins": [
+ "jest"
+ ],
+ "env": {
+ "jest/globals": true
+ },
+ "rules": {
+ "nullstack/prettier": [
+ "warn",
+ {
+ "trailingComma": "all",
+ "tabWidth": 2,
+ "semi": false,
+ "singleQuote": true,
+ "printWidth": 120
+ },
+ {
+ "usePrettierrc": false
+ }
+ ]
+ },
+ "globals": {
+ "page": "writable",
+ "browser": "writable"
+ },
+ "ignorePatterns": [
+ "temp.js",
+ "types/*.d.ts",
+ "workers/*.js"
+ ]
+}
\ No newline at end of file
diff --git a/builders/spa.js b/builders/spa.js
index 6904fc7d..c7eeb28b 100644
--- a/builders/spa.js
+++ b/builders/spa.js
@@ -1,16 +1,16 @@
module.exports = async function spa({ output, cache, environment }) {
- const folder = output || 'spa';
- process.env.NULLSTACK_ENVIRONMENT_MODE = 'spa';
+ const folder = output || 'spa'
+ process.env.NULLSTACK_ENVIRONMENT_MODE = 'spa'
- const dir = process.cwd();
- const application = require(`${dir}/.${environment}/server`).default;
- const projectName = application.project.name || 'The Nullstack application';
- const { existsSync, mkdirSync, writeFileSync, copySync, removeSync } = require('fs-extra');
- const path = `${dir}/${folder}`;
+ const dir = process.cwd()
+ const application = require(`${dir}/.${environment}/server`).default
+ const projectName = application.project.name || 'The Nullstack application'
+ const { existsSync, mkdirSync, writeFileSync, copySync, removeSync } = require('fs-extra')
+ const path = `${dir}/${folder}`
async function copy(url, file) {
- console.log(` ⚙️ ${file || url}`)
- const content = await application.server.prerender(url);
+ console.info(` ⚙️ ${file || url}`)
+ const content = await application.server.prerender(url)
const target = `${dir}/${folder}${file || url}`
writeFileSync(target, content)
}
@@ -19,26 +19,26 @@ module.exports = async function spa({ output, cache, environment }) {
return dest.endsWith(folder) || (src.includes('client') && !src.includes('.txt'))
}
- console.log()
+ console.info()
if (existsSync(path)) {
- removeSync(path);
+ removeSync(path)
}
mkdirSync(path)
- console.log(` ⚙️ /public/`)
- copySync(`${dir}/public`, path);
+ console.info(` ⚙️ /public/`)
+ copySync(`${dir}/public`, path)
await copy('/', '/index.html')
- console.log(` ⚙️ /.${environment}/`)
+ console.info(` ⚙️ /.${environment}/`)
copySync(`${dir}/.${environment}`, path, { filter })
await copy(`/manifest.webmanifest`)
await copy(`/service-worker.js`)
await copy('/robots.txt')
- console.log()
+ console.info()
- console.log('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`);
+ console.info('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`)
if (cache) {
- console.log('Storing cache...');
+ console.info('Storing cache...')
} else if (environment === 'production') {
- process.exit();
+ process.exit()
}
-}
\ No newline at end of file
+}
diff --git a/builders/ssg.js b/builders/ssg.js
index dcd2156f..bd13fdbc 100644
--- a/builders/ssg.js
+++ b/builders/ssg.js
@@ -1,55 +1,60 @@
module.exports = async function ssg({ output, cache, environment }) {
- const folder = output || 'ssg';
- process.env.NULLSTACK_ENVIRONMENT_MODE = 'ssg';
+ const folder = output || 'ssg'
+ process.env.NULLSTACK_ENVIRONMENT_MODE = 'ssg'
- const dir = process.cwd();
- const application = require(`${dir}/.${environment}/server`).default;
- const projectName = application.project.name || 'The Nullstack application';
+ const dir = process.cwd()
+ const application = require(`${dir}/.${environment}/server`).default
+ const projectName = application.project.name || 'The Nullstack application'
const { resolve } = require('path')
- const { existsSync, mkdirSync, writeFileSync, copySync, removeSync } = require('fs-extra');
+ const { existsSync, mkdirSync, writeFileSync, copySync, removeSync } = require('fs-extra')
function path(file = '') {
- const target = file.startsWith('/') ? file.slice(1) : file;
+ const target = file.startsWith('/') ? file.slice(1) : file
return resolve(`${dir}/${folder}`, target).split('?')[0]
}
- const links = {};
- const pages = {};
+ const links = {}
+ const pages = {}
async function copyRoute(url = '/') {
- links[url] = true;
+ links[url] = true
if (url.indexOf('.') > -1) {
- return;
+ return
}
- const content = await application.server.prerender(url);
+ const content = await application.server.prerender(url)
const target = path(url)
- console.log(` ⚙️ ${url}`)
+ console.info(` ⚙️ ${url}`)
if (!existsSync(target)) {
- mkdirSync(target, { recursive: true });
+ mkdirSync(target, { recursive: true })
}
writeFileSync(`${target}/index.html`, content)
if (url !== '/') {
writeFileSync(`${target}.html`, content)
}
- const stateLookup = ' line.indexOf(stateLookup) > -1).split(stateLookup)[1].slice(0, -2);
- const { instances, page } = JSON.parse(decodeURIComponent(state));
+ const stateLookup = ' {
- const page = pages[path];
- const canonical = `https://${application.project.domain}${path}`;
- return `${canonical}${timestamp}${page.changes ? `${page.changes}` : ''}${page.priority ? `${page.priority.toFixed(1)}` : ''}`;
- });
- const xml = `${urls.join('')}`;
- writeFileSync(`${path()}/sitemap.xml`, xml);
+ console.info(' ⚙️ /sitemap.xml')
+ const timestamp = new Date().toJSON().substring(0, 10)
+ const urls = Object.keys(pages).map((p) => {
+ const page = pages[p]
+ const canonical = `https://${application.project.domain}${p}`
+ return `${canonical}${timestamp}${
+ page.changes ? `${page.changes}` : ''
+ }${page.priority ? `${page.priority.toFixed(1)}` : ''}`
+ })
+ const xml = `${urls.join(
+ '',
+ )}`
+ writeFileSync(`${path()}/sitemap.xml`, xml)
}
function filter(src, dest) {
return dest.endsWith(folder) || (src.includes('client') && !src.includes('.txt'))
}
- console.log()
+ console.info()
if (existsSync(path())) {
- removeSync(path());
+ removeSync(path())
}
mkdirSync(path())
- console.log(` ⚙️ /public/`)
- copySync(path(`../public`), path());
- console.log(` ⚙️ /.${environment}/`)
- copySync(path(`../.${environment}`), path(), { filter });
+ console.info(` ⚙️ /public/`)
+ copySync(path(`../public`), path())
+ console.info(` ⚙️ /.${environment}/`)
+ copySync(path(`../.${environment}`), path(), { filter })
await copyRoute()
- await copyRoute(`/nullstack/${application.environment.key}/offline`);
- await copyRoute(`/404`);
+ await copyRoute(`/nullstack/${application.environment.key}/offline`)
+ await copyRoute(`/404`)
await copyBundle(`/manifest.webmanifest`)
await copyBundle(`/service-worker.js`)
await copyBundle('/robots.txt')
await createSitemap()
- console.log()
+ console.info()
- console.log('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`);
+ console.info('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`)
if (cache) {
- console.log('Storing cache...');
+ console.info('Storing cache...')
} else if (environment === 'production') {
- process.exit();
+ process.exit()
}
-}
\ No newline at end of file
+}
diff --git a/builders/ssr.js b/builders/ssr.js
index 808e3f48..eb3ead35 100644
--- a/builders/ssr.js
+++ b/builders/ssr.js
@@ -1,13 +1,13 @@
module.exports = async function ssr({ cache }) {
- const dir = process.cwd();
- const application = require(`${dir}/.production/server`).default;
- const projectName = application.project.name || 'The Nullstack application';
+ const dir = process.cwd()
+ const application = require(`${dir}/.production/server`).default
+ const projectName = application.project.name || 'The Nullstack application'
- console.log('\x1b[36m%s\x1b[0m', `\n ✅️ ${projectName} is ready for production\n`);
+ console.info('\x1b[36m%s\x1b[0m', `\n ✅️ ${projectName} is ready for production\n`)
if (cache) {
- console.log('Storing cache...');
+ console.info('Storing cache...')
} else {
- process.exit();
+ process.exit()
}
-}
\ No newline at end of file
+}
diff --git a/client/client.js b/client/client.js
index 15f0a670..3e4b821d 100644
--- a/client/client.js
+++ b/client/client.js
@@ -55,7 +55,7 @@ client.processLifecycleQueues = async function processLifecycleQueues() {
let shouldScroll = router._hash
while (client.initiationQueue.length) {
const instance = client.initiationQueue.shift()
- instance.initiate && await instance.initiate()
+ instance.initiate && (await instance.initiate())
instance.initiated = true
instance.launch && instance.launch()
shouldUpdate = true
@@ -72,7 +72,7 @@ client.processLifecycleQueues = async function processLifecycleQueues() {
while (client.realHydrationQueue.length) {
shouldUpdate = true
const instance = client.realHydrationQueue.shift()
- instance.hydrate && await instance.hydrate()
+ instance.hydrate && (await instance.hydrate())
instance.hydrated = true
}
shouldUpdate && client.update()
@@ -86,7 +86,7 @@ client.processLifecycleQueues = async function processLifecycleQueues() {
for (const key in client.instances) {
const instance = client.instances[key]
if (!client.renewalQueue.includes(instance) && !instance.terminated) {
- instance.terminate && await instance.terminate()
+ instance.terminate && (await instance.terminate())
if (instance.persistent) {
instance.terminated = true
} else {
@@ -97,4 +97,4 @@ client.processLifecycleQueues = async function processLifecycleQueues() {
router._changed = false
}
-export default client
\ No newline at end of file
+export default client
diff --git a/client/context.js b/client/context.js
index c2a99868..8e819a06 100644
--- a/client/context.js
+++ b/client/context.js
@@ -1,27 +1,27 @@
-import client from './client';
-import { generateObjectProxy } from './objectProxyHandler';
-import state from './state';
+import client from './client'
+import { generateObjectProxy } from './objectProxyHandler'
+import state from './state'
-const context = {};
+const context = {}
for (const key of Object.keys(state.context)) {
- context[key] = generateObjectProxy(key, state.context[key]);
+ context[key] = generateObjectProxy(key, state.context[key])
}
const contextProxyHandler = {
set(target, name, value) {
- context[name] = generateObjectProxy(name, value);
- client.update();
- return true;
+ context[name] = generateObjectProxy(name, value)
+ client.update()
+ return true
},
get(target, name) {
- if (name === '_isProxy') return true;
- return target[name] === undefined ? context[name] : target[name];
- }
+ if (name === '_isProxy') return true
+ return target[name] === undefined ? context[name] : target[name]
+ },
}
export function generateContext(temporary) {
- return new Proxy(temporary, contextProxyHandler);
+ return new Proxy(temporary, contextProxyHandler)
}
-export default context;
\ No newline at end of file
+export default context
diff --git a/client/environment.js b/client/environment.js
index 246ce1f6..a80b4089 100644
--- a/client/environment.js
+++ b/client/environment.js
@@ -1,10 +1,10 @@
-import state from './state';
+import state from './state'
const environment = {
...state.environment,
client: true,
server: false,
- event: 'nullstack.environment'
-};
+ event: 'nullstack.environment',
+}
-export default environment;
\ No newline at end of file
+export default environment
diff --git a/client/events.js b/client/events.js
index 7ac83483..2027fb56 100644
--- a/client/events.js
+++ b/client/events.js
@@ -1,6 +1,6 @@
-import router from './router'
-import { camelize } from '../shared/string';
import noop from '../shared/noop'
+import { camelize } from '../shared/string'
+import router from './router'
export const eventCallbacks = new WeakMap()
export const eventSubjects = new WeakMap()
@@ -8,9 +8,9 @@ export const eventDebouncer = new WeakMap()
function executeEvent(callback, subject, event, data) {
if (typeof callback === 'object') {
- Object.assign(subject.source, callback);
+ Object.assign(subject.source, callback)
} else {
- callback({ ...subject, event, data });
+ callback({ ...subject, event, data })
}
}
@@ -35,28 +35,28 @@ export function generateCallback(selector, name) {
router.url = subject.href
}
} else if (subject.default !== true) {
- event.preventDefault();
+ event.preventDefault()
}
debounce(selector, name, subject.debounce, () => {
const data = { ...subject.data }
for (const attribute in subject) {
if (attribute.startsWith('data-')) {
- const key = camelize(attribute.slice(5));
- data[key] = subject[attribute];
+ const key = camelize(attribute.slice(5))
+ data[key] = subject[attribute]
}
}
if (subject?.bind !== undefined) {
- const valueName = (subject.type === 'checkbox' || subject.type === 'radio') ? 'checked' : 'value'
+ const valueName = subject.type === 'checkbox' || subject.type === 'radio' ? 'checked' : 'value'
const object = subject.bind.object
const property = subject.bind.property
if (valueName === 'checked') {
- object[property] = event.target[valueName];
+ object[property] = event.target[valueName]
} else if (object[property] === true || object[property] === false) {
- object[property] = event.target[valueName] === 'true';
+ object[property] = event.target[valueName] === 'true'
} else if (typeof object[property] === 'number') {
- object[property] = +event.target[valueName] || 0;
+ object[property] = +event.target[valueName] || 0
} else {
- object[property] = event.target[valueName];
+ object[property] = event.target[valueName]
}
}
if (subject[name] === noop) return
@@ -69,4 +69,4 @@ export function generateCallback(selector, name) {
}
})
}
-};
\ No newline at end of file
+}
diff --git a/client/hydrate.js b/client/hydrate.js
index e07821ac..732aa7f2 100644
--- a/client/hydrate.js
+++ b/client/hydrate.js
@@ -1,30 +1,32 @@
-import { ref } from './ref';
-import { isFalse } from '../shared/nodes';
-import { anchorableElement } from './anchorableNode';
-import client from './client';
+import { isFalse } from '../shared/nodes'
+import { anchorableElement } from './anchorableNode'
+import client from './client'
+import { ref } from './ref'
let pool = []
function hydrateBody(selector, node) {
if (node?.attributes?.html) {
- anchorableElement(selector);
+ anchorableElement(selector)
}
node.element = selector
ref(node.attributes, selector)
for (const element of selector.childNodes) {
if ((element.tagName === 'TEXTAREA' || element.tagName === 'textarea') && element.childNodes.length === 0) {
- element.appendChild(document.createTextNode(''));
+ element.appendChild(document.createTextNode(''))
} else if (element.COMMENT_NODE === 8 && element.textContent === '#') {
pool.push(element.remove())
}
}
if (!node.children) return
- const limit = node.children.length;
+ const limit = node.children.length
for (let i = limit - 1; i > -1; i--) {
if (node.type !== 'head' && typeof selector?.childNodes?.[i] === 'undefined') {
console.error(
- `${node.type.toUpperCase()} expected tag ${node.children[i].type.toUpperCase()} to be child at index ${i} but instead found undefined. This error usually happens because of an invalid HTML hierarchy or changes in comparisons after serialization.`,
- selector
+ `${node.type.toUpperCase()} expected tag ${node.children[
+ i
+ ].type.toUpperCase()} to be child at index ${i} but instead found undefined. This error usually happens because of an invalid HTML hierarchy or changes in comparisons after serialization.`,
+ selector,
)
throw new Error('Virtual DOM does not match the DOM.')
}
@@ -35,7 +37,7 @@ function hydrateBody(selector, node) {
function hydrateHead() {
for (const node of client.nextHead) {
if (isFalse(node)) {
- node.element = pool.pop() || document.createComment("")
+ node.element = pool.pop() || document.createComment('')
client.head.append(node.element)
} else {
node.element = document.getElementById(node.attributes.id)
@@ -47,4 +49,4 @@ function hydrateHead() {
export default function hydrate(selector, node) {
hydrateBody(selector, node)
hydrateHead()
-}
\ No newline at end of file
+}
diff --git a/client/index.js b/client/index.js
index e43bea37..adcdf6a5 100644
--- a/client/index.js
+++ b/client/index.js
@@ -1,90 +1,90 @@
+import element from '../shared/element'
+import fragment from '../shared/fragment'
+import generateTree from '../shared/generateTree'
+import { loadPlugins, useClientPlugins } from '../shared/plugins'
+import client from './client'
+import context, { generateContext } from './context'
+import environment from './environment'
+import hydrate from './hydrate'
+import instanceProxyHandler, { instanceProxies } from './instanceProxyHandler'
+import invoke from './invoke'
+import page from './page'
+import params, { updateParams } from './params'
+import project from './project'
+import render from './render'
+import rerender from './rerender'
+import router from './router'
+import settings from './settings'
import state from './state'
-import element from '../shared/element';
-import fragment from '../shared/fragment';
-import generateTree from '../shared/generateTree';
-import { loadPlugins, useClientPlugins } from '../shared/plugins';
-import client from './client';
-import context, { generateContext } from './context';
-import environment from './environment';
-import instanceProxyHandler, { instanceProxies } from './instanceProxyHandler';
-import invoke from './invoke';
-import page from './page';
-import params, { updateParams } from './params';
-import project from './project';
-import render from './render';
-import rerender from './rerender';
-import hydrate from './hydrate';
-import router from './router';
-import settings from './settings';
-import worker from './worker';
import windowEvent from './windowEvent'
+import worker from './worker'
-context.page = page;
-context.router = router;
-context.settings = settings;
-context.worker = worker;
-context.params = params;
-context.project = project;
-context.environment = state.environment;
+context.page = page
+context.router = router
+context.settings = settings
+context.worker = worker
+context.params = params
+context.project = project
+context.environment = state.environment
-client.memory = state.instances;
+client.memory = state.instances
-const scope = client;
-scope.generateContext = generateContext;
-scope.context = context;
+const scope = client
+scope.generateContext = generateContext
+scope.context = context
-client.plugins = loadPlugins(scope);
+client.plugins = loadPlugins(scope)
export default class Nullstack {
- static element = element;
- static invoke = invoke;
- static fragment = fragment;
- static use = useClientPlugins;
+ static element = element
+ static invoke = invoke
+ static fragment = fragment
+ static use = useClientPlugins
static context = generateContext({})
static start(Starter) {
setTimeout(async () => {
window.addEventListener('popstate', () => {
- router._popState();
- });
+ router._popState()
+ })
if (client.initializer) {
- client.initializer = () => element(Starter);
+ client.initializer = () => element(Starter)
client.update()
return this.context
}
- client.routes = {};
- updateParams(router.url);
- client.currentInstance = null;
- client.initializer = () => element(Starter);
- client.selector = document.getElementById('application');
+ client.routes = {}
+ updateParams(router.url)
+ client.currentInstance = null
+ client.initializer = () => element(Starter)
+ client.selector = document.getElementById('application')
if (environment.mode === 'spa') {
- scope.plugins = loadPlugins(scope);
- worker.online = navigator.onLine;
- typeof context.start === 'function' && await context.start(context);
- context.environment = environment;
- client.virtualDom = await generateTree(client.initializer(), scope);
- const body = render(client.virtualDom);
- client.selector.replaceWith(body);
+ scope.plugins = loadPlugins(scope)
+ worker.online = navigator.onLine
+ typeof context.start === 'function' && (await context.start(context))
+ context.environment = environment
+ client.virtualDom = await generateTree(client.initializer(), scope)
+ const body = render(client.virtualDom)
+ client.selector.replaceWith(body)
client.selector = body
} else {
- client.virtualDom = await generateTree(client.initializer(), scope);
+ client.virtualDom = await generateTree(client.initializer(), scope)
hydrate(client.selector, client.virtualDom)
client.currentBody = client.nextBody
client.currentHead = client.nextHead
client.nextBody = {}
client.nextHead = []
- context.environment = environment;
- scope.plugins = loadPlugins(scope);
- worker.online = navigator.onLine;
- typeof context.start === 'function' && await context.start(context);
- client.nextVirtualDom = await generateTree(client.initializer(), scope);
- rerender();
+ context.environment = environment
+ scope.plugins = loadPlugins(scope)
+ worker.online = navigator.onLine
+ typeof context.start === 'function' && (await context.start(context))
+ client.nextVirtualDom = await generateTree(client.initializer(), scope)
+ rerender()
}
- client.processLifecycleQueues();
- delete state.context;
+ client.processLifecycleQueues()
+ delete state.context
}, 0)
- return this.context;
+ return this.context
}
prerendered = false
@@ -94,13 +94,13 @@ export default class Nullstack {
key = null
constructor() {
- const proxy = new Proxy(this, instanceProxyHandler);
+ const proxy = new Proxy(this, instanceProxyHandler)
instanceProxies.set(this, proxy)
- return proxy;
+ return proxy
}
render() {
- return false;
+ return false
}
}
@@ -109,7 +109,7 @@ if (module.hot) {
Nullstack.serverHashes ??= {}
Nullstack.serverPings = 0
Nullstack.clientPings = 0
- const socket = new WebSocket('ws' + router.base.slice(4) + '/ws');
+ const socket = new WebSocket(`ws${router.base.slice(4)}/ws`)
socket.onmessage = async function (e) {
const data = JSON.parse(e.data)
if (data.type === 'NULLSTACK_SERVER_STARTED') {
@@ -118,12 +118,12 @@ if (module.hot) {
window.location.reload()
}
}
- };
+ }
Nullstack.updateInstancesPrototypes = function updateInstancesPrototypes(klass, hash, serverHash) {
for (const key in context.instances) {
const instance = context.instances[key]
if (instance.constructor.hash === hash) {
- Object.setPrototypeOf(instance, klass.prototype);
+ Object.setPrototypeOf(instance, klass.prototype)
}
}
if (Nullstack.serverHashes[hash]) {
@@ -143,9 +143,9 @@ if (module.hot) {
if (client.skipHotReplacement) {
window.location.reload()
} else {
- Nullstack.start(klass);
- windowEvent('environment');
+ Nullstack.start(klass)
+ windowEvent('environment')
}
}
module.hot.decline()
-}
\ No newline at end of file
+}
diff --git a/client/instanceProxyHandler.js b/client/instanceProxyHandler.js
index fdf83cbd..6c68ac68 100644
--- a/client/instanceProxyHandler.js
+++ b/client/instanceProxyHandler.js
@@ -1,12 +1,12 @@
-import client from './client';
-import { generateContext } from './context';
-import { generateObjectProxy } from './objectProxyHandler';
+import client from './client'
+import { generateContext } from './context'
+import { generateObjectProxy } from './objectProxyHandler'
export const instanceProxies = new WeakMap()
const instanceProxyHandler = {
- get(target, name) {
- if (name === '_isProxy') return true;
+ get(target, name, receiver) {
+ if (name === '_isProxy') return true
if (target.constructor[name]?.name === '_invoke') return target.constructor[name].bind(target.constructor)
if (typeof target[name] === 'function' && name !== 'constructor') {
const proxy = instanceProxies.get(target)
@@ -15,23 +15,23 @@ const instanceProxyHandler = {
}
const { [name]: named } = {
[name]: (args) => {
- const context = generateContext({ ...target._attributes, ...args });
- return target[name].call(proxy, context);
- }
+ const context = generateContext({ ...target._attributes, ...args })
+ return target[name].call(proxy, context)
+ },
}
- return named;
+ return named
}
- return Reflect.get(...arguments);
+ return Reflect.get(target, name, receiver)
},
set(target, name, value) {
if (!name.startsWith('_')) {
- target[name] = generateObjectProxy(name, value);
- client.update();
+ target[name] = generateObjectProxy(name, value)
+ client.update()
} else {
- target[name] = value;
+ target[name] = value
}
- return true;
- }
+ return true
+ },
}
-export default instanceProxyHandler;
\ No newline at end of file
+export default instanceProxyHandler
diff --git a/client/invoke.js b/client/invoke.js
index 5c60b046..c5b78d4c 100644
--- a/client/invoke.js
+++ b/client/invoke.js
@@ -1,20 +1,20 @@
-import deserialize from '../shared/deserialize';
-import prefix from '../shared/prefix';
-import page from './page';
-import worker from './worker';
+import deserialize from '../shared/deserialize'
+import prefix from '../shared/prefix'
+import page from './page'
+import worker from './worker'
export default function invoke(name, hash) {
return async function _invoke(params = {}) {
- let payload;
- worker.fetching = true;
+ let payload
+ worker.fetching = true
if (Object.isFrozen(worker.queues[name])) {
- worker.queues[name] = [params];
+ worker.queues[name] = [params]
} else {
- worker.queues[name] = [...worker.queues[name], params];
+ worker.queues[name] = [...worker.queues[name], params]
}
- const finalHash = hash === this.hash ? hash : `${hash}-${this.hash}`;
- let url = `${worker.api}/${prefix}/${finalHash}/${name}.json`;
- let body = JSON.stringify(params || {});
+ const finalHash = hash === this.hash ? hash : `${hash}-${this.hash}`
+ let url = `${worker.api}/${prefix}/${finalHash}/${name}.json`
+ const body = JSON.stringify(params || {})
const options = {
headers: worker.headers,
mode: 'cors',
@@ -24,35 +24,35 @@ export default function invoke(name, hash) {
referrerPolicy: 'no-referrer',
}
if (/get[A-Z]([*]*)/.test(name)) {
- options.method = 'GET';
- url += `?payload=${encodeURIComponent(body)}`;
+ options.method = 'GET'
+ url += `?payload=${encodeURIComponent(body)}`
} else {
- options.body = body;
+ options.body = body
if (/patch[A-Z]([*]*)/.test(name)) {
- options.method = 'PATCH';
+ options.method = 'PATCH'
} else if (/put[A-Z]([*]*)/.test(name)) {
- options.method = 'PUT';
+ options.method = 'PUT'
} else if (/delete[A-Z]([*]*)/.test(name)) {
- options.method = 'DELETE';
+ options.method = 'DELETE'
} else {
- options.method = 'POST';
+ options.method = 'POST'
}
}
try {
- const response = await fetch(url, options);
- page.status = response.status;
- const text = await response.text();
- payload = deserialize(text).result;
- worker.responsive = true;
+ const response = await fetch(url, options)
+ page.status = response.status
+ const text = await response.text()
+ payload = deserialize(text).result
+ worker.responsive = true
} catch (e) {
- worker.responsive = false;
+ worker.responsive = false
}
if (worker.queues[name]?.length === 1) {
- delete worker.queues[name];
+ delete worker.queues[name]
} else {
- worker.queues[name] = worker.queues[name].filter((task) => task !== params);
+ worker.queues[name] = worker.queues[name].filter((task) => task !== params)
}
- worker.fetching = !!Object.keys(worker.queues).length;
- return payload;
+ worker.fetching = !!Object.keys(worker.queues).length
+ return payload
}
-}
\ No newline at end of file
+}
diff --git a/client/objectProxyHandler.js b/client/objectProxyHandler.js
index 0c544440..c269bfb7 100644
--- a/client/objectProxyHandler.js
+++ b/client/objectProxyHandler.js
@@ -1,21 +1,21 @@
-import client from './client';
+import client from './client'
const objectProxyHandler = {
set(target, name, value) {
if (isProxyable(name, value)) {
- target[name] = new Proxy(value, this);
+ target[name] = new Proxy(value, this)
} else {
- target[name] = value;
+ target[name] = value
}
if (!name.startsWith('_')) {
- client.update();
+ client.update()
}
- return true;
+ return true
+ },
+ get(target, name, receiver) {
+ if (name === '_isProxy') return true
+ return Reflect.get(target, name, receiver)
},
- get(target, name) {
- if (name === '_isProxy') return true;
- return Reflect.get(...arguments);
- }
}
function isProxyable(name, value) {
@@ -28,15 +28,14 @@ function isProxyable(name, value) {
export function generateObjectProxy(name, value) {
if (isProxyable(name, value)) {
- if (typeof (value) === 'object') {
+ if (typeof value === 'object') {
for (const key of Object.keys(value)) {
- value[key] = generateObjectProxy(key, value[key]);
+ value[key] = generateObjectProxy(key, value[key])
}
}
- return new Proxy(value, objectProxyHandler);
- } else {
- return value;
+ return new Proxy(value, objectProxyHandler)
}
+ return value
}
-export default objectProxyHandler;
\ No newline at end of file
+export default objectProxyHandler
diff --git a/client/page.js b/client/page.js
index bb5e5417..c0ae045b 100644
--- a/client/page.js
+++ b/client/page.js
@@ -1,28 +1,28 @@
-import client from './client';
-import windowEvent from './windowEvent';
-import state from './state';
+import client from './client'
+import state from './state'
+import windowEvent from './windowEvent'
const page = {
...state.page,
- event: 'nullstack.page'
+ event: 'nullstack.page',
}
-delete state.page;
+delete state.page
const pageProxyHandler = {
- set(target, name, value) {
+ set(target, name, value, receiver) {
if (name === 'title') {
- document.title = value;
+ document.title = value
}
- const result = Reflect.set(...arguments);
+ const result = Reflect.set(target, name, value, receiver)
if (name === 'title') {
- windowEvent('page');
+ windowEvent('page')
}
- client.update();
- return result;
- }
+ client.update()
+ return result
+ },
}
-const proxy = new Proxy(page, pageProxyHandler);
+const proxy = new Proxy(page, pageProxyHandler)
-export default proxy;
\ No newline at end of file
+export default proxy
diff --git a/client/params.js b/client/params.js
index 323b6d6f..783ac415 100644
--- a/client/params.js
+++ b/client/params.js
@@ -1,38 +1,38 @@
-import router from './router';
-import getQueryStringParams from '../shared/getQueryStringParams';
-import seserializeParam from '../shared/serializeParam';
-import serializeSearch from '../shared/serializeSearch';
-import segments, { resetSegments } from './segments';
-import state from './state';
+import getQueryStringParams from '../shared/getQueryStringParams'
+import seserializeParam from '../shared/serializeParam'
+import serializeSearch from '../shared/serializeSearch'
+import router from './router'
+import segments, { resetSegments } from './segments'
+import state from './state'
const paramsProxyHandler = {
set(target, name, value) {
- const serializedValue = seserializeParam(value);
- target[name] = serializedValue;
- const search = serializeSearch(target);
- router.url = router.path + (search ? '?' : '') + search;
- return true;
+ const serializedValue = seserializeParam(value)
+ target[name] = serializedValue
+ const search = serializeSearch(target)
+ router.url = router.path + (search ? '?' : '') + search
+ return true
},
get(target, name) {
- if (target[name] === false) return false;
- if (segments[name] === false) return false;
- return target[name] || segments[name] || '';
- }
+ if (target[name] === false) return false
+ if (segments[name] === false) return false
+ return target[name] || segments[name] || ''
+ },
}
-const params = { ...state.params };
+const params = { ...state.params }
-delete state.params;
+delete state.params
-const proxy = new Proxy(params, paramsProxyHandler);
+const proxy = new Proxy(params, paramsProxyHandler)
export function updateParams(query) {
- resetSegments();
- const delta = getQueryStringParams(query);
+ resetSegments()
+ const delta = getQueryStringParams(query)
for (const key of Object.keys({ ...delta, ...params })) {
- params[key] = delta[key];
+ params[key] = delta[key]
}
- return proxy;
+ return proxy
}
-export default proxy;
\ No newline at end of file
+export default proxy
diff --git a/client/project.js b/client/project.js
index f4e426ed..f3ad5e87 100644
--- a/client/project.js
+++ b/client/project.js
@@ -1,9 +1,9 @@
import state from './state'
-const project = { ...state.project };
+const project = { ...state.project }
-delete state.project;
+delete state.project
-Object.freeze(project);
+Object.freeze(project)
-export default project;
\ No newline at end of file
+export default project
diff --git a/client/ref.js b/client/ref.js
index 5cb9775f..d80a332d 100644
--- a/client/ref.js
+++ b/client/ref.js
@@ -24,4 +24,4 @@ export function reref(attributes, element) {
const map = refMap.get(attributes.ref.object)
if (map?.[attributes.ref.property]) return
setup(attributes, element)
-}
\ No newline at end of file
+}
diff --git a/client/render.js b/client/render.js
index 6ca7e134..e4b6276d 100644
--- a/client/render.js
+++ b/client/render.js
@@ -1,57 +1,56 @@
-import { isFalse, isText } from '../shared/nodes';
-import { anchorableElement } from './anchorableNode';
+import generateTruthyString from '../shared/generateTruthyString'
+import { isFalse, isText } from '../shared/nodes'
+import { anchorableElement } from './anchorableNode'
import { eventCallbacks, eventSubjects, generateCallback } from './events'
import { ref } from './ref'
-import generateTruthyString from '../shared/generateTruthyString';
export default function render(node, options) {
-
if (isFalse(node) || node.type === 'head') {
- node.element = document.createComment("");
+ node.element = document.createComment('')
return node.element
}
if (isText(node)) {
- node.element = document.createTextNode(node.text);
+ node.element = document.createTextNode(node.text)
return node.element
}
- const svg = (options && options.svg) || node.type === 'svg';
+ const svg = (options && options.svg) || node.type === 'svg'
if (svg) {
- node.element = document.createElementNS("http://www.w3.org/2000/svg", node.type);
+ node.element = document.createElementNS('http://www.w3.org/2000/svg', node.type)
} else {
- node.element = document.createElement(node.type);
+ node.element = document.createElement(node.type)
}
ref(node.attributes, node.element)
- for (let name in node.attributes) {
+ for (const name in node.attributes) {
if (name === 'debounce') continue
if (name === 'html') {
- node.element.innerHTML = node.attributes[name];
- node.head || anchorableElement(node.element);
+ node.element.innerHTML = node.attributes[name]
+ node.head || anchorableElement(node.element)
} else if (name.startsWith('on')) {
if (node.attributes[name] !== undefined) {
- const eventName = name.substring(2);
+ const eventName = name.substring(2)
const callback = generateCallback(node.element, name)
- node.element.addEventListener(eventName, callback);
+ node.element.addEventListener(eventName, callback)
eventCallbacks.set(node.element, callback)
eventSubjects.set(node.element, node.attributes)
}
} else {
- let nodeValue;
+ let nodeValue
if ((name === 'class' || name === 'style') && Array.isArray(node.attributes[name])) {
nodeValue = generateTruthyString(node.attributes[name])
} else {
nodeValue = node.attributes[name]
}
- const type = typeof nodeValue;
+ const type = typeof nodeValue
if (type !== 'object' && type !== 'function') {
- if (name != 'value' && nodeValue === true) {
- node.element.setAttribute(name, '');
+ if (name !== 'value' && nodeValue === true) {
+ node.element.setAttribute(name, '')
} else if (name === 'value' || (nodeValue !== false && nodeValue !== null && nodeValue !== undefined)) {
- node.element.setAttribute(name, nodeValue);
+ node.element.setAttribute(name, nodeValue)
}
}
}
@@ -59,15 +58,14 @@ export default function render(node, options) {
if (!node.attributes.html) {
for (let i = 0; i < node.children.length; i++) {
- const child = render(node.children[i], { svg });
- node.element.appendChild(child);
+ const child = render(node.children[i], { svg })
+ node.element.appendChild(child)
}
if (node.type === 'select') {
- node.element.value = node.attributes.value;
+ node.element.value = node.attributes.value
}
}
- return node.element;
-
-}
\ No newline at end of file
+ return node.element
+}
diff --git a/client/rerender.js b/client/rerender.js
index 870c27a8..e5f03f2e 100644
--- a/client/rerender.js
+++ b/client/rerender.js
@@ -1,63 +1,63 @@
-import { isUndefined, isFalse, isText } from '../shared/nodes';
-import { anchorableElement } from './anchorableNode';
-import client from './client';
-import render from './render';
-import { generateCallback, eventCallbacks, eventSubjects } from './events'
-import generateTruthyString from '../shared/generateTruthyString';
-import { reref } from './ref';
+import generateTruthyString from '../shared/generateTruthyString'
+import { isFalse, isText, isUndefined } from '../shared/nodes'
+import { anchorableElement } from './anchorableNode'
+import client from './client'
+import { eventCallbacks, eventSubjects, generateCallback } from './events'
+import { reref } from './ref'
+import render from './render'
function updateAttributes(selector, currentAttributes, nextAttributes) {
- const attributeNames = Object.keys({ ...currentAttributes, ...nextAttributes });
+ const attributeNames = Object.keys({ ...currentAttributes, ...nextAttributes })
for (const name of attributeNames) {
if (name === 'debounce') continue
if (name === 'ref' && nextAttributes?.ref?.property) {
reref(nextAttributes, selector)
} else if (name === 'html') {
if (nextAttributes[name] !== currentAttributes[name]) {
- selector.innerHTML = nextAttributes[name];
- anchorableElement(selector);
+ selector.innerHTML = nextAttributes[name]
+ anchorableElement(selector)
}
} else if (name === 'checked' || name === 'value') {
if (nextAttributes[name] !== currentAttributes[name] && nextAttributes[name] !== selector[name]) {
- selector[name] = nextAttributes[name];
+ selector[name] = nextAttributes[name]
}
} else if (name.startsWith('on')) {
- const eventName = name.substring(2);
+ const eventName = name.substring(2)
if (eventCallbacks.has(selector) && !nextAttributes[name]) {
- selector.removeEventListener(eventName, eventCallbacks.get(selector));
+ selector.removeEventListener(eventName, eventCallbacks.get(selector))
}
if (nextAttributes[name]) {
if (!eventCallbacks.has(selector)) {
const callback = generateCallback(selector, name)
- selector.addEventListener(eventName, callback);
+ selector.addEventListener(eventName, callback)
eventCallbacks.set(selector, callback)
}
eventSubjects.set(selector, nextAttributes)
}
} else {
- let currentValue;
+ let currentValue
if ((name === 'class' || name === 'style') && Array.isArray(currentAttributes[name])) {
currentValue = generateTruthyString(currentAttributes[name])
} else {
currentValue = currentAttributes[name]
}
- let nextValue;
+ let nextValue
if ((name === 'class' || name === 'style') && Array.isArray(nextAttributes[name])) {
nextValue = generateTruthyString(nextAttributes[name])
} else {
nextValue = nextAttributes[name]
}
- const type = typeof nextValue;
+ const type = typeof nextValue
if (type !== 'object' && type !== 'function') {
if (currentValue !== undefined && nextValue === undefined) {
- selector.removeAttribute(name);
+ selector.removeAttribute(name)
} else if (currentValue !== nextValue) {
- if (name != 'value' && nextValue === false || nextValue === null || nextValue === undefined) {
- selector.removeAttribute(name);
- } else if (name != 'value' && nextValue === true) {
- selector.setAttribute(name, '');
+ if ((name !== 'value' && nextValue === false) || nextValue === null || nextValue === undefined) {
+ selector.removeAttribute(name)
+ } else if (name !== 'value' && nextValue === true) {
+ selector.setAttribute(name, '')
} else {
- selector.setAttribute(name, nextValue);
+ selector.setAttribute(name, nextValue)
}
}
}
@@ -67,7 +67,7 @@ function updateAttributes(selector, currentAttributes, nextAttributes) {
function updateHeadChild(current, next) {
if (isUndefined(current) && !isUndefined(next)) {
- const nextSelector = render(next);
+ const nextSelector = render(next)
client.head.append(nextSelector)
return
}
@@ -80,7 +80,7 @@ function updateHeadChild(current, next) {
return
}
if (current.type !== next.type) {
- const nextSelector = render(next);
+ const nextSelector = render(next)
current.element.replaceWith(nextSelector)
return
}
@@ -88,24 +88,23 @@ function updateHeadChild(current, next) {
}
function updateHeadChildren(currentChildren, nextChildren) {
- const limit = Math.max(currentChildren.length, nextChildren.length);
+ const limit = Math.max(currentChildren.length, nextChildren.length)
for (let i = 0; i < limit; i++) {
updateHeadChild(currentChildren[i], nextChildren[i])
}
}
function _rerender(current, next) {
-
const selector = current.element
next.element = current.element
if (isFalse(current) && isFalse(next)) {
- return;
+ return
}
if (current.type !== next.type) {
- const nextSelector = render(next);
- selector.replaceWith(nextSelector);
+ const nextSelector = render(next)
+ selector.replaceWith(nextSelector)
return
}
@@ -118,37 +117,36 @@ function _rerender(current, next) {
if (isText(current) && isText(next)) {
if (current.text !== next.text) {
- selector.textContent = next.text;
+ selector.textContent = next.text
}
- return;
+ return
}
if (!next.attributes.html) {
- const limit = Math.max(current.children.length, next.children.length);
+ const limit = Math.max(current.children.length, next.children.length)
if (next.children.length > current.children.length) {
for (let i = 0; i < current.children.length; i++) {
- _rerender(current.children[i], next.children[i]);
+ _rerender(current.children[i], next.children[i])
}
for (let i = current.children.length; i < next.children.length; i++) {
- const nextSelector = render(next.children[i]);
- selector.appendChild(nextSelector);
+ const nextSelector = render(next.children[i])
+ selector.appendChild(nextSelector)
}
} else if (current.children.length > next.children.length) {
for (let i = 0; i < next.children.length; i++) {
- _rerender(current.children[i], next.children[i]);
+ _rerender(current.children[i], next.children[i])
}
for (let i = current.children.length - 1; i >= next.children.length; i--) {
selector.childNodes[i].remove()
}
} else {
for (let i = limit - 1; i > -1; i--) {
- _rerender(current.children[i], next.children[i]);
+ _rerender(current.children[i], next.children[i])
}
}
}
updateAttributes(selector, current.attributes, next.attributes)
-
}
export default function rerender() {
diff --git a/client/router.js b/client/router.js
index 28e2415d..cbf1e536 100644
--- a/client/router.js
+++ b/client/router.js
@@ -1,91 +1,91 @@
-import extractLocation from '../shared/extractLocation';
-import client from './client';
-import environment from './environment';
-import page from './page';
-import { updateParams } from './params';
-import segments from './segments';
-import windowEvent from './windowEvent';
-import worker from './worker';
+import extractLocation from '../shared/extractLocation'
+import client from './client'
+import environment from './environment'
+import page from './page'
+import { updateParams } from './params'
+import segments from './segments'
+import windowEvent from './windowEvent'
+import worker from './worker'
-let redirectTimer = null;
+let redirectTimer = null
class Router {
event = 'nullstack.router';
- previous = null;
- _changed = false;
- _segments = segments;
+ previous = null
+ _changed = false
+ _segments = segments
constructor() {
- const { hash, url } = extractLocation(window.location.pathname + window.location.search);
- this._url = url;
- this._hash = hash;
+ const { hash, url } = extractLocation(window.location.pathname + window.location.search)
+ this._url = url
+ this._hash = hash
}
async _popState() {
- const { urlWithHash } = extractLocation(window.location.pathname + window.location.search);
- await this._update(urlWithHash, false);
+ const { urlWithHash } = extractLocation(window.location.pathname + window.location.search)
+ await this._update(urlWithHash, false)
}
async _update(target, push) {
- const { url, path, hash, urlWithHash } = extractLocation(target);
+ const { url, path, hash, urlWithHash } = extractLocation(target)
if (url === this._url && this._hash === hash) return
- this.previous = this.url;
- clearTimeout(redirectTimer);
+ this.previous = this.url
+ clearTimeout(redirectTimer)
redirectTimer = setTimeout(async () => {
- page.status = 200;
+ page.status = 200
if (environment.mode === 'ssg') {
- worker.fetching = true;
- const api = '/index.json';
- const endpoint = path === '/' ? api : path + api;
+ worker.fetching = true
+ const api = '/index.json'
+ const endpoint = path === '/' ? api : path + api
try {
- const response = await fetch(endpoint);
- const payload = await response.json(url);
- client.memory = payload.instances;
+ const response = await fetch(endpoint)
+ const payload = await response.json(url)
+ client.memory = payload.instances
for (const key in payload.page) {
- page[key] = payload.page[key];
+ page[key] = payload.page[key]
}
- worker.responsive = true;
+ worker.responsive = true
} catch (error) {
- worker.responsive = false;
+ worker.responsive = false
}
- worker.fetching = false;
+ worker.fetching = false
}
if (push) {
- history.pushState({}, document.title, urlWithHash);
+ history.pushState({}, document.title, urlWithHash)
}
- this._url = url;
- this._hash = hash;
- this._changed = true;
- updateParams(url);
- client.update();
- windowEvent('router');
- }, 0);
+ this._url = url
+ this._hash = hash
+ this._changed = true
+ updateParams(url)
+ client.update()
+ windowEvent('router')
+ }, 0)
}
async _redirect(target) {
if (/^(\w+:|\/\/)([^.]+.)/.test(target)) {
- return window.location.href = target;
+ return (window.location.href = target)
}
- const absoluteUrl = new URL(target, document.baseURI);
- await this._update(absoluteUrl.pathname + absoluteUrl.search + absoluteUrl.hash, true);
+ const absoluteUrl = new URL(target, document.baseURI)
+ await this._update(absoluteUrl.pathname + absoluteUrl.search + absoluteUrl.hash, true)
window.scroll(0, 0)
}
get url() {
- return this._url;
+ return this._url
}
set url(target) {
- this._redirect(target);
+ this._redirect(target)
}
get path() {
- return extractLocation(this._url).path;
+ return extractLocation(this._url).path
}
set path(target) {
- this._redirect(target + window.location.search);
+ this._redirect(target + window.location.search)
}
get base() {
@@ -96,6 +96,6 @@ class Router {
}
-const router = new Router();
+const router = new Router()
-export default router;
\ No newline at end of file
+export default router
diff --git a/client/segments.js b/client/segments.js
index 21ee36ce..7aabab6e 100644
--- a/client/segments.js
+++ b/client/segments.js
@@ -1,8 +1,8 @@
-const segments = {};
-export default segments;
+const segments = {}
+export default segments
export function resetSegments() {
- for(const key in segments) {
- delete segments[key];
+ for (const key in segments) {
+ delete segments[key]
}
-}
\ No newline at end of file
+}
diff --git a/client/settings.js b/client/settings.js
index 90d5daf1..7f0adedd 100644
--- a/client/settings.js
+++ b/client/settings.js
@@ -1,8 +1,8 @@
-import state from './state';
+import state from './state'
-const settings = { ...state.settings };
-delete state.settings;
+const settings = { ...state.settings }
+delete state.settings
-Object.freeze(settings);
+Object.freeze(settings)
-export default settings;
\ No newline at end of file
+export default settings
diff --git a/client/state.js b/client/state.js
index 5d2cf3bd..5ba1af33 100644
--- a/client/state.js
+++ b/client/state.js
@@ -1,5 +1,5 @@
-import deserialize from '../shared/deserialize';
+import deserialize from '../shared/deserialize'
-const state = deserialize(decodeURIComponent(document.querySelector(`[name=nullstack]`).content));
+const state = deserialize(decodeURIComponent(document.querySelector(`[name=nullstack]`).content))
-export default state;
\ No newline at end of file
+export default state
diff --git a/client/windowEvent.js b/client/windowEvent.js
index 601e51bf..54a8b280 100644
--- a/client/windowEvent.js
+++ b/client/windowEvent.js
@@ -1,9 +1,9 @@
-let timer = null;
+const timer = null
export default function windowEvent(name) {
- clearTimeout(timer);
+ clearTimeout(timer)
setTimeout(() => {
- const event = new Event('nullstack.' + name);
- window.dispatchEvent(event);
- }, 0);
-}
\ No newline at end of file
+ const event = new Event(`nullstack.${name}`)
+ window.dispatchEvent(event)
+ }, 0)
+}
diff --git a/client/worker.js b/client/worker.js
index 492a94a6..3a079dfd 100644
--- a/client/worker.js
+++ b/client/worker.js
@@ -1,71 +1,68 @@
-import client from './client';
-import environment from './environment';
-import router from './router';
+import client from './client'
+import environment from './environment'
+import router from './router'
import state from './state'
-const worker = { ...state.worker };
-delete state.worker;
+const worker = { ...state.worker }
+delete state.worker
-const emptyQueue = Object.freeze([]);
+const emptyQueue = Object.freeze([])
const queuesProxyHandler = {
set(target, name, value) {
- target[name] = value;
- client.update();
- return true;
+ target[name] = value
+ client.update()
+ return true
},
get(target, name) {
- return target[name] || emptyQueue;
- }
+ return target[name] || emptyQueue
+ },
}
-worker.queues = new Proxy({}, queuesProxyHandler);
+worker.queues = new Proxy({}, queuesProxyHandler)
const workerProxyHandler = {
set(target, name, value) {
if (target[name] !== value) {
- target[name] = value;
- client.update();
+ target[name] = value
+ client.update()
}
- return true;
- }
+ return true
+ },
}
-const proxy = new Proxy(worker, workerProxyHandler);
+const proxy = new Proxy(worker, workerProxyHandler)
-if (worker.enabled) {
-
- window.addEventListener('beforeinstallprompt', function (event) {
- event.preventDefault();
- proxy.installation = event;
- });
-
- async function register() {
- if ('serviceWorker' in navigator) {
- const request = `/service-worker.js`;
- try {
- proxy.registration = await navigator.serviceWorker.register(request, { scope: '/' });
- } catch (error) {
- console.log(error);
- };
+async function register() {
+ if ('serviceWorker' in navigator) {
+ const request = `/service-worker.js`
+ try {
+ proxy.registration = await navigator.serviceWorker.register(request, { scope: '/' })
+ } catch (error) {
+ console.error(error)
}
- };
-
- register();
+ }
+}
+if (worker.enabled) {
+ window.addEventListener('beforeinstallprompt', function (event) {
+ event.preventDefault()
+ proxy.installation = event
+ })
+ register()
}
window.addEventListener('online', () => {
- proxy.online = true;
+ proxy.online = true
if (environment.mode === 'ssg') {
- router._update(router.url);
+ router._update(router.url)
} else {
- proxy.responsive = true;
+ proxy.responsive = true
}
-});
+})
window.addEventListener('offline', () => {
- proxy.online = false;
-});
+ proxy.online = false
+})
-export default proxy;
\ No newline at end of file
+export default proxy
diff --git a/loaders/add-source-to-node.js b/loaders/add-source-to-node.js
index 48d89ce5..435306c6 100644
--- a/loaders/add-source-to-node.js
+++ b/loaders/add-source-to-node.js
@@ -1,10 +1,41 @@
+const parse = require('@babel/parser').parse
+const traverse = require('@babel/traverse').default
+
module.exports = function (source) {
- let tags = source.split('<');
- return tags.map((tag) => {
- match = tag.match(/\ on([a-z]*?)\=\{(.*?)\}/);
- if (match && tag.indexOf('source={') === -1) {
- return tag.substring(0, match.index) + ' source={this}' + tag.substring(match.index);
+ this.cacheable && this.cacheable()
+ const uniquePositions = new Set()
+ const ast = parse(source, {
+ sourceType: 'module',
+ plugins: ['classProperties', 'jsx', 'typescript'],
+ })
+ traverse(ast, {
+ JSXIdentifier(path) {
+ if (path.parent.type === 'JSXAttribute') {
+ if (path.node.name.startsWith('on')) {
+ const element = path.findParent((p) => p.type === 'JSXOpeningElement' && p.node.attributes)
+ const hasSource = element.node.attributes.find((a) => a.name.name === 'source')
+ if (!hasSource) {
+ const start = element.node.attributes[0].start
+ uniquePositions.add(start)
+ }
+ }
+ }
+ },
+ })
+ if (uniquePositions.size === 0) return source
+ const positions = [...uniquePositions]
+ positions.reverse()
+ positions.push(0)
+ const outputs = []
+ let last
+ for (const position of positions) {
+ const code = source.slice(position, last)
+ last = position
+ outputs.push(code)
+ if (position) {
+ outputs.push(`source={this} `)
}
- return tag;
- }).join('<');
-}
\ No newline at end of file
+ }
+
+ return outputs.reverse().join('')
+}
diff --git a/loaders/ignore-import.js b/loaders/ignore-import.js
index 27ca5da3..a980b3a1 100644
--- a/loaders/ignore-import.js
+++ b/loaders/ignore-import.js
@@ -1,10 +1,10 @@
-'use strict';
+'use strict'
/*
Copyright (c) 2016 Cherry Ng
MIT Licensed
https://npmjs.com/package/ignore-loader
*/
-module.exports = function() {
- this.cacheable && this.cacheable();
- return '';
-}
\ No newline at end of file
+module.exports = function () {
+ this.cacheable && this.cacheable()
+ return ''
+}
diff --git a/loaders/inject-hmr.js b/loaders/inject-hmr.js
index 28fb5af8..24dd6ae2 100644
--- a/loaders/inject-hmr.js
+++ b/loaders/inject-hmr.js
@@ -1,11 +1,11 @@
-const parse = require('@babel/parser').parse;
-const traverse = require("@babel/traverse").default;
+const parse = require('@babel/parser').parse
+const traverse = require('@babel/traverse').default
module.exports = function (source) {
const ast = parse(source, {
sourceType: 'module',
- plugins: ['classProperties', 'jsx']
- });
+ plugins: ['classProperties', 'jsx'],
+ })
let klassName
let klassPath
traverse(ast, {
@@ -13,18 +13,18 @@ module.exports = function (source) {
if (path.node.property.name === 'start' && path.node.object && path.node.object.name === 'Nullstack') {
klassName = path.parent.arguments[0].name
}
- }
- });
+ },
+ })
if (!klassName) return source
traverse(ast, {
ImportDeclaration(path) {
if (path.node.specifiers[0].local.name === klassName) {
klassPath = path.node.source.extra.rawValue
}
- }
- });
+ },
+ })
- return source + `
+ return `${source}
if (module.hot) {
if (Nullstack.needsClientReload) {
window.location.reload()
@@ -36,4 +36,4 @@ module.exports = function (source) {
})
}
`
-}
\ No newline at end of file
+}
diff --git a/loaders/inject-nullstack.js b/loaders/inject-nullstack.js
index 396aca8c..e58d65c0 100644
--- a/loaders/inject-nullstack.js
+++ b/loaders/inject-nullstack.js
@@ -1,21 +1,21 @@
-const parse = require('@babel/parser').parse;
-const traverse = require("@babel/traverse").default;
+const parse = require('@babel/parser').parse
+const traverse = require('@babel/traverse').default
-module.exports = function(source) {
+module.exports = function (source) {
const ast = parse(source, {
sourceType: 'module',
- plugins: ['classProperties', 'jsx']
- });
- let shouldImport = true;
+ plugins: ['classProperties', 'jsx'],
+ })
+ let shouldImport = true
traverse(ast, {
ImportDeclaration(path) {
- if(path.node.source.value === 'nullstack') {
- shouldImport = false;
+ if (path.node.source.value === 'nullstack') {
+ shouldImport = false
}
- }
- });
- if(shouldImport) {
- source = `import Nullstack from 'nullstack'\n${source}`;
+ },
+ })
+ if (shouldImport) {
+ source = `import Nullstack from 'nullstack'\n${source}`
}
- return source;
-}
\ No newline at end of file
+ return source
+}
diff --git a/loaders/register-inner-components.js b/loaders/register-inner-components.js
index fa5f32c2..ab0db29e 100644
--- a/loaders/register-inner-components.js
+++ b/loaders/register-inner-components.js
@@ -1,47 +1,52 @@
-const parse = require('@babel/parser').parse;
-const traverse = require("@babel/traverse").default;
+const parse = require('@babel/parser').parse
+const traverse = require('@babel/traverse').default
module.exports = function (source) {
- const injections = {};
- const positions = [];
+ const injections = {}
+ const positions = []
const ast = parse(source, {
sourceType: 'module',
- plugins: ['classProperties', 'jsx', 'typescript']
- });
+ plugins: ['classProperties', 'jsx', 'typescript'],
+ })
traverse(ast, {
ClassMethod(path) {
- if (path.node.key.name.startsWith('render')) {
- function identify(subpath) {
- if (/^[A-Z]/.test(subpath.node.name)) {
- if (!path.scope.hasBinding(subpath.node.name)) {
- const start = path.node.body.body[0].start;
- if (!positions.includes(start)) {
- positions.push(start);
- }
- if (!injections[start]) {
- injections[start] = [];
- }
- if (!injections[start].includes(subpath.node.name)) {
- injections[start].push(subpath.node.name);
- }
+ function identify(subpath) {
+ if (/^[A-Z]/.test(subpath.node.name)) {
+ if (!path.scope.hasBinding(subpath.node.name)) {
+ const start = path.node.body.body[0].start
+ if (!positions.includes(start)) {
+ positions.push(start)
+ }
+ if (!injections[start]) {
+ injections[start] = []
+ }
+ if (!injections[start].includes(subpath.node.name)) {
+ injections[start].push(subpath.node.name)
}
}
}
- traverse(path.node, {
- JSXIdentifier: identify,
- Identifier: identify,
- }, path.scope, path);
}
- }
- });
- positions.reverse();
- positions.push(0);
- let outputs = [];
- let last;
+ if (path.node.key.name.startsWith('render')) {
+ traverse(
+ path.node,
+ {
+ JSXIdentifier: identify,
+ Identifier: identify,
+ },
+ path.scope,
+ path,
+ )
+ }
+ },
+ })
+ positions.reverse()
+ positions.push(0)
+ const outputs = []
+ let last
for (const position of positions) {
- let code = source.slice(position, last);
- last = position;
- outputs.push(code);
+ const code = source.slice(position, last)
+ last = position
+ outputs.push(code)
if (position) {
for (const injection of injections[position]) {
if (injection) {
@@ -50,5 +55,5 @@ module.exports = function (source) {
}
}
}
- return outputs.reverse().join('');
-}
\ No newline at end of file
+ return outputs.reverse().join('')
+}
diff --git a/loaders/register-static-from-server.js b/loaders/register-static-from-server.js
index c299c3c5..36b629e1 100644
--- a/loaders/register-static-from-server.js
+++ b/loaders/register-static-from-server.js
@@ -1,34 +1,34 @@
-const crypto = require('crypto');
-const parse = require('@babel/parser').parse;
-const traverse = require("@babel/traverse").default;
+const parse = require('@babel/parser').parse
+const traverse = require('@babel/traverse').default
+const crypto = require('crypto')
module.exports = function (source) {
- let hasClass = false;
- const legacyHash = crypto.createHash('md5').update(source).digest("hex");
+ let hasClass = false
+ const legacyHash = crypto.createHash('md5').update(source).digest('hex')
const id = this.resourcePath.replace(this.rootContext, '')
- const hash = crypto.createHash('md5').update(id).digest("hex");
- let klassName;
- let klassEnd;
- const methodNames = [];
+ const hash = crypto.createHash('md5').update(id).digest('hex')
+ let klassName
+ let klassEnd
+ const methodNames = []
const ast = parse(source, {
sourceType: 'module',
- plugins: ['classProperties', 'jsx']
- });
+ plugins: ['classProperties', 'jsx'],
+ })
traverse(ast, {
ClassDeclaration(path) {
- hasClass = true;
- klassEnd = path.node.end - 1;
- klassName = path.node.id.name;
+ hasClass = true
+ klassEnd = path.node.end - 1
+ klassName = path.node.id.name
},
ClassMethod(path) {
if (path.node.static && path.node.async && !path.node.key.name.startsWith('_')) {
- methodNames.push(path.node.key.name);
+ methodNames.push(path.node.key.name)
}
- }
- });
- if (!hasClass) return source;
- let output = source.substring(0, klassEnd);
- output += source.substring(klassEnd);
+ },
+ })
+ if (!hasClass) return source
+ let output = source.substring(0, klassEnd)
+ output += source.substring(klassEnd)
for (const methodName of methodNames) {
output += `\nNullstack.registry["${hash}.${methodName}"] = ${klassName}.${methodName};`
output += `\nNullstack.registry["${legacyHash}.${methodName}"] = ${klassName}.${methodName};`
@@ -37,5 +37,5 @@ module.exports = function (source) {
output += `\nNullstack.registry["${legacyHash}"] = ${klassName};`
output += `\n${klassName}.hash = "${hash}";`
output += `\n${klassName}.bindStaticFunctions(${klassName});`
- return output;
-}
\ No newline at end of file
+ return output
+}
diff --git a/loaders/remove-import-from-client.js b/loaders/remove-import-from-client.js
index d9e2e3ab..80d2d211 100644
--- a/loaders/remove-import-from-client.js
+++ b/loaders/remove-import-from-client.js
@@ -1,32 +1,32 @@
-const parse = require('@babel/parser').parse;
-const traverse = require("@babel/traverse").default;
+const parse = require('@babel/parser').parse
+const traverse = require('@babel/traverse').default
const parentTypes = ['ImportDefaultSpecifier', 'ImportSpecifier', 'ImportNamespaceSpecifier']
module.exports = function (source) {
const ast = parse(source, {
sourceType: 'module',
- plugins: ['classProperties', 'jsx']
- });
- const imports = {};
+ plugins: ['classProperties', 'jsx'],
+ })
+ const imports = {}
function findImports(path) {
if (path.node.local.name !== 'Nullstack') {
- const parent = path.findParent((path) => path.isImportDeclaration());
- const start = parent.node.loc.start.line;
- const end = parent.node.loc.end.line;
- const lines = new Array(end - start + 1).fill().map((d, i) => i + start);
- const key = lines.join('.');
- imports[path.node.local.name] = { lines, key };
+ const parent = path.findParent((p) => p.isImportDeclaration())
+ const start = parent.node.loc.start.line
+ const end = parent.node.loc.end.line
+ const lines = new Array(end - start + 1).fill().map((d, i) => i + start)
+ const key = lines.join('.')
+ imports[path.node.local.name] = { lines, key }
}
}
function findIdentifiers(path) {
if (parentTypes.indexOf(path.parent.type) === -1) {
- const target = imports[path.node.name];
+ const target = imports[path.node.name]
if (target) {
for (const name in imports) {
if (imports[name].key === target.key) {
if (path.parent.type !== 'MemberExpression' || path.parent.object?.type !== 'ThisExpression') {
- delete imports[name];
+ delete imports[name]
}
}
}
@@ -36,12 +36,17 @@ module.exports = function (source) {
traverse(ast, {
ImportSpecifier: findImports,
ImportDefaultSpecifier: findImports,
- ImportNamespaceSpecifier: findImports
- });
+ ImportNamespaceSpecifier: findImports,
+ })
traverse(ast, {
Identifier: findIdentifiers,
- JSXIdentifier: findIdentifiers
- });
- const lines = Object.keys(imports).map((name) => imports[name].lines).flat();
- return source.split(`\n`).filter((line, index) => !lines.includes(index + 1)).join(`\n`);
-}
\ No newline at end of file
+ JSXIdentifier: findIdentifiers,
+ })
+ const lines = Object.keys(imports)
+ .map((name) => imports[name].lines)
+ .flat()
+ return source
+ .split(`\n`)
+ .filter((line, index) => !lines.includes(index + 1))
+ .join(`\n`)
+}
diff --git a/loaders/remove-static-from-client.js b/loaders/remove-static-from-client.js
index 9a310dc2..3a999d4f 100644
--- a/loaders/remove-static-from-client.js
+++ b/loaders/remove-static-from-client.js
@@ -1,65 +1,65 @@
-const crypto = require('crypto');
-const parse = require('@babel/parser').parse;
-const traverse = require("@babel/traverse").default;
+const parse = require('@babel/parser').parse
+const traverse = require('@babel/traverse').default
+const crypto = require('crypto')
module.exports = function removeStaticFromClient(source) {
const id = this.resourcePath.replace(this.rootContext, '')
- const hash = crypto.createHash('md5').update(id).digest("hex");
+ const hash = crypto.createHash('md5').update(id).digest('hex')
let serverSource = ''
- let hashPosition;
- let klassName;
- const injections = {};
- const positions = [];
+ let hashPosition
+ let klassName
+ const injections = {}
+ const positions = []
const ast = parse(source, {
sourceType: 'module',
- plugins: ['classProperties', 'jsx']
- });
+ plugins: ['classProperties', 'jsx'],
+ })
traverse(ast, {
ClassDeclaration(path) {
- klassName = path.node.id.name;
+ klassName = path.node.id.name
},
ClassBody(path) {
- const start = path.node.body[0].start;
- hashPosition = start;
- positions.push(start);
+ const start = path.node.body[0].start
+ hashPosition = start
+ positions.push(start)
},
ClassMethod(path) {
if (path.node.static && path.node.async) {
- injections[path.node.start] = { end: path.node.end, name: path.node.key.name };
+ injections[path.node.start] = { end: path.node.end, name: path.node.key.name }
serverSource += source.slice(path.node.start, path.node.end)
if (!positions.includes(path.node.start)) {
- positions.push(path.node.start);
+ positions.push(path.node.start)
}
}
- }
- });
- positions.reverse();
- positions.push(0);
- let outputs = [];
- let last;
+ },
+ })
+ positions.reverse()
+ positions.push(0)
+ const outputs = []
+ let last
for (const position of positions) {
- let code = source.slice(position, last);
- last = position;
- const injection = injections[position];
+ let code = source.slice(position, last)
+ last = position
+ const injection = injections[position]
if (position && injection) {
- const location = injection.end - position;
+ const location = injection.end - position
if (injection.name.startsWith('_')) {
- code = code.substring(location).trimStart();
+ code = code.substring(location).trimStart()
} else {
- code = `static ${injection.name} = Nullstack.invoke('${injection.name}', '${hash}');` + code.substring(location);
+ code = `static ${injection.name} = Nullstack.invoke('${injection.name}', '${hash}');${code.substring(location)}`
}
- outputs.push(code);
+ outputs.push(code)
} else {
- outputs.push(code);
+ outputs.push(code)
}
if (position === hashPosition) {
- outputs.push(`static hash = '${hash}';\n\n `);
+ outputs.push(`static hash = '${hash}';\n\n `)
}
}
let newSource = outputs.reverse().join('')
if (klassName) {
- const serverHash = crypto.createHash('md5').update(serverSource).digest("hex");
- newSource += `\nif (module.hot) { module.hot.accept(); Nullstack.updateInstancesPrototypes(${klassName}, ${klassName}.hash, '${serverHash}') }`;
+ const serverHash = crypto.createHash('md5').update(serverSource).digest('hex')
+ newSource += `\nif (module.hot) { module.hot.accept(); Nullstack.updateInstancesPrototypes(${klassName}, ${klassName}.hash, '${serverHash}') }`
}
return newSource
-}
\ No newline at end of file
+}
diff --git a/loaders/string-replace.js b/loaders/string-replace.js
index 2600c201..3ac18f6a 100644
--- a/loaders/string-replace.js
+++ b/loaders/string-replace.js
@@ -3,18 +3,18 @@ Copyright (c) 2015 Valentyn Barmashyn
MIT Licensed
Original: https://npmjs.com/package/string-replace-loader
*/
-module.exports = function(source, map) {
- this.cacheable();
+module.exports = function (source, map) {
+ this.cacheable()
- const optionsArray = this.getOptions().multiple;
- let newSource = source;
+ const optionsArray = this.getOptions().multiple
+ let newSource = source
for (const options of optionsArray) {
newSource = newSource.replace(
new RegExp(options.search, options.search.flags || options.flags || ''),
- options.replace
- );
+ options.replace,
+ )
}
- this.callback(null, newSource, map);
-}
\ No newline at end of file
+ this.callback(null, newSource, map)
+}
diff --git a/loaders/transform-node-ref.js b/loaders/transform-node-ref.js
index 8be96e23..6b3a813a 100644
--- a/loaders/transform-node-ref.js
+++ b/loaders/transform-node-ref.js
@@ -1,13 +1,13 @@
-const parse = require('@babel/parser').parse;
-const traverse = require("@babel/traverse").default;
+const parse = require('@babel/parser').parse
+const traverse = require('@babel/traverse').default
const attributes = ['ref', 'bind']
module.exports = function removeStaticFromClient(source) {
const ast = parse(source, {
sourceType: 'module',
- plugins: ['classProperties', 'jsx', 'typescript']
- });
+ plugins: ['classProperties', 'jsx', 'typescript'],
+ })
const refs = []
traverse(ast, {
JSXAttribute(path) {
@@ -22,16 +22,16 @@ module.exports = function removeStaticFromClient(source) {
if (property.type === 'Identifier' && !expression.computed) {
refProperty = `'${refProperty}'`
}
- replacement = `${attribute}={{object: ${refObject}, property: ${refProperty}}}`
+ const replacement = `${attribute}={{object: ${refObject}, property: ${refProperty}}}`
refs.push({
start: path.node.start,
end: path.node.end,
- replacement
+ replacement,
})
}
}
- }
- });
+ },
+ })
if (refs.length === 0) return source
const sources = []
for (let i = 0; i <= refs.length; i++) {
@@ -46,4 +46,4 @@ module.exports = function removeStaticFromClient(source) {
}
}
return sources.join('')
-}
\ No newline at end of file
+}
diff --git a/logo.tsx b/logo.tsx
index 3097d049..4402ff99 100644
--- a/logo.tsx
+++ b/logo.tsx
@@ -6,8 +6,8 @@ type NullstackLogoProps = {
}
export default function Logo({ light, height, monotone }: NullstackLogoProps) {
- const themeColor = light ? '#fff' : '#2d3748';
- const accentColor = monotone ? themeColor : '#d22365';
+ const themeColor = light ? '#fff' : '#2d3748'
+ const accentColor = monotone ? themeColor : '#d22365'
return (
diff --git a/nullstack.js b/nullstack.js
index 56955b97..53d12948 100644
--- a/nullstack.js
+++ b/nullstack.js
@@ -1,3 +1,4 @@
-import Nullstack from "./{{NULLSTACK_ENVIRONMENT_NAME}}";
+// eslint-disable-next-line import/no-unresolved
+import Nullstack from './{{NULLSTACK_ENVIRONMENT_NAME}}'
-export default Nullstack;
\ No newline at end of file
+export default Nullstack
diff --git a/package.json b/package.json
index 8d8741fc..708e3994 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nullstack",
- "version": "0.16.6",
+ "version": "0.17.0",
"description": "Full-stack Javascript Components for one-dev armies",
"main": "nullstack.js",
"author": "Mortaro",
@@ -11,14 +11,17 @@
"nullstack": "./scripts/index.js"
},
"types": "./types/index.d.ts",
+ "scripts": {
+ "lint": "eslint \"**/*.{js,jsx,ts,tsx,njs,nts}\" --fix"
+ },
"dependencies": {
"@babel/core": "^7.18.13",
"@babel/parser": "7.17.12",
- "@babel/preset-env": "^7.18.10",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-export-default-from": "^7.18.10",
"@babel/plugin-transform-react-jsx": "^7.18.10",
"@babel/plugin-transform-typescript": "^7.18.12",
+ "@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@babel/traverse": "7.17.12",
"@swc/core": "1.2.179",
@@ -26,9 +29,10 @@
"body-parser": "1.20.0",
"commander": "8.3.0",
"copy-webpack-plugin": "^11.0.0",
- "cors": "2.8.5",
"css-loader": "6.7.1",
"dotenv": "8.6.0",
+ "eslint-plugin-jest": "^27.1.6",
+ "eslint-plugin-nullstack": "^0.0.10",
"express": "4.18.1",
"fs-extra": "10.1.0",
"mini-css-extract-plugin": "2.6.0",
diff --git a/plugins/anchorable.js b/plugins/anchorable.js
index 8769b4f9..92c5ae49 100644
--- a/plugins/anchorable.js
+++ b/plugins/anchorable.js
@@ -2,11 +2,7 @@ import noop from '../shared/noop'
function match(node) {
return (
- node &&
- node.type === 'a' &&
- node.attributes.href &&
- node.attributes.href.startsWith('/') &&
- !node.attributes.target
+ node && node.type === 'a' && node.attributes.href && node.attributes.href.startsWith('/') && !node.attributes.target
)
}
diff --git a/plugins/bindable.js b/plugins/bindable.js
index f30a8a4b..efcf938e 100644
--- a/plugins/bindable.js
+++ b/plugins/bindable.js
@@ -5,23 +5,23 @@ function match(node) {
}
function transform({ node, environment }) {
- if (!match(node)) return;
- const object = node.attributes.bind.object ?? {};
- const property = node.attributes.bind.property;
+ if (!match(node)) return
+ const object = node.attributes.bind.object ?? {}
+ const property = node.attributes.bind.property
if (node.type === 'textarea') {
- node.children = [object[property]];
+ node.children = [object[property]]
} else if (node.type === 'input' && node.attributes.type === 'checkbox') {
- node.attributes.checked = object[property];
+ node.attributes.checked = object[property]
} else {
- node.attributes.value = object[property] ?? '';
+ node.attributes.value = object[property] ?? ''
}
if (environment.client) {
if (node.attributes.type === 'checkbox' || node.attributes.type === 'radio') {
- node.attributes.onclick ??= noop;
+ node.attributes.onclick ??= noop
} else if (node.type !== 'input' && node.type !== 'textarea') {
- node.attributes.onchange ??= noop;
+ node.attributes.onchange ??= noop
} else {
- node.attributes.oninput ??= noop;
+ node.attributes.oninput ??= noop
}
}
}
diff --git a/plugins/parameterizable.js b/plugins/parameterizable.js
index 25700d16..a5e65c39 100644
--- a/plugins/parameterizable.js
+++ b/plugins/parameterizable.js
@@ -1,30 +1,26 @@
-import serializeParam from '../shared/serializeParam';
-import serializeSearch from '../shared/serializeSearch';
+import serializeParam from '../shared/serializeParam'
+import serializeSearch from '../shared/serializeSearch'
function match(node) {
- return (
- node &&
- node.attributes &&
- (node.attributes.params || node.attributes.path)
- )
+ return node && node.attributes && (node.attributes.params || node.attributes.path)
}
-function transform({node, router, params}) {
- if(!match(node)) return;
- let serializedParams;
- if(node.attributes.params) {
- serializedParams = {};
- for(const key in node.attributes.params) {
- serializedParams[key] = serializeParam(node.attributes.params[key]);
+function transform({ node, router, params }) {
+ if (!match(node)) return
+ let serializedParams
+ if (node.attributes.params) {
+ serializedParams = {}
+ for (const key in node.attributes.params) {
+ serializedParams[key] = serializeParam(node.attributes.params[key])
}
} else {
- serializedParams = params;
+ serializedParams = params
}
- const search = serializeSearch(serializedParams);
- const path = node.attributes.path || router.path;
- node.attributes.href = path + (search ? '?' : '') + search;
- delete node.attributes.path;
- delete node.attributes.params;
+ const search = serializeSearch(serializedParams)
+ const path = node.attributes.path || router.path
+ node.attributes.href = path + (search ? '?' : '') + search
+ delete node.attributes.path
+ delete node.attributes.params
}
-export default { transform, client: true, server: true }
\ No newline at end of file
+export default { transform, client: true, server: true }
diff --git a/plugins/routable.js b/plugins/routable.js
index a8c20737..8fbc2baa 100644
--- a/plugins/routable.js
+++ b/plugins/routable.js
@@ -1,37 +1,33 @@
-import routeMatches from '../shared/routeMatches';
+import routeMatches from '../shared/routeMatches'
function erase(node) {
- node.type = false;
- delete node.attributes;
- delete node.children;
+ node.type = false
+ delete node.attributes
+ delete node.children
}
function match(node) {
- return (
- node &&
- node.attributes !== undefined &&
- node.attributes.route !== undefined
- )
+ return node && node.attributes !== undefined && node.attributes.route !== undefined
}
function load({ router }) {
- router._routes = {};
+ router._routes = {}
}
function transform({ node, depth, router }) {
- if (!match(node)) return;
+ if (!match(node)) return
const routeDepth = depth.slice(0, depth.lastIndexOf('-'))
if (router._routes[routeDepth] !== undefined) {
- erase(node);
+ erase(node)
} else {
- const params = routeMatches(router.url, node.attributes.route);
+ const params = routeMatches(router.url, node.attributes.route)
if (params) {
- router._routes[routeDepth] = true;
- Object.assign(router._segments, params);
+ router._routes[routeDepth] = true
+ Object.assign(router._segments, params)
} else {
- erase(node);
+ erase(node)
}
}
}
-export default { load, transform, client: true, server: true }
\ No newline at end of file
+export default { load, transform, client: true, server: true }
diff --git a/scripts/index.js b/scripts/index.js
index e699ca24..23fae58b 100755
--- a/scripts/index.js
+++ b/scripts/index.js
@@ -1,14 +1,14 @@
#! /usr/bin/env node
-const { program } = require('commander');
-const { version } = require('../package.json');
-
-const webpack = require('webpack');
-const path = require('path');
-const { existsSync, readdir, unlink } = require('fs');
-const customConfig = path.resolve(process.cwd(), './webpack.config.js');
-const config = existsSync(customConfig) ? require(customConfig) : require('../webpack.config');
+const { program } = require('commander')
const dotenv = require('dotenv')
+const { existsSync, readdir, unlink } = require('fs')
const fetch = require('node-fetch')
+const path = require('path')
+const webpack = require('webpack')
+
+const { version } = require('../package.json')
+const customConfig = path.resolve(process.cwd(), './webpack.config.js')
+const config = existsSync(customConfig) ? require(customConfig) : require('../webpack.config')
function getConfig(options) {
return config.map((env) => env(null, options))
@@ -27,62 +27,59 @@ function loadEnv(env) {
}
function getFreePorts() {
- return new Promise((resolve, reject) => {
- const app1 = require('express')();
- const app2 = require('express')();
+ return new Promise((resolve) => {
+ const app1 = require('express')()
+ const app2 = require('express')()
const server1 = app1.listen(0, () => {
const server2 = app2.listen(0, () => {
- const ports = [
- server1.address().port,
- server2.address().port
- ]
+ const ports = [server1.address().port, server2.address().port]
server1.close()
server2.close()
resolve(ports)
- });
- });
+ })
+ })
})
}
function getPort(port) {
- return port || process.env['NULLSTACK_SERVER_PORT'] || process.env['PORT'] || 3000
+ return port || process.env.NULLSTACK_SERVER_PORT || process.env.PORT || 3000
}
function clearOutput(outputPath) {
if (!existsSync(outputPath)) return
readdir(outputPath, (err, files) => {
- if (err) throw err;
+ if (err) throw err
for (const file of files) {
- if (file === '.cache') continue;
- unlink(path.join(outputPath, file), err => {
- if (err) throw err;
- });
+ if (file === '.cache') continue
+ unlink(path.join(outputPath, file), (errr) => {
+ if (errr) throw errr
+ })
}
- });
+ })
}
async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc' }) {
const environment = 'development'
- console.log(` 🚀️ Starting your application in ${environment} mode...`);
+ console.info(` 🚀️ Starting your application in ${environment} mode...`)
loadEnv(env)
- const WebpackDevServer = require('webpack-dev-server');
+ const WebpackDevServer = require('webpack-dev-server')
const { setLogLevel } = require('webpack/hot/log')
setLogLevel('none')
- process.env['NULLSTACK_ENVIRONMENT_MODE'] = mode
- process.env['NULLSTACK_SERVER_PORT'] = getPort(port)
+ process.env.NULLSTACK_ENVIRONMENT_MODE = mode
+ process.env.NULLSTACK_SERVER_PORT = getPort(port)
const ports = await getFreePorts()
- process.env['NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT'] = ports[0]
- process.env['NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT'] = ports[1]
- process.env['NULLSTACK_ENVIRONMENT_HOT'] = (!cold).toString()
- process.env['NULLSTACK_ENVIRONMENT_DISK'] = (!!disk).toString()
- if (!process.env['NULLSTACK_PROJECT_DOMAIN']) process.env['NULLSTACK_PROJECT_DOMAIN'] = 'localhost'
- if (!process.env['NULLSTACK_WORKER_PROTOCOL']) process.env['NULLSTACK_WORKER_PROTOCOL'] = 'http'
- const target = `${process.env['NULLSTACK_WORKER_PROTOCOL']}://${process.env['NULLSTACK_PROJECT_DOMAIN']}:${process.env['NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT']}`
- const writeToDisk = disk ? true : (path) => path.includes('server')
+ process.env.NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT = ports[0]
+ process.env.NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT = ports[1]
+ process.env.NULLSTACK_ENVIRONMENT_HOT = (!cold).toString()
+ process.env.NULLSTACK_ENVIRONMENT_DISK = (!!disk).toString()
+ if (!process.env.NULLSTACK_PROJECT_DOMAIN) process.env.NULLSTACK_PROJECT_DOMAIN = 'localhost'
+ if (!process.env.NULLSTACK_WORKER_PROTOCOL) process.env.NULLSTACK_WORKER_PROTOCOL = 'http'
+ const target = `${process.env.NULLSTACK_WORKER_PROTOCOL}://${process.env.NULLSTACK_PROJECT_DOMAIN}:${process.env.NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT}`
+ const writeToDisk = disk ? true : (p) => p.includes('server')
const devServerOptions = {
hot: 'only',
open: false,
- host: process.env['NULLSTACK_PROJECT_DOMAIN'],
+ host: process.env.NULLSTACK_PROJECT_DOMAIN,
devMiddleware: {
index: false,
stats: 'errors-only',
@@ -93,7 +90,9 @@ async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc
logging: 'none',
progress: false,
reconnect: true,
- webSocketURL: `${process.env['NULLSTACK_WORKER_PROTOCOL'].replace('http', 'ws')}://${process.env['NULLSTACK_PROJECT_DOMAIN']}:${process.env['NULLSTACK_SERVER_PORT']}/ws`
+ webSocketURL: `${process.env.NULLSTACK_WORKER_PROTOCOL.replace('http', 'ws')}://${
+ process.env.NULLSTACK_PROJECT_DOMAIN
+ }:${process.env.NULLSTACK_SERVER_PORT}/ws`,
},
proxy: {
context: () => true,
@@ -103,14 +102,14 @@ async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc
},
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
- throw new Error('webpack-dev-server is not defined');
+ throw new Error('webpack-dev-server is not defined')
}
middlewares.unshift(async (req, res, next) => {
if (req.originalUrl.indexOf('.hot-update.') === -1) {
if (req.originalUrl.startsWith('/nullstack/')) {
- console.log(` ⚙️ [${req.method}] ${req.originalUrl}`)
+ console.info(` ⚙️ [${req.method}] ${req.originalUrl}`)
} else {
- console.log(` 🕸️ [${req.method}] ${req.originalUrl}`)
+ console.info(` 🕸️ [${req.method}] ${req.originalUrl}`)
}
}
async function waitForServer() {
@@ -129,38 +128,41 @@ async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc
}
}
waitForServer()
- });
- return middlewares;
+ })
+ return middlewares
},
webSocketServer: require.resolve('./socket'),
- port: process.env['NULLSTACK_SERVER_PORT']
- };
- const compiler = getCompiler({ environment, input, disk, loader });
+ port: process.env.NULLSTACK_SERVER_PORT,
+ }
+ const compiler = getCompiler({ environment, input, disk, loader })
clearOutput(compiler.compilers[0].outputPath)
- const server = new WebpackDevServer(devServerOptions, compiler);
- const portChecker = require('express')().listen(process.env['NULLSTACK_SERVER_PORT'], () => {
+ const server = new WebpackDevServer(devServerOptions, compiler)
+ const portChecker = require('express')().listen(process.env.NULLSTACK_SERVER_PORT, () => {
portChecker.close()
server.startCallback(() => {
- console.log('\x1b[36m%s\x1b[0m', ` ✅️ Your application is ready at http://${process.env['NULLSTACK_PROJECT_DOMAIN']}:${process.env['NULLSTACK_SERVER_PORT']}\n`);
- });
+ console.info(
+ '\x1b[36m%s\x1b[0m',
+ ` ✅️ Your application is ready at http://${process.env.NULLSTACK_PROJECT_DOMAIN}:${process.env.NULLSTACK_SERVER_PORT}\n`,
+ )
+ })
})
}
function build({ input, output, cache, env, mode = 'ssr' }) {
- const environment = 'production';
- const compiler = getCompiler({ environment, input, cache });
+ const environment = 'production'
+ const compiler = getCompiler({ environment, input, cache })
if (env) {
- process.env['NULLSTACK_ENVIRONMENT_NAME'] = env;
+ process.env.NULLSTACK_ENVIRONMENT_NAME = env
}
- console.log(` 🚀️ Building your application in ${mode} mode...`);
+ console.info(` 🚀️ Building your application in ${mode} mode...`)
compiler.run((error, stats) => {
if (stats.hasErrors()) {
- console.log(stats.toString({ colors: true }))
- process.exit(1);
+ console.info(stats.toString({ colors: true }))
+ process.exit(1)
}
- if (stats.hasErrors()) process.exit(1);
- require(`../builders/${mode}`)({ output, cache, environment });
- });
+ if (stats.hasErrors()) process.exit(1)
+ require(`../builders/${mode}`)({ output, cache, environment })
+ })
}
program
@@ -190,8 +192,8 @@ program
.action(build)
program
- .name("nullstack")
+ .name('nullstack')
.addHelpCommand(false)
.helpOption('-h, --help', 'Learn more about a specific command')
.version(version, '-v, --version', 'Nullstack version being used')
- .parse(process.argv);
\ No newline at end of file
+ .parse(process.argv)
diff --git a/scripts/socket.js b/scripts/socket.js
index b1960028..f07cd572 100644
--- a/scripts/socket.js
+++ b/scripts/socket.js
@@ -1,70 +1,74 @@
-"use strict";
+'use strict'
-const WebSocket = require("ws");
-const BaseServer = require("webpack-dev-server/lib/servers/BaseServer");
+const BaseServer = require('webpack-dev-server/lib/servers/BaseServer')
+const WebSocket = require('ws')
+
+class WebsocketServer extends BaseServer {
-module.exports = class WebsocketServer extends BaseServer {
constructor(server) {
- super(server);
+ super(server)
const options = {
- ...(this.server.options.webSocketServer).options,
+ ...this.server.options.webSocketServer.options,
clientTracking: false,
- port: process.env['NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT']
- };
+ port: process.env.NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT,
+ }
- this.implementation = new WebSocket.Server(options);
+ this.implementation = new WebSocket.Server(options)
- this.server.server.on("upgrade", (req, sock, head) => {
+ this.server.server.on('upgrade', (req, sock, head) => {
if (!this.implementation.shouldHandle(req)) {
- return;
+ return
}
this.implementation.handleUpgrade(req, sock, head, (connection) => {
- this.implementation.emit("connection", connection, req);
- });
- });
+ this.implementation.emit('connection', connection, req)
+ })
+ })
- this.implementation.on("error", (err) => {
- this.server.logger.error(err.message);
- });
+ this.implementation.on('error', (err) => {
+ this.server.logger.error(err.message)
+ })
const interval = setInterval(() => {
this.clients.forEach((client) => {
if (client.isAlive === false) {
- client.terminate();
+ client.terminate()
- return;
+ return
}
- client.isAlive = false;
- client.ping(() => { });
- });
- }, 1000);
+ client.isAlive = false
+ client.ping(() => {})
+ })
+ }, 1000)
- this.implementation.on("connection", (client) => {
- this.clients.push(client);
+ this.implementation.on('connection', (client) => {
+ this.clients.push(client)
- client.isAlive = true;
+ client.isAlive = true
- client.on("message", (data) => {
+ client.on('message', (data) => {
if (data === '{"type":"NULLSTACK_SERVER_STARTED"}') {
- this.clients.forEach((client) => {
- client.send('{"type":"NULLSTACK_SERVER_STARTED"}')
- });
+ this.clients.forEach((c) => {
+ c.send('{"type":"NULLSTACK_SERVER_STARTED"}')
+ })
}
})
- client.on("pong", () => {
- client.isAlive = true;
- });
+ client.on('pong', () => {
+ client.isAlive = true
+ })
- client.on("close", () => {
- this.clients.splice(this.clients.indexOf(client), 1);
- });
- });
+ client.on('close', () => {
+ this.clients.splice(this.clients.indexOf(client), 1)
+ })
+ })
- this.implementation.on("close", () => {
- clearInterval(interval);
- });
+ this.implementation.on('close', () => {
+ clearInterval(interval)
+ })
}
-};
+
+}
+
+module.exports = WebsocketServer
diff --git a/server/client.js b/server/client.js
index c0cf1cde..fd5eaa56 100644
--- a/server/client.js
+++ b/server/client.js
@@ -1,14 +1,14 @@
export function generateContext(context) {
const contextProxyHandler = {
- set(target, name, value) {
- context[name] = value;
- return Reflect.set(...arguments);
+ set(target, name, value, receiver) {
+ context[name] = value
+ return Reflect.set(target, name, value, receiver)
},
get(target, name) {
- return target[name] === undefined ? context[name] : target[name];
- }
- }
- return function(temporary) {
- return new Proxy({...context, ...temporary}, contextProxyHandler);
+ return target[name] === undefined ? context[name] : target[name]
+ },
+ }
+ return function (temporary) {
+ return new Proxy({ ...context, ...temporary }, contextProxyHandler)
}
-}
\ No newline at end of file
+}
diff --git a/server/configurable.js b/server/configurable.js
index 73bbbbc1..9bfb144d 100644
--- a/server/configurable.js
+++ b/server/configurable.js
@@ -1,13 +1,13 @@
-import { camelize } from '../shared/string';
+import { camelize } from '../shared/string'
export function createConfigurable(label) {
- const configurable = {};
+ const configurable = {}
for (const key in process.env) {
- const lookup = `NULLSTACK_${label}_`;
+ const lookup = `NULLSTACK_${label}_`
if (key.startsWith(lookup)) {
- const camelCaseKey = camelize(key.substring(lookup.length));
- configurable[camelCaseKey] = process.env[key];
+ const camelCaseKey = camelize(key.substring(lookup.length))
+ configurable[camelCaseKey] = process.env[key]
}
}
- return configurable;
-}
\ No newline at end of file
+ return configurable
+}
diff --git a/server/context.js b/server/context.js
index b3278a80..e3b87d4c 100644
--- a/server/context.js
+++ b/server/context.js
@@ -1,14 +1,14 @@
-const context = {};
+const context = {}
const contextProxyHandler = {
- set(target, name, value) {
- context[name] = value;
- return Reflect.set(...arguments);
- }
+ set(target, name, value, receiver) {
+ context[name] = value
+ return Reflect.set(target, name, value, receiver)
+ },
}
export function generateContext(temporary) {
- return new Proxy({...context, ...temporary}, contextProxyHandler);
+ return new Proxy({ ...context, ...temporary }, contextProxyHandler)
}
-export default context;
\ No newline at end of file
+export default context
diff --git a/server/dotenv.js b/server/dotenv.js
index 33838de1..6de25639 100644
--- a/server/dotenv.js
+++ b/server/dotenv.js
@@ -3,7 +3,7 @@ import dotenv from 'dotenv'
let path = '.env'
if (process.env.NULLSTACK_ENVIRONMENT_NAME) {
- path += `.${process.env.NULLSTACK_ENVIRONMENT_NAME}`
+ path += `.${process.env.NULLSTACK_ENVIRONMENT_NAME}`
}
-dotenv.config({ path })
\ No newline at end of file
+dotenv.config({ path })
diff --git a/server/environment.js b/server/environment.js
index 7f74e2b4..345eaeba 100644
--- a/server/environment.js
+++ b/server/environment.js
@@ -1,18 +1,18 @@
-const environment = { client: false, server: true };
+const environment = { client: false, server: true }
-environment.development = __dirname.indexOf('.development') > -1;
-environment.production = !environment.development;
+environment.development = __dirname.indexOf('.development') > -1
+environment.production = !environment.development
-environment.mode = process.env.NULLSTACK_ENVIRONMENT_MODE || 'ssr';
+environment.mode = process.env.NULLSTACK_ENVIRONMENT_MODE || 'ssr'
-environment.key = "{{NULLSTACK_ENVIRONMENT_KEY}}"
+environment.key = '{{NULLSTACK_ENVIRONMENT_KEY}}'
-environment.name = process.env.NULLSTACK_ENVIRONMENT_NAME || '';
+environment.name = process.env.NULLSTACK_ENVIRONMENT_NAME || ''
if (environment.development) {
environment.hot = process.env.NULLSTACK_ENVIRONMENT_HOT === 'true'
}
-Object.freeze(environment);
+Object.freeze(environment)
-export default environment;
\ No newline at end of file
+export default environment
diff --git a/server/files.js b/server/files.js
index 023225fe..daa5fc08 100644
--- a/server/files.js
+++ b/server/files.js
@@ -1,22 +1,23 @@
-import { readFileSync, existsSync } from 'fs';
-import environment from './environment';
-import path from 'path';
-import { generateIntegrity } from './integrities';
+import { existsSync, readFileSync } from 'fs'
+import path from 'path'
-const files = {};
+import environment from './environment'
+import { generateIntegrity } from './integrities'
+
+const files = {}
export function generateFile(file, server) {
- if (files[file] && environment.production) return files[file];
+ if (files[file] && environment.production) return files[file]
const filePath = path.join(__dirname, file)
if (existsSync(filePath)) {
- files[file] = readFileSync(filePath, 'utf-8');
+ files[file] = readFileSync(filePath, 'utf-8')
} else {
files[file] = ''
}
if (!server.less) {
- generateIntegrity(file, files[file]);
+ generateIntegrity(file, files[file])
}
- return files[file];
+ return files[file]
}
-export default files;
\ No newline at end of file
+export default files
diff --git a/server/generator.js b/server/generator.js
index 0ef86436..4102962e 100644
--- a/server/generator.js
+++ b/server/generator.js
@@ -1,2 +1,2 @@
-const generator = {};
-export default generator;
\ No newline at end of file
+const generator = {}
+export default generator
diff --git a/server/index.js b/server/index.js
index 87743ad0..3506606b 100644
--- a/server/index.js
+++ b/server/index.js
@@ -1,39 +1,39 @@
-import './dotenv';
-import { normalize } from 'path';
-import element from '../shared/element';
-import fragment from '../shared/fragment';
-import getProxyableMethods from '../shared/getProxyableMethods';
-import { useServerPlugins } from '../shared/plugins';
-import context from './context';
-import environment from './environment';
-import generator from './generator';
-import instanceProxyHandler from './instanceProxyHandler';
-import project from './project';
-import registry from './registry';
-import secrets from './secrets';
-import server from './server';
-import settings from './settings';
-import worker from './worker';
+import './dotenv'
+import { normalize } from 'path'
+
+import element from '../shared/element'
+import fragment from '../shared/fragment'
+import getProxyableMethods from '../shared/getProxyableMethods'
+import { useServerPlugins } from '../shared/plugins'
+import context, { generateContext } from './context'
+import environment from './environment'
+import generator from './generator'
+import instanceProxyHandler from './instanceProxyHandler'
+import project from './project'
+import registry from './registry'
import reqres from './reqres'
-import { generateContext } from './context';
+import secrets from './secrets'
+import server from './server'
+import settings from './settings'
+import worker from './worker'
globalThis.window = {}
-context.server = server;
-context.project = project;
-context.environment = environment;
-context.settings = settings;
-context.secrets = secrets;
-context.worker = worker;
+context.server = server
+context.project = project
+context.environment = environment
+context.settings = settings
+context.secrets = secrets
+context.worker = worker
server.less = normalize(__filename) !== normalize(process.argv[1])
class Nullstack {
- static registry = registry;
- static element = element;
- static fragment = fragment;
- static use = useServerPlugins;
+ static registry = registry
+ static element = element
+ static fragment = fragment
+ static use = useServerPlugins
static bindStaticFunctions(klass) {
let parent = klass
@@ -51,12 +51,12 @@ class Nullstack {
}
function _invoke(...args) {
if (underscored) {
- return klass[propName].call(klass, ...args);
+ return klass[propName].call(klass, ...args)
}
const params = args[0] || {}
const { request, response } = reqres
- const context = generateContext({ request, response, ...params });
- return klass[propName].call(klass, context);
+ const subcontext = generateContext({ request, response, ...params })
+ return klass[propName].call(klass, subcontext)
}
klass[prop] = _invoke
klass.prototype[prop] = _invoke
@@ -68,9 +68,9 @@ class Nullstack {
static start(Starter) {
if (this.name.indexOf('Nullstack') > -1) {
- generator.starter = () => element(Starter);
+ generator.starter = () => element(Starter)
setTimeout(server.start, 0)
- return context;
+ return context
}
}
@@ -81,16 +81,16 @@ class Nullstack {
key = null
constructor() {
- const methods = getProxyableMethods(this);
- const proxy = new Proxy(this, instanceProxyHandler);
+ const methods = getProxyableMethods(this)
+ const proxy = new Proxy(this, instanceProxyHandler)
for (const method of methods) {
- this[method] = this[method].bind(proxy);
+ this[method] = this[method].bind(proxy)
}
- return proxy;
+ return proxy
}
toJSON() {
- const serialized = {};
+ const serialized = {}
for (const name of Object.getOwnPropertyNames(this)) {
if (name === 'prerendered') continue
if (name === 'initiated') continue
@@ -100,15 +100,15 @@ class Nullstack {
if (name === '_attributes') continue
if (name === '_scope') continue
if (typeof this[name] === 'function') continue
- serialized[name] = this[name];
+ serialized[name] = this[name]
}
- return serialized;
+ return serialized
}
render() {
- return false;
+ return false
}
}
-export default Nullstack;
\ No newline at end of file
+export default Nullstack
diff --git a/server/instanceProxyHandler.js b/server/instanceProxyHandler.js
index 3daf4e60..fb481413 100644
--- a/server/instanceProxyHandler.js
+++ b/server/instanceProxyHandler.js
@@ -1,16 +1,16 @@
const instanceProxyHandler = {
- get(target, name) {
+ get(target, name, receiver) {
if (typeof target[name] === 'function' && name !== 'constructor') {
if (name.startsWith('_')) {
return target[name].bind(target)
}
return (args) => {
- const context = target._scope.generateContext({ ...target._attributes, ...args });
- return target[name](context);
+ const context = target._scope.generateContext({ ...target._attributes, ...args })
+ return target[name](context)
}
}
- return Reflect.get(...arguments);
- }
+ return Reflect.get(target, name, receiver)
+ },
}
-export default instanceProxyHandler;
\ No newline at end of file
+export default instanceProxyHandler
diff --git a/server/integrities.js b/server/integrities.js
index 30635b06..4c34ba97 100644
--- a/server/integrities.js
+++ b/server/integrities.js
@@ -1,11 +1,12 @@
-import { createHash } from 'crypto';
+import { createHash } from 'crypto'
+
import environment from './environment'
-const integrities = {};
+const integrities = {}
export function generateIntegrity(key, source) {
if (environment.development) return
- integrities[key] = 'sha512-' + createHash('sha512', 'utf8').update(source).digest('base64');
+ integrities[key] = `sha512-${createHash('sha512', 'utf8').update(source).digest('base64')}`
}
-export default integrities;
\ No newline at end of file
+export default integrities
diff --git a/server/links.js b/server/links.js
index 60019794..5951965a 100644
--- a/server/links.js
+++ b/server/links.js
@@ -1,24 +1,23 @@
-import { generateBase } from './project';
-import environment from './environment';
-import worker from './worker';
+import { generateBase } from './project'
+import worker from './worker'
export function absolute(path) {
- if(path.indexOf('//') === -1) {
- return `${generateBase()}${path}`;
- }
- return path;
+ if (path.indexOf('//') === -1) {
+ return `${generateBase()}${path}`
+ }
+ return path
}
export function cdn(path) {
- if(!worker.cdn) return path;
- if(path.indexOf('//') === -1) {
- return `${worker.cdn}${path}`;
- }
- return path;
+ if (!worker.cdn) return path
+ if (path.indexOf('//') === -1) {
+ return `${worker.cdn}${path}`
+ }
+ return path
}
export function cdnOrAbsolute(path) {
- if(path.indexOf('//') > -1) return path;
- if(worker.cdn) return `${worker.cdn}${path}`;
- return `${generateBase()}${path}`;
-}
\ No newline at end of file
+ if (path.indexOf('//') > -1) return path
+ if (worker.cdn) return `${worker.cdn}${path}`
+ return `${generateBase()}${path}`
+}
diff --git a/server/manifest.js b/server/manifest.js
index 348a7a08..b7703061 100644
--- a/server/manifest.js
+++ b/server/manifest.js
@@ -1,41 +1,42 @@
-import { existsSync, readFileSync } from 'fs';
-import path from 'path';
-import project from './project';
-import { generateIntegrity } from './integrities';
-import files from './files';
-import { cdn } from './links';
+import { existsSync, readFileSync } from 'fs'
+import path from 'path'
+
+import files from './files'
+import { generateIntegrity } from './integrities'
+import { cdn } from './links'
+import project from './project'
export default function generateManifest(server) {
- if (files['manifest.webmanifest']) return files['manifest.webmanifest'];
- const file = path.join(__dirname, '../', 'public', 'manifest.webmanifest');
+ if (files['manifest.webmanifest']) return files['manifest.webmanifest']
+ const file = path.join(__dirname, '../', 'public', 'manifest.webmanifest')
if (existsSync(file)) {
- return readFileSync(file, 'utf-8');
+ return readFileSync(file, 'utf-8')
}
const json = {
- "name": project.name,
- "short_name": project.shortName || project.name,
- "theme_color": project.color,
- "background_color": project.backgroundColor || project.color,
- "display": project.display,
- "orientation": project.orientation,
- "scope": project.scope,
- "start_url": project.root,
- "icons": [],
- "splash_pages": null
+ name: project.name,
+ short_name: project.shortName || project.name,
+ theme_color: project.color,
+ background_color: project.backgroundColor || project.color,
+ display: project.display,
+ orientation: project.orientation,
+ scope: project.scope,
+ start_url: project.root,
+ icons: [],
+ splash_pages: null,
}
for (const size in project.icons) {
- const icon = project.icons[size];
+ const icon = project.icons[size]
json.icons.push({
- "src": cdn(icon),
- "sizes": `${size}x${size}`,
- "type": "image/png",
- "purpose": "maskable any"
- });
+ src: cdn(icon),
+ sizes: `${size}x${size}`,
+ type: 'image/png',
+ purpose: 'maskable any',
+ })
}
- const manifest = JSON.stringify(json);
+ const manifest = JSON.stringify(json)
if (!server.less) {
- generateIntegrity('manifest.webmanifest', manifest);
+ generateIntegrity('manifest.webmanifest', manifest)
}
- files['manifest.webmanifest'] = manifest;
- return manifest;
-}
\ No newline at end of file
+ files['manifest.webmanifest'] = manifest
+ return manifest
+}
diff --git a/server/params.js b/server/params.js
index ce141386..6a3b2cba 100644
--- a/server/params.js
+++ b/server/params.js
@@ -1,13 +1,13 @@
-import getQueryStringParams from '../shared/getQueryStringParams';
+import getQueryStringParams from '../shared/getQueryStringParams'
const paramsProxyHandler = {
get(target, name) {
- if(target[name] === false) return false;
- return target[name] || '';
- }
+ if (target[name] === false) return false
+ return target[name] || ''
+ },
}
export function generateParams(url) {
- const params = getQueryStringParams(url);
- return new Proxy(params, paramsProxyHandler);
-}
\ No newline at end of file
+ const params = getQueryStringParams(url)
+ return new Proxy(params, paramsProxyHandler)
+}
diff --git a/server/prerender.js b/server/prerender.js
index b2905535..b6507080 100644
--- a/server/prerender.js
+++ b/server/prerender.js
@@ -1,66 +1,66 @@
-import environment from './environment';
-import project from './project';
-import Router from './router';
-import generator from './generator';
-import { generateParams } from './params';
-import render from './render';
-import settings from './settings';
-import worker from './worker';
-import printError from './printError';
-import { generateContext } from './client';
-import generateTree from '../shared/generateTree';
-import { loadPlugins } from '../shared/plugins';
+import generateTree from '../shared/generateTree'
+import { loadPlugins } from '../shared/plugins'
+import { generateContext } from './client'
+import environment from './environment'
+import generator from './generator'
+import { generateParams } from './params'
+import printError from './printError'
+import project from './project'
+import render from './render'
+import Router from './router'
+import settings from './settings'
+import worker from './worker'
export async function prerender(request, response) {
- const context = {};
- context.page = { image: '/image-1200x630.png', status: 200 };
- context.project = project;
- context.environment = environment;
- context.settings = settings;
- context.params = generateParams(request.originalUrl);
- context.router = new Router(request, response);
- const online = context.router.url !== `/nullstack/${environment.key}/offline`;
- context.worker = { ...worker, online, responsive: online };
- const scope = {};
- scope.instances = {};
- context.instances = scope.instances;
- context.router._segments = context.params;
- scope.request = request;
- scope.response = response;
- scope.head = '';
- scope.body = '';
- scope.context = context;
- scope.generateContext = generateContext(context);
+ const context = {}
+ context.page = { image: '/image-1200x630.png', status: 200 }
+ context.project = project
+ context.environment = environment
+ context.settings = settings
+ context.params = generateParams(request.originalUrl)
+ context.router = new Router(request, response)
+ const online = context.router.url !== `/nullstack/${environment.key}/offline`
+ context.worker = { ...worker, online, responsive: online }
+ const scope = {}
+ scope.instances = {}
+ context.instances = scope.instances
+ context.router._segments = context.params
+ scope.request = request
+ scope.response = response
+ scope.head = ''
+ scope.body = ''
+ scope.context = context
+ scope.generateContext = generateContext(context)
scope.nextBody = {}
scope.nextHead = []
- scope.plugins = loadPlugins(scope);
+ scope.plugins = loadPlugins(scope)
try {
if (environment.production || environment.mode !== 'spa') {
- const tree = await generateTree(generator.starter(), scope);
- scope.body = render(tree, scope);
+ const tree = await generateTree(generator.starter(), scope)
+ scope.body = render(tree, scope)
}
if (!online) {
- context.page.status = 200;
+ context.page.status = 200
}
} catch (error) {
- printError(error);
- context.page.status = 500;
+ printError(error)
+ context.page.status = 500
} finally {
if (context.page.status !== 200) {
scope.nextBody = {}
scope.nextHead = []
for (const key in context.router._routes) {
- delete context.router._routes[key];
+ delete context.router._routes[key]
}
for (const key in scope.instances) {
- delete scope.instances[key];
+ delete scope.instances[key]
}
- scope.head = '';
- scope.plugins = loadPlugins(scope);
- const tree = await generateTree(generator.starter(), scope);
- scope.body = render(tree, scope);
+ scope.head = ''
+ scope.plugins = loadPlugins(scope)
+ const tree = await generateTree(generator.starter(), scope)
+ scope.body = render(tree, scope)
}
}
- return scope;
-}
\ No newline at end of file
+ return scope
+}
diff --git a/server/printError.js b/server/printError.js
index 43a2e067..a2daa484 100644
--- a/server/printError.js
+++ b/server/printError.js
@@ -1,42 +1,44 @@
export default function (error) {
- const lines = error.stack.split(`\n`);
- let initiator = lines.find((line) => line.indexOf('Proxy') > -1);
+ const lines = error.stack.split(`\n`)
+ let initiator = lines.find((line) => line.indexOf('Proxy') > -1)
if (initiator) {
- initiator = initiator.split('(')[0];
+ initiator = initiator.split('(')[0]
if (initiator) {
- initiator = initiator.trim();
- initiator = initiator.replace('at', '').trim();
+ initiator = initiator.trim()
+ initiator = initiator.replace('at', '').trim()
}
}
- let source = lines.find((line) => line.indexOf('webpack:') > -1);
+ let source = lines.find((line) => line.indexOf('webpack:') > -1)
if (source) {
- source = source.split('webpack:')[1];
+ source = source.split('webpack:')[1]
if (source) {
- source = source.split('\\').join('/');
+ source = source.split('\\').join('/')
}
}
- let file, line;
+ let file, line
if (source) {
- [file, line] = source.split(':');
- let className = file.split('/').find((segment) => segment.indexOf('.') > -1);
+ const sourceAndLine = source.split(':')
+ file = sourceAndLine[0]
+ line = sourceAndLine[1]
+ let className = file.split('/').find((segment) => segment.indexOf('.') > -1)
if (className) {
- className = className.replace('.njs', '');
+ className = className.replace('.njs', '')
if (initiator) {
- initiator = initiator.replace('Proxy', className);
+ initiator = initiator.replace('Proxy', className)
}
}
}
- console.log();
- console.log('\x1b[31m', error.name, '-', error.message, '\x1b[0m');
- console.log();
+ console.info()
+ console.info('\x1b[31m', error.name, '-', error.message, '\x1b[0m')
+ console.info()
if (initiator) {
- console.log('\x1b[2m', 'initiator:', '\x1b[0m', '\x1b[37m', initiator, '\x1b[0m');
+ console.info('\x1b[2m', 'initiator:', '\x1b[0m', '\x1b[37m', initiator, '\x1b[0m')
}
if (file) {
- console.log('\x1b[2m', 'file: ', '\x1b[0m', '\x1b[37m', file, '\x1b[0m');
+ console.info('\x1b[2m', 'file: ', '\x1b[0m', '\x1b[37m', file, '\x1b[0m')
}
if (line) {
- console.log('\x1b[2m', 'line: ', '\x1b[0m', '\x1b[37m', line, '\x1b[0m');
+ console.info('\x1b[2m', 'line: ', '\x1b[0m', '\x1b[37m', line, '\x1b[0m')
}
- console.log();
-}
\ No newline at end of file
+ console.info()
+}
diff --git a/server/project.js b/server/project.js
index 83915cbf..b5787720 100644
--- a/server/project.js
+++ b/server/project.js
@@ -1,36 +1,36 @@
-import environment from './environment';
-import worker from './worker';
-import reqres from './reqres';
+import environment from './environment'
+import reqres from './reqres'
+import worker from './worker'
-const project = {};
+const project = {}
-project.domain = process.env['NULLSTACK_PROJECT_DOMAIN']
-project.name = process.env['NULLSTACK_PROJECT_NAME']
-project.shortName = process.env['NULLSTACK_PROJECT_SHORT_NAME']
-project.color = process.env['NULLSTACK_PROJECT_COLOR']
-project.viewport = process.env['NULLSTACK_PROJECT_VIEWPORT'] || 'width=device-width, initial-scale=1, shrink-to-fit=no';
-project.type = 'website';
-project.display = 'standalone';
-project.orientation = 'portrait';
-project.scope = '/';
-project.root = '/';
-project.sitemap = environment.mode === 'ssg';
-project.favicon = '/favicon-96x96.png';
-project.disallow = [];
-project.icons = JSON.parse(`{{NULLSTACK_PROJECT_ICONS}}`);
+project.domain = process.env.NULLSTACK_PROJECT_DOMAIN
+project.name = process.env.NULLSTACK_PROJECT_NAME
+project.shortName = process.env.NULLSTACK_PROJECT_SHORT_NAME
+project.color = process.env.NULLSTACK_PROJECT_COLOR
+project.viewport = process.env.NULLSTACK_PROJECT_VIEWPORT || 'width=device-width, initial-scale=1, shrink-to-fit=no'
+project.type = 'website'
+project.display = 'standalone'
+project.orientation = 'portrait'
+project.scope = '/'
+project.root = '/'
+project.sitemap = environment.mode === 'ssg'
+project.favicon = '/favicon-96x96.png'
+project.disallow = []
+project.icons = JSON.parse(`{{NULLSTACK_PROJECT_ICONS}}`)
function getHost() {
if (reqres.request?.headers?.host) {
return reqres.request.headers.host
}
if (project.domain === 'localhost') {
- return `localhost:${process.env['NULLSTACK_SERVER_PORT']}`
+ return `localhost:${process.env.NULLSTACK_SERVER_PORT}`
}
return project.domain
}
export function generateBase() {
- return `${worker.protocol}://${getHost()}`;
+ return `${worker.protocol}://${getHost()}`
}
-export default project;
\ No newline at end of file
+export default project
diff --git a/server/registry.js b/server/registry.js
index dee1de3e..5305b2bd 100644
--- a/server/registry.js
+++ b/server/registry.js
@@ -1,2 +1,2 @@
-const registry = {};
-export default registry;
\ No newline at end of file
+const registry = {}
+export default registry
diff --git a/server/render.js b/server/render.js
index a67f2d18..4d2c469d 100644
--- a/server/render.js
+++ b/server/render.js
@@ -1,53 +1,53 @@
-import { isFalse } from "../shared/nodes";
-import { sanitizeHtml } from "../shared/sanitizeString";
-import renderAttributes from "./renderAttributes";
+import { isFalse } from '../shared/nodes'
+import { sanitizeHtml } from '../shared/sanitizeString'
+import renderAttributes from './renderAttributes'
function isSelfClosing(type) {
- if (type === 'input') return true;
- if (type === 'img') return true;
- if (type === 'link') return true;
- if (type === 'meta') return true;
- if (type === 'br') return true;
- if (type === 'hr') return true;
- if (type === 'area') return true;
- if (type === 'base') return true;
- if (type === 'col') return true;
- if (type === 'embed') return true;
- if (type === 'param') return true;
- if (type === 'source') return true;
- if (type === 'track') return true;
- if (type === 'wbr') return true;
- if (type === 'menuitem') return true;
- return false;
+ if (type === 'input') return true
+ if (type === 'img') return true
+ if (type === 'link') return true
+ if (type === 'meta') return true
+ if (type === 'br') return true
+ if (type === 'hr') return true
+ if (type === 'area') return true
+ if (type === 'base') return true
+ if (type === 'col') return true
+ if (type === 'embed') return true
+ if (type === 'param') return true
+ if (type === 'source') return true
+ if (type === 'track') return true
+ if (type === 'wbr') return true
+ if (type === 'menuitem') return true
+ return false
}
function renderBody(node, scope, next) {
if (isFalse(node)) {
- return "";
+ return ''
}
if (node.type === 'text') {
const text = node.text === '' ? ' ' : sanitizeHtml(node.text.toString())
- return next && next.type === 'text' ? text + "" : text
+ return next && next.type === 'text' ? `${text}` : text
}
- let element = `<${node.type}`;
+ let element = `<${node.type}`
element += renderAttributes(node.attributes)
if (isSelfClosing(node.type)) {
- element += '/>';
+ element += '/>'
} else {
- element += '>';
+ element += '>'
if (node.attributes.html) {
- const source = node.attributes.html;
- element += source;
+ const source = node.attributes.html
+ element += source
} else if (node.type === 'textarea') {
- element += node.children[0].text;
+ element += node.children[0].text
} else {
for (let i = 0; i < node.children.length; i++) {
- element += renderBody(node.children[i], scope, node.children[i + 1]);
+ element += renderBody(node.children[i], scope, node.children[i + 1])
}
}
- element += `${node.type}>`;
+ element += `${node.type}>`
}
- return element;
+ return element
}
function renderHead(scope) {
@@ -55,14 +55,14 @@ function renderHead(scope) {
for (let i = 0; i < limit; i++) {
const node = scope.nextHead[i]
if (isFalse(node)) {
- continue;
+ continue
}
- scope.head += `<${node.type}`;
+ scope.head += `<${node.type}`
scope.head += renderAttributes(node.attributes)
if (isSelfClosing(node.type)) {
- scope.head += '/>';
+ scope.head += '/>'
} else {
- scope.head += '>';
+ scope.head += '>'
scope.head += node.attributes.html
scope.head += `${node.type}>`
}
@@ -72,4 +72,4 @@ function renderHead(scope) {
export default function render(node, scope, next) {
renderHead(scope)
return renderBody(node, scope, next)
-}
\ No newline at end of file
+}
diff --git a/server/renderAttributes.js b/server/renderAttributes.js
index 01e5a37a..1428a20f 100644
--- a/server/renderAttributes.js
+++ b/server/renderAttributes.js
@@ -1,25 +1,25 @@
-import generateTruthyString from "../shared/generateTruthyString";
+import generateTruthyString from '../shared/generateTruthyString'
export default function renderAttributes(attributes) {
let element = ''
- for (let name in attributes) {
+ for (const name in attributes) {
if (name === 'debounce') continue
if (!name.startsWith('on') && name !== 'html') {
- let attribute = attributes[name];
+ let attribute = attributes[name]
if ((name === 'class' || name === 'style') && Array.isArray(attributes[name])) {
- attribute = generateTruthyString(attributes[name]);
+ attribute = generateTruthyString(attributes[name])
} else {
- attribute = attributes[name];
+ attribute = attributes[name]
}
- const type = typeof attribute;
+ const type = typeof attribute
if (type !== 'object' && type !== 'function') {
- if (name != 'value' && attribute === true) {
- element += ` ${name}`;
+ if (name !== 'value' && attribute === true) {
+ element += ` ${name}`
} else if (name === 'value' || (attribute !== false && attribute !== null && attribute !== undefined)) {
- element += ` ${name}="${attribute}"`;
+ element += ` ${name}="${attribute}"`
}
}
}
}
return element
-}
\ No newline at end of file
+}
diff --git a/server/reqres.js b/server/reqres.js
index 1e86fc66..c743ad43 100644
--- a/server/reqres.js
+++ b/server/reqres.js
@@ -1,2 +1,2 @@
const reqres = {}
-export default reqres
\ No newline at end of file
+export default reqres
diff --git a/server/robots.js b/server/robots.js
index 6c90e04f..760258a5 100644
--- a/server/robots.js
+++ b/server/robots.js
@@ -1,25 +1,25 @@
-import project, { generateBase } from './project';
-import files from './files';
+import files from './files'
+import project, { generateBase } from './project'
export default function generateRobots() {
- if(files['robots.txt']) return files['robots.txt'];
+ if (files['robots.txt']) return files['robots.txt']
const lines = []
- lines.push('User-Agent: *');
- if(!project.disallow.includes('/')) {
- lines.push(`Allow: ${project.root}`);
+ lines.push('User-Agent: *')
+ if (!project.disallow.includes('/')) {
+ lines.push(`Allow: ${project.root}`)
}
- for(const path of project.disallow) {
- lines.push(`Disallow: ${path}`);
+ for (const path of project.disallow) {
+ lines.push(`Disallow: ${path}`)
}
- if(project.sitemap) {
- if(project.sitemap === true) {
- lines.push(`Sitemap: ${generateBase()}/sitemap.xml`);
+ if (project.sitemap) {
+ if (project.sitemap === true) {
+ lines.push(`Sitemap: ${generateBase()}/sitemap.xml`)
} else if (project.sitemap.indexOf('//') === -1) {
- lines.push(`Sitemap: ${generateBase()}${project.sitemap}`);
+ lines.push(`Sitemap: ${generateBase()}${project.sitemap}`)
} else {
- lines.push(`Sitemap: ${project.sitemap}`);
+ lines.push(`Sitemap: ${project.sitemap}`)
}
}
- files['robots.txt'] = lines.join(`\n`);
+ files['robots.txt'] = lines.join(`\n`)
return files['robots.txt']
-}
\ No newline at end of file
+}
diff --git a/server/router.js b/server/router.js
index 270fa71a..940925e8 100644
--- a/server/router.js
+++ b/server/router.js
@@ -1,39 +1,39 @@
-import extractLocation from '../shared/extractLocation';
-import { generateBase } from './project';
+import extractLocation from '../shared/extractLocation'
+import { generateBase } from './project'
export default class Router {
previous = null
constructor(request, response) {
- this.request = request;
- this.response = response;
+ this.request = request
+ this.response = response
}
_redirect(target) {
if (!this.response.headersSent) {
- const { url } = extractLocation(target);
- this.response.redirect(url);
+ const { url } = extractLocation(target)
+ this.response.redirect(url)
}
}
get url() {
- return extractLocation(this.request.originalUrl).url;
+ return extractLocation(this.request.originalUrl).url
}
set url(target) {
- this._redirect(target);
+ this._redirect(target)
}
get path() {
- return extractLocation(this.request.path).path;
+ return extractLocation(this.request.path).path
}
set path(target) {
- const { search } = extractLocation(this.request.originalUrl);
+ const { search } = extractLocation(this.request.originalUrl)
if (search) {
- this._redirect(target + '?' + search);
+ this._redirect(`${target}?${search}`)
} else {
- this._redirect(target);
+ this._redirect(target)
}
}
diff --git a/server/secrets.js b/server/secrets.js
index d1ef3b32..e8fd909b 100644
--- a/server/secrets.js
+++ b/server/secrets.js
@@ -1,5 +1,5 @@
-import { createConfigurable } from './configurable';
+import { createConfigurable } from './configurable'
-const secrets = createConfigurable('SECRETS');
+const secrets = createConfigurable('SECRETS')
-export default secrets;
+export default secrets
diff --git a/server/server.js b/server/server.js
index 919b1f83..ef823bb2 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1,50 +1,85 @@
-import bodyParser from 'body-parser';
-import cors from 'cors';
-import express from 'express';
-import fetch from 'node-fetch';
-import path from 'path';
-import deserialize from '../shared/deserialize';
-import prefix from '../shared/prefix';
-import context, { generateContext } from './context';
-import environment from './environment';
-import { generateFile } from './files';
-import generateManifest from './manifest';
-import { prerender } from './prerender';
-import printError from './printError';
-import registry from './registry';
-import generateRobots from './robots';
-import template from './template';
-import { generateServiceWorker } from './worker';
-import reqres from './reqres'
-import WebSocket from 'ws';
+import bodyParser from 'body-parser'
+import express from 'express'
import { writeFileSync } from 'fs'
+import fetch from 'node-fetch'
+import path from 'path'
+import WebSocket from 'ws'
+
+import deserialize from '../shared/deserialize'
+import extractParamValue from '../shared/extractParamValue'
+import prefix from '../shared/prefix'
+import context, { generateContext } from './context'
+import environment from './environment'
+import { generateFile } from './files'
+import generateManifest from './manifest'
+import { prerender } from './prerender'
+import printError from './printError'
+import registry from './registry'
+import reqres from './reqres'
+import generateRobots from './robots'
+import template from './template'
+import { generateServiceWorker } from './worker'
if (!global.fetch) {
- global.fetch = fetch;
+ global.fetch = fetch
}
-const server = express();
+const server = express()
-server.port = process.env['NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT'] || process.env['NULLSTACK_SERVER_PORT'] || process.env['PORT'] || 3000
+server.port =
+ process.env.NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT ||
+ process.env.NULLSTACK_SERVER_PORT ||
+ process.env.PORT ||
+ 3000
let contextStarted = false
let serverStarted = false
+for (const method of ['get', 'post', 'put', 'patch', 'delete', 'all']) {
+ const original = server[method].bind(server)
+ server[method] = function (...args) {
+ if (typeof args[1] === 'function' && args[1].name === '_invoke') {
+ original(args[0], bodyParser.text({ limit: server.maximumPayloadSize }), async (request, response) => {
+ const params = {}
+ for (const key of Object.keys(request.params)) {
+ params[key] = extractParamValue(request.params[key])
+ }
+ for (const key of Object.keys(request.query)) {
+ params[key] = extractParamValue(request.query[key])
+ }
+ if (request.method !== 'GET') {
+ Object.assign(params, deserialize(request.body))
+ }
+ try {
+ const subcontext = generateContext({ request, response, ...params })
+ const result = await args[1](subcontext)
+ response.json(result)
+ } catch (error) {
+ printError(error)
+ response.status(500).json({})
+ }
+ })
+ } else {
+ original(...args)
+ }
+ }
+}
+
server.use(async (request, response, next) => {
if (!contextStarted) {
- typeof context.start === 'function' && await context.start();
- contextStarted = true;
+ typeof context.start === 'function' && (await context.start())
+ contextStarted = true
}
next()
})
-function createRequest(path) {
+function createRequest(url) {
return {
- method: "GET",
- host: "",
+ method: 'GET',
+ host: '',
cookies: {},
query: {},
- url: path,
+ url,
headers: {},
}
}
@@ -60,183 +95,183 @@ function createResponse(callback) {
set statusCode(status) {
this._statusCode = status
this.status(status)
- }
- };
- const headers = {};
- let code = 200;
+ },
+ }
+ const headers = {}
+ let code = 200
res.set = res.header = (x, y) => {
if (arguments.length === 2) {
- res.setHeader(x, y);
+ res.setHeader(x, y)
} else {
for (const key in x) {
- res.setHeader(key, x[key]);
+ res.setHeader(key, x[key])
}
}
- return res;
+ return res
}
res.setHeader = (x, y) => {
- headers[x] = y;
- headers[x.toLowerCase()] = y;
- return res;
- };
- res.getHeader = (x) => headers[x];
+ headers[x] = y
+ headers[x.toLowerCase()] = y
+ return res
+ }
+ res.getHeader = (x) => headers[x]
res.redirect = function (_code, url) {
if (typeof _code !== 'number') {
- code = 301;
- url = _code;
+ code = 301
+ url = _code
} else {
- code = _code;
+ code = _code
}
- res.setHeader("Location", url);
- res.end();
- };
+ res.setHeader('Location', url)
+ res.end()
+ }
res.status = res.sendStatus = function (number) {
- code = number;
- return res;
- };
- res.end = res.send = res.write = function (data = '') {
- if (callback) callback(code, data, headers);
- };
- return res;
+ code = number
+ return res
+ }
+ res.end =
+ res.send =
+ res.write =
+ function (data = '') {
+ if (callback) callback(code, data, headers)
+ }
+ return res
}
-server.prerender = async function (originalUrl, options) {
+server.prerender = async function (originalUrl) {
server.start()
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
server._router.handle(
createRequest(originalUrl),
- createResponse((code, data, headers) => resolve(data)),
- () => { }
+ createResponse((code, data) => resolve(data)),
+ () => {},
)
})
}
server.start = function () {
+ if (serverStarted) return
+ serverStarted = true
- if (serverStarted) return;
- serverStarted = true;
+ server.use(express.static(path.join(__dirname, '..', 'public')))
- server.use(cors(server.cors));
-
- server.use(express.static(path.join(__dirname, '..', 'public')));
-
- server.use(bodyParser.text({ limit: server.maximumPayloadSize }));
+ server.use(bodyParser.text({ limit: server.maximumPayloadSize }))
if (environment.production) {
-
server.get(`/:number.client.js`, (request, response) => {
- response.setHeader('Cache-Control', 'max-age=31536000, immutable');
- response.contentType('text/javascript');
- response.send(generateFile(`${request.params.number}.client.js`, server));
- });
+ response.setHeader('Cache-Control', 'max-age=31536000, immutable')
+ response.contentType('text/javascript')
+ response.send(generateFile(`${request.params.number}.client.js`, server))
+ })
server.get(`/:number.client.css`, (request, response) => {
- response.setHeader('Cache-Control', 'max-age=31536000, immutable');
- response.contentType('text/css');
- response.send(generateFile(`${request.params.number}.client.css`, server));
- });
+ response.setHeader('Cache-Control', 'max-age=31536000, immutable')
+ response.contentType('text/css')
+ response.send(generateFile(`${request.params.number}.client.css`, server))
+ })
server.get(`/client.css`, (request, response) => {
- response.setHeader('Cache-Control', 'max-age=31536000, immutable');
- response.contentType('text/css');
- response.send(generateFile('client.css', server));
- });
+ response.setHeader('Cache-Control', 'max-age=31536000, immutable')
+ response.contentType('text/css')
+ response.send(generateFile('client.css', server))
+ })
server.get(`/client.js`, (request, response) => {
- response.setHeader('Cache-Control', 'max-age=31536000, immutable');
- response.contentType('text/javascript');
- response.send(generateFile('client.js', server));
- });
-
+ response.setHeader('Cache-Control', 'max-age=31536000, immutable')
+ response.contentType('text/javascript')
+ response.send(generateFile('client.js', server))
+ })
}
server.get(`/manifest.webmanifest`, (request, response) => {
- response.setHeader('Cache-Control', 'max-age=31536000, immutable');
- response.contentType('application/manifest+json');
- response.send(generateManifest(server));
- });
+ response.setHeader('Cache-Control', 'max-age=31536000, immutable')
+ response.contentType('application/manifest+json')
+ response.send(generateManifest(server))
+ })
server.get(`/service-worker.js`, (request, response) => {
- response.setHeader('Cache-Control', 'max-age=31536000, immutable');
- response.contentType('text/javascript');
- response.send(generateServiceWorker());
- });
+ response.setHeader('Cache-Control', 'max-age=31536000, immutable')
+ response.contentType('text/javascript')
+ response.send(generateServiceWorker())
+ })
server.get('/robots.txt', (request, response) => {
- response.send(generateRobots());
- });
+ response.send(generateRobots())
+ })
server.all(`/${prefix}/:hash/:methodName.json`, async (request, response) => {
- const payload = request.method === 'GET' ? request.query.payload : request.body;
- const args = deserialize(payload);
- const { hash, methodName } = request.params;
- const [invokerHash, boundHash] = hash.split('-');
- const key = `${invokerHash}.${methodName}`;
- const invokerKlass = registry[invokerHash];
- let boundKlass = invokerKlass;
+ const payload = request.method === 'GET' ? request.query.payload : request.body
+ const args = deserialize(payload)
+ const { hash, methodName } = request.params
+ const [invokerHash, boundHash] = hash.split('-')
+ const key = `${invokerHash}.${methodName}`
+ const invokerKlass = registry[invokerHash]
+ let boundKlass = invokerKlass
if (boundHash) {
- boundKlass = registry[boundHash];
+ boundKlass = registry[boundHash]
if (!(boundKlass.prototype instanceof invokerKlass)) {
- return response.status(401).json({});
+ return response.status(401).json({})
}
}
- const method = registry[key];
+ const method = registry[key]
if (method !== undefined) {
try {
- const context = generateContext({ request, response, ...args });
- const result = await method.call(boundKlass, context);
- response.json({ result });
+ const subcontext = generateContext({ request, response, ...args })
+ const result = await method.call(boundKlass, subcontext)
+ response.json({ result })
} catch (error) {
- printError(error);
- response.status(500).json({});
+ printError(error)
+ response.status(500).json({})
}
} else {
- response.status(404).json({});
+ response.status(404).json({})
}
- });
+ })
server.get('*', async (request, response, next) => {
if (request.originalUrl.split('?')[0].indexOf('.') > -1) {
- return next();
+ return next()
}
reqres.request = request
reqres.response = response
- const scope = await prerender(request, response);
+ const scope = await prerender(request, response)
if (!response.headersSent) {
- const status = scope.context.page.status;
- const html = template(scope);
+ const status = scope.context.page.status
+ const html = template(scope)
reqres.request = null
reqres.response = null
- response.status(status).send(html);
+ response.status(status).send(html)
} else {
reqres.request = null
reqres.response = null
}
- });
+ })
if (!server.less) {
if (!server.port) {
- console.log('\x1b[31mServer port is not defined!\x1b[0m');
- process.exit();
+ console.info('\x1b[31mServer port is not defined!\x1b[0m')
+ process.exit()
}
server.listen(server.port, async () => {
if (environment.development) {
- if (process.env['NULLSTACK_ENVIRONMENT_DISK'] === 'true') {
- const content = await server.prerender('/');
- const target = process.cwd() + `/.development/index.html`
+ if (process.env.NULLSTACK_ENVIRONMENT_DISK === 'true') {
+ const content = await server.prerender('/')
+ const target = `${process.cwd()}/.development/index.html`
writeFileSync(target, content)
}
- const socket = new WebSocket(`ws://localhost:${process.env['NULLSTACK_SERVER_PORT']}/ws`);
- socket.onopen = async function (e) {
+ const socket = new WebSocket(`ws://localhost:${process.env.NULLSTACK_SERVER_PORT}/ws`)
+ socket.onopen = async function () {
socket.send('{"type":"NULLSTACK_SERVER_STARTED"}')
}
} else {
- console.log('\x1b[36m%s\x1b[0m', ` ✅️ Your application is ready at http://${process.env['NULLSTACK_PROJECT_DOMAIN']}:${process.env['NULLSTACK_SERVER_PORT']}\n`);
+ console.info(
+ '\x1b[36m%s\x1b[0m',
+ ` ✅️ Your application is ready at http://${process.env.NULLSTACK_PROJECT_DOMAIN}:${process.env.NULLSTACK_SERVER_PORT}\n`,
+ )
}
- });
+ })
}
-
}
-export default server;
+export default server
diff --git a/server/settings.js b/server/settings.js
index 14446d40..17192b09 100644
--- a/server/settings.js
+++ b/server/settings.js
@@ -1,5 +1,5 @@
-import { createConfigurable } from './configurable';
+import { createConfigurable } from './configurable'
-const secrets = createConfigurable('SETTINGS');
+const secrets = createConfigurable('SETTINGS')
-export default secrets;
\ No newline at end of file
+export default secrets
diff --git a/server/template.js b/server/template.js
index e4c2879c..c405e42a 100644
--- a/server/template.js
+++ b/server/template.js
@@ -1,34 +1,39 @@
-import { sanitizeString } from '../shared/sanitizeString';
-import environment from './environment';
-import integrities from './integrities';
-import { absolute, cdn, cdnOrAbsolute } from './links';
-import project from './project';
-import settings from './settings';
-import renderAttributes from './renderAttributes';
+import { sanitizeString } from '../shared/sanitizeString'
+import environment from './environment'
+import integrities from './integrities'
+import { absolute, cdn, cdnOrAbsolute } from './links'
+import project from './project'
+import renderAttributes from './renderAttributes'
+import settings from './settings'
export default function ({ head, body, nextBody, context, instances }) {
- const { page, router, worker, params } = context;
- const canonical = absolute(page.canonical || router.url);
- const image = cdnOrAbsolute(page.image);
- const serializableContext = {};
- const blacklist = ['scope', 'router', 'page', 'environment', 'settings', 'worker', 'params', 'project', 'instances'];
+ const { page, router, worker, params } = context
+ const canonical = absolute(page.canonical || router.url)
+ const image = cdnOrAbsolute(page.image)
+ const serializableContext = {}
+ const blacklist = ['scope', 'router', 'page', 'environment', 'settings', 'worker', 'params', 'project', 'instances']
for (const [key, value] of Object.entries(context)) {
if (!blacklist.includes(key) && typeof value !== 'function') {
- serializableContext[key] = value;
+ serializableContext[key] = value
}
}
- const serializableInstances = {};
+ const serializableInstances = {}
for (const [key, value] of Object.entries(instances)) {
if (Object.keys(value).length) {
- serializableInstances[key] = value;
+ serializableInstances[key] = value
}
}
const state = {
- page, environment, settings, worker, params, project,
+ page,
+ environment,
+ settings,
+ worker,
+ params,
+ project,
instances: environment.mode === 'spa' ? {} : serializableInstances,
- context: environment.mode === 'spa' ? {} : serializableContext
- };
- return (`
+ context: environment.mode === 'spa' ? {} : serializableContext,
+ }
+ return `
@@ -53,16 +58,20 @@ export default function ({ head, body, nextBody, context, instances }) {
${page.robots ? `` : ''}
${project.viewport ? `` : ''}
-
+
${page.schema ? `` : ''}
${project.icons['180'] ? `` : ''}
${head.split('').join('')}
-
+
${environment.mode === 'spa' ? '' : body}
-`)
-}
\ No newline at end of file
+