mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
feature (fastboot): parallelize the loading of assets
This commit is contained in:
parent
9ad8ec1f87
commit
c24d22d650
3 changed files with 98 additions and 65 deletions
|
|
@ -2,29 +2,35 @@ window.bundler = (function(origin) {
|
|||
const esModules = {};
|
||||
return {
|
||||
register: (path, code) => {
|
||||
const fullpath = origin + path;
|
||||
if (path.endsWith(".js")) {
|
||||
code = code.replace(/from\s?"([^"]+)"/g, (_, spec) =>
|
||||
`from "${new URL(spec, origin + path).href}"`,
|
||||
`from "${new URL(spec, fullpath).href}"`,
|
||||
);
|
||||
code = code.replace(/\bimport\s+"([^"]+)"/g, (_, spec) =>
|
||||
`import "${new URL(spec, origin + path).href}"`,
|
||||
`import "${new URL(spec, fullpath).href}"`,
|
||||
);
|
||||
code = code.replace(
|
||||
/(?<!["])\bimport\.meta\.url\b(?!["])/g,
|
||||
`"${fullpath}"`,
|
||||
);
|
||||
esModules[fullpath] = "data:text/javascript," + encodeURIComponent(
|
||||
code + `\n//# sourceURL=${path}`,
|
||||
);
|
||||
code = code.replace(/(?<!["])\bimport\.meta\.url\b(?!["])/g, `"${origin + path}"`);
|
||||
code += `\n//# sourceURL=${path}`;
|
||||
esModules[origin + path] = "data:text/javascript," + encodeURIComponent(code);
|
||||
} else if (path.endsWith(".css")) {
|
||||
code = code.replace(/@import url\("([^"]+)"\);/g, (m, rel) => {
|
||||
const $style = document.head.querySelector(`style[id="${new URL(rel, origin + path).href}"]`);
|
||||
const $style = document.head.querySelector(
|
||||
`style[id="${new URL(rel, fullpath).href}"]`
|
||||
);
|
||||
if (!$style) throw new DOMException(
|
||||
`Missing CSS dependency: ${rel} (referenced from ${path})`,
|
||||
"NotFoundError",
|
||||
);
|
||||
return `/* ${m} */`;
|
||||
});
|
||||
code += `\n/*# sourceURL=${path} */`;
|
||||
document.head.appendChild(Object.assign(document.createElement("style"), {
|
||||
innerHTML: code,
|
||||
id: origin + path,
|
||||
innerHTML: code + `\n/*# sourceURL=${path} */`,
|
||||
id: fullpath,
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -39,12 +39,12 @@
|
|||
try {
|
||||
if (!HTMLScriptElement.supports?.("importmap")) throw new Error("fastboot is not supported on this platform");
|
||||
{{ load_asset "assets/boot/bundler_init.js" }}
|
||||
await new Promise((resolve, reject) => document.head.appendChild(Object.assign(document.createElement("script"), {
|
||||
await Promise.all(Array({{ .bundle_size }}).fill().map((_, i) => new Promise((resolve, reject) => document.head.appendChild(Object.assign(document.createElement("script"), {
|
||||
type: "module",
|
||||
src: `./assets/bundle.js?version=${window.VERSION}`,
|
||||
src: `./assets/bundle.js?version=${window.VERSION}&chunk=${i+1}`,
|
||||
onload: resolve,
|
||||
onerror: reject,
|
||||
})));
|
||||
})))));
|
||||
{{ load_asset "assets/boot/bundler_complete.js" }}
|
||||
} catch (err) { console.error(err); }
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
|
|
@ -238,6 +239,7 @@ func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request)
|
|||
"license": LICENSE,
|
||||
"hash": sign,
|
||||
"favicon": favicon(),
|
||||
"bundle_size": len(preload),
|
||||
}
|
||||
calculatedEtag := QuickHash(base+BUILD_REF+LICENSE+sign, 10)
|
||||
head.Set("ETag", calculatedEtag)
|
||||
|
|
@ -258,8 +260,8 @@ func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request)
|
|||
}
|
||||
}
|
||||
|
||||
func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
||||
paths := []string{
|
||||
var preload = [][]string{
|
||||
{
|
||||
"/assets/" + BUILD_REF + "/boot/ctrl_boot_frontoffice.js",
|
||||
"/assets/" + BUILD_REF + "/boot/router_frontoffice.js",
|
||||
"/assets/" + BUILD_REF + "/boot/common.js",
|
||||
|
|
@ -303,6 +305,9 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
|||
"/assets/" + BUILD_REF + "/helpers/sdk.js",
|
||||
|
||||
"/assets/" + BUILD_REF + "/lib/rx.js",
|
||||
"/assets/" + BUILD_REF + "/lib/ajax.js",
|
||||
},
|
||||
{
|
||||
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs.min.js",
|
||||
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-ajax.min.js",
|
||||
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-shared.min.js",
|
||||
|
|
@ -311,7 +316,6 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
|||
"/assets/" + BUILD_REF + "/lib/path.js",
|
||||
"/assets/" + BUILD_REF + "/lib/random.js",
|
||||
"/assets/" + BUILD_REF + "/lib/settings.js",
|
||||
"/assets/" + BUILD_REF + "/lib/ajax.js",
|
||||
"/assets/" + BUILD_REF + "/lib/animate.js",
|
||||
"/assets/" + BUILD_REF + "/lib/assert.js",
|
||||
"/assets/" + BUILD_REF + "/lib/dom.js",
|
||||
|
|
@ -321,7 +325,6 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
|||
"/assets/" + BUILD_REF + "/lib/error.js",
|
||||
|
||||
"/assets/" + BUILD_REF + "/locales/index.js",
|
||||
|
||||
"/assets/" + BUILD_REF + "/model/config.js",
|
||||
"/assets/" + BUILD_REF + "/model/chromecast.js",
|
||||
"/assets/" + BUILD_REF + "/model/session.js",
|
||||
|
|
@ -329,8 +332,9 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
|||
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_logout.js",
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_error.js",
|
||||
},
|
||||
{
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_homepage.js",
|
||||
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.js",
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.css",
|
||||
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.css",
|
||||
|
|
@ -341,18 +345,24 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
|||
"/assets/" + BUILD_REF + "/pages/connectpage/model_config.js",
|
||||
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form_state.js",
|
||||
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.js",
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.css",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.css",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/model_acl.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/cache.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/thing.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/thing.css",
|
||||
},
|
||||
{
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.js",
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_filespage.css",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/model_acl.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/cache.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.css",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.css",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.css",
|
||||
},
|
||||
{
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.css",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/state_config.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/helper.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/model_files.js",
|
||||
|
|
@ -367,8 +377,6 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
|||
"/assets/" + BUILD_REF + "/pages/filespage/modal_delete.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/state_selection.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/state_newthing.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.js",
|
||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.css",
|
||||
|
||||
// "/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.js", // TODO: dynamic imports
|
||||
"/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.css",
|
||||
|
|
@ -379,13 +387,20 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
|||
"/assets/" + BUILD_REF + "/pages/viewerpage/application_downloader.css",
|
||||
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.js",
|
||||
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.css",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var isDebug = os.Getenv("DEBUG") == "true"
|
||||
|
||||
build := func(quality int) (bundlePlain []byte, bundleBr []byte, etag string) {
|
||||
var buf bytes.Buffer
|
||||
for _, path := range paths {
|
||||
func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
||||
isDebug := os.Getenv("DEBUG") == "true"
|
||||
buildChunks := func(quality int) (chunks [][]byte, chunksBr [][]byte, etags []string) {
|
||||
numChunks := len(preload)
|
||||
chunks = make([][]byte, numChunks+1)
|
||||
chunksBr = make([][]byte, numChunks+1)
|
||||
etags = make([]string, numChunks+1)
|
||||
var fullBuf bytes.Buffer
|
||||
for i := 0; i < numChunks; i++ {
|
||||
var chunkBuf bytes.Buffer
|
||||
for _, path := range preload[i] {
|
||||
curPath := "/assets/" + strings.TrimPrefix(path, "/assets/"+BUILD_REF+"/")
|
||||
f := applyPatch(curPath)
|
||||
if f == nil {
|
||||
|
|
@ -406,39 +421,51 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
|||
Log.Warning("static::bundle msg=marshal_failed path=%s err=%s", path, err.Error())
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&buf, "bundler.register(%q, %s);\n", WithBase(path), code)
|
||||
line := fmt.Sprintf("bundler.register(%q, %s);\n", WithBase(path), code)
|
||||
chunkBuf.WriteString(line)
|
||||
fullBuf.WriteString(line)
|
||||
}
|
||||
etag = QuickHash(string(bundlePlain), 10)
|
||||
bundlePlain = buf.Bytes()
|
||||
if quality > 0 {
|
||||
bundleBr, _ = cbrotli.Encode(bundlePlain, cbrotli.WriterOptions{Quality: quality})
|
||||
chunks[i+1] = chunkBuf.Bytes()
|
||||
chunksBr[i+1], _ = cbrotli.Encode(chunks[i+1], cbrotli.WriterOptions{Quality: quality})
|
||||
etags[i+1] = QuickHash(string(chunks[i+1]), 10)
|
||||
}
|
||||
return bundlePlain, bundleBr, etag
|
||||
chunks[0] = fullBuf.Bytes()
|
||||
chunksBr[0], _ = cbrotli.Encode(chunks[0], cbrotli.WriterOptions{Quality: quality})
|
||||
etags[0] = QuickHash(string(chunks[0]), 10)
|
||||
return chunks, chunksBr, etags
|
||||
}
|
||||
|
||||
quality := 11
|
||||
if isDebug {
|
||||
quality = 8
|
||||
}
|
||||
bundlePlain, bundleBr, etag := build(quality)
|
||||
chunks, chunksBr, etags := buildChunks(quality)
|
||||
|
||||
return func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
if isDebug {
|
||||
bundlePlain, bundleBr, etag = build(quality)
|
||||
chunks, chunksBr, etags = buildChunks(quality)
|
||||
}
|
||||
chunkIndex := 0
|
||||
if parsed, err := strconv.Atoi(req.URL.Query().Get("chunk")); err == nil {
|
||||
chunkIndex = parsed
|
||||
}
|
||||
if chunkIndex >= len(chunks) {
|
||||
http.NotFound(res, req)
|
||||
return
|
||||
}
|
||||
head := res.Header()
|
||||
head.Set("Content-Type", "application/javascript")
|
||||
head.Set("Cache-Control", "no-cache")
|
||||
head.Set("Etag", etag)
|
||||
if req.Header.Get("If-None-Match") == etag && etag != "" {
|
||||
head.Set("Etag", etags[chunkIndex])
|
||||
if req.Header.Get("If-None-Match") == etags[chunkIndex] && etags[chunkIndex] != "" {
|
||||
res.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
} else if strings.Contains(req.Header.Get("Accept-Encoding"), "br") && len(bundleBr) > 0 {
|
||||
} else if strings.Contains(req.Header.Get("Accept-Encoding"), "br") && len(chunksBr[chunkIndex]) > 0 {
|
||||
head.Set("Content-Encoding", "br")
|
||||
res.Write(bundleBr)
|
||||
res.Write(chunksBr[chunkIndex])
|
||||
return
|
||||
}
|
||||
res.Write(bundlePlain)
|
||||
res.Write(chunks[chunkIndex])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue