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 = {};
|
const esModules = {};
|
||||||
return {
|
return {
|
||||||
register: (path, code) => {
|
register: (path, code) => {
|
||||||
|
const fullpath = origin + path;
|
||||||
if (path.endsWith(".js")) {
|
if (path.endsWith(".js")) {
|
||||||
code = code.replace(/from\s?"([^"]+)"/g, (_, spec) =>
|
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) =>
|
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")) {
|
} else if (path.endsWith(".css")) {
|
||||||
code = code.replace(/@import url\("([^"]+)"\);/g, (m, rel) => {
|
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(
|
if (!$style) throw new DOMException(
|
||||||
`Missing CSS dependency: ${rel} (referenced from ${path})`,
|
`Missing CSS dependency: ${rel} (referenced from ${path})`,
|
||||||
"NotFoundError",
|
"NotFoundError",
|
||||||
);
|
);
|
||||||
return `/* ${m} */`;
|
return `/* ${m} */`;
|
||||||
});
|
});
|
||||||
code += `\n/*# sourceURL=${path} */`;
|
|
||||||
document.head.appendChild(Object.assign(document.createElement("style"), {
|
document.head.appendChild(Object.assign(document.createElement("style"), {
|
||||||
innerHTML: code,
|
innerHTML: code + `\n/*# sourceURL=${path} */`,
|
||||||
id: origin + path,
|
id: fullpath,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,12 @@
|
||||||
try {
|
try {
|
||||||
if (!HTMLScriptElement.supports?.("importmap")) throw new Error("fastboot is not supported on this platform");
|
if (!HTMLScriptElement.supports?.("importmap")) throw new Error("fastboot is not supported on this platform");
|
||||||
{{ load_asset "assets/boot/bundler_init.js" }}
|
{{ 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",
|
type: "module",
|
||||||
src: `./assets/bundle.js?version=${window.VERSION}`,
|
src: `./assets/bundle.js?version=${window.VERSION}&chunk=${i+1}`,
|
||||||
onload: resolve,
|
onload: resolve,
|
||||||
onerror: reject,
|
onerror: reject,
|
||||||
})));
|
})))));
|
||||||
{{ load_asset "assets/boot/bundler_complete.js" }}
|
{{ load_asset "assets/boot/bundler_complete.js" }}
|
||||||
} catch (err) { console.error(err); }
|
} catch (err) { console.error(err); }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
|
@ -233,11 +234,12 @@ func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request)
|
||||||
sign := signature()
|
sign := signature()
|
||||||
base := WithBase("/")
|
base := WithBase("/")
|
||||||
templateData := map[string]any{
|
templateData := map[string]any{
|
||||||
"base": base,
|
"base": base,
|
||||||
"version": BUILD_REF,
|
"version": BUILD_REF,
|
||||||
"license": LICENSE,
|
"license": LICENSE,
|
||||||
"hash": sign,
|
"hash": sign,
|
||||||
"favicon": favicon(),
|
"favicon": favicon(),
|
||||||
|
"bundle_size": len(preload),
|
||||||
}
|
}
|
||||||
calculatedEtag := QuickHash(base+BUILD_REF+LICENSE+sign, 10)
|
calculatedEtag := QuickHash(base+BUILD_REF+LICENSE+sign, 10)
|
||||||
head.Set("ETag", calculatedEtag)
|
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) {
|
var preload = [][]string{
|
||||||
paths := []string{
|
{
|
||||||
"/assets/" + BUILD_REF + "/boot/ctrl_boot_frontoffice.js",
|
"/assets/" + BUILD_REF + "/boot/ctrl_boot_frontoffice.js",
|
||||||
"/assets/" + BUILD_REF + "/boot/router_frontoffice.js",
|
"/assets/" + BUILD_REF + "/boot/router_frontoffice.js",
|
||||||
"/assets/" + BUILD_REF + "/boot/common.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 + "/helpers/sdk.js",
|
||||||
|
|
||||||
"/assets/" + BUILD_REF + "/lib/rx.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.min.js",
|
||||||
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-ajax.min.js",
|
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-ajax.min.js",
|
||||||
"/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-shared.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/path.js",
|
||||||
"/assets/" + BUILD_REF + "/lib/random.js",
|
"/assets/" + BUILD_REF + "/lib/random.js",
|
||||||
"/assets/" + BUILD_REF + "/lib/settings.js",
|
"/assets/" + BUILD_REF + "/lib/settings.js",
|
||||||
"/assets/" + BUILD_REF + "/lib/ajax.js",
|
|
||||||
"/assets/" + BUILD_REF + "/lib/animate.js",
|
"/assets/" + BUILD_REF + "/lib/animate.js",
|
||||||
"/assets/" + BUILD_REF + "/lib/assert.js",
|
"/assets/" + BUILD_REF + "/lib/assert.js",
|
||||||
"/assets/" + BUILD_REF + "/lib/dom.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 + "/lib/error.js",
|
||||||
|
|
||||||
"/assets/" + BUILD_REF + "/locales/index.js",
|
"/assets/" + BUILD_REF + "/locales/index.js",
|
||||||
|
|
||||||
"/assets/" + BUILD_REF + "/model/config.js",
|
"/assets/" + BUILD_REF + "/model/config.js",
|
||||||
"/assets/" + BUILD_REF + "/model/chromecast.js",
|
"/assets/" + BUILD_REF + "/model/chromecast.js",
|
||||||
"/assets/" + BUILD_REF + "/model/session.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_logout.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/ctrl_error.js",
|
"/assets/" + BUILD_REF + "/pages/ctrl_error.js",
|
||||||
|
},
|
||||||
|
{
|
||||||
"/assets/" + BUILD_REF + "/pages/ctrl_homepage.js",
|
"/assets/" + BUILD_REF + "/pages/ctrl_homepage.js",
|
||||||
|
|
||||||
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.js",
|
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.css",
|
"/assets/" + BUILD_REF + "/pages/ctrl_connectpage.css",
|
||||||
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.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/model_config.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form_state.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.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/thing.css",
|
"/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.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.css",
|
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.css",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.js",
|
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.css",
|
"/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.js",
|
||||||
|
"/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.css",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/state_config.js",
|
"/assets/" + BUILD_REF + "/pages/filespage/state_config.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/helper.js",
|
"/assets/" + BUILD_REF + "/pages/filespage/helper.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/model_files.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/modal_delete.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/state_selection.js",
|
"/assets/" + BUILD_REF + "/pages/filespage/state_selection.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/filespage/state_newthing.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.js", // TODO: dynamic imports
|
||||||
"/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.css",
|
"/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.css",
|
||||||
|
|
@ -379,66 +387,85 @@ func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
||||||
"/assets/" + BUILD_REF + "/pages/viewerpage/application_downloader.css",
|
"/assets/" + BUILD_REF + "/pages/viewerpage/application_downloader.css",
|
||||||
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.js",
|
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.js",
|
||||||
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.css",
|
"/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.css",
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var isDebug = os.Getenv("DEBUG") == "true"
|
func ServeBundle() func(*App, http.ResponseWriter, *http.Request) {
|
||||||
|
isDebug := os.Getenv("DEBUG") == "true"
|
||||||
build := func(quality int) (bundlePlain []byte, bundleBr []byte, etag string) {
|
buildChunks := func(quality int) (chunks [][]byte, chunksBr [][]byte, etags []string) {
|
||||||
var buf bytes.Buffer
|
numChunks := len(preload)
|
||||||
for _, path := range paths {
|
chunks = make([][]byte, numChunks+1)
|
||||||
curPath := "/assets/" + strings.TrimPrefix(path, "/assets/"+BUILD_REF+"/")
|
chunksBr = make([][]byte, numChunks+1)
|
||||||
f := applyPatch(curPath)
|
etags = make([]string, numChunks+1)
|
||||||
if f == nil {
|
var fullBuf bytes.Buffer
|
||||||
file, err := WWWPublic.Open(curPath)
|
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 {
|
||||||
|
file, err := WWWPublic.Open(curPath)
|
||||||
|
if err != nil {
|
||||||
|
Log.Warning("static::bundler failed to find file %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f = new(bytes.Buffer)
|
||||||
|
if _, err := io.Copy(f, file); err != nil {
|
||||||
|
Log.Warning("static::bundler msg=copy_error err=%s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
code, err := json.Marshal(f.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Log.Warning("static::bundler failed to find file %s", err.Error())
|
Log.Warning("static::bundle msg=marshal_failed path=%s err=%s", path, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f = new(bytes.Buffer)
|
line := fmt.Sprintf("bundler.register(%q, %s);\n", WithBase(path), code)
|
||||||
if _, err := io.Copy(f, file); err != nil {
|
chunkBuf.WriteString(line)
|
||||||
Log.Warning("static::bundler msg=copy_error err=%s", err.Error())
|
fullBuf.WriteString(line)
|
||||||
continue
|
|
||||||
}
|
|
||||||
file.Close()
|
|
||||||
}
|
}
|
||||||
code, err := json.Marshal(f.String())
|
chunks[i+1] = chunkBuf.Bytes()
|
||||||
if err != nil {
|
chunksBr[i+1], _ = cbrotli.Encode(chunks[i+1], cbrotli.WriterOptions{Quality: quality})
|
||||||
Log.Warning("static::bundle msg=marshal_failed path=%s err=%s", path, err.Error())
|
etags[i+1] = QuickHash(string(chunks[i+1]), 10)
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "bundler.register(%q, %s);\n", WithBase(path), code)
|
|
||||||
}
|
}
|
||||||
etag = QuickHash(string(bundlePlain), 10)
|
chunks[0] = fullBuf.Bytes()
|
||||||
bundlePlain = buf.Bytes()
|
chunksBr[0], _ = cbrotli.Encode(chunks[0], cbrotli.WriterOptions{Quality: quality})
|
||||||
if quality > 0 {
|
etags[0] = QuickHash(string(chunks[0]), 10)
|
||||||
bundleBr, _ = cbrotli.Encode(bundlePlain, cbrotli.WriterOptions{Quality: quality})
|
return chunks, chunksBr, etags
|
||||||
}
|
|
||||||
return bundlePlain, bundleBr, etag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quality := 11
|
quality := 11
|
||||||
if isDebug {
|
if isDebug {
|
||||||
quality = 8
|
quality = 8
|
||||||
}
|
}
|
||||||
bundlePlain, bundleBr, etag := build(quality)
|
chunks, chunksBr, etags := buildChunks(quality)
|
||||||
|
|
||||||
return func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
return func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||||
if isDebug {
|
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 := res.Header()
|
||||||
head.Set("Content-Type", "application/javascript")
|
head.Set("Content-Type", "application/javascript")
|
||||||
head.Set("Cache-Control", "no-cache")
|
head.Set("Cache-Control", "no-cache")
|
||||||
head.Set("Etag", etag)
|
head.Set("Etag", etags[chunkIndex])
|
||||||
if req.Header.Get("If-None-Match") == etag && etag != "" {
|
if req.Header.Get("If-None-Match") == etags[chunkIndex] && etags[chunkIndex] != "" {
|
||||||
res.WriteHeader(http.StatusNotModified)
|
res.WriteHeader(http.StatusNotModified)
|
||||||
return
|
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")
|
head.Set("Content-Encoding", "br")
|
||||||
res.Write(bundleBr)
|
res.Write(chunksBr[chunkIndex])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.Write(bundlePlain)
|
res.Write(chunks[chunkIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue