ui: 优化资源加载

pull/194/head
undefined 3 years ago
parent fffacdc63c
commit c8a0d48a41

@ -1,5 +1,6 @@
window.Hydro = {
extraPages: [],
preload: [],
components: {},
utils: {},
node_modules: {},
@ -19,9 +20,28 @@ console.log(
`,
);
window.UiContext = JSON.parse(window.UiContext);
document.addEventListener('DOMContentLoaded', () => {
window.UiContext = JSON.parse(window.UiContext);
// eslint-disable-next-line
try { __webpack_public_path__ = UiContext.cdn_prefix } catch (e) { }
// eslint-disable-next-line
try { __webpack_public_path__ = UiContext.cdn_prefix } catch (e) { }
import('./hydro');
// Locale & langs
const { version, payload } = JSON.parse(localStorage.getItem('hydro-constant') || '{}');
if (version === UiContext.constantVersion) {
eval(payload[0]); // eslint-disable-line no-eval
payload.shift();
window.Hydro.preload = payload;
import('./hydro');
} else {
fetch(`/constant?version=${UiContext.constantVersion}`)
.then((res) => res.json())
.then((data) => {
eval(data.payload[0]); // eslint-disable-line no-eval
localStorage.setItem('hydro-constant', JSON.stringify(data));
data.payload.shift();
window.Hydro.preload = data.payload;
import('./hydro');
});
}
}, false);

@ -1,24 +1,82 @@
/* eslint-disable no-return-await */
/* eslint-disable camelcase */
const { readdirSync, readFileSync } = require('fs');
const { join } = require('path');
const crypto = require('crypto');
const { tmpdir } = require('os');
const { ObjectID } = require('mongodb');
const bus = require('hydrooj/src/service/bus');
const { PERM } = require('hydrooj/src/model/builtin');
const markdown = require('./backendlib/markdown');
import crypto from 'crypto';
import { readdir, readFile } from 'fs-extra';
import { join } from 'path';
import { tmpdir } from 'os';
import { ObjectID } from 'mongodb';
import * as bus from 'hydrooj/src/service/bus';
import { Route, Handler } from 'hydrooj/src/service/server';
import { PERM } from 'hydrooj/src/model/builtin';
import markdown from './backendlib/markdown';
const {
system, domain, user, setting, problem, contest,
} = global.Hydro.model;
const { Route, Handler, UiContextBase } = global.Hydro.service.server;
class WikiHelpHandler extends Handler {
constructor(args) {
super(args);
this.noCheckPermView = true;
interface ConstantArgs {
lang: string;
domainId: string;
}
const cache = {};
const basedir = join(tmpdir(), 'hydro', 'public');
async function constant(args: ConstantArgs) {
// CompileLangs
const payload = [`window.LANGS=${JSON.stringify(setting.langs)};`];
// Locale
let { lang } = args;
if (!global.Hydro.locales[lang]) lang = system.get('server.language');
payload[0] += `window.LOCALES=${JSON.stringify(global.Hydro.locales[lang])};`;
// Extra style
let [nav_logo_dark, nav_logo_dark_2x] = system.getMany([
'ui-default.nav_logo_dark', 'ui-default.nav_logo_dark_2x',
]);
const ddoc = await domain.get(args.domainId);
nav_logo_dark = ddoc.ui?.nav_logo_dark || nav_logo_dark;
nav_logo_dark_2x = ddoc.ui?.nav_logo_dark_2x || nav_logo_dark_2x;
payload[0] += `\
const e = document.createElement('style');
e.innerHTML = \`\
${nav_logo_dark ? `.nav__logo { background-image: url(${nav_logo_dark}) !important }` : ''}
${nav_logo_dark_2x ? `\
@media
only screen and (-webkit-min-device-pixel-ratio: 1.5),
only screen and (min-resolution: 1.5dppx),
only screen and (min-resolution: 144dpi) {
.nav__logo, .header--mobile__domain {
background-image: url(${nav_logo_dark_2x}) !important
}
}` : ''}\`;
document.body.appendChild(e);`;
const files = await readdir(basedir);
const pages = files
.filter((file) => file.endsWith('.page.js'))
.map((i) => readFile(join(basedir, i), 'utf-8'));
payload.push(...await Promise.all(pages));
const c = crypto.createHash('sha1');
c.update(JSON.stringify(payload));
const version = c.digest('hex');
cache[version] = { version, payload };
return version;
}
bus.on('handler/after', async (that) => {
if (that.response.template) {
that.UiContext.constantVersion = await constant({
domainId: that.domainId,
lang: that.session.viewLang || that.user.viewLang,
});
}
});
class WikiHelpHandler extends Handler {
noCheckPermView = true;
async get({ domainId }) {
const LANGS = setting.langs;
@ -34,72 +92,15 @@ class WikiHelpHandler extends Handler {
}
class WikiAboutHandler extends Handler {
constructor(args) {
super(args);
this.noCheckPermView = true;
}
noCheckPermView = true;
async get() {
this.response.template = 'about.html';
}
}
class UiConstantsHandler extends Handler {
constructor(args) {
super(args);
this.noCheckPermView = true;
}
async get() {
this.response.body = `window.LANGS=${JSON.stringify(setting.langs)}`;
this.response.type = 'text/javascript';
this.ctx.set('nolog', '1');
}
}
class UiSettingsHandler extends Handler {
constructor(args) {
super(args);
this.noCheckPermView = true;
}
async get({ domainId }) {
const [nav_logo_dark, nav_logo_dark_2x] = system.getMany([
'ui-default.nav_logo_dark', 'ui-default.nav_logo_dark_2x',
]);
const ddoc = await domain.get(domainId);
this.response.body = await this.renderHTML('extra.css', {
nav_logo_dark,
nav_logo_dark_2x,
...(ddoc.ui || {}),
});
this.response.type = 'text/css';
this.ctx.set('nolog', '1');
}
}
class LocaleHandler extends Handler {
constructor(args) {
super(args);
this.noCheckPermView = true;
}
async get({ id }) {
// eslint-disable-next-line prefer-destructuring
id = id.split('.')[0];
// TODO use language_default setting
if (!global.Hydro.locales[id]) id = system.get('server.language');
this.response.body = `window.LOCALES=${JSON.stringify(global.Hydro.locales[id])}`;
this.response.type = 'text/javascript';
this.ctx.set('nolog', '1');
}
}
class SetThemeHandler extends Handler {
constructor(args) {
super(args);
this.noCheckPermView = true;
}
noCheckPermView = true
async get({ theme }) {
await user.setById(this.user._id, { theme });
@ -108,10 +109,7 @@ class SetThemeHandler extends Handler {
}
class MarkdownHandler extends Handler {
constructor(args) {
super(args);
this.noCheckPermView = true;
}
noCheckPermView = true;
async post({ text, html = false, inline = false }) {
this.response.body = inline
@ -122,6 +120,14 @@ class MarkdownHandler extends Handler {
}
}
class UiConstantsHandler extends Handler {
noCheckPermView = true;
async get({ version }) {
this.response.body = cache[version];
}
}
class RichMediaHandler extends Handler {
async renderUser(domainId, payload) {
let d = payload.domainId || domainId;
@ -162,35 +168,11 @@ class RichMediaHandler extends Handler {
}
}
const getHash = (i) => {
const shasum = crypto.createHash('sha1');
const file = readFileSync(join(tmpdir(), 'hydro', 'public', i));
shasum.update(file);
return shasum.digest('hex').substr(0, 10);
};
const getUrl = (files) => files.map((i) => `/${i}?${getHash(i)}`);
bus.on('app/started', () => {
setTimeout(() => {
const files = readdirSync(join(tmpdir(), 'hydro', 'public'));
const pages = files.filter((file) => file.endsWith('.page.js'));
const themes = files.filter((file) => file.endsWith('.theme.js'));
UiContextBase.extraPages = getUrl(pages);
UiContextBase.themes = {};
for (const theme of themes) {
UiContextBase.themes[theme] = `/${theme}?${getHash(theme)}`;
}
}, 1000);
});
global.Hydro.handler.ui = async () => {
Route('wiki_help', '/wiki/help', WikiHelpHandler);
Route('wiki_about', '/wiki/about', WikiAboutHandler);
Route('ui_constants', '/ui-constants.js', UiConstantsHandler);
Route('locale', '/locale/:id', LocaleHandler);
Route('set_theme', '/set_theme/:id', SetThemeHandler);
Route('ui_extracss', '/extra.css', UiSettingsHandler);
Route('constant', '/constant', UiConstantsHandler);
Route('markdown', '/markdown', MarkdownHandler);
Route('media', '/media', RichMediaHandler);
};

@ -24,25 +24,7 @@ function buildSequence(pages, type) {
}
async function load() {
if (UiContext.extraPages) {
const tasks = [];
const ts = new Date().getTime();
for (const page of UiContext.extraPages) {
const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
if (page.includes('.module.')) script.type = 'module';
script.src = page;
head.appendChild(script);
tasks.push(new Promise((resolve) => {
script.onload = resolve;
}));
}
await Promise.all(tasks);
const time = new Date().getTime() - ts;
if ((process.env.NODE_ENV !== 'production' && time > 16) || time > 256) {
console.warn(`Extra pages loading took ${time}ms`);
}
}
for (const page of window.Hydro.preload) await eval(page); // eslint-disable-line no-eval
const pageLoader = new PageLoader();

@ -1,6 +1,6 @@
{
"name": "@hydrooj/ui-default",
"version": "4.18.15",
"version": "4.19.0",
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0",
"main": "hydro.js",

@ -2,7 +2,7 @@
{% if udoc %}
<span class="user-profile-link">
{% if avatar %}
<img class="small user-profile-avatar v-center" src="{{ avatarUrl(udoc.avatar|default('')) }}" width="20" height="20">
<img class="small user-profile-avatar v-center" loading="lazy" src="{{ avatarUrl(udoc.avatar|default('')) }}" width="20" height="20">
{% endif %}
<a class="user-profile-name" href="{{ url('user_detail', uid=udoc._id) }}">
{% if udoc.displayName and udoc.displayName != udoc.uname %}

@ -1,13 +0,0 @@
/* {% if nav_logo_dark %} */
.nav__logo {
background-image: url({{nav_logo_dark}}) !important
}
/* {% endif %} */
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx), only screen and (min-resolution: 144dpi) {
/* {% if nav_logo_dark_2x %} */
.nav__logo, .header--mobile__domain {
background-image: url({{nav_logo_dark_2x}}) !important
}
/* {% endif %} */
}

@ -26,9 +26,9 @@
{% endblock %}
{% if not isIE(handler.request.headers['user-agent']) %}
<script src="{{ static_url((handler.user|default({})).theme|default('default') + '.theme.js') }}"></script>
<script type="text/javascript" src="{{ static_url('hydro.js') }}"></script>
{% endif %}
<link rel="stylesheet" media="all" href="{{ static_url((handler.user|default({})).theme|default('default') + '.theme.css') }}">
<link rel="stylesheet" media="all" href="{{ url('ui_extracss') }}">
<title>{{ handler.renderTitle(page_name) }}</title>
{% if not isIE(handler.request.headers['user-agent']) %}
<script>
@ -46,9 +46,6 @@
UiContext = '{{ UiContext|json|jsesc|safe }}';
UserContext = '{{ UserContext|json|jsesc|safe }}';
</script>
<script type="text/javascript" src="{{ ('/locale/{0}.js'.format(handler.translate('__id'))) }}"></script>
<script type="text/javascript" src="/ui-constants.js"></script>
<script type="text/javascript" src="{{ static_url('hydro.js') }}"></script>
{% endif %}
</body>
</html>

Loading…
Cancel
Save