diff --git a/packages/ui-default/common/common.inc.styl b/packages/ui-default/common/common.inc.styl index ff72825e0..0d222529e 100644 --- a/packages/ui-default/common/common.inc.styl +++ b/packages/ui-default/common/common.inc.styl @@ -1,10 +1,278 @@ -// This file is imported in all stylus source code + +base-font-size ?= 16px +rasterise-media-queries ?= false + +rupture = { + rasterise-media-queries: rasterise-media-queries + mobile-cutoff: 400px + desktop-cutoff: 1050px + hd-cutoff: 1800px + enable-em-breakpoints: false + base-font-size: base-font-size + anti-overlap: false + density-queries: 'dppx' 'webkit' 'moz' 'dpi' + retina-density: 1.5 + use-device-width: false +} +rupture.scale = 0 (rupture.mobile-cutoff) 600px 800px (rupture.desktop-cutoff) (rupture.hd-cutoff) +rupture.scale-names = 'xs' 's' 'm' 'l' 'xl' 'hd' + +-is-string(val) + if typeof(val) is not 'unit' + if val is a 'string' or val is a 'ident' + true + else + false + else + false + +-get-scale-number(scale-name) + for list-item, i in rupture.scale-names + if list-item is scale-name + return i + 1 + return false + +-convert-to(to-unit, value, context = rupture.base-font-size) + from-unit = unit(value) + return value if to-unit is from-unit + if to-unit in ('em' 'rem') + return value if from-unit in ('em' 'rem') + return unit((value / context), to-unit) + if to-unit is 'px' + return unit((value * context), 'px') + +-on-scale(n) + return unit(n) is '' + +-larger-than-scale(n) + return (n > (length(rupture.scale) - 1)) and -on-scale(n) + +-is-zero(n) + return n is 0 + +-overlap-shift(anti-overlap, n) + shift-unit = unit(n) + anti-overlap = 0px unless anti-overlap + anti-overlap = 1px if anti-overlap is true + if length(anti-overlap) is 1 + return -convert-to(shift-unit, anti-overlap) + for val in anti-overlap + return val if unit(val) is shift-unit + +-adjust-overlap(anti-overlap, n, side = 'min') + -shift = -overlap-shift(anti-overlap, n) + if (side is 'min' and -shift > 0) or (side is 'max' and -shift < 0) + n = n + -shift + return n + +-is-positive(n) + return n >= 0 + +-density-queries(density) + if typeof(density) is not 'unit' + if not -is-string(density) + density = '%s' % density + density = rupture.retina-density if density is 'retina' + queries = () + for query in rupture.density-queries + if query is 'webkit' + push(queries, '(-webkit-min-device-pixel-ratio: %s)' % (density)) + else if query is 'moz' + push(queries, '(min--moz-device-pixel-ratio: %s)' % (density)) + else if query is 'o' + push(queries, '(-o-min-device-pixel-ratio: %s/1)' % (density)) + else if query is 'ratio' + push(queries, '(min-device-pixel-ratio: %s)' % (density)) + else if query is 'dpi' + if -is-string(density) + density=convert(density) + push(queries, '(min-resolution: %sdpi)' % (round(density * 96, 1))) + else if query is 'dppx' + push(queries, '(min-resolution: %sdppx)' % (density)) + return queries + +create-fallback-class(selected, class) + /{'' + class + ' ' + selected} + {block} + +// +between(min, max) +// usage (scale can be mixed with custom values): +// - +between(1, 3) scale:scale +// - +between(0, 3) 0 width:scale +// - +between(200px, 500px) custom:custom +// - +between(0, 300px) 0 width:custom +// - +between(1, 300px) scale:custom +// - +between(200px, 4) custom:scale + +between(min, max, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width, fallback-class = null) + selected = selector() + + if -is-string(orientation) + orientation = convert(orientation) + if -is-string(density) + density = convert(density) + if -is-string(min) + min = -get-scale-number(min) + if -is-string(max) + max = -get-scale-number(max) + + -min = rupture.scale[min - 1] unless -is-zero(min) or (not -on-scale(min)) + -max = rupture.scale[max] unless not -on-scale(max) + -min ?= min + -max ?= max + + if (rupture.rasterise-media-queries) + if not (density or -max or orientation) + {block} + else + condition = 'only screen' + use-device-width = use-device-width ? 'device-' : '' + unless -min is 0 + -min = -convert-to('em', -min) if rupture.enable-em-breakpoints + -min = -adjust-overlap(anti-overlap, -min, side: 'min') + condition = condition + ' and (min-' + use-device-width + 'width: %s)' % (-min) + unless -larger-than-scale(max) + -max = -convert-to('em', -max) if rupture.enable-em-breakpoints + -max = -adjust-overlap(anti-overlap, -max, side: 'max') + condition = condition + ' and (max-' + use-device-width + 'width: %s)' % (-max) + if orientation + condition = condition + ' and (orientation: %s)' % (orientation) + if density + conditions = () + for query in -density-queries(density) + push(conditions, condition + ' and %s' % (query)) + condition = join(', ', conditions) + @media condition + {block} + if fallback-class + +create-fallback-class(selected, fallback-class) + {block} + +at(scale-point, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width, fallback-class = null) + if -is-string(orientation) + orientation = convert(orientation) + if -is-string(density) + density = convert(density) + +between(scale-point, scale-point, anti-overlap, density, orientation, use-device-width, fallback-class) + {block} + +from-width(scale-point, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width, fallback-class = null) + if -is-string(orientation) + orientation = convert(orientation) + if -is-string(density) + density = convert(density) + +between(scale-point, length(rupture.scale), anti-overlap, density, orientation, use-device-width, fallback-class) + {block} + +above = from-width + +to-width(scale-point, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width, fallback-class = null) + if -is-string(orientation) + orientation = convert(orientation) + if -is-string(density) + density = convert(density) + +between(1, scale-point, anti-overlap, density, orientation, use-device-width, fallback-class) + {block} + +below = to-width + +mobile(anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width, fallback-class = null) + if -is-string(orientation) + orientation = convert(orientation) + if -is-string(density) + density = convert(density) + +below(rupture.mobile-cutoff, anti-overlap, density, orientation, use-device-width, fallback-class) + {block} + +tablet(anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width, fallback-class = null) + if -is-string(orientation) + orientation = convert(orientation) + if -is-string(density) + density = convert(density) + +between(rupture.mobile-cutoff, rupture.desktop-cutoff, anti-overlap, density, orientation, use-device-width, fallback-class) + {block} + +desktop(anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width, fallback-class = null) + if -is-string(orientation) + orientation = convert(orientation) + if -is-string(density) + density = convert(density) + +above(rupture.desktop-cutoff, anti-overlap, density, orientation, use-device-width, fallback-class) + {block} + +hd(anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width, fallback-class = null) + if -is-string(orientation) + orientation = convert(orientation) + if -is-string(density) + density = convert(density) + +above(rupture.hd-cutoff, anti-overlap, density, orientation, use-device-width, fallback-class) + {block} + +density(density, orientation = null, fallback-class = null) + selected = selector() + if not (rupture.rasterise-media-queries) + conditions = () + for query in -density-queries(density) + condition = 'only screen and %s' % (query) + if orientation + condition = condition + ' and (orientation: %s)' % (orientation) + push(conditions, condition) + condition = join(', ', conditions) + @media condition + {block} + if fallback-class + +create-fallback-class(selected, fallback-class) + {block} + +pixel-ratio = density + +retina(orientation = null, fallback-class = null) + +density('retina', orientation, fallback-class) + {block} + +landscape(density = null, fallback-class = null) + selected = selector() + if not (rupture.rasterise-media-queries) + if -is-string(density) + density = convert(density) + if density + +pixel-ratio(density, orientation: landscape, fallback-class) + {block} + else + @media only screen and (orientation: landscape) + {block} + if fallback-class + +create-fallback-class(selected, fallback-class) + {block} + +portrait(density = null, fallback-class = null) + selected = selector() + if not (rupture.rasterise-media-queries) + if -is-string(density) + density = convert(density) + if density + +pixel-ratio(density, orientation: portrait, fallback-class) + {block} + else + @media only screen and (orientation: portrait) + {block} + if fallback-class + +create-fallback-class(selected, fallback-class) + {block} + +rupture-hover(density = null, orientation = null, fallback-class = null) + condition = "only screen and (hover: hover)"; + @media ({condition}) + {block} + +hover = rupture-hover + breakpoints = json('../breakpoints.json', { hash: true }) rupture.mobile-cutoff = unit(breakpoints.mobile, 'px') rupture.desktop-cutoff = unit(breakpoints.desktop, 'px') rupture.hd-cutoff = unit(breakpoints.hd, 'px') -@import '~vj/misc/.iconfont/webicon.inc.styl' +@import '../misc/.iconfont/webicon.inc.styl' @import 'color.inc.styl' @import 'variables.inc.styl' @import 'easing.inc.styl' diff --git a/packages/ui-default/components/highlighter/prismjs.js b/packages/ui-default/components/highlighter/prismjs.js index 4e5f23779..0b7481973 100644 --- a/packages/ui-default/components/highlighter/prismjs.js +++ b/packages/ui-default/components/highlighter/prismjs.js @@ -13,14 +13,14 @@ import getLoader from 'prismjs/dependencies'; import Notification from 'vj/components/notification/index'; import { i18n } from 'vj/utils'; import languageMeta from './meta'; +import langs from 'prismjs/components/index'; -const files = require.context('prismjs/components/', true, /prism-[a-z0-9-]+\.js/); const loadedLanguages = new Set(); function loadLanguages() { const languages = Object.keys(components.languages).filter((l) => l !== 'meta'); const loaded = [...loadedLanguages, ...Object.keys(Prism.languages)]; getLoader(components, languages, loaded).load((lang) => { - files(`./prism-${lang}.js`); + langs[lang]?.(); loadedLanguages.add(lang); }); } diff --git a/packages/ui-default/components/message/index.page.ts b/packages/ui-default/components/message/index.page.ts index 2707b3737..5e13d3329 100644 --- a/packages/ui-default/components/message/index.page.ts +++ b/packages/ui-default/components/message/index.page.ts @@ -61,7 +61,7 @@ const onmessage = (msg) => { const initWorkerMode = (endpoint) => { console.log('Messages: using SharedWorker'); - const worker = new SharedWorker('/messages-shared-worker.js', { name: 'HydroMessagesWorker' }); + const worker = new SharedWorker(new URL('./worker.ts?worker', import.meta.url), { name: 'HydroMessagesWorker' }); worker.port.start(); window.addEventListener('beforeunload', () => { worker.port.postMessage({ type: 'unload' }); diff --git a/packages/ui-default/components/scratchpad/DataInputComponent.jsx b/packages/ui-default/components/scratchpad/DataInputComponent.jsx index 8fef6b7da..af8e78002 100644 --- a/packages/ui-default/components/scratchpad/DataInputComponent.jsx +++ b/packages/ui-default/components/scratchpad/DataInputComponent.jsx @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import React from 'react'; export default function DataInputComponent(props) { const { diff --git a/packages/ui-default/components/scratchpad/ToolbarComponent.jsx b/packages/ui-default/components/scratchpad/ToolbarComponent.jsx index 35b49b67f..31930977e 100644 --- a/packages/ui-default/components/scratchpad/ToolbarComponent.jsx +++ b/packages/ui-default/components/scratchpad/ToolbarComponent.jsx @@ -1,5 +1,6 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; +import React from 'react'; export default function ToolbarComponent(props) { const { diff --git a/packages/ui-default/components/time/time.page.js b/packages/ui-default/components/time/time.page.js index 487fc3d4f..69b39883e 100644 --- a/packages/ui-default/components/time/time.page.js +++ b/packages/ui-default/components/time/time.page.js @@ -1,19 +1,21 @@ import $ from 'jquery'; import * as timeago from 'timeago.js'; +import en_US from 'timeago.js/lib/lang/en_US'; +import ko from 'timeago.js/lib/lang/ko'; +import zh_CN from 'timeago.js/lib/lang/zh_CN'; +import zh_TW from 'timeago.js/lib/lang/zh_TW'; import { AutoloadPage } from 'vj/misc/Page'; import { i18n } from 'vj/utils'; try { - const locales = require.context('timeago.js/lib/lang', false, /\.js$/); - let locale; - try { - locale = locales(`./${i18n('timeago_locale')}.js`).default; - } catch (e) { - locale = locales('./en_US.js').default; - } + const locales = { + // eslint-disable-next-line @typescript-eslint/naming-convention + zh_CN, zh_TW, ko, en_US, + }; + const locale = locales[i18n('timeago_locale')] || locales.en_US; timeago.register(i18n('timeago_locale'), locale); } catch (e) { - console.error(`Cannot register timeago locale: ${i18n('timeago_locale')}`); + console.error(`Cannot register timeago locale: ${i18n('timeago_locale')}`, e); } function runRelativeTime($container) { $container.find('span.time.relative[data-timestamp]').get().forEach((element) => { diff --git a/packages/ui-default/entry.js b/packages/ui-default/entry.jsx similarity index 85% rename from packages/ui-default/entry.js rename to packages/ui-default/entry.jsx index a675ae082..1ca9b1744 100644 --- a/packages/ui-default/entry.js +++ b/packages/ui-default/entry.jsx @@ -7,6 +7,7 @@ window.Hydro = { node_modules: {}, version: process.env.VERSION, }; +window.jQuery = $; // sticky-kit require this window.externalModules = {}; window.lazyModuleResolver = {}; @@ -30,7 +31,11 @@ window.UserContext = JSON.parse(window.UserContext); try { __webpack_public_path__ = UiContext.cdn_prefix; } catch (e) { } if ('serviceWorker' in navigator) { const encodedConfig = encodeURIComponent(JSON.stringify(UiContext.SWConfig)); - navigator.serviceWorker.register(`/service-worker.js?config=${encodedConfig}`).then((registration) => { + const dev = process.env.NODE_ENV === 'development'; + navigator.serviceWorker.register( + dev ? '/dev-sw.js?dev-sw' : `/sw.js?config=${encodedConfig}`, + { scope: '/', type: dev ? 'module' : 'classic' }, + ).then((registration) => { console.log('SW registered: ', registration); }).catch((registrationError) => { console.log('SW registration failed: ', registrationError); diff --git a/packages/ui-default/hydro.ts b/packages/ui-default/hydro.ts index cf4b47dc4..186c45433 100644 --- a/packages/ui-default/hydro.ts +++ b/packages/ui-default/hydro.ts @@ -82,6 +82,7 @@ export async function initPageLoader() { Notification.warn(`Failed to call '${type}Loading' of ${page.name}`); console.error(`Failed to call '${type}Loading' of ${page.name}\n${e.stack}`); console.error(e); + console.info(func.toString()); } if (process.env.NODE_ENV !== 'production') { console.time(`${page.name}: ${type}Loading`); diff --git a/packages/ui-default/misc/PageLoader.js b/packages/ui-default/misc/PageLoader.js index cec650cc0..ba28eb8dd 100644 --- a/packages/ui-default/misc/PageLoader.js +++ b/packages/ui-default/misc/PageLoader.js @@ -1,12 +1,10 @@ +import pages from '../__glob_page'; import { Page } from './Page'; export default class PageLoader { constructor() { - const pages = require.context('../pages/', true, /\.page\.[jt]sx?$/i); - const components = require.context('../components/', true, /\.page\.[jt]sx?$/i); this.pageInstances = [ - ...pages.keys().map((key) => pages(key)), - ...components.keys().map((key) => components(key)), + ...pages, ...window.Hydro.extraPages, ].map((page) => { page = page?.default || page; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 44216943f..6b1eaf110 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@blueprintjs/core": "^5.10.3", + "@codingame/esbuild-import-meta-url-plugin": "^1.0.2", "@fontsource/dm-mono": "^5.0.20", "@fontsource/fira-code": "^5.0.18", "@fontsource/inconsolata": "^5.0.18", @@ -24,6 +25,7 @@ "@fontsource/source-code-pro": "^5.0.18", "@fontsource/ubuntu-mono": "^5.0.20", "@hydrooj/utils": "workspace:^", + "@rollup/plugin-inject": "^5.0.5", "@simplewebauthn/browser": "7.4.0", "@svgr/webpack": "^8.1.0", "@types/gulp-if": "^0.0.34", @@ -117,6 +119,10 @@ "ts-loader": "^9.5.1", "vditor": "^3.10.4", "vinyl-buffer": "^1.0.1", + "vite": "^5.2.12", + "vite-plugin-pwa": "^0.20.0", + "vite-plugin-static-copy": "^1.0.5", + "vite-plugin-svgr": "^4.2.0", "web-streams-polyfill": "^4.0.0", "webpack": "^5.91.0", "webpack-bundle-analyzer": "^4.10.2", diff --git a/packages/ui-default/pages/user_detail.page.styl b/packages/ui-default/pages/user_detail.page.styl index b37ec25c3..5796027ac 100644 --- a/packages/ui-default/pages/user_detail.page.styl +++ b/packages/ui-default/pages/user_detail.page.styl @@ -1,4 +1,4 @@ -@import '~vj/components/tab/var.inc.styl' +@import '../components/tab/var.inc.styl' .page--user_detail diff --git a/packages/ui-default/service-worker.ts b/packages/ui-default/service-worker.ts index 514212c0b..7902ca7ee 100644 --- a/packages/ui-default/service-worker.ts +++ b/packages/ui-default/service-worker.ts @@ -4,6 +4,16 @@ export { }; // make it a module so that declare self works declare const self: ServiceWorkerGlobalScope; +let dev = false; +let version = 'dev'; +try { + dev = process.env.NODE_ENV !== 'production'; + version = process.env.VERSION; +} catch (e) { + // process doesnt exist + dev = location.search.includes('dev-sw'); +} + const map = new Map(); function createStream(port) { @@ -88,13 +98,13 @@ self.addEventListener('notificationclick', (event) => { })); }); -const PRECACHE = `precache-${process.env.VERSION}`; +const PRECACHE = `precache-${version}`; const DO_NOT_PRECACHE = ['vditor', '.worker.js', 'fonts', 'i.monaco']; function shouldCachePath(path: string) { if (!path.split('?')[0].split('/').pop()) return false; if (!path.split('?')[0].split('/').pop().includes('.')) return false; - if (process.env.NODE_ENV !== 'production' && (path.includes('.hot-update.') || path.includes('?version='))) return false; + if (dev && (path.includes('.hot-update.') || path.includes('?version='))) return false; return true; } function shouldCache(request: Request) { @@ -119,7 +129,7 @@ interface ServiceWorkerConfig { let config: ServiceWorkerConfig = null; function initConfig() { - config = JSON.parse(new URLSearchParams(location.search).get('config')); + config = JSON.parse(new URLSearchParams(location.search).get('config')) || {}; config.hosts ||= []; if (!config.domains?.length) config.domains = [location.host]; console.log('Config:', config); @@ -127,7 +137,7 @@ function initConfig() { self.addEventListener('install', (event) => event.waitUntil((async () => { initConfig(); - if (process.env.NODE_ENV === 'production' && config?.preload) { + if (!dev && config?.preload) { const [cache, manifest] = await Promise.all([ caches.open(PRECACHE), fetch('/manifest.json').then((res) => res.json()), @@ -193,6 +203,7 @@ async function cachedRespond(request: Request) { caches.open(PRECACHE), get(request), ]); + if (response.headers.get('Cache-Control') === 'no-cache') return response; if (response.ok) { cache.put(url, response.clone()); return response; diff --git a/packages/ui-default/templates/layout/html5.html b/packages/ui-default/templates/layout/html5.html index dc06df6fb..17fe5e61f 100644 --- a/packages/ui-default/templates/layout/html5.html +++ b/packages/ui-default/templates/layout/html5.html @@ -37,8 +37,7 @@ {% endif %} {% if process.env.DEV %} - - + {% else %} {% endif %} @@ -67,7 +66,7 @@ {% set UserContext = Object.create(UserContext) %} {% if process.env.DEV %} - + {% else %} diff --git a/packages/ui-default/theme/default.js b/packages/ui-default/theme/default.js index 0c7a2a817..b0b2547ae 100644 --- a/packages/ui-default/theme/default.js +++ b/packages/ui-default/theme/default.js @@ -27,9 +27,6 @@ import 'vj/misc/nothing.styl'; import 'vj/components/editor/cmeditor.styl'; import 'vj/components/datepicker/datepicker.styl'; import './dark.styl'; - // load all page stylesheets -const pageStyleReq = require.context('../', true, /\.page\.styl$/i); -pageStyleReq.keys().map((key) => pageStyleReq(key)); -const pageStyleReqDefault = require.context('../', true, /\.page\.default\.styl$/i); -pageStyleReqDefault.keys().map((key) => pageStyleReqDefault(key)); +import '../__glob_page.styl'; +import '../__glob_default.styl'; diff --git a/packages/ui-default/vite.config.mjs b/packages/ui-default/vite.config.mjs new file mode 100644 index 000000000..6517cc7c3 --- /dev/null +++ b/packages/ui-default/vite.config.mjs @@ -0,0 +1,268 @@ +import importMetaUrlPlugin from '@codingame/esbuild-import-meta-url-plugin'; +import inject from '@rollup/plugin-inject'; +import fs from 'fs'; +import { globbySync } from 'globby'; +import httpProxy from 'http-proxy'; +import path from 'path'; +import colors from 'picocolors'; +// import federation from '@originjs/vite-plugin-federation'; +import { defineConfig } from 'vite'; +import { VitePWA } from 'vite-plugin-pwa'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; +import svgr from 'vite-plugin-svgr'; +import { version } from './package.json'; + +const proxy = httpProxy.createProxyServer({ + ws: true, + target: 'http://localhost:2333', +}); + +const setOriginHeader = (proxyReq, options) => { + if (options.changeOrigin) { + const { target } = options; + + if (proxyReq.getHeader('origin') && target) { + const changedOrigin = typeof target === 'object' + ? `${target.protocol}//${target.host}` + : target; + + proxyReq.setHeader('origin', changedOrigin); + } + } +}; +proxy.on('error', (err, req, originalRes) => { + const res = originalRes; + if (!res) { + console.error( + `${colors.red(`http proxy error: ${err.message}`)}\n${err.stack}`, + { + timestamp: true, + error: err, + }, + ); + } else if ('req' in res) { + console.error( + `${colors.red(`http proxy error: ${originalRes.req.url}`)}\n${err.stack + }`, + { + timestamp: true, + error: err, + }, + ); + if (!res.headersSent && !res.writableEnded) { + res + .writeHead(500, { + 'Content-Type': 'text/plain', + }) + .end(); + } + } else { + console.error(`${colors.red('ws proxy error:')}\n${err.stack}`, { + timestamp: true, + error: err, + }); + res.end(); + } +}); +proxy.on('proxyReq', (proxyReq, req, res, options) => { + setOriginHeader(proxyReq, options); +}); +proxy.on('proxyReqWs', (proxyReq, req, socket, options) => { + setOriginHeader(proxyReq, options); + socket.on('error', (err) => { + console.error( + `${colors.red('ws proxy socket error:')}\n${err.stack}`, + { + timestamp: true, + error: err, + }, + ); + }); +}); +proxy.on('proxyRes', (proxyRes, req, res) => { + res.on('close', () => { + if (!res.writableEnded) { + proxyRes.destroy(); + } + }); +}); + +function getGlobStyle(glob = '**/*.page.styl') { + const files = globbySync(glob, { cwd: __dirname, ignore: ['node_modules'] }); + const result = []; + for (const file of files) { + const content = fs.readFileSync(path.join(__dirname, file), 'utf-8'); + if (content.includes('@import \'.')) { + result.push(`@import ${JSON.stringify(file)}`); + } else { + result.push(content); + } + } + return `${result.filter((i) => i.startsWith('@import')).join('\n')} +${result.filter((i) => !i.startsWith('@import')).join('\n')}`; +} +function updateGlobStyle(glob, filename) { + const file = path.join(__dirname, filename); + const content = getGlobStyle(glob); + if (!fs.existsSync(file) || fs.readFileSync(file, 'utf-8') !== content) { + fs.writeFileSync(file, content); + } +} +updateGlobStyle('**/*.page.styl', '__glob_page.styl'); +updateGlobStyle('**/*.page.default.styl', '__glob_default.styl'); +function getGlobPage(glob = '**/*.page.[jt]sx?') { + const files = globbySync(glob, { cwd: __dirname, ignore: ['node_modules'] }); + const result = []; + const exps = []; + for (const file of files) { + result.push(`import ${file.replace(/[/\\.-]/g, '_')} from "${file.startsWith('/') ? file : `./${file}`}";`); + exps.push(file.replace(/[/\\.-]/g, '_')); + } + result.push(`export default [${exps.join(',')}]`); + return result.join('\n'); +} +function updateGlobPage(glob, filename) { + const file = path.join(__dirname, filename); + const content = getGlobPage(glob); + if (!fs.existsSync(file) || fs.readFileSync(file, 'utf-8') !== content) { + fs.writeFileSync(file, content); + } +} +updateGlobPage(['**/*.page.js', '**/*.page.ts', '**/*.page.jsx', '**/*.page.tsx'], '__glob_page.js'); + +function patchPrism() { + const langs = globbySync('prism-*.js', { cwd: import.meta.resolve('prismjs/components').split(':')[1], ignore: ['node_modules'] }); + const lines = langs.filter((i) => !i.endsWith('.min.js')).map((i) => { + const name = i.split('prism-')[1].split('.')[0]; + return `'${name}': () => require('./${i}'),`; + }); + const loader = `export default { + ${lines.join('\n')} + }`; + fs.writeFileSync(import.meta.resolve('prismjs/components/index.js').split(':')[1], loader); +} +patchPrism(); + +export default defineConfig({ + root: __dirname, + esbuild: { + jsx: 'transform', + loader: 'tsx', + }, + publicDir: 'static', + build: { + minify: process.env.NODE_NEV === 'production', + outDir: 'public', + rollupOptions: { + input: './entry.jsx', + output: { + globals: { + jQuery: 'jQuery', + }, + }, + }, + }, + resolve: { + dedupe: ['monaco-editor', 'vscode'], + alias: { + vj: __dirname, + }, + }, + define: { + 'process.env.VERSION': JSON.stringify(version), + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + ...(process.env.NODE_ENV === 'development' ? { 'process.cwd': '()=>null' } : {}), + }, + optimizeDeps: { + include: ['jquery'], + esbuildOptions: { + plugins: [importMetaUrlPlugin], + }, + }, + css: { + preprocessorOptions: { + styl: { + use: [], + imports: [ + // import.meta.resolve('rupture/rupture/index.styl'), + path.join(__dirname, 'common/common.inc.styl'), + ], + }, + }, + }, + server: { + host: true, + port: 8000, + headers: { + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp', + }, + proxy: { + }, + }, + worker: { + format: 'es', + }, + plugins: [ + inject({ + $: 'jquery', + React: 'react', + include: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'], + }), + svgr({}), + viteStaticCopy({ + targets: [ + { src: 'components/navigation/nav-logo-small_dark.png', dest: 'components/navigation/nav-logo-small_dark.png' }, + { src: import.meta.resolve('streamsaver/mitm.html'), dest: 'streamsaver/mitm.html' }, + { src: import.meta.resolve('streamsaver/sw.js'), dest: 'streamsaver/sw.js' }, + { src: import.meta.resolve('vditor/dist'), dest: 'vditor/dist' }, + // { from: root(`${dirname(require.resolve('graphiql/package.json'))}/graphiql.min.css`), to: 'graphiql.min.css' }, + // { from: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' }, + ], + }), + VitePWA({ + injectRegister: null, + registerType: 'autoUpdate', + strategies: 'injectManifest', + srcDir: '.', + filename: 'service-worker.ts', + injectManifest: { + injectionPoint: undefined, + }, + workbox: { + globPatterns: ['**/*.{js,css,html,ico,png,svg}'], + }, + devOptions: { + enabled: true, + type: 'module', + }, + }), + { + name: 'configure-server', + watchChange(id) { + if (id.endsWith('.styl') && !id.includes('/__')) { + console.log('change', id); + updateGlobStyle('**/*.page.styl', '__glob_page.styl'); + updateGlobStyle('**/*.page.default.styl', '__glob_default.styl'); + } + }, + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + // console.log(req.url); + const resource = req.url.split('?')[0]; + if (['/@fs/', '/@vite/', '/sw.js', '/dev-sw.js', '/vditor'].some((t) => resource.startsWith(t))) next(); + else if (resource !== '/' && fs.existsSync(path.join(__dirname, resource.substring(1)))) next(); + else { + const options = {}; + proxy.web(req, res, options); + } + }); + server.httpServer.on('upgrade', (req, socket, head) => { + if (req.url !== '/') { + proxy.ws(req, socket, head); + } + }); + }, + }, + ], +});