Skip to content
Merged
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
15 changes: 12 additions & 3 deletions client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,18 @@ export default class Nullstack {

if (module.hot) {
const socket = new WebSocket('ws' + window.location.origin.slice(4) + '/ws');
socket.onmessage = function (e) {
if (e.data.indexOf('still-ok') > -1) {
window.location.reload()
window.lastHash
socket.onmessage = async function (e) {
const data = JSON.parse(e.data)
if (data.type === 'NULLSTACK_SERVER_STARTED') {
(window.needsReload || !environment.hot) && window.location.reload()
} else if (data.type === 'hash') {
const newHash = data.data.slice(20)
if (newHash === window.lastHash) {
window.needsReload = true
} else {
window.lastHash = newHash
}
}
};
Nullstack.updateInstancesPrototypes = function updateInstancesPrototypes(hash, klass) {
Expand Down
5 changes: 2 additions & 3 deletions loaders/register-static-from-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,14 @@ module.exports = function (source) {
});
if (!hasClass) return source;
let output = source.substring(0, klassEnd);
for (const methodName of methodNames) {
output += `${methodName} = Nullstack.invoke('${methodName}');\n`
}
output += source.substring(klassEnd);
for (const methodName of methodNames) {
output += `\nNullstack.registry["${hash}.${methodName}"] = ${klassName}.${methodName};`
output += `\nNullstack.registry["${legacyHash}.${methodName}"] = ${klassName}.${methodName};`
}
output += `\nNullstack.registry["${hash}"] = ${klassName};`
output += `\nNullstack.registry["${legacyHash}"] = ${klassName};`
output += `\n${klassName}.hash = "${hash}";`
output += `\n${klassName}.bindStaticFunctions(${klassName});`
return output;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"fs-extra": "10.1.0",
"mini-css-extract-plugin": "2.6.0",
"node-fetch": "2.6.7",
"nodemon-webpack-plugin": "^4.8.1",
"sass": "1.51.0",
"sass-loader": "8.0.2",
"swc-loader": "0.2.1",
Expand Down
4 changes: 2 additions & 2 deletions plugins/bindable.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function match(node) {

function transform({ node, environment }) {
if (!match(node)) return;
const object = node.attributes.bind.object;
const object = node.attributes.bind.object ?? {};
const property = node.attributes.bind.property;
if (node.type === 'textarea') {
node.children = [object[property]];
Expand All @@ -27,4 +27,4 @@ function transform({ node, environment }) {
}
}

export default { transform, client: true, server: true }
export default { transform, client: true, server: true }
140 changes: 83 additions & 57 deletions scripts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,74 +21,100 @@ function getServerCompiler(options) {
return webpack(getConfig(options)[0])
}

function getClientCompiler(options) {
return webpack(getConfig(options)[1])
}

async function start({ input, port, env, mode = 'ssr', hot }) {
const environment = 'development';
const serverCompiler = getServerCompiler({ environment, input });
let clientStarted = false
function loadEnv(env) {
let envPath = '.env'
if (env) {
envPath += `.${process.env.NULLSTACK_ENVIRONMENT_NAME}`
}
dotenv.config({ path: envPath })
if (!port) {
port = process.env['NULLSTACK_SERVER_PORT'] || process.env['PORT'] || 3000
}
process.env['NULLSTACK_ENVIRONMENT_MODE'] = mode
}

function getFreePorts() {
return new Promise((resolve, reject) => {
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
]
server1.close()
server2.close()
resolve(ports)
});
});
})
}

function getPort(port) {
return port || process.env['NULLSTACK_SERVER_PORT'] || process.env['PORT'] || 3000
}

async function start({ input, port, env, mode = 'spa', hot }) {
const environment = 'development'
console.log(` 🚀️ Starting your application in ${environment} mode...`);
loadEnv(env)
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)
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'] = (!!hot).toString()
const devServerOptions = {
hot: 'only',
open: false,
devMiddleware: {
index: false,
stats: 'none'
},
client: {
overlay: { errors: true, warnings: false },
logging: 'none',
progress: false,
reconnect: true
},
proxy: {
context: () => true,
target: `http://localhost:${process.env['NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT']}`,
proxyTimeout: 10 * 60 * 1000,
timeout: 10 * 60 * 1000,
},
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error('webpack-dev-server is not defined');
}
middlewares.unshift((req, res, next) => {
if (req.originalUrl.indexOf('.hot-update.') === -1) {
if (req.originalUrl.startsWith('/nullstack/')) {
console.log(` ⚙️ [${req.method}] ${req.originalUrl}`)
} else {
console.log(` 🕸️ [${req.method}] ${req.originalUrl}`)
}
}
next()
});
return middlewares;
},
webSocketServer: require.resolve('./socket'),
port: process.env['NULLSTACK_SERVER_PORT']
};
const clientCompiler = getCompiler({ environment, input });
const server = new WebpackDevServer(devServerOptions, clientCompiler);
const serverCompiler = getServerCompiler({ environment, input });
let once = false
serverCompiler.watch({}, (error, stats) => {
if (stats.hasErrors()) {
console.log(`\n 💥️ There is an error preventing compilation`);
} else {
console.log('\x1b[36m%s\x1b[0m', `\n ✅️ Your application is ready at http://localhost:${port}\n`);
if (!once) {
server.startCallback(() => {
console.log('\x1b[36m%s\x1b[0m', ` ✅️ Your application is ready at http://localhost:${process.env['NULLSTACK_SERVER_PORT']}\n`);
});
once = true
}
const bundlePath = path.resolve(process.cwd(), '.development/server.js')
delete require.cache[require.resolve(bundlePath)]
if (!clientStarted) {
clientStarted = true
const devServerOptions = {
hot: !!hot,
open: false,
devMiddleware: {
index: false
},
client: {
overlay: true,
logging: 'none',
progress: false,
},
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error('webpack-dev-server is not defined');
}
middlewares.unshift((req, res, next) => {
if (req.originalUrl.indexOf('.hot-update.') === -1) {
if (req.originalUrl.startsWith('/nullstack/')) {
console.log(` ⚙️ [${req.method}] ${req.originalUrl}`)
} else {
console.log(` 🕸️ [${req.method}] ${req.originalUrl}`)
}
}
const serverBundle = require(bundlePath)
const server = serverBundle.default.server
server.less = true
server.port = port
server.start()
server(req, res, next)
});
return middlewares;
},
port
};
const clientCompiler = getClientCompiler({ environment, input });
const server = new WebpackDevServer(devServerOptions, clientCompiler);
server.start();
if (stats.hasErrors()) {
console.log(stats.toString({ colors: true }))
}
});
}
Expand Down
70 changes: 70 additions & 0 deletions scripts/socket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"use strict";

const WebSocket = require("ws");
const BaseServer = require("webpack-dev-server/lib/servers/BaseServer");

module.exports = class WebsocketServer extends BaseServer {
constructor(server) {
super(server);

const options = {
...(this.server.options.webSocketServer).options,
clientTracking: false,
port: process.env['NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT']
};

this.implementation = new WebSocket.Server(options);

this.server.server.on("upgrade", (req, sock, head) => {
if (!this.implementation.shouldHandle(req)) {
return;
}
this.implementation.handleUpgrade(req, sock, head, (connection) => {
this.implementation.emit("connection", connection, req);
});
});

this.implementation.on("error", (err) => {
this.server.logger.error(err.message);
});

const interval = setInterval(() => {
this.clients.forEach((client) => {
if (client.isAlive === false) {
client.terminate();

return;
}

client.isAlive = false;
client.ping(() => { });
});
}, 1000);

this.implementation.on("connection", (client) => {
this.clients.push(client);

client.isAlive = true;

client.on("message", (data) => {
if (data === '{"type":"NULLSTACK_SERVER_STARTED"}') {
this.clients.forEach((client) => {
client.send('{"type":"NULLSTACK_SERVER_STARTED"}')
});
}
})

client.on("pong", () => {
client.isAlive = true;
});

client.on("close", () => {
this.clients.splice(this.clients.indexOf(client), 1);
});
});

this.implementation.on("close", () => {
clearInterval(interval);
});
}
};
3 changes: 1 addition & 2 deletions server/environment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//import files from './files';

const environment = { client: false, server: true };

environment.development = __dirname.indexOf('.development') > -1;
Expand All @@ -10,6 +8,7 @@ environment.mode = process.env.NULLSTACK_ENVIRONMENT_MODE || 'ssr';
environment.key = "{{NULLSTACK_ENVIRONMENT_KEY}}"

environment.name = process.env.NULLSTACK_ENVIRONMENT_NAME || '';
environment.hot = process.env.NULLSTACK_ENVIRONMENT_HOT === 'true'

Object.freeze(environment);

Expand Down
39 changes: 34 additions & 5 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import context from './context';
import environment from './environment';
import generator from './generator';
import instanceProxyHandler from './instanceProxyHandler';
import invoke from './invoke';
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 reqres from './reqres'
import { generateContext } from './context';

globalThis.window = {}

Expand All @@ -31,10 +32,40 @@ class Nullstack {

static registry = registry;
static element = element;
static invoke = invoke;
static fragment = fragment;
static use = useServerPlugins;

static bindStaticFunctions(klass) {
let parent = klass
while (parent.name !== 'Nullstack') {
const props = Object.getOwnPropertyNames(parent)
for (const prop of props) {
const underscored = prop.startsWith('_')
if (typeof klass[prop] === 'function') {
if (!underscored && !registry[`${parent.hash}.${prop}`]) {
return
}
const propName = `__NULLSTACK_${prop}`
if (!klass[propName]) {
klass[propName] = klass[prop]
}
function _invoke(...args) {
if (underscored) {
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);
}
klass[prop] = _invoke
klass.prototype[prop] = _invoke
}
}
parent = Object.getPrototypeOf(parent)
}
}

static start(Starter) {
if (this.name.indexOf('Nullstack') > -1) {
generator.starter = () => element(Starter);
Expand All @@ -49,9 +80,7 @@ class Nullstack {
terminated = false
key = null

constructor(scope) {
this._request = () => scope.request;
this._response = () => scope.response;
constructor() {
const methods = getProxyableMethods(this);
const proxy = new Proxy(this, instanceProxyHandler);
for (const method of methods) {
Expand Down
10 changes: 0 additions & 10 deletions server/invoke.js

This file was deleted.

2 changes: 2 additions & 0 deletions server/reqres.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const reqres = {}
export default reqres
Loading