Skip to content

Commit

Permalink
Merge pull request lichess-org#16535 from schlawg/ui-build-docs-and-r…
Browse files Browse the repository at this point in the history
…efacterz

UI build docs and refacterz
  • Loading branch information
ornicar authored Dec 6, 2024
2 parents 7629954 + 8e9e41b commit f6d4dab
Show file tree
Hide file tree
Showing 90 changed files with 1,164 additions and 1,237 deletions.
2 changes: 1 addition & 1 deletion app/views/base/page.scala
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ object page:
)
)(p.transform(p.body)),
bottomHtml,
ctx.nonce.map(inlineJs.apply),
ctx.nonce.map(inlineJs(_, allModules)),
modulesInit(allModules, ctx.nonce),
p.jsFrag.fold(emptyFrag)(_(ctx.nonce)),
p.pageModule.map { mod => frag(jsonScript(mod.data)) }
Expand Down
2 changes: 1 addition & 1 deletion app/views/relay.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ def embed(
div(id := "main-wrap", cls := "is2d"):
ui.showPreload(rt, data)(cls := "relay-embed")
,
views.base.page.ui.inlineJs(ctx.nonce)
views.base.page.ui.inlineJs(ctx.nonce, List(Esm("site").some))
)
12 changes: 7 additions & 5 deletions modules/web/src/main/AssetManifest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import java.nio.file.Files

import lila.core.config.NetConfig

case class SplitAsset(name: String, imports: List[String]):
case class SplitAsset(name: String, imports: List[String], inlineJs: Option[String]):
def all = name :: imports
case class AssetMaps(
js: Map[String, SplitAsset],
Expand All @@ -31,7 +31,8 @@ final class AssetManifest(environment: Environment, net: NetConfig)(using ws: St
def jsAndDeps(keys: List[String]): List[String] = keys.flatMap { key =>
maps.js.get(key).so(_.all)
}.distinct
def lastUpdate: Instant = maps.modified
def inlineJs(key: String): Option[String] = maps.js.get(key).flatMap(_.inlineJs)
def lastUpdate: Instant = maps.modified

def update(): Unit =
if environment.mode.isProd || net.externalManifest then
Expand Down Expand Up @@ -73,9 +74,10 @@ final class AssetManifest(environment: Environment, net: NetConfig)(using ws: St
.as[JsObject]
.value
.map { (k, value) =>
val name = (value \ "hash").asOpt[String].fold(s"$k.js")(h => s"$k.$h.js")
val imports = (value \ "imports").asOpt[List[String]].getOrElse(Nil)
(k, SplitAsset(name, imports))
val name = (value \ "hash").asOpt[String].fold(s"$k.js")(h => s"$k.$h.js")
val imports = (value \ "imports").asOpt[List[String]].getOrElse(Nil)
val inlineJs = (value \ "inline").asOpt[String]
(k, SplitAsset(name, imports, inlineJs))
}
.toMap

Expand Down
21 changes: 7 additions & 14 deletions modules/web/src/main/ui/layout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ final class layout(helpers: Helpers, assetHelper: lila.web.ui.AssetFullHelper)(
def modulesInit(modules: EsmList, nonce: Optionce) =
modules.flatMap(_.map(_.init(nonce))) // in body

def inlineJs(nonce: Nonce, modules: EsmList = Nil): Frag =
val code =
(Esm("site").some :: modules)
.flatMap(_.flatMap(m => assetHelper.manifest.inlineJs(m.key).map(js => s"(()=>{${js}})()")))
.mkString(";")
embedJsUnsafe(code)(nonce.some)

private def hrefLang(langStr: String, path: String) =
s"""<link rel="alternate" hreflang="$langStr" href="$netBaseUrl$path"/>"""

Expand Down Expand Up @@ -331,17 +338,3 @@ final class layout(helpers: Helpers, assetHelper: lila.web.ui.AssetFullHelper)(
.getOrElse { (!error).option(anonDasher) }
)
)

object inlineJs:
def apply(nonce: Nonce)(using Translate): Frag = embedJsUnsafe(jsCode)(nonce.some)

private val cache = new java.util.concurrent.ConcurrentHashMap[Lang, String]
lila.common.Bus.subscribeFun("i18n.load"):
case lang: Lang => cache.remove(lang)

private def jsCode(using t: Translate) =
cache.computeIfAbsent(
t.lang,
_ => """window.site={load:new Promise(r=>document.addEventListener("DOMContentLoaded",r))};"""
)
end inlineJs
15 changes: 0 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 61 additions & 42 deletions ui/.build/src/build.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import fs from 'node:fs';
import cps from 'node:child_process';
import ps from 'node:process';
import { parsePackages } from './parse.ts';
import { tsc, stopTsc } from './tsc.ts';
import path from 'node:path';
import { parsePackages, globArray } from './parse.ts';
import { tsc, stopTscWatch } from './tsc.ts';
import { sass, stopSass } from './sass.ts';
import { esbuild, stopEsbuild } from './esbuild.ts';
import { esbuild, stopEsbuildWatch } from './esbuild.ts';
import { sync, stopSync } from './sync.ts';
import { monitor, stopMonitor } from './monitor.ts';
import { writeManifest } from './manifest.ts';
import { type Package, env, errorMark, colors as c } from './main.ts';
import { i18n, stopI18n } from './i18n.ts';
import { stopManifest } from './manifest.ts';
import { env, errorMark, colors as c } from './env.ts';
import { i18n, stopI18nWatch } from './i18n.ts';
import { unique } from './algo.ts';
import { clean } from './clean.ts';

export async function build(pkgs: string[]): Promise<void> {
if (env.install)
cps.execSync('pnpm install --prefer-frozen-lockfile', { cwd: env.rootDir, stdio: 'inherit' });
if (env.install) cps.execSync('pnpm install', { cwd: env.rootDir, stdio: 'inherit' });
if (!pkgs.length) env.log(`Parsing packages in '${c.cyan(env.uiDir)}'`);

ps.chdir(env.uiDir);
[env.packages, env.deps] = await parsePackages();
await parsePackages();

pkgs
.filter(x => !env.packages.has(x))
.forEach(x => env.exit(`${errorMark} - unknown package '${c.magenta(x)}'`));

env.building = pkgs.length === 0 ? [...env.packages.values()] : depsMany(pkgs);
env.building =
pkgs.length === 0 ? [...env.packages.values()] : unique(pkgs.flatMap(p => env.transitiveDeps(p)));

if (pkgs.length) env.log(`Building ${c.grey(env.building.map(x => x.name).join(', '))}`);

Expand All @@ -36,43 +38,60 @@ export async function build(pkgs: string[]): Promise<void> {
]);

await Promise.all([sass(), sync(), i18n()]);
await tsc().then(esbuild);
monitor(pkgs);
await Promise.all([tsc(), esbuild()]);
if (env.watch) monitor(pkgs);
}

export async function stopBuild(): Promise<void> {
stopMonitor();
export async function stopBuildWatch(): Promise<void> {
for (const w of watchers) w.close();
watchers.length = 0;
clearTimeout(tscTimeout);
clearTimeout(packageTimeout);
tscTimeout = packageTimeout = undefined;
stopSass();
stopTsc();
stopSync();
stopI18n();
await stopEsbuild();
stopI18nWatch();
stopManifest();
await Promise.allSettled([stopTscWatch(), stopEsbuildWatch()]);
}

export function postBuild(): void {
writeManifest();
for (const pkg of env.building) {
pkg.post.forEach((args: string[]) => {
env.log(`[${c.grey(pkg.name)}] exec - ${c.cyanBold(args.join(' '))}`);
const stdout = cps.execSync(`${args.join(' ')}`, { cwd: pkg.root });
if (stdout) env.log(stdout, { ctx: pkg.name });
});
}
}

export function prePackage(pkg: Package | undefined): void {
pkg?.pre.forEach((args: string[]) => {
env.log(`[${c.grey(pkg.name)}] exec - ${c.cyanBold(args.join(' '))}`);
const stdout = cps.execSync(`${args.join(' ')}`, { cwd: pkg.root });
if (stdout) env.log(stdout, { ctx: pkg.name });
});
}
const watchers: fs.FSWatcher[] = [];

function depsOne(pkgName: string): Package[] {
const collect = (dep: string): string[] => [...(env.deps.get(dep) || []).flatMap(d => collect(d)), dep];
return unique(collect(pkgName).map(name => env.packages.get(name)));
}
let packageTimeout: NodeJS.Timeout | undefined;
let tscTimeout: NodeJS.Timeout | undefined;

const depsMany = (pkgNames: string[]): Package[] => unique(pkgNames.flatMap(depsOne));
async function monitor(pkgs: string[]): Promise<void> {
const [typePkgs, typings] = await Promise.all([
globArray('*/package.json', { cwd: env.typesDir }),
globArray('*/*.d.ts', { cwd: env.typesDir }),
]);
const tscChange = async () => {
if (packageTimeout) return;
stopManifest();
await Promise.allSettled([stopTscWatch(), stopEsbuildWatch()]);
clearTimeout(tscTimeout);
tscTimeout = setTimeout(() => {
if (packageTimeout) return;
tsc().then(esbuild);
}, 2000);
};
const packageChange = async () => {
if (env.watch && env.install) {
clearTimeout(tscTimeout);
clearTimeout(packageTimeout);
await stopBuildWatch();
packageTimeout = setTimeout(() => clean().then(() => build(pkgs)), 2000);
return;
}
env.warn('Exiting due to package.json change');
ps.exit(0);
};

const unique = <T>(pkgs: (T | undefined)[]): T[] => [...new Set(pkgs.filter(x => x))] as T[];
watchers.push(fs.watch(path.join(env.rootDir, 'package.json'), packageChange));
for (const p of typePkgs) watchers.push(fs.watch(p, packageChange));
for (const t of typings) watchers.push(fs.watch(t, tscChange));
for (const pkg of env.building) {
watchers.push(fs.watch(path.join(pkg.root, 'package.json'), packageChange));
watchers.push(fs.watch(path.join(pkg.root, 'tsconfig.json'), tscChange));
}
}
2 changes: 1 addition & 1 deletion ui/.build/src/clean.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { promises as fs } from 'fs';
import fg from 'fast-glob';
import { env, colors as c } from './main.ts';
import { env, colors as c } from './env.ts';

const globOpts: fg.Options = {
absolute: true,
Expand Down
2 changes: 1 addition & 1 deletion ui/.build/src/console.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createServer, IncomingMessage, ServerResponse } from 'node:http';
import { env, errorMark, warnMark, colors as c } from './main.ts';
import { env, errorMark, warnMark, colors as c } from './env.ts';

export async function startConsole() {
if (!env.remoteLog || !env.watch) return;
Expand Down
Loading

0 comments on commit f6d4dab

Please sign in to comment.