mirror of
https://gitlab.com/SpaccInc/SpaccDotWeb.git
synced 2024-12-12 08:26:48 +01:00
[Server] Handle linkClientScripts, polish static HTML gen with embedded files
This commit is contained in:
parent
b47baf408b
commit
b315fdce00
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
*.tmp
|
*.tmp
|
||||||
/Build/*
|
/Build/*
|
||||||
/node_modules/*
|
/node_modules/*
|
||||||
/Example.Server.html
|
/Example.Server/index.html
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const SpaccDotWebServer = require('./SpaccDotWeb.Server.js');
|
const SpaccDotWebServer = require('../SpaccDotWeb.Server.js');
|
||||||
const server = SpaccDotWebServer.setup({
|
const server = SpaccDotWebServer.setup({
|
||||||
appName: 'Example',
|
appName: 'Example',
|
||||||
// staticPrefix: '/static/',
|
// staticPrefix: '/static/',
|
||||||
|
// staticRoot: '', // not (yet) implemented
|
||||||
// staticFiles: [],
|
// staticFiles: [],
|
||||||
linkStyles: [ 'Example.css' ],
|
linkStyles: [ 'index.css' ],
|
||||||
// linkScripts: [],
|
// linkRuntimeScripts: [], // not (yet) implemented
|
||||||
|
linkClientScripts: [ 'particles.js' ],
|
||||||
// pageTitler: (title, opts={}) => `...`,
|
// pageTitler: (title, opts={}) => `...`,
|
||||||
// appPager: (content, title, opts={}) => `...`,
|
// appPager: (content, title, opts={}) => `...`,
|
||||||
// htmlPager: (content, title, opts={}) => `...`,
|
// htmlPager: (content, title, opts={}) => `...`,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (SpaccDotWebServer.envIsNode && ['dump', 'html'].includes(process.argv[2])) {
|
if (SpaccDotWebServer.envIsNode && ['dump', 'html', 'writeStaticHtml'].includes(process.argv[2])) {
|
||||||
const fileName = server.writeStaticHtml();
|
const fileName = server.writeStaticHtml(Number(process.argv[3] || 0));
|
||||||
console.log(`Dumped Static HTML to '${fileName}'!`);
|
console.log(`Dumped Static HTML to '${fileName}'!`);
|
||||||
} else {
|
} else {
|
||||||
const serverData = server.initServer({
|
const serverData = server.initServer({
|
17
Example.Server/particles.js
Normal file
17
Example.Server/particles.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
window.addEventListener('load', function(){
|
||||||
|
for (var i=0; i<(window.innerWidth * window.innerHeight / 6000); i++) (function(){
|
||||||
|
var v = (Math.random() * window.innerHeight), h = (100 * Math.random());
|
||||||
|
var n = document.createElement('span');
|
||||||
|
n.textContent = '✨️';
|
||||||
|
n.style.position = 'absolute';
|
||||||
|
document.body.appendChild(n);
|
||||||
|
var e = setInterval(function(){
|
||||||
|
var r = Math.random();
|
||||||
|
n.style.top = (v += 1).toString() + 'px';
|
||||||
|
n.style.left = (h += (r > 0.7 ? r/5 : -r/5)).toString() + '%';
|
||||||
|
if (v > window.innerHeight) v = -16;
|
||||||
|
if (h > 100) h = -2;
|
||||||
|
else if (h < -2) h = 100;
|
||||||
|
}, 20);
|
||||||
|
})();
|
||||||
|
});
|
@ -1,12 +1,10 @@
|
|||||||
/* TODO:
|
/* TODO:
|
||||||
* built-in logging
|
* built-in logging
|
||||||
* configure to embed linked styles and scripts into the HTML output, or just link to file
|
* relative URL root for redirects and internal functions? (or useless since HTML must be custom anyways?)
|
||||||
* handle linkScripts to insert in the HTML, like linkStyles
|
|
||||||
* differentiate between client-only, served-only, and both-mode scripts?
|
|
||||||
* relative URL root
|
|
||||||
* utility functions for rewriting url query parameters?
|
* utility functions for rewriting url query parameters?
|
||||||
* fix hash navigation to prevent no-going-back issue (possible?)
|
* fix hash navigation to prevent no-going-back issue (possible?)
|
||||||
* finish polishing the cookie API
|
* finish polishing the cookie API
|
||||||
|
* implement some nodejs `fs` functions for client-side
|
||||||
* other things listed in the file
|
* other things listed in the file
|
||||||
*/
|
*/
|
||||||
(() => {
|
(() => {
|
||||||
@ -24,11 +22,14 @@ const cookieMaxAge2Years = (2 * 365 * 24 * 60 * 60);
|
|||||||
const setup = (globalOptions={}) => {
|
const setup = (globalOptions={}) => {
|
||||||
allOpts.global = globalOptions;
|
allOpts.global = globalOptions;
|
||||||
//allOpts.global.appName ||= 'Untitled SpaccDotWeb App';
|
//allOpts.global.appName ||= 'Untitled SpaccDotWeb App';
|
||||||
|
//allOpts.global.appRoot ||= ''; //TODO
|
||||||
allOpts.global.staticPrefix ||= '/static/';
|
allOpts.global.staticPrefix ||= '/static/';
|
||||||
|
//allOpts.global.staticRoot ||= ''; //TODO
|
||||||
allOpts.global.staticFiles ||= [];
|
allOpts.global.staticFiles ||= [];
|
||||||
allOpts.global.linkStyles ||= [];
|
allOpts.global.linkStyles ||= [];
|
||||||
allOpts.global.linkScripts ||= [];
|
//allOpts.global.linkRuntimeScripts ||= []; //TODO
|
||||||
for (const item of [...allOpts.global.linkStyles, ...allOpts.global.linkScripts]) {
|
allOpts.global.linkClientScripts ||= [];
|
||||||
|
for (const item of [ ...allOpts.global.linkStyles, ...allOpts.global.linkClientScripts ]) {
|
||||||
const itemLow = item.toLowerCase();
|
const itemLow = item.toLowerCase();
|
||||||
if (!(itemLow.startsWith('http://') || itemLow.startsWith('https://') || itemLow.startsWith('/'))) {
|
if (!(itemLow.startsWith('http://') || itemLow.startsWith('https://') || itemLow.startsWith('/'))) {
|
||||||
allOpts.global.staticFiles.push(item);
|
allOpts.global.staticFiles.push(item);
|
||||||
@ -40,9 +41,8 @@ const setup = (globalOptions={}) => {
|
|||||||
--><meta charset="utf-8"/><!--
|
--><meta charset="utf-8"/><!--
|
||||||
--><meta name="viewport" content="width=device-width, initial-scale=1.0"/><!--
|
--><meta name="viewport" content="width=device-width, initial-scale=1.0"/><!--
|
||||||
--><title>${(opts.pageTitler || allOpts.global.pageTitler)(title, opts)}</title><!--
|
--><title>${(opts.pageTitler || allOpts.global.pageTitler)(title, opts)}</title><!--
|
||||||
-->${allOpts.global.linkStyles.map((item) => {
|
-->${allOpts.global.linkStyles.map((path) => makeHtmlStyleFragment(path, opts.selfContained)).join('')}<!--
|
||||||
return `<link rel="stylesheet" href="${allOpts.global.staticFiles.includes(item) ? (allOpts.global.staticPrefix + item) : item}"/>`;
|
-->${allOpts.global.linkClientScripts.map((path) => makeHtmlScriptFragment(path, opts.selfContained)).join('')}<!--
|
||||||
}).join('')}<!--
|
|
||||||
--></head><body><!--
|
--></head><body><!--
|
||||||
--><div id="transition"></div><!--
|
--><div id="transition"></div><!--
|
||||||
--><div id="app">${(opts.appPager || allOpts.global.appPager)(content, title, opts)}</div><!--
|
--><div id="app">${(opts.appPager || allOpts.global.appPager)(content, title, opts)}</div><!--
|
||||||
@ -80,30 +80,50 @@ const initServer = (serverOptions) => {
|
|||||||
return allOpts.server;
|
return allOpts.server;
|
||||||
};
|
};
|
||||||
|
|
||||||
const writeStaticHtml = () => {
|
const writeStaticHtml = (selfContained=false) => {
|
||||||
// TODO: fix script paths
|
// TODO fix selfContained to embed everything when true, even the runtime files
|
||||||
// TODO: this should somehow set envIsBrowser to true to maybe allow for correct template rendering, but how to do it without causing race conditions? maybe we should expose another variable
|
// TODO: this should somehow set envIsBrowser to true to maybe allow for correct template rendering, but how to do it without causing race conditions? maybe we should expose another variable
|
||||||
const fileName = (process.mainModule.filename.split('.').slice(0, -1).join('.') + '.html');
|
const appFilePath = process.mainModule.filename;
|
||||||
fs.writeFileSync(fileName, allOpts.global.htmlPager(`
|
const htmlFilePath = (appFilePath.split('.').slice(0, -1).join('.') + '.html');
|
||||||
<script src="./${path.basename(__filename)}"></script>
|
// path.relative seems to always append an extra '../', so we must slice it
|
||||||
<script src="./SpaccDotWeb.Alt.js"></script>
|
const libraryPath = path.relative(appFilePath, __filename).split(path.sep).slice(1).join(path.sep);
|
||||||
<script>
|
const libraryFolder = libraryPath.split(path.sep).slice(0, -1).join(path.sep);
|
||||||
|
fs.writeFileSync(htmlFilePath, allOpts.global.htmlPager(`
|
||||||
|
${makeHtmlScriptFragment(libraryPath, selfContained)}
|
||||||
|
${makeHtmlScriptFragment(((libraryFolder && (libraryFolder + '/')) + 'SpaccDotWeb.Alt.js'), selfContained)}
|
||||||
|
<${'script'}>
|
||||||
window.require = () => {
|
window.require = () => {
|
||||||
window.require = async (src, type) => {
|
window.require = async (src, type) => {
|
||||||
await SpaccDotWeb.RequireScript((src.startsWith('./') ? src : ('node_modules/' + src)), type);
|
await SpaccDotWeb.RequireScript((src.startsWith('./') ? src : ('node_modules/' + src)), type);
|
||||||
};
|
};
|
||||||
return window.SpaccDotWebServer;
|
return window.SpaccDotWebServer;
|
||||||
};
|
};
|
||||||
window.SpaccDotWebServer.staticFilesData = { ${allOpts.global.staticFiles.map((file) => {
|
window.SpaccDotWebServer.staticFilesData = { ${selfContained ? allOpts.global.staticFiles.map((file) => {
|
||||||
const filePath = (process.mainModule.filename.split(path.sep).slice(0, -1).join(path.sep) + path.sep + file);
|
// TODO check if these paths are correct or must still be fixed
|
||||||
|
const filePath = (appFilePath.split(path.sep).slice(0, -1).join(path.sep) + path.sep + file);
|
||||||
return `"${file}":"data:${mime.lookup(filePath)};base64,${fs.readFileSync(filePath).toString('base64')}"`;
|
return `"${file}":"data:${mime.lookup(filePath)};base64,${fs.readFileSync(filePath).toString('base64')}"`;
|
||||||
})} };
|
}).join() : ''} };
|
||||||
</script>
|
</${'script'}>
|
||||||
<script src="./${path.basename(process.mainModule.filename)}"></script>
|
${makeHtmlScriptFragment(path.basename(appFilePath), selfContained)}
|
||||||
`));
|
`, null, { selfContained }));
|
||||||
return fileName;
|
return htmlFilePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const makeHtmlStyleFragment = (path, getContent) => {
|
||||||
|
const data = getFilePathContent(path, getContent);
|
||||||
|
return (data[1] ? `<style>${data[1]}</style>` : `<link rel="stylesheet" href="${data[0]}"/>`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeHtmlScriptFragment = (path, getContent) => {
|
||||||
|
const data = getFilePathContent(path, getContent);
|
||||||
|
return `<${'script'}${data[1] ? `>${data[1]}` : ` src="${data[0]}">`}</${'script'}>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilePathContent = (path, getContent) => ([
|
||||||
|
(allOpts.global.staticFiles.includes(path) ? (allOpts.global.staticPrefix + path) : ('./' + path)),
|
||||||
|
(getContent && fs.existsSync(path) && fs.readFileSync(path)),
|
||||||
|
]);
|
||||||
|
|
||||||
const handleRequest = async (request, response={}) => {
|
const handleRequest = async (request, response={}) => {
|
||||||
// build request context and handle special tasks
|
// build request context and handle special tasks
|
||||||
let result = allOpts.server.defaultResponse;
|
let result = allOpts.server.defaultResponse;
|
||||||
@ -193,10 +213,18 @@ const renderPage = (response, content, title, opts={}) => {
|
|||||||
document.title = (opts.pageTitler || allOpts.global.pageTitler)(title, opts);
|
document.title = (opts.pageTitler || allOpts.global.pageTitler)(title, opts);
|
||||||
document.querySelector(allOpts.server.appElement).innerHTML = ((opts.appPager || allOpts.global.appPager)(content, title, opts));
|
document.querySelector(allOpts.server.appElement).innerHTML = ((opts.appPager || allOpts.global.appPager)(content, title, opts));
|
||||||
for (const srcElem of document.querySelectorAll(`[src^="${allOpts.global.staticPrefix}"]`)) {
|
for (const srcElem of document.querySelectorAll(`[src^="${allOpts.global.staticPrefix}"]`)) {
|
||||||
srcElem.src = window.SpaccDotWebServer.staticFilesData[srcElem.getAttribute('src')];
|
var fileUrl = makeStaticClientFileUrl(srcElem.getAttribute('src'));
|
||||||
|
if (srcElem.tagName === 'SCRIPT') {
|
||||||
|
// script elements die immediately after being first set up,
|
||||||
|
// so we must re-create them to have them run with correct uri
|
||||||
|
srcElem.remove();
|
||||||
|
document.head.appendChild(Object.assign(document.createElement('script'), { src: fileUrl }));
|
||||||
|
} else {
|
||||||
|
srcElem.src = fileUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const linkElem of document.querySelectorAll(`link[rel="stylesheet"][href^="${allOpts.global.staticPrefix}"]`)) {
|
for (const linkElem of document.querySelectorAll(`link[rel="stylesheet"][href^="${allOpts.global.staticPrefix}"]`)) {
|
||||||
linkElem.href = window.SpaccDotWebServer.staticFilesData[linkElem.getAttribute('href').slice(allOpts.global.staticPrefix.length)];
|
linkElem.href = makeStaticClientFileUrl(linkElem.getAttribute('href'));
|
||||||
}
|
}
|
||||||
for (const aElem of document.querySelectorAll('a[href^="/"]')) {
|
for (const aElem of document.querySelectorAll('a[href^="/"]')) {
|
||||||
aElem.href = `#${aElem.getAttribute('href')}`;
|
aElem.href = `#${aElem.getAttribute('href')}`;
|
||||||
@ -218,6 +246,11 @@ const renderPage = (response, content, title, opts={}) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const makeStaticClientFileUrl = (url) => {
|
||||||
|
url = url.slice(allOpts.global.staticPrefix.length);
|
||||||
|
return (window.SpaccDotWebServer.staticFilesData[url] || ('./' + url));
|
||||||
|
};
|
||||||
|
|
||||||
const setClientTransition = (status) => {
|
const setClientTransition = (status) => {
|
||||||
let transitionElement;
|
let transitionElement;
|
||||||
if (envIsBrowser && (transitionElement = document.querySelector(allOpts.server.transitionElement))) {
|
if (envIsBrowser && (transitionElement = document.querySelector(allOpts.server.transitionElement))) {
|
||||||
@ -295,7 +328,7 @@ const parseCookieString = (cookieString) => {
|
|||||||
cookieData[key] = { value: rest.join('=') };
|
cookieData[key] = { value: rest.join('=') };
|
||||||
}
|
}
|
||||||
if (allOpts.server.metaCookie) {
|
if (allOpts.server.metaCookie) {
|
||||||
const metaData = parseMetaCookie(cookieData[allOpts.server.metaCookie].value);
|
const metaData = parseMetaCookie(cookieData[allOpts.server.metaCookie]?.value);
|
||||||
for (const cookieName in cookieData) {
|
for (const cookieName in cookieData) {
|
||||||
cookieData[cookieName] = { ...cookieData[cookieName], ...metaData[cookieName] };
|
cookieData[cookieName] = { ...cookieData[cookieName], ...metaData[cookieName] };
|
||||||
}
|
}
|
||||||
@ -457,16 +490,16 @@ const parseclientLanguages = (request) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportObj = { envIsNode, envIsBrowser, setup };
|
const exportObj = { envIsNode, envIsBrowser, setup, makeHtmlStyleFragment, makeHtmlScriptFragment };
|
||||||
if (envIsNode) {
|
if (envIsNode) {
|
||||||
fs = require('fs');
|
fs = require('fs');
|
||||||
path = require('path');
|
path = require('path');
|
||||||
mime = require('mime-types');
|
mime = require('mime-types');
|
||||||
multipart = require('parse-multipart-data');
|
multipart = require('parse-multipart-data');
|
||||||
module.exports = exportObj;
|
module.exports = exportObj;
|
||||||
};
|
}
|
||||||
if (envIsBrowser) {
|
if (envIsBrowser) {
|
||||||
window.SpaccDotWebServer = exportObj;
|
window.SpaccDotWebServer = exportObj;
|
||||||
};
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
Loading…
Reference in New Issue
Block a user