Skip to content
Merged

Next #136

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4ddb6ae
:construction: typescript
Mortaro Dec 12, 2021
6aabcfd
:label: Add all type definition files
GuiDevloper Dec 12, 2021
d57704b
:recycle: Use real Context type on TS example
GuiDevloper Dec 12, 2021
bce423a
Merge pull request #132 from nullstack/master
Mortaro Dec 21, 2021
090ec69
Merge pull request #128 from GuiDevloper/typescript
Mortaro Dec 23, 2021
2f54f40
:recycle: refactor types
Mortaro Dec 24, 2021
34e0df1
:recycle: readd module declaration
Mortaro Dec 24, 2021
d271b9e
:white_check_mark: add typescript tests
Mortaro Dec 24, 2021
9ec088f
:bug: fix bind for TS files
Mortaro Dec 25, 2021
9a0a803
:sparkles: router previous
Mortaro Dec 28, 2021
b28db29
Merge pull request #134 from nullstack/router-previous
Mortaro Dec 28, 2021
3316240
Merge branch 'next' into typescript
Mortaro Dec 28, 2021
7f715b2
Merge pull request #135 from nullstack/typescript
Mortaro Dec 28, 2021
a6d2b9f
:wrench: remove default port 3000 hardcoded
MarceloBorgesP Dec 30, 2021
ec5d3aa
:ambulance: only set port if not in server.less mode
MarceloBorgesP Dec 31, 2021
fa7c49c
Merge pull request #138 from MarceloBorgesP/feature/remove-port-3000
Mortaro Dec 31, 2021
6757de6
:sparkles: external server functions
Mortaro Dec 31, 2021
56d0be4
Merge pull request #139 from nullstack/external-server-functions
Mortaro Dec 31, 2021
7bead1b
:sparkles: undefined error messages
Mortaro Jan 1, 2022
ef03323
:mute: remove logs
Mortaro Jan 1, 2022
fa8bad0
Merge pull request #140 from nullstack/undefined-errors
Mortaro Jan 1, 2022
778af64
:bug: fix ternary outter components
Mortaro Jan 1, 2022
b50d1b7
:ambulance: only user server port from env if not yet defined
MarceloBorgesP Jan 3, 2022
7a217b1
Merge pull request #143 from MarceloBorgesP/fix/server-port
Mortaro Jan 3, 2022
969e29e
chore: remove redundant if statement
edysegura Jan 7, 2022
532b7dc
Merge pull request #142 from nullstack/ternary-outter-components
Mortaro Jan 7, 2022
01a67c5
:sparkles: undefined bind
Mortaro Jan 9, 2022
d680149
Merge pull request #148 from nullstack/undefined-bind
Mortaro Jan 9, 2022
ced37e0
:white_check_mark: tests for tailwind 3 purge
Mortaro Jan 9, 2022
763f6a2
:construction: improve purge test cases
Mortaro Jan 9, 2022
3cae9ba
fix: don't purge tailwind 3 new CSS classes
edysegura Jan 12, 2022
6b11bd7
Merge pull request #151 from edysegura/origin/tailwind-purge
Mortaro Jan 13, 2022
9b6096a
:mute: remove logs
Mortaro Jan 13, 2022
27847ea
Merge remote-tracking branch 'upstream/tailwind-purge'
edysegura Jan 14, 2022
4d7fb4a
Merge pull request #150 from edysegura/master
Mortaro Jan 16, 2022
be69737
Merge pull request #149 from nullstack/tailwind-purge
Mortaro Jan 16, 2022
7458af5
:bookmark: version 0.13.0
Mortaro Jan 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions client/instanceProxyHandler.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { generateObjectProxy } from './objectProxyHandler';
import client from './client';
import { generateContext } from './context';
import { generateObjectProxy } from './objectProxyHandler';

const instanceProxyHandler = {
get(target, name) {
if (name === '_isProxy') return true;
if (target.constructor[name]?.name === '_invoke') return target.constructor[name].bind(target.constructor)
if (!target[name]?.name?.startsWith('_') && !name.startsWith('_') && typeof (target[name]) == 'function' && name !== 'constructor') {
const { [name]: named } = {
[name]: (args) => {
Expand All @@ -21,7 +22,7 @@ const instanceProxyHandler = {
target[name] = generateObjectProxy(name, value);
client.update();
} else {
target[name] = value
target[name] = value;
}
return true;
}
Expand Down
3 changes: 1 addition & 2 deletions client/invoke.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ export default function invoke(name, hash) {
} else {
worker.queues[name] = [...worker.queues[name], params];
}
const finalHash = hash === this.constructor.hash ? hash : `${hash}-${this.constructor.hash}`;
const finalHash = hash === this.hash ? hash : `${hash}-${this.hash}`;
let url = `${worker.api}/${prefix}/${finalHash}/${name}.json`;
let body = JSON.stringify(params || {});

const options = {
headers: worker.headers,
mode: 'cors',
Expand Down
88 changes: 44 additions & 44 deletions client/rerender.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
import {isFalse, isText} from '../shared/nodes';
import { isFalse, isText } from '../shared/nodes';
import { anchorableElement } from './anchorableNode';
import client from './client';
import render from './render';
import {anchorableElement} from './anchorableNode';

export default function rerender(selector, current, next) {

current = current === undefined ? client.virtualDom : current;
next = next === undefined ? client.nextVirtualDom : next;

if(next.instance) {
if (next.instance) {
next.instance._self.element = selector;
}

if(!client.hydrated && selector) {
for(const element of selector.childNodes) {
if(element.tagName && element.tagName.toLowerCase() == 'textarea' && element.childNodes.length == 0) {
if (!client.hydrated && selector) {
for (const element of selector.childNodes) {
if (element.tagName && element.tagName.toLowerCase() == 'textarea' && element.childNodes.length == 0) {
element.appendChild(document.createTextNode(''));
}
if(element.COMMENT_NODE === 8 && element.textContent === '#') {
if (element.COMMENT_NODE === 8 && element.textContent === '#') {
selector.removeChild(element);
}
}
}

if(isFalse(current) && isFalse(next)) {
if (isFalse(current) && isFalse(next)) {
return;
}

if((isFalse(current) || isFalse(next)) && current != next) {
if ((isFalse(current) || isFalse(next)) && current != next) {
const nextSelector = render(next);
return selector.replaceWith(nextSelector);
}

if(current.type == 'head' && next.type == 'head') {
if (current.type == 'head' && next.type == 'head') {
return;
}

if(current.type == 'head' || next.type == 'head') {
if (current.type == 'head' || next.type == 'head') {
const nextSelector = render(next);
return selector.replaceWith(nextSelector);
}
Expand All @@ -47,51 +47,51 @@ export default function rerender(selector, current, next) {
}

if (isText(current) && isText(next)) {
if(current != next) {
if (current != next) {
selector.nodeValue = next;
}
return;
}

if (current.type === next.type) {

const attributeNames = Object.keys({...current.attributes, ...next.attributes});
for(const name of attributeNames) {
if(name === 'html') {
if(next.attributes[name] !== current.attributes[name]) {
const attributeNames = Object.keys({ ...current.attributes, ...next.attributes });
for (const name of attributeNames) {
if (name === 'html') {
if (next.attributes[name] !== current.attributes[name]) {
selector.innerHTML = next.attributes[name];
anchorableElement(selector);
}
} else if(name === 'checked') {
if(next.attributes[name] !== selector.value) {
}
} else if (name === 'checked') {
if (next.attributes[name] !== selector.value) {
selector.checked = next.attributes[name];
}
} else if(name === 'value') {
if(next.attributes[name] !== selector.value) {
} else if (name === 'value') {
if (next.attributes[name] !== selector.value) {
selector.value = next.attributes[name];
}
} else if(name.startsWith('on')) {
} else if (name.startsWith('on')) {
const eventName = name.replace('on', '');
const key = '_event.' + eventName;
selector.removeEventListener(eventName, current[key]);
if(next.attributes[name]) {
if (next.attributes[name]) {
next[key] = (event) => {
if(next.attributes.default !== true) {
if (next.attributes.default !== true) {
event.preventDefault();
}
next.attributes[name]({...next.attributes, event});
next.attributes[name]({ ...next.attributes, event });
};
selector.addEventListener(eventName, next[key]);
}
} else {
const type = typeof(next.attributes[name]);
if(type !== 'object' && type !== 'function') {
if(current.attributes[name] !== undefined && next.attributes[name] === undefined) {
const type = typeof (next.attributes[name]);
if (type !== 'object' && type !== 'function') {
if (current.attributes[name] !== undefined && next.attributes[name] === undefined) {
selector.removeAttribute(name);
} else if(current.attributes[name] !== next.attributes[name]) {
if(name != 'value' && next.attributes[name] === false || next.attributes[name] === null || next.attributes[name] === undefined) {
} else if (current.attributes[name] !== next.attributes[name]) {
if (name != 'value' && next.attributes[name] === false || next.attributes[name] === null || next.attributes[name] === undefined) {
selector.removeAttribute(name);
} else if(name != 'value' && next.attributes[name] === true) {
} else if (name != 'value' && next.attributes[name] === true) {
selector.setAttribute(name, '');
} else {
selector.setAttribute(name, next.attributes[name]);
Expand All @@ -101,39 +101,39 @@ export default function rerender(selector, current, next) {
}
}

if(next.attributes.html) return;
if (next.attributes.html) return;

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++) {
if (next.children.length > current.children.length) {
for (let i = 0; i < current.children.length; i++) {
rerender(selector.childNodes[i], current.children[i], next.children[i]);
}
for(let i = current.children.length; i < next.children.length; i++) {
for (let i = current.children.length; i < next.children.length; i++) {
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++) {
} else if (current.children.length > next.children.length) {
for (let i = 0; i < next.children.length; i++) {
rerender(selector.childNodes[i], current.children[i], next.children[i]);
}
for(let i = current.children.length - 1; i >= next.children.length; i--) {
selector.removeChild(selector.childNodes[i]);
for (let i = current.children.length - 1; i >= next.children.length; i--) {
selector.removeChild(selector.childNodes[i]);
}
} else {
for(let i = limit - 1; i > -1; i--) {
if(typeof selector.childNodes[i] === 'undefined') {
console.error(`Virtual DOM does not match the DOM. Expected tag ${current.type} but instead found undefined. This error usually happens because of an invalid HTML hierarchy like nested forms or tables without tr.`);
for (let i = limit - 1; i > -1; i--) {
if (typeof selector.childNodes[i] === 'undefined') {
throw new Error(`Virtual DOM does not match the DOM. Expected tag ${current.type} but instead found undefined. This error usually happens because of an invalid HTML hierarchy like nested forms or tables without tr.`);
return;
}
rerender(selector.childNodes[i], current.children[i], next.children[i]);
}
}

if(next.type == 'textarea') {
if (next.type == 'textarea') {
selector.value = next.children.join("");
}

if(next.type == 'select') {
if (next.type == 'select') {
selector.value = next.attributes.value;
}

Expand Down
37 changes: 19 additions & 18 deletions client/router.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,56 @@
import {updateParams} from './params';
import environment from './environment';
import extractLocation from '../shared/extractLocation';
import worker from './worker';
import page from './page';
import windowEvent from './windowEvent';
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;

class Router {

event = 'nullstack.router';

previous = null;
_changed = false;
_segments = segments;

constructor() {
const {hash, url} = extractLocation(window.location.pathname+window.location.search);
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);
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);
this.previous = this.url;
const { url, path, hash, urlWithHash } = extractLocation(target);
clearTimeout(redirectTimer);
redirectTimer = setTimeout(async () => {
page.status = 200;
if(environment.mode === 'ssg') {
if (environment.mode === 'ssg') {
worker.fetching = true;
const api = '/index.json';
const endpoint = path === '/' ? api : path+api;
const endpoint = path === '/' ? api : path + api;
try {
const response = await fetch(endpoint);
const payload = await response.json(url);
client.memory = payload.instances;
for(const key in payload.page) {
for (const key in payload.page) {
page[key] = payload.page[key];
}
worker.responsive = true;
} catch(error) {
} catch (error) {
worker.responsive = false;
}
worker.fetching = false;
}
if(push) {
if (push) {
history.pushState({}, document.title, urlWithHash);
}
this._url = url;
Expand All @@ -65,11 +66,11 @@ class Router {
if (target.startsWith('http')) {
return (window.location.href = target);
}
const {url, hash, urlWithHash} = extractLocation(target);
if(url !== this._url || this._hash !== hash) {
const { url, hash, urlWithHash } = extractLocation(target);
if (url !== this._url || this._hash !== hash) {
await this._update(urlWithHash, true);
}
if(!hash) {
if (!hash) {
window.scroll(0, 0);
}
}
Expand All @@ -87,7 +88,7 @@ class Router {
}

set path(target) {
this._redirect(target+window.location.search);
this._redirect(target + window.location.search);
}

}
Expand Down
24 changes: 12 additions & 12 deletions loaders/register-inner-components.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
const parse = require('@babel/parser').parse;
const traverse = require("@babel/traverse").default;

module.exports = function(source) {
module.exports = function (source) {
const injections = {};
const positions = [];
const ast = parse(source, {
sourceType: 'module',
plugins: ['classProperties', 'jsx']
plugins: ['classProperties', 'jsx', 'typescript']
});
traverse(ast, {
ClassMethod(path) {
if(path.node.key.name.startsWith('render')) {
if (path.node.key.name.startsWith('render')) {
traverse(path.node, {
JSXIdentifier(subpath) {
if(/^[A-Z]/.test(subpath.node.name)) {
if(!path.scope.hasBinding(subpath.node.name)) {
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)) {
if (!positions.includes(start)) {
positions.push(start);
}
if(!injections[start]) {
if (!injections[start]) {
injections[start] = [];
}
if(!injections[start].includes(subpath.node.name)) {
if (!injections[start].includes(subpath.node.name)) {
injections[start].push(subpath.node.name);
}
}
Expand All @@ -36,13 +36,13 @@ module.exports = function(source) {
positions.push(0);
let outputs = [];
let last;
for(const position of positions) {
for (const position of positions) {
let code = source.slice(position, last);
last = position;
outputs.push(code);
if(position) {
for(const injection of injections[position]) {
if(injection) {
if (position) {
for (const injection of injections[position]) {
if (injection) {
outputs.push(`const ${injection} = this.render${injection};\n `)
}
}
Expand Down
10 changes: 5 additions & 5 deletions loaders/register-static-from-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const crypto = require('crypto');
const parse = require('@babel/parser').parse;
const traverse = require("@babel/traverse").default;

module.exports = function(source) {
module.exports = function (source) {
let hasClass = false;
const hash = crypto.createHash('md5').update(source).digest("hex");
let klassName;
Expand All @@ -19,18 +19,18 @@ module.exports = function(source) {
klassName = path.node.id.name;
},
ClassMethod(path) {
if(path.node.static && path.node.async && path.node.key.name.split(/[A-Z]/)[0] !== 'start') {
if (path.node.static && path.node.async && path.node.key.name.split(/[A-Z]/)[0] !== 'start') {
methodNames.push(path.node.key.name);
}
}
});
if(!hasClass) return source;
if (!hasClass) return source;
let output = source.substring(0, klassEnd);
for(const methodName of methodNames) {
for (const methodName of methodNames) {
output += `${methodName} = Nullstack.invoke('${methodName}');\n`
}
output += source.substring(klassEnd);
for(const methodName of methodNames) {
for (const methodName of methodNames) {
output += `\nNullstack.registry["${hash}.${methodName}"] = ${klassName}.${methodName};`
}
output += `\nNullstack.registry["${hash}"] = ${klassName};`
Expand Down
Loading