Skip to content
Merged

Next #293

Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -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"
]
}
38 changes: 19 additions & 19 deletions builders/spa.js
Original file line number Diff line number Diff line change
@@ -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)
}
Expand All @@ -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()
}
}
}
105 changes: 57 additions & 48 deletions builders/ssg.js
Original file line number Diff line number Diff line change
@@ -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 = '<meta name="nullstack" content="';
const state = content.split("\n").find((line) => line.indexOf(stateLookup) > -1).split(stateLookup)[1].slice(0, -2);
const { instances, page } = JSON.parse(decodeURIComponent(state));
const stateLookup = '<meta name="nullstack" content="'
const state = content
.split('\n')
.find((line) => line.indexOf(stateLookup) > -1)
.split(stateLookup)[1]
.slice(0, -2)
const { instances, page } = JSON.parse(decodeURIComponent(state))

if (url !== `/nullstack/${application.environment.key}/offline` && url !== '/404') {
pages[url] = page;
pages[url] = page
}

const json = JSON.stringify({ instances, page });
writeFileSync(`${target}/index.json`, json);
const json = JSON.stringify({ instances, page })
writeFileSync(`${target}/index.json`, json)

const pattern = /href="(.*?)"/g;
while (match = pattern.exec(content)) {
const link = match[1].split('#')[0];
const pattern = /href="(.*?)"/g
let match
while ((match = pattern.exec(content))) {
const link = match[1].split('#')[0]
if (link.startsWith('/')) {
if (links[link] === undefined) {
links[link] = false;
links[link] = false
}
}
}
Expand All @@ -62,51 +67,55 @@ module.exports = async function ssg({ output, cache, environment }) {
}

async function copyBundle(url) {
console.log(` ⚙️ ${url}`)
const content = await application.server.prerender(url);
console.info(` ⚙️ ${url}`)
const content = await application.server.prerender(url)
const target = path(url)
writeFileSync(target, content)
}

async function createSitemap() {
console.log(' ⚙️ /sitemap.xml')
const timestamp = new Date().toJSON().substring(0, 10);
const urls = Object.keys(pages).map((path) => {
const page = pages[path];
const canonical = `https://${application.project.domain}${path}`;
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('')}</urlset>`;
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 `<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(
'',
)}</urlset>`
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()
}
}
}
14 changes: 7 additions & 7 deletions builders/ssr.js
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
8 changes: 4 additions & 4 deletions client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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 {
Expand All @@ -97,4 +97,4 @@ client.processLifecycleQueues = async function processLifecycleQueues() {
router._changed = false
}

export default client
export default client
26 changes: 13 additions & 13 deletions client/context.js
Original file line number Diff line number Diff line change
@@ -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;
export default context
8 changes: 4 additions & 4 deletions client/environment.js
Original file line number Diff line number Diff line change
@@ -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;
export default environment
Loading