diff --git a/.eslintrc.js b/.eslintrc.js index eb6a594bd..c28de7e31 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,9 +14,7 @@ module.exports = { }, globals: { // RainLoop - '__rlah_set': "readonly", - '__rlah_clear': "readonly", - '__rlah_data': "readonly", + 'RainLoop': "readonly", 'rainloopI18N': "readonly", 'rainloopTEMPLATES': "readonly", 'rl': "readonly", diff --git a/README.md b/README.md index 8d2ff4055..e079824bc 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ The result is faster and smaller download code (good for mobile networks). Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill for array.flat(). * Added dev/prototype-*.js for some additional features +* boot.js without webpack overhead * Replaced jQuery with jQuery.slim * Replaced ProgressJS with simple native dropin * Replaced Autolinker with simple https/email detection @@ -85,23 +86,23 @@ Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill |js/* |1.14.0 |native | |----------- |--------: |--------: | -|admin.js |2.130.942 | 958.580 | -|app.js |4.184.455 |2.591.395 | -|boot.js | 671.522 | 37.334 | -|libs.js | 647.614 | 313.217 | +|admin.js |2.130.942 | 958.397 | +|app.js |4.184.455 |2.586.232 | +|boot.js | 671.522 | 6.947 | +|libs.js | 647.614 | 312.269 | |polyfills.js | 325.834 | 0 | -|TOTAL |7.960.367 |3.900.526 | +|TOTAL |7.960.367 |3.863.845 | |js/min/* |1.14.0 |native |gzip 1.14 |gzip |brotli | |--------------- |--------: |--------: |--------: |--------: |--------: | -|admin.min.js | 252.147 | 130.139 | 73.657 | 37.785 | 32.445 | -|app.min.js | 511.202 | 351.142 |140.462 | 92.245 | 74.050 | -|boot.min.js | 66.007 | 4.938 | 22.567 | 2.097 | 1.767 | -|libs.min.js | 572.545 | 296.365 |176.720 | 91.813 | 80.999 | +|admin.min.js | 252.147 | 130.128 | 73.657 | 37.783 | 32.395 | +|app.min.js | 511.202 | 350.575 |140.462 | 92.070 | 73.949 | +|boot.min.js | 66.007 | 4.141 | 22.567 | 1.869 | 1.557 | +|libs.min.js | 572.545 | 295.776 |176.720 | 91.524 | 80.746 | |polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 | -|TOTAL |1.434.353 | 782.584 |424.718 |223.940 |189.261 | +|TOTAL |1.434.353 | 780.620 |424.718 |223.246 |188.642 | -651.769 bytes (200.778 gzip) is not much, but it feels faster. +653.733 bytes (201.472 gzip) is not much, but it feels faster. ### CSS changes diff --git a/dev/App/Abstract.js b/dev/App/Abstract.js index da744495f..748333e23 100644 --- a/dev/App/Abstract.js +++ b/dev/App/Abstract.js @@ -98,8 +98,8 @@ class AbstractApp extends AbstractBoot { } clearClientSideToken() { - if (window.__rlah_clear) { - __rlah_clear(); + if (RainLoop.hash.clear) { + RainLoop.hash.clear(); } } @@ -107,8 +107,8 @@ class AbstractApp extends AbstractBoot { * @param {string} token */ setClientSideToken(token) { - if (window.__rlah_set) { - __rlah_set(token); + if (RainLoop.hash.set) { + RainLoop.hash.set(token); Settings.settingsSet('AuthAccountHash', token); populateAuthSuffix(); diff --git a/dev/App/User.js b/dev/App/User.js index d3d21eaac..1095d453c 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -57,7 +57,6 @@ import QuotaStore from 'Stores/User/Quota'; import * as Local from 'Storage/Client'; import * as Settings from 'Storage/Settings'; -import { checkTimestamp } from 'Storage/RainLoop'; import Remote from 'Remote/User/Ajax'; import Promises from 'Promises/User/Ajax'; @@ -95,7 +94,7 @@ class AppUser extends AbstractApp { setInterval(() => { const currentTime = (new Date()).getTime(); if (currentTime > (lastTime + interval + 1000)) { - if (checkTimestamp()) { + if (RainLoop.hash.check()) { this.reload(); } Remote.jsVersion((sResult, oData) => { @@ -107,7 +106,7 @@ class AppUser extends AbstractApp { lastTime = currentTime; }, interval); - if (checkTimestamp()) { + if (RainLoop.hash.check()) { this.reload(); } @@ -919,7 +918,7 @@ class AppUser extends AbstractApp { bootend() { if (window.progressJs) { - progressJs.set(100).end(); + progressJs.end(); } hideLoading(); } diff --git a/dev/Common/Booter.js b/dev/Common/Booter.js deleted file mode 100644 index e8f0b1196..000000000 --- a/dev/Common/Booter.js +++ /dev/null @@ -1,187 +0,0 @@ -import { getHash, setHash, clearHash } from 'Storage/RainLoop'; - -let RL_APP_DATA_STORAGE = null; - -const doc = document; - -/* eslint-disable camelcase,spaced-comment */ -window.__rlah_set = () => setHash(); -window.__rlah_clear = () => clearHash(); -window.__rlah_data = () => RL_APP_DATA_STORAGE; - -/** - * @returns {void} - */ -function showError() { - const oR = doc.getElementById('rl-loading'), - oL = doc.getElementById('rl-loading-error'); - - if (oR) { - oR.style.display = 'none'; - } - - if (oL) { - oL.style.display = 'block'; - } - - if (window.progressJs) { - progressJs.set(100).end(); - } -} - -/** - * @param {boolean} withError - * @returns {void} - */ -function runMainBoot(withError) { - if (window.__APP_BOOT && !withError) { - window.__APP_BOOT(() => showError()); - } else { - showError(); - } -} - -function writeCSS(css) { - const style = doc.createElement('style'); - style.type = 'text/css'; - style.textContent = css; -// style.append(doc.createTextNode(styles)); - doc.head.append(style); -} - -function loadScript(src) { - if (!src) { - throw new Error('src should not be empty.'); - } - return new Promise((resolve, reject) => { - const script = doc.createElement('script'); - script.onload = () => resolve(); - script.onerror = () => reject(new Error(src)); - script.src = src; -// script.type = 'text/javascript'; - doc.head.append(script); -// doc.body.append(element); - }); -} - -/** - * @param {mixed} data - * @returns {void} - */ -window.__initAppData = data => { - RL_APP_DATA_STORAGE = data; - - window.__rlah_set(); - - if (RL_APP_DATA_STORAGE) { - const css = RL_APP_DATA_STORAGE.IncludeCss, - theme = RL_APP_DATA_STORAGE.NewThemeLink, - description= RL_APP_DATA_STORAGE.LoadingDescriptionEsc || '', - oE = doc.getElementById('rl-loading'), - oElDesc = doc.getElementById('rl-loading-desc'); - - if (theme) { - (doc.getElementById('app-theme-link') || {}).href = theme; - } - - css && writeCSS(css); - - if (oElDesc && description) { - oElDesc.innerHTML = description; - } - if (oE && oE.style) { - oE.style.opacity = 0; - setTimeout(() => oE.style.opacity = 1, 300); - } - } - - const appData = window.__rlah_data(), p = progressJs; - - if ( - p && - appData && - appData.TemplatesLink && - appData.LangLink && - appData.StaticLibJsLink && - appData.StaticAppJsLink && - appData.StaticEditorJsLink - ) { - p.set(5); - - const libs = () => - loadScript(appData.StaticLibJsLink).then(() => { - doc.getElementById('rl-check').remove(); - if (appData.IncludeBackground) { - const img = appData.IncludeBackground.replace('{{USER}}', getHash() || '0'); - if (img) { - doc.documentElement.classList.add('UserBackground'); - doc.body.style.backgroundImage = "url("+img+")"; - } - } - }); - - libs() - .then(() => { - p.set(20); - return Promise.all([loadScript(appData.TemplatesLink), loadScript(appData.LangLink)]); - }) - .then(() => { - p.set(30); - return loadScript(appData.StaticAppJsLink); - }) - .then(() => { - p.set(50); - return appData.PluginsLink ? loadScript(appData.PluginsLink) : Promise.resolve(); - }) - .then(() => { - p.set(70); - runMainBoot(false); - }) - .catch((e) => { - runMainBoot(true); - throw e; - }) - .then(() => loadScript(appData.StaticEditorJsLink)) - .then(() => { - if (window.CKEDITOR && window.__initEditor) { - window.__initEditor(); - window.__initEditor = null; - } - }); - } else { - runMainBoot(true); - } -}; - -/** - * @returns {void} - */ -window.__runBoot = () => { - const app = doc.getElementById('rl-app'); - - if (!navigator || !navigator.cookieEnabled) { - doc.location.replace('./?/NoCookie'); - } - - // require('Styles/@Boot.css'); - writeCSS('#rl-content{display:none;}.internal-hiddden{display:none !important;}'); - - if (app) { - const layout = require('Html/Layout.html'), - meta = doc.getElementById('app-boot-data'), - options = meta ? JSON.parse(meta.getAttribute('content')) || {} : {}; - - app.innerHTML = ((layout && layout.default ? layout.default : layout) || '').replace(/[\r\n\t]+/g, ''); - - loadScript('./?/' - + (options.admin ? 'Admin' : '') - + 'AppData@' - + (options.mobile ? 'mobile' : 'no-mobile') - + (options.mobileDevice ? '-1' : '-0') - + '/' - + (getHash() || '0') - + '/' - + Math.random().toString().substr(2) - + '/').then(() => {}); - } -}; diff --git a/dev/Storage/RainLoop.js b/dev/Storage/RainLoop.js deleted file mode 100644 index fd843b9e2..000000000 --- a/dev/Storage/RainLoop.js +++ /dev/null @@ -1,49 +0,0 @@ -const STORAGE_KEY = '__rlA'; -const TIME_KEY = '__rlT'; - -const storage = ()=>window.sessionStorage; - -const timestamp = () => Math.round(Date.now() / 1000); - -const setTimestamp = () => storage().setItem(TIME_KEY, timestamp()); - -/** - * @returns {string} - */ -export function getHash() { - return storage().getItem(STORAGE_KEY) || null; -} - -/** - * @returns {void} - */ -export function setHash() { - const key = 'AuthAccountHash', - appData = window.__rlah_data(); - - storage().setItem(STORAGE_KEY, appData && appData[key] ? appData[key] : ''); - setTimestamp(); -} - -/** - * @returns {void} - */ -export function clearHash() { - storage().setItem(STORAGE_KEY, ''); - setTimestamp(); -} - -/** - * @returns {boolean} - */ -export function checkTimestamp() { - if (timestamp() > (parseInt(storage().getItem(TIME_KEY) || 0, 10) || 0) + 3600000) { - // 60m - clearHash(); - return true; - } - return false; -} - -// init section -setInterval(setTimestamp, 60000); // 1m diff --git a/dev/Storage/Settings.js b/dev/Storage/Settings.js index 0bd59133f..19ece021f 100644 --- a/dev/Storage/Settings.js +++ b/dev/Storage/Settings.js @@ -1,9 +1,6 @@ -let SETTINGS = window.__rlah_data() || null; +let SETTINGS = RainLoop.data() || null; SETTINGS = null != SETTINGS ? SETTINGS : {}; -let APP_SETTINGS = SETTINGS.System || null; -APP_SETTINGS = null != APP_SETTINGS ? APP_SETTINGS : {}; - /** * @param {string} name * @returns {*} @@ -25,6 +22,7 @@ export function settingsSet(name, value) { * @returns {*} */ export function appSettingsGet(name) { + const APP_SETTINGS = SETTINGS.System || {}; return null == APP_SETTINGS[name] ? null : APP_SETTINGS[name]; } @@ -33,6 +31,6 @@ export function appSettingsGet(name) { * @returns {boolean} */ export function capa(name) { - const values = settingsGet('Capa'); + const values = SETTINGS.Capa; return Array.isArray(values) && null != name && values.includes(name); } diff --git a/dev/boot.js b/dev/boot.js index 5e121a50b..b26455331 100644 --- a/dev/boot.js +++ b/dev/boot.js @@ -1,15 +1,108 @@ -import { getHash, setHash, clearHash } from 'Storage/RainLoop'; (win => { const doc = win.document, - setPercentWidth = (percent) => { - setTimeout(() => progress.style.width = parseInt(Math.min(percent, 100)) + '%', 50); + app = doc.getElementById('rl-app'), + setPercentWidth = percent => setTimeout(() => progress.style.width = parseInt(Math.min(percent, 100)) + '%', 50), + + Storage = type => { + let name = type+'Storage'; + try { + win[name].setItem('storage', ''); + win[name].getItem('storage'); + win[name].removeItem('storage'); + } catch (e) { + console.error(e); + const cookieName = encodeURIComponent(name+('session' === type ? win.name || (win.name = Date.now()) : '')); + + // initialise if there's already data + let data = document.cookie.match('(^|;) ?'+cookieName+'=([^;]*)(;|$)'); + data = data ? decodeURIComponent(data[2]) : null; + data = data ? JSON.parse(data) : {}; + + win[name] = { + getItem: key => data[key] === undefined ? null : data[key], + setItem: function (key, value) { + data[key] = ''+value; // forces the value to a string + document.cookie = cookieName+'='+encodeURIComponent(JSON.stringify(data)) + +"; expires="+('local' === type ? (new Date(Date.now()+(365*24*60*60*1000))).toGMTString() : '') + +"; path=/; samesite=strict"; + } + }; + } + }, + STORAGE_KEY = '__rlA', + TIME_KEY = '__rlT', + AUTH_KEY = 'AuthAccountHash', + storage = () => window.sessionStorage, + timestamp = () => Math.round(Date.now() / 1000), + setTimestamp = () => storage().setItem(TIME_KEY, timestamp()), + + showError = () => { + const oR = doc.getElementById('rl-loading'), + oL = doc.getElementById('rl-loading-error'); + + if (oR) { + oR.style.display = 'none'; + } + + if (oL) { + oL.style.display = 'block'; + } + + p.end(); + }, + + runMainBoot = withError => { + if (win.__APP_BOOT && !withError) { + win.__APP_BOOT(() => showError()); + } else { + showError(); + } + }, + + writeCSS = css => { + const style = doc.createElement('style'); + style.type = 'text/css'; + style.textContent = css; + doc.head.append(style); + }, + + loadScript = src => { + if (!src) { + throw new Error('src should not be empty.'); + } + return new Promise((resolve, reject) => { + const script = doc.createElement('script'); + script.onload = () => resolve(); + script.onerror = () => reject(new Error(src)); + script.src = src; +// script.type = 'text/javascript'; + doc.head.append(script); +// doc.body.append(element); + }); + }, + + p = win.progressJs = { + set: percent => setPercentWidth(percent), + end: () => { + if (container) { + progress.addEventListener('transitionend', () => { + if (container) { + container.hidden = true; + setTimeout(() => {container.remove();container=null;}, 200); + } + }, false); + setPercentWidth(100); + } + } }; let container = doc.createElement('div'), - progress = container.appendChild(doc.createElement("div")); + progress = container.appendChild(doc.createElement("div")), + + RL_APP_DATA_STORAGE = null; container.className = 'progressjs-progress progressjs-theme-rainloop'; progress.className = "progressjs-inner"; @@ -18,99 +111,53 @@ progress.appendChild(doc.createElement('div')).className = "progressjs-percent"; setPercentWidth(1); doc.body.append(container); -win.progressJs = new class { - set(percent) { - setPercentWidth(percent); - return this; - } +Storage('local'); +Storage('session'); - end() { - if (progress) { - progress.addEventListener('transitionend', () => { - if (container) { - container.hidden = true; - setTimeout(() => {container.remove();container=null;}, 200); - } - }, false); - setPercentWidth(100); +win.RainLoop = { + hash: { + // getHash + get: () => storage().getItem(STORAGE_KEY) || null, + // setHash + set: () => { + storage().setItem(STORAGE_KEY, RL_APP_DATA_STORAGE && RL_APP_DATA_STORAGE[AUTH_KEY] + ? RL_APP_DATA_STORAGE[AUTH_KEY] : ''); + setTimestamp(); + }, + // clearHash + clear: () => { + storage().setItem(STORAGE_KEY, ''); + setTimestamp(); + }, + // checkTimestamp + check: () => { + if (timestamp() > (parseInt(storage().getItem(TIME_KEY) || 0, 10) || 0) + 3600000) { + // 60m + RainLoop.hash.clear(); + return true; + } + return false; } - return this; - } + }, + data: () => RL_APP_DATA_STORAGE }; -let RL_APP_DATA_STORAGE = null; - -win.__rlah_set = () => setHash(); -win.__rlah_clear = () => clearHash(); -win.__rlah_data = () => RL_APP_DATA_STORAGE; +// init section +setInterval(setTimestamp, 60000); // 1m /** + * @param {mixed} appData * @returns {void} */ -function showError() { - const oR = doc.getElementById('rl-loading'), - oL = doc.getElementById('rl-loading-error'); +win.__initAppData = appData => { + RL_APP_DATA_STORAGE = appData; - if (oR) { - oR.style.display = 'none'; - } + RainLoop.hash.set(); - if (oL) { - oL.style.display = 'block'; - } - - if (win.progressJs) { - progressJs.set(100).end(); - } -} - -/** - * @param {boolean} withError - * @returns {void} - */ -function runMainBoot(withError) { - if (win.__APP_BOOT && !withError) { - win.__APP_BOOT(() => showError()); - } else { - showError(); - } -} - -function writeCSS(css) { - const style = doc.createElement('style'); - style.type = 'text/css'; - style.textContent = css; - doc.head.append(style); -} - -function loadScript(src) { - if (!src) { - throw new Error('src should not be empty.'); - } - return new Promise((resolve, reject) => { - const script = doc.createElement('script'); - script.onload = () => resolve(); - script.onerror = () => reject(new Error(src)); - script.src = src; -// script.type = 'text/javascript'; - doc.head.append(script); -// doc.body.append(element); - }); -} - -/** - * @param {mixed} data - * @returns {void} - */ -win.__initAppData = data => { - RL_APP_DATA_STORAGE = data; - - win.__rlah_set(); - - if (RL_APP_DATA_STORAGE) { - const css = RL_APP_DATA_STORAGE.IncludeCss, - theme = RL_APP_DATA_STORAGE.NewThemeLink, - description= RL_APP_DATA_STORAGE.LoadingDescriptionEsc || '', + if (appData) { + const css = appData.IncludeCss, + theme = appData.NewThemeLink, + description= appData.LoadingDescriptionEsc || '', oE = doc.getElementById('rl-loading'), oElDesc = doc.getElementById('rl-loading-desc'); @@ -129,10 +176,7 @@ win.__initAppData = data => { } } - const appData = win.__rlah_data(), p = progressJs; - if ( - p && appData && appData.TemplatesLink && appData.LangLink && @@ -146,7 +190,7 @@ win.__initAppData = data => { loadScript(appData.StaticLibJsLink).then(() => { doc.getElementById('rl-check').remove(); if (appData.IncludeBackground) { - const img = appData.IncludeBackground.replace('{{USER}}', getHash() || '0'); + const img = appData.IncludeBackground.replace('{{USER}}', RainLoop.hash.get() || '0'); if (img) { doc.documentElement.classList.add('UserBackground'); doc.body.style.backgroundImage = "url("+img+")"; @@ -187,8 +231,6 @@ win.__initAppData = data => { } }; -const app = doc.getElementById('rl-app'); - if (!navigator || !navigator.cookieEnabled) { doc.location.replace('./?/NoCookie'); } @@ -230,7 +272,7 @@ if (app) { + (options.mobile ? 'mobile' : 'no-mobile') + (options.mobileDevice ? '-1' : '-0') + '/' - + (getHash() || '0') + + (RainLoop.hash.get() || '0') + '/' + Math.random().toString().substr(2) + '/').then(() => {}); diff --git a/dev/polyfill.js b/dev/polyfill.js index c8bf76e2a..1fa0faa0f 100644 --- a/dev/polyfill.js +++ b/dev/polyfill.js @@ -30,33 +30,4 @@ w.ResizeObserver || (w.ResizeObserver = class { } }); -const Storage = type => { - let name = type+'Storage'; - try { - w[name].setItem('storage', ''); - w[name].getItem('storage'); - w[name].removeItem('storage'); - } catch (e) { - console.error(e); - const cookieName = encodeURIComponent(name+('session' === type ? w.name || (w.name = Date.now()) : '')); - - // initialise if there's already data - let data = document.cookie.match('(^|;) ?'+cookieName+'=([^;]*)(;|$)'); - data = data ? decodeURIComponent(data[2]) : null; - data = data ? JSON.parse(data) : {}; - - w[name] = { - getItem: key => data[key] === undefined ? null : data[key], - setItem: function (key, value) { - data[key] = ''+value; // forces the value to a string - document.cookie = cookieName+'='+encodeURIComponent(JSON.stringify(data)) - +"; expires="+('local' === type ? (new Date(Date.now()+(365*24*60*60*1000))).toGMTString() : '') - +"; path=/; samesite=strict"; - } - }; - } -}; -Storage('local'); -Storage('session'); - })(window); diff --git a/tasks/js.js b/tasks/js.js index a7c46bad7..c5e92b1b0 100644 --- a/tasks/js.js +++ b/tasks/js.js @@ -23,6 +23,13 @@ const { webpack } = require('./webpack'); const jsClean = () => del(config.paths.staticJS + '/**/*.{js,map}'); +// boot +const jsBoot = () => { + return gulp + .src('dev/boot.js') + .pipe(gulp.dest('rainloop/v/' + config.devVersion + '/static/js')); +}; + // libs const jsLibs = () => { const src = config.paths.js.libs.src; @@ -90,7 +97,7 @@ const jsLint = () => .pipe(eslint.failAfterError()); const jsState1 = gulp.series(jsLint); -const jsState3 = gulp.parallel(jsLibs, jsApp, jsAdmin); +const jsState3 = gulp.parallel(jsBoot, jsLibs, jsApp, jsAdmin); const jsState2 = gulp.series(jsClean, webpack, jsState3, jsMin); exports.jsLint = jsLint; diff --git a/webpack.config.builder.js b/webpack.config.builder.js index 3c338a423..79830d45e 100644 --- a/webpack.config.builder.js +++ b/webpack.config.builder.js @@ -48,7 +48,6 @@ module.exports = function(publicPath, pro, mode) { mode: mode || 'development', devtool: 'inline-source-map', entry: { - 'js/boot': path.join(devPathJoin, 'boot.js'), 'js/app': path.join(devPathJoin, 'app.js'), 'js/admin': path.join(devPathJoin, 'admin.js') },