diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..6a086fa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,17 @@ +image: alpine:latest + +before_script: | + apk update + apk add nodejs npm + +pages: + stage: deploy + script: | + npm install + npm run build:lib + node ./SpaccDotWeb.js 'SpaccDotWeb.AppBuildStandalone({ Page: "Example.html" })' + mkdir -p ./public + mv ./Build/* ./public + artifacts: + paths: + - public diff --git a/Example.html b/Example.html new file mode 100644 index 0000000..86a358d --- /dev/null +++ b/Example.html @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/SpaccDotWeb.html b/SpaccDotWeb.html deleted file mode 100644 index 1fe3a59..0000000 --- a/SpaccDotWeb.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - diff --git a/SpaccDotWeb.js b/SpaccDotWeb.js index 34c3882..63f803e 100644 --- a/SpaccDotWeb.js +++ b/SpaccDotWeb.js @@ -8,14 +8,16 @@ let __scriptname; const platformIsNode = (typeof module === 'object' && typeof module.exports === 'object'); const platformIsBrowser = (typeof window !== 'undefined' && typeof window.document !== 'undefined'); +const __filename__ = (typeof __filename !== 'undefined' ? __filename : ''); +const JsdomOptions = { /*resources: "usable",*/ runScripts: /*"dangerously"*/"outside-only" }; if (platformIsNode) { Lib.fs = require('fs'); Lib.crypto = require('crypto'); Lib.childProcess = require('child_process'); Lib.jsdom = require('jsdom'); - __scriptname = __filename.split('/').slice(-1)[0]; - windowObject = new Lib.jsdom.JSDOM().window; + __scriptname = __filename__.split('/').slice(-1)[0]; + windowObject = new Lib.jsdom.JSDOM('', JsdomOptions).window; }; if (platformIsBrowser) { @@ -31,7 +33,7 @@ const SpaccDotWeb = ((args) => { ////////////////////////////////////////////// let SpaccDotWeb = {}; if (platformIsNode) { - SpaccDotWeb.AppBuildStandalone = (opts) => { + SpaccDotWeb.AppBuildStandalone = (opts) => { // TODO: build result of dom after JS, to make base page usable without JS isBuildingApp = true; opts ||= {}; opts.Page ||= 'index.html'; @@ -40,7 +42,7 @@ if (platformIsNode) { Lib.fs.mkdirSync(`${__dirname}/Build/App-${opts.Page}`, { recursive: true }); let htmlIndex = Lib.fs.readFileSync(opts.Page, 'utf8'); - windowObject = new Lib.jsdom.JSDOM(htmlIndex).window; + windowObject = new Lib.jsdom.JSDOM(htmlIndex, JsdomOptions).window; documentObject = windowObject.document; DomSetup(opts.Modules); @@ -56,11 +58,11 @@ if (platformIsNode) { const minifiedPath = `${__dirname}/Build/SpaccDotWeb.min.js`; const hashPath = `${__dirname}/Build/SpaccDotWeb.js.hash`; const hashOld = (Lib.fs.existsSync(hashPath) && Lib.fs.readFileSync(hashPath, 'utf8')); - const hashNew = Lib.crypto.createHash('sha256').update(Lib.fs.readFileSync(__filename, 'utf8')).digest('hex'); + const hashNew = Lib.crypto.createHash('sha256').update(Lib.fs.readFileSync(__filename__, 'utf8')).digest('hex'); if (!Lib.fs.existsSync(compiledPath) || !Lib.fs.existsSync(minifiedPath) || !(hashOld === hashNew)) { uptodate = false; Lib.fs.writeFileSync(hashPath, hashNew); - Lib.fs.writeFileSync(compiledPath, Lib.childProcess.execSync(`cat "${__filename}" | npx babel -f "${__scriptname}"`)); + Lib.fs.writeFileSync(compiledPath, Lib.childProcess.execSync(`cat "${__filename__}" | npx babel -f "${__scriptname}"`)); Lib.fs.writeFileSync(minifiedPath, Lib.childProcess.execSync(`cat "${compiledPath}" | npx uglifyjs`)); }; uptodate && console.log('Library is up-to-date.'); @@ -68,9 +70,10 @@ if (platformIsNode) { }; }; -SpaccDotWeb.AppInit = function AppInit(){ +SpaccDotWeb.AppInit = () => { try { DomSetup(); + return SpaccDotWeb; } catch(err) { console.log(err) }; }; @@ -90,46 +93,78 @@ SpaccDotWeb.Select = (query) => { return elem; }; -const AppMetaGet = () => JSON.parse(SpaccDotWeb.Select('#Meta').innerHTML); +SpaccDotWeb.RequireScript = (src) => { + if (platformIsBrowser) { + SpaccDotWeb.Select('body').Insert(SpaccDotWeb.Create('script', { src: src })); + //} else if (platformIsNode) { + // require(src); + } +}; + +// TODO: make Meta element optional without breaking things +const AppMetaGet = () => { + const elem = SpaccDotWeb.Select('script[module="Meta"]'); + if (elem) { + if (['application/json', 'text/json'].includes(elem.getAttribute('type'))) { + return JSON.parse(elem.innerHTML); + } else { + return eval(elem.innerHTML); + }; + }; +}; const DomMakeBase = (Modules) => { const meta = AppMetaGet(); - const htmlFrags = { - Title: (meta.Name ? `${meta.Name}` : ''), - Description: (meta.Description ? `` : ''), - Uri: (meta.Uri ? `` : '') - }; - - let scripts = ''; - - if (isBuildingApp) { - scripts += ``; - scripts += ``; - scripts += `${SpaccDotWeb.LibBuild().minified}`; - for (const elem of documentObject.querySelectorAll('script[module]')) { - if (!Modules || (Modules && Modules.includes(elem.getAttribute('module')))) { - if (elem.getAttribute('src')) { - scripts += `` - } else { - const tmpHash = Lib.crypto.createHash('sha256').update(elem.innerHTML).digest('hex'); - const tmpPath = `${__dirname}/Build/Assets.tmp/${tmpHash}.js`; - Lib.fs.writeFileSync(tmpPath, elem.innerHTML); - scripts += `${Lib.childProcess.execSync(`cat "${tmpPath}" | npx babel -f "${tmpHash}.js" | npx uglifyjs`)}`; - }; - }; + if (meta) { + const htmlFrags = { + Title: (meta.Name ? `${meta.Name}` : ''), + Description: (meta.Description ? `` : ''), + Uri: (meta.Uri ? `` : '') }; - }; - return { - head: ` - - - ${htmlFrags.Title} - ${htmlFrags.Description} - ${htmlFrags.Uri} - `, - body: `
${scripts}`, + let [scriptsCode, elementsHtml, scriptsHtml] = ['', '', '']; + + if (isBuildingApp) { + scriptsHtml += ``; + scriptsHtml += ``; + scriptsHtml += `${SpaccDotWeb.LibBuild().minified}`; + for (const elem of documentObject.querySelectorAll('script[module]')) { + //if (elem.module === 'Meta' && !['application/json', 'text/json'].includes(elem.type)) { + // elem.innerHTML = `(${elem.innerHTML})`; + //}; + if (elem.getAttribute('module') !== 'SpaccDotWeb' && (!Modules || (Modules && Modules.includes(elem.getAttribute('module'))))) { + if (elem.getAttribute('src')) { + scriptsHtml += ``; + // TODO somehow include this in prerendered DOM? + } else { + const tmpHash = Lib.crypto.createHash('sha256').update(elem.innerHTML).digest('hex'); + const tmpPath = `${__dirname}/Build/Assets.tmp/${tmpHash}.js`; + Lib.fs.writeFileSync(tmpPath, elem.innerHTML); + const scriptCode = Lib.childProcess.execSync(`cat "${tmpPath}" | npx babel -f "${tmpHash}.js" | npx uglifyjs`); + scriptsCode += scriptCode; + scriptsHtml += `${Lib.childProcess.execSync(`cat "${tmpPath}" | npx babel -f "${tmpHash}.js" | npx uglifyjs`)}`; + }; + }; + elem.remove(); + }; + // select and include all remaining actual elements, except parent meta-elements + for (const elem of documentObject.querySelectorAll('* > * > *')) { + elementsHtml += elem.outerHTML; + } + }; + + return { + head: ` + + + ${htmlFrags.Title} + ${htmlFrags.Description} + ${htmlFrags.Uri} + `, + body: `
${elementsHtml}${scriptsHtml}`, + scriptsCode: scriptsCode, + }; }; }; @@ -139,9 +174,14 @@ const DomSetup = (Modules) => { ? documentObject.replaceChild(doctypeNew, documentObject.doctype) : documentObject.insertBefore(doctypeNew, documentObject.childNodes[0]); const domBase = DomMakeBase(Modules); - documentObject.write(domBase.head + domBase.body); - documentObject.head.innerHTML = domBase.head; - documentObject.body.innerHTML = domBase.body; + if (domBase) { + documentObject.write(domBase.head + domBase.body); + documentObject.head.innerHTML = domBase.head; + documentObject.body.innerHTML = domBase.body; + if (isBuildingApp) { + windowObject.eval(Lib.fs.readFileSync(__filename__, 'utf-8') + domBase.scriptsCode); + }; + }; }; return SpaccDotWeb; @@ -151,7 +191,7 @@ return SpaccDotWeb; platformIsBrowser && (window.SpaccDotWeb = SpaccDotWeb); -platformIsNode && (console.log(eval(process.argv.slice(-1)[0]))); +platformIsNode && process.argv.length >= 2 && console.log(eval(process.argv.slice(-1)[0])); return SpaccDotWeb; diff --git a/package.json b/package.json index 371df2c..27ac0fe 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "SpaccDotWeb", "version": "indev", "scripts": { - "build:lib": "node ./SpaccDotWeb.js 'SpaccDotWeb.LibBuild()'", + "build:lib": "sh -c \"node ./SpaccDotWeb.js 'SpaccDotWeb.LibBuild()'\"", "build:clear": "rm -rf ./Build" }, "devDependencies": {