Skip to content
Merged

Next #343

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
9f6abc4
:zap: :construction: unstable hot server
Mortaro Feb 6, 2023
24ef07d
:construction: inject hmr
Mortaro Feb 6, 2023
583a33a
:construction: improving hmr
Mortaro Feb 9, 2023
1843a2f
:construction: improve startup
Mortaro Feb 10, 2023
20fda52
:construction: apply loader to silence HMR logs
ddanielcruz Feb 11, 2023
85c4255
Merge pull request #325 from ddanielcruz/unstable-hot
Mortaro Feb 11, 2023
ae03034
:construction: fix reload getting stuck
Mortaro Feb 11, 2023
bbdd543
:construction: clean up unused code
Mortaro Feb 13, 2023
1d1abd2
:sparkles: hot reload for vanilla express routes
Mortaro Feb 13, 2023
2047466
:construction: more swc magic
Mortaro Feb 24, 2023
59dc87b
:construction: :recycle: cleaning up webpack
Mortaro Feb 26, 2023
c29362a
:construction: accept visitor
Mortaro Feb 27, 2023
3ef6522
Merge pull request #331 from nullstack/unstable-next
Mortaro Mar 1, 2023
6e815a1
:construction: minify improvements
Mortaro Mar 1, 2023
0553fd2
Merge branch 'even-more-unstable-hot' of https://github.com/nullstack…
Mortaro Mar 1, 2023
fb4d2c3
:construction: private functions
Mortaro Mar 2, 2023
4189426
:construction: change development target
Mortaro Mar 2, 2023
bab3e7e
:construction: optimize builds
Mortaro Mar 3, 2023
fafdb3b
:construction: types as entry
Mortaro Mar 3, 2023
8228cb4
:construction: multiple inner components
Mortaro Mar 4, 2023
2b755b6
:construction: :pushpin: temporarily change webpack-dev-middleware ve…
Mortaro Mar 4, 2023
b0e928c
:construction: logo
Mortaro Mar 4, 2023
830bb93
:construction: logo
Mortaro Mar 4, 2023
3cb5e59
:construction: accept imports
Mortaro Mar 5, 2023
d58d7d0
Merge branch 'next' of https://github.com/nullstack/nullstack into ev…
Mortaro Mar 9, 2023
ca9cce9
:construction: debug loader
Mortaro Mar 10, 2023
a85607c
:construction: fine tunning timings
Mortaro Mar 10, 2023
ace6da5
:construction: fine tunning timings
Mortaro Mar 10, 2023
8328085
:construction: :recycle: refactor server file
Mortaro Mar 10, 2023
304a88d
:construction: renaming env to name
Mortaro Mar 11, 2023
97de7c1
:fire: remove unused code
Mortaro Mar 11, 2023
4656591
:construction: accept initiator
Mortaro Mar 12, 2023
e00803c
:construction: wait for compiler
Mortaro Mar 12, 2023
bd8b616
:construction: :recycle: clear dir
Mortaro Mar 12, 2023
3e455fb
:construction: improve client accept
Mortaro Mar 12, 2023
669cc31
:construction: :fire: deprecate babel loaders
Mortaro Mar 12, 2023
daadf8e
:construction: :fire: remove debuggers
Mortaro Mar 12, 2023
ae77c87
:construction: wait for server rebuild
Mortaro Mar 13, 2023
38a0eaa
:construction: :recycle: clean up experiments flag
Mortaro Mar 13, 2023
19d19e6
:construction: lazy load with swc
Mortaro Mar 21, 2023
8c15083
:construction: lazy files
Mortaro Mar 21, 2023
d69cdbb
:construction: lazy debug
Mortaro Mar 22, 2023
71fc789
:recycle: cleanup
Mortaro Mar 22, 2023
49ddbed
:construction: cleanup
Mortaro Mar 23, 2023
8409d64
Merge pull request #337 from nullstack/lazy-even-more-unstable-next
Mortaro Mar 23, 2023
e7f0d78
:construction: fix lazy importer
Mortaro Mar 25, 2023
dd10347
:construction: lazy and trace
Mortaro Mar 26, 2023
8b68e91
:pushpin: update webpack
Mortaro Mar 26, 2023
c90ce81
:construction: better terminal logs
Mortaro Mar 27, 2023
768e26b
🐛 fix: error thrown when using exposed server function with HTTP POST
edysegura Apr 14, 2023
432537b
Merge pull request #340 from edysegura/even-more-unstable-hot
Mortaro Apr 21, 2023
7134683
:bookmark: version 0.18.0
Mortaro Apr 21, 2023
4a189c3
:pushpin: upgrade webpack
Mortaro Apr 22, 2023
1bbf511
:pushpin: upgrade swc loader
Mortaro Apr 26, 2023
ed4aeae
:sparkles: update dom on page changes
Mortaro Apr 28, 2023
d181f33
Merge branch 'next' into even-more-unstable-hot
Mortaro Apr 28, 2023
535f463
:white_check_mark: fixing tests
Mortaro May 1, 2023
2167f84
:bug: empty class inheritance
Mortaro May 3, 2023
7cab474
:recycle: move dependencies to devdeps
Mortaro May 4, 2023
e3c666f
:pushpin: update dependencies types
Mortaro May 4, 2023
91c069d
:bug: fix ssg builder
Mortaro May 4, 2023
0e08e1f
:bug: fix ssg for html snippets
Mortaro May 4, 2023
403c064
:bug: fix ssg for html snippets
Mortaro May 4, 2023
fbd2497
:bug: fix ssg
Mortaro May 4, 2023
f63331e
Merge pull request #341 from nullstack/even-even-more-unstable-hot
Mortaro May 4, 2023
bd72889
:white_check_mark: improve redirect tests
Mortaro May 10, 2023
4b075fc
Merge branch 'even-more-unstable-hot' of https://github.com/nullstack…
Mortaro May 10, 2023
3f5e792
:bookmark: nullstack 0.18.0
Mortaro May 10, 2023
96c3637
Merge pull request #336 from nullstack/even-more-unstable-hot
Mortaro May 10, 2023
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
43 changes: 43 additions & 0 deletions builders/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

const clocks = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚']

function logger(bundle, mode) {
let cindex = 0
let timer = null
let stoped = false

function dots() {
return Array((cindex % 3) + 1).fill('.').join('')
}

function reset() {
if (process.stdout.clearLine) {
process.stdout.clearLine()
process.stdout.cursorTo(0)
}
}

function start() {
reset()
if (cindex >= clocks.length) {
cindex = 0
}
let symbol = clocks[cindex]
process.stdout.write(` ${symbol} Building your ${bundle} in ${mode} mode ${dots()}`)
cindex++;
timer = setTimeout(start, 200)
}
start()

function stop() {
if (stoped) return
clearTimeout(timer)
reset()
process.stdout.write(` ✅ Starting your ${bundle} in ${mode} mode\n`)
stoped = true
}

return { stop }
}

module.exports = logger
2 changes: 1 addition & 1 deletion builders/spa.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module.exports = async function spa({ output, cache, environment }) {
await copy('/robots.txt')
console.info()

console.info('\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.info('Storing cache...')
Expand Down
7 changes: 3 additions & 4 deletions builders/ssg.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ module.exports = async function ssg({ output, cache, environment }) {
const urls = Object.keys(pages).map((p) => {
const page = pages[p]
const canonical = `https://${application.project.domain}${p}`
return `<url><loc>${canonical}</loc><lastmod>${timestamp}</lastmod>${
page.changes ? `<changefreq>${page.changes}</changefreq>` : ''
}${page.priority ? `<priority>${page.priority.toFixed(1)}</priority>` : ''}</url>`
return `<url><loc>${canonical}</loc><lastmod>${timestamp}</lastmod>${page.changes ? `<changefreq>${page.changes}</changefreq>` : ''
}${page.priority ? `<priority>${page.priority.toFixed(1)}</priority>` : ''}</url>`
})
const xml = `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls.join(
'',
Expand Down Expand Up @@ -111,7 +110,7 @@ module.exports = async function ssg({ output, cache, environment }) {
await createSitemap()
console.info()

console.info('\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.info('Storing cache...')
Expand Down
2 changes: 1 addition & 1 deletion builders/ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module.exports = async function ssr({ cache }) {
const application = require(`${dir}/.production/server`).default
const projectName = application.project.name || 'The Nullstack application'

console.info('\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.info('Storing cache...')
Expand Down
14 changes: 11 additions & 3 deletions client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ client.nextHead = []
client.head = document.head
client.body = document.body

client.update = async function update() {
client.update = function update() {
if (client.initialized) {
clearInterval(client.renderQueue)
client.renderQueue = setTimeout(async () => {
Expand All @@ -39,9 +39,13 @@ client.update = async function update() {
client.nextVirtualDom = await generateTree(client.initializer(), scope)
rerender()
client.processLifecycleQueues()
} catch (e) {
} catch (error) {
client.skipHotReplacement = true
console.error(e)
if (context.catch) {
context.catch(error)
} else {
throw error
}
}
}, 16)
}
Expand Down Expand Up @@ -97,4 +101,8 @@ client.processLifecycleQueues = async function processLifecycleQueues() {
router._changed = false
}

if (module.hot) {
client.klasses = {}
}

export default client
55 changes: 4 additions & 51 deletions client/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
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'
Expand All @@ -16,7 +14,6 @@ import rerender from './rerender'
import router from './router'
import settings from './settings'
import state from './state'
import windowEvent from './windowEvent'
import worker from './worker'

context.page = page
Expand All @@ -35,11 +32,12 @@ scope.context = context

client.plugins = loadPlugins(scope)

if (environment.development) {
globalThis.$nullstack = context
}

export default class Nullstack {

static element = element
static invoke = invoke
static fragment = fragment
static use = useClientPlugins
static context = generateContext({})

Expand Down Expand Up @@ -104,48 +102,3 @@ export default class Nullstack {
}

}

if (module.hot) {
Nullstack.serverHashes ??= {}
Nullstack.serverPings = 0
Nullstack.clientPings = 0
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') {
Nullstack.serverPings++
if (Nullstack.needsReload || !environment.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)
}
}
if (Nullstack.serverHashes[hash]) {
if (Nullstack.serverHashes[hash] !== serverHash) {
if (Nullstack.clientPings < Nullstack.serverPings) {
window.location.reload()
} else {
Nullstack.needsReload = true
}
}
Nullstack.clientPings++
}
Nullstack.serverHashes[hash] = serverHash
client.update()
}
Nullstack.hotReload = function hotReload(klass) {
if (client.skipHotReplacement) {
window.location.reload()
} else {
Nullstack.start(klass)
windowEvent('environment')
}
}
module.hot.decline()
}
7 changes: 6 additions & 1 deletion client/invoke.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import deserialize from '../shared/deserialize'
import prefix from '../shared/prefix'
import page from './page'
import worker from './worker'
import client from './client'

export default function invoke(name, hash) {
return async function _invoke(params = {}) {
Expand All @@ -12,8 +13,12 @@ export default function invoke(name, hash) {
} else {
worker.queues[name] = [...worker.queues[name], params]
}
const finalHash = hash === this.hash ? hash : `${hash}-${this.hash}`
let finalHash = hash === this.hash ? hash : `${hash}-${this.hash}`
let url = `${worker.api}/${prefix}/${finalHash}/${name}.json`
if (module.hot) {
const version = client.klasses[hash].__hashes[name]
url = `${worker.api}/${prefix}/${version}/${finalHash}/${name}.json`
}
const body = JSON.stringify(params || {})
const options = {
headers: worker.headers,
Expand Down
37 changes: 37 additions & 0 deletions client/lazy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import client from './client'

const queue = {}
let next = null

async function preload() {
cancelIdleCallback(next)
let entry = Object.entries(queue)[0]
if (!entry) return
let loader = entry[1]
if (!loader) return
await loader.load()
next = requestIdleCallback(preload)
}

window.addEventListener('blur', () => {
preload()
})

window.addEventListener('focus', () => {
cancelIdleCallback(next)
})

export default function lazy(hash, importer) {
const loader = {
load: async () => {
const mod = await importer()
loader.component = mod.default
delete queue[hash]
client.update()
},
component: null,
__nullstack_lazy: true
}
queue[hash] = loader
return loader
}
1 change: 1 addition & 0 deletions client/logo.njs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '../logo'
16 changes: 13 additions & 3 deletions client/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@ delete state.page

const pageProxyHandler = {
set(target, name, value, receiver) {
if (name === 'title') {
document.title = value
}
const result = Reflect.set(target, name, value, receiver)
if (name === 'title') {
document.title = value
document.querySelector('head > meta[property="og:title"]').setAttribute('content', value)
windowEvent('page')
} else if (name === 'description') {
document.querySelector('head > meta[name="description"]').setAttribute('content', value)
document.querySelector('head > meta[property="og:description"]').setAttribute('content', value)
} else if (name === 'locale') {
document.querySelector('html').setAttribute('lang', value)
document.querySelector('head > meta[property="og:locale"]').setAttribute('content', value)
} else if (name === 'image') {
document.querySelector('head > meta[property="og:image"]').setAttribute('content', value)
} else if (name === 'canonical') {
canonical = (path.indexOf('//') === -1) ? router.base + value : value
document.querySelector('head > link[rel="canonical"]').setAttribute('href', canonical)
}
client.update()
return result
Expand Down
3 changes: 2 additions & 1 deletion client/render.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sanitizeInnerHtml } from '../shared/sanitizeString'
import generateTruthyString from '../shared/generateTruthyString'
import { isFalse, isText } from '../shared/nodes'
import { anchorableElement } from './anchorableNode'
Expand Down Expand Up @@ -28,7 +29,7 @@ export default function render(node, options) {
for (const name in node.attributes) {
if (name === 'debounce') continue
if (name === 'html') {
node.element.innerHTML = node.attributes[name]
node.element.innerHTML = sanitizeInnerHtml(node.attributes[name])
node.head || anchorableElement(node.element)
} else if (name.startsWith('on')) {
if (node.attributes[name] !== undefined) {
Expand Down
3 changes: 2 additions & 1 deletion client/rerender.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sanitizeInnerHtml } from '../shared/sanitizeString'
import generateTruthyString from '../shared/generateTruthyString'
import { isFalse, isText, isUndefined } from '../shared/nodes'
import { anchorableElement } from './anchorableNode'
Expand All @@ -14,7 +15,7 @@ function updateAttributes(selector, currentAttributes, nextAttributes) {
reref(nextAttributes, selector)
} else if (name === 'html') {
if (nextAttributes[name] !== currentAttributes[name]) {
selector.innerHTML = nextAttributes[name]
selector.innerHTML = sanitizeInnerHtml(nextAttributes[name])
anchorableElement(selector)
}
} else if (name === 'checked' || name === 'value') {
Expand Down
4 changes: 3 additions & 1 deletion client/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { updateParams } from './params'
import segments from './segments'
import windowEvent from './windowEvent'
import worker from './worker'
import deserialize from '../shared/deserialize'

let redirectTimer = null

Expand Down Expand Up @@ -40,7 +41,8 @@ class Router {
const endpoint = path === '/' ? api : path + api
try {
const response = await fetch(endpoint)
const payload = await response.json(url)
const meta = await response.text()
const payload = deserialize(meta)
client.memory = payload.instances
for (const key in payload.page) {
page[key] = payload.page[key]
Expand Down
Loading