Skip to content

Commit ab61d28

Browse files
feature (fastboot): parallelize the loading of assets
1 parent 9ad8ec1 commit ab61d28

File tree

2 files changed

+84
-52
lines changed

2 files changed

+84
-52
lines changed

public/index.frontoffice.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@
3939
try {
4040
if (!HTMLScriptElement.supports?.("importmap")) throw new Error("fastboot is not supported on this platform");
4141
{{ load_asset "assets/boot/bundler_init.js" }}
42-
await new Promise((resolve, reject) => document.head.appendChild(Object.assign(document.createElement("script"), {
42+
await Promise.all(Array({{ .bundle_size }}).fill().map((_, i) => new Promise((resolve, reject) => document.head.appendChild(Object.assign(document.createElement("script"), {
4343
type: "module",
44-
src: `./assets/bundle.js?version=${window.VERSION}`,
44+
src: `./assets/bundle.js?version=${window.VERSION}&chunk=${i+1}`,
4545
onload: resolve,
4646
onerror: reject,
47-
})));
47+
})))));
4848
{{ load_asset "assets/boot/bundler_complete.js" }}
4949
} catch (err) { console.error(err); }
5050

server/ctrl/static.go

Lines changed: 81 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"os"
1414
"path/filepath"
1515
"regexp"
16+
"strconv"
1617
"strings"
1718
"text/template"
1819

@@ -233,11 +234,12 @@ func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request)
233234
sign := signature()
234235
base := WithBase("/")
235236
templateData := map[string]any{
236-
"base": base,
237-
"version": BUILD_REF,
238-
"license": LICENSE,
239-
"hash": sign,
240-
"favicon": favicon(),
237+
"base": base,
238+
"version": BUILD_REF,
239+
"license": LICENSE,
240+
"hash": sign,
241+
"favicon": favicon(),
242+
"bundle_size": len(preload),
241243
}
242244
calculatedEtag := QuickHash(base+BUILD_REF+LICENSE+sign, 10)
243245
head.Set("ETag", calculatedEtag)
@@ -258,8 +260,8 @@ func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request)
258260
}
259261
}
260262

261-
func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
262-
paths := []string{
263+
var preload = [][]string{
264+
{
263265
"/assets/" + BUILD_REF + "/boot/ctrl_boot_frontoffice.js",
264266
"/assets/" + BUILD_REF + "/boot/router_frontoffice.js",
265267
"/assets/" + BUILD_REF + "/boot/common.js",
@@ -303,6 +305,9 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
303305
"/assets/" + BUILD_REF + "/helpers/sdk.js",
304306

305307
"/assets/" + BUILD_REF + "/lib/rx.js",
308+
"/assets/" + BUILD_REF + "/lib/ajax.js",
309+
},
310+
{
306311
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs.min.js",
307312
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-ajax.min.js",
308313
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-shared.min.js",
@@ -311,7 +316,6 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
311316
"/assets/" + BUILD_REF + "/lib/path.js",
312317
"/assets/" + BUILD_REF + "/lib/random.js",
313318
"/assets/" + BUILD_REF + "/lib/settings.js",
314-
"/assets/" + BUILD_REF + "/lib/ajax.js",
315319
"/assets/" + BUILD_REF + "/lib/animate.js",
316320
"/assets/" + BUILD_REF + "/lib/assert.js",
317321
"/assets/" + BUILD_REF + "/lib/dom.js",
@@ -321,16 +325,16 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
321325
"/assets/" + BUILD_REF + "/lib/error.js",
322326

323327
"/assets/" + BUILD_REF + "/locales/index.js",
324-
325328
"/assets/" + BUILD_REF + "/model/config.js",
326329
"/assets/" + BUILD_REF + "/model/chromecast.js",
327330
"/assets/" + BUILD_REF + "/model/session.js",
328331
"/assets/" + BUILD_REF + "/model/plugin.js",
329332

330333
"/assets/" + BUILD_REF + "/pages/ctrl_logout.js",
331334
"/assets/" + BUILD_REF + "/pages/ctrl_error.js",
335+
},
336+
{
332337
"/assets/" + BUILD_REF + "/pages/ctrl_homepage.js",
333-
334338
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.js",
335339
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.css",
336340
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.css",
@@ -341,18 +345,24 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
341345
"/assets/" + BUILD_REF + "/pages/connectpage/model_config.js",
342346
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form_state.js",
343347

348+
"/assets/" + BUILD_REF + "/pages/filespage/thing.js",
349+
"/assets/" + BUILD_REF + "/pages/filespage/thing.css",
350+
},
351+
{
344352
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.js",
345353
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.css",
346-
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.css",
347354
"/assets/" + BUILD_REF + "/pages/filespage/model_acl.js",
348355
"/assets/" + BUILD_REF + "/pages/filespage/cache.js",
349-
"/assets/" + BUILD_REF + "/pages/filespage/thing.js",
350-
"/assets/" + BUILD_REF + "/pages/filespage/thing.css",
351356
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.js",
352357
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.css",
353358
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.js",
354359
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.css",
360+
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.js",
361+
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.css",
362+
},
363+
{
355364
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.js",
365+
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.css",
356366
"/assets/" + BUILD_REF + "/pages/filespage/state_config.js",
357367
"/assets/" + BUILD_REF + "/pages/filespage/helper.js",
358368
"/assets/" + BUILD_REF + "/pages/filespage/model_files.js",
@@ -367,8 +377,6 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
367377
"/assets/" + BUILD_REF + "/pages/filespage/modal_delete.js",
368378
"/assets/" + BUILD_REF + "/pages/filespage/state_selection.js",
369379
"/assets/" + BUILD_REF + "/pages/filespage/state_newthing.js",
370-
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.js",
371-
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.css",
372380

373381
// "/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.js", // TODO: dynamic imports
374382
"/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.css",
@@ -379,66 +387,90 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
379387
"/assets/" + BUILD_REF + "/pages/viewerpage/application_downloader.css",
380388
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.js",
381389
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.css",
382-
}
390+
},
391+
}
383392

393+
func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
384394
var isDebug = os.Getenv("DEBUG") == "true"
385395

386-
build := func(quality int) (bundlePlain []byte, bundleBr []byte, etag string) {
387-
var buf bytes.Buffer
388-
for _, path := range paths {
389-
curPath := "/assets/" + strings.TrimPrefix(path, "/assets/"+BUILD_REF+"/")
390-
f := applyPatch(curPath)
391-
if f == nil {
392-
file, err := WWWPublic.Open(curPath)
393-
if err != nil {
394-
Log.Warning("static::bundler failed to find file %s", err.Error())
395-
continue
396+
buildChunks := func(quality int) (chunks [][]byte, chunksBr [][]byte, etags []string) {
397+
numChunks := len(preload)
398+
chunks = make([][]byte, numChunks+1)
399+
chunksBr = make([][]byte, numChunks+1)
400+
etags = make([]string, numChunks+1)
401+
var fullBuf bytes.Buffer
402+
for i := 0; i < numChunks; i++ {
403+
var chunkBuf bytes.Buffer
404+
for _, path := range preload[i] {
405+
curPath := "/assets/" + strings.TrimPrefix(path, "/assets/"+BUILD_REF+"/")
406+
f := applyPatch(curPath)
407+
if f == nil {
408+
file, err := WWWPublic.Open(curPath)
409+
if err != nil {
410+
Log.Warning("static::bundler failed to find file %s", err.Error())
411+
continue
412+
}
413+
f = new(bytes.Buffer)
414+
if _, err := io.Copy(f, file); err != nil {
415+
Log.Warning("static::bundler msg=copy_error err=%s", err.Error())
416+
continue
417+
}
418+
file.Close()
396419
}
397-
f = new(bytes.Buffer)
398-
if _, err := io.Copy(f, file); err != nil {
399-
Log.Warning("static::bundler msg=copy_error err=%s", err.Error())
420+
code, err := json.Marshal(f.String())
421+
if err != nil {
422+
Log.Warning("static::bundle msg=marshal_failed path=%s err=%s", path, err.Error())
400423
continue
401424
}
402-
file.Close()
425+
bundleCall := fmt.Sprintf("bundler.register(%q, %s);\n", WithBase(path), code)
426+
chunkBuf.WriteString(bundleCall)
427+
fullBuf.WriteString(bundleCall)
403428
}
404-
code, err := json.Marshal(f.String())
405-
if err != nil {
406-
Log.Warning("static::bundle msg=marshal_failed path=%s err=%s", path, err.Error())
407-
continue
408-
}
409-
fmt.Fprintf(&buf, "bundler.register(%q, %s);\n", WithBase(path), code)
410-
}
411-
etag = QuickHash(string(bundlePlain), 10)
412-
bundlePlain = buf.Bytes()
413-
if quality > 0 {
414-
bundleBr, _ = cbrotli.Encode(bundlePlain, cbrotli.WriterOptions{Quality: quality})
429+
chunks[i+1] = chunkBuf.Bytes()
430+
etags[i+1] = QuickHash(string(chunks[i+1]), 10)
431+
chunksBr[i+1], _ = cbrotli.Encode(chunks[i+1], cbrotli.WriterOptions{Quality: quality})
415432
}
416-
return bundlePlain, bundleBr, etag
433+
chunks[0] = fullBuf.Bytes()
434+
etags[0] = QuickHash(string(chunks[0]), 10)
435+
chunksBr[0], _ = cbrotli.Encode(chunks[0], cbrotli.WriterOptions{Quality: quality})
436+
return chunks, chunksBr, etags
417437
}
418438

419439
quality := 11
420440
if isDebug {
421441
quality = 8
422442
}
423-
bundlePlain, bundleBr, etag := build(quality)
443+
chunks, chunksBr, etags := buildChunks(quality)
424444

425445
return func(ctx *App, res http.ResponseWriter, req *http.Request) {
426446
if isDebug {
427-
bundlePlain, bundleBr, etag = build(quality)
447+
chunks, chunksBr, etags = buildChunks(quality)
428448
}
449+
450+
chunkIndex := 0
451+
if parsed, err := strconv.Atoi(req.URL.Query().Get("chunk")); err == nil && parsed < len(chunks) {
452+
chunkIndex = parsed
453+
}
454+
455+
if chunkIndex >= len(chunks) {
456+
http.NotFound(res, req)
457+
return
458+
}
459+
429460
head := res.Header()
430461
head.Set("Content-Type", "application/javascript")
431462
head.Set("Cache-Control", "no-cache")
432-
head.Set("Etag", etag)
433-
if req.Header.Get("If-None-Match") == etag && etag != "" {
463+
head.Set("Etag", etags[chunkIndex])
464+
465+
if req.Header.Get("If-None-Match") == etags[chunkIndex] && etags[chunkIndex] != "" {
434466
res.WriteHeader(http.StatusNotModified)
435467
return
436-
} else if strings.Contains(req.Header.Get("Accept-Encoding"), "br") && len(bundleBr) > 0 {
468+
} else if strings.Contains(req.Header.Get("Accept-Encoding"), "br") && len(chunksBr[chunkIndex]) > 0 {
437469
head.Set("Content-Encoding", "br")
438-
res.Write(bundleBr)
470+
res.Write(chunksBr[chunkIndex])
439471
return
440472
}
441-
res.Write(bundlePlain)
473+
res.Write(chunks[chunkIndex])
442474
}
443475
}
444476

0 commit comments

Comments
 (0)