diff --git a/Makefile b/Makefile
index 77aac782e113..5da7ce7547cf 100644
--- a/Makefile
+++ b/Makefile
@@ -104,8 +104,8 @@ FOMANTIC_WORK_DIR := web_src/fomantic
WEBPACK_SOURCES := $(shell find web_src/js web_src/less -type f)
WEBPACK_CONFIGS := webpack.config.js
-WEBPACK_DEST := public/js/index.js public/css/index.css
-WEBPACK_DEST_ENTRIES := public/js public/css public/fonts public/img/webpack public/serviceworker.js
+WEBPACK_DEST := public/js
+WEBPACK_DEST_ENTRIES := public/js public/css public/fonts public/img/webpack $(wildcard public/serviceworker.*.js) public/assets-manifest.json
BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
diff --git a/modules/public/manifest.go b/modules/public/manifest.go
new file mode 100644
index 000000000000..7cc759256a80
--- /dev/null
+++ b/modules/public/manifest.go
@@ -0,0 +1,95 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build !bindata
+// +build !bindata
+
+package public
+
+import (
+ "io"
+ "io/fs"
+ "net/http"
+ "sync"
+ "time"
+
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+var (
+ manifest map[string]string
+ manifestName = "assets-manifest.json"
+ manifestMutex = &sync.Mutex{}
+ manifestModified time.Time
+)
+
+func readManifestFile(fs http.FileSystem) (http.File, fs.FileInfo, error) {
+ f, err := fs.Open(manifestName)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return f, fi, nil
+}
+
+func readManifest(fs http.FileSystem) map[string]string {
+ manifestMutex.Lock()
+ var assetMap map[string]string
+
+ f, fi, err := readManifestFile(fs)
+ if err != nil {
+ log.Error("[Static] Failed to open %q: %v", manifestName, err)
+ return assetMap
+ }
+ defer f.Close()
+
+ bytes, err := io.ReadAll(f)
+ if err != nil {
+ log.Error("[Static] Failed to read %q: %v", manifestName, err)
+ return assetMap
+ }
+
+ err = json.Unmarshal(bytes, &assetMap)
+ if err != nil {
+ log.Error("[Static] Failed to parse %q: %v", manifestName, err)
+ return assetMap
+ }
+
+ manifestModified = fi.ModTime()
+ manifestMutex.Unlock()
+ return assetMap
+}
+
+// ResolveWithManifest turns /js/index.js into /js/index.5ed90373e37c.js using assets-manifest.json
+func ResolveWithManifest(fs http.FileSystem, file string) string {
+ if len(manifest) == 0 {
+ manifest = readManifest(fs)
+ }
+
+ // in development, the manifest can frequently change, check and reload if necessary
+ if !setting.IsProd {
+ f, fi, err := readManifestFile(fs)
+ if err != nil {
+ log.Error("[Static] Failed to open %q: %v", manifestName, err)
+ } else {
+ defer f.Close()
+ }
+
+ if fi.ModTime().After(manifestModified) {
+ manifest = readManifest(fs)
+ }
+ }
+
+ if mappedFile, ok := manifest[file]; ok {
+ return mappedFile
+ }
+ return file
+}
diff --git a/modules/public/manifest_bindata.go b/modules/public/manifest_bindata.go
new file mode 100644
index 000000000000..9f915ea9d966
--- /dev/null
+++ b/modules/public/manifest_bindata.go
@@ -0,0 +1,8 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build bindata
+// +build bindata
+
+package public
diff --git a/modules/public/public.go b/modules/public/public.go
index 7804e945e798..70900673c848 100644
--- a/modules/public/public.go
+++ b/modules/public/public.go
@@ -69,12 +69,12 @@ func AssetsHandlerFunc(opts *Options) http.HandlerFunc {
}
// custom files
- if opts.handle(resp, req, http.Dir(custPath), file) {
+ if opts.handle(resp, req, http.Dir(custPath), file, false) {
return
}
// internal files
- if opts.handle(resp, req, fileSystem(opts.Directory), file) {
+ if opts.handle(resp, req, fileSystem(opts.Directory), file, true) {
return
}
@@ -101,9 +101,14 @@ func setWellKnownContentType(w http.ResponseWriter, file string) {
}
}
-func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
+func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string, isInternal bool) bool {
+ // respect webpack assets manifest for internal files
+ if isInternal {
+ file = ResolveWithManifest(fs, path.Clean(file))
+ }
+
// use clean to keep the file is a valid path with no . or ..
- f, err := fs.Open(path.Clean(file))
+ f, err := fs.Open(file)
if err != nil {
if os.IsNotExist(err) {
return false
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 8a15cec2c68a..6e7d03e74f78 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -150,7 +150,6 @@ func NewFuncMap() []template.FuncMap {
"DiffTypeToStr": DiffTypeToStr,
"DiffLineTypeToStr": DiffLineTypeToStr,
"ShortSha": base.ShortSha,
- "MD5": base.EncodeMD5,
"ActionContent2Commits": ActionContent2Commits,
"PathEscape": url.PathEscape,
"PathEscapeSegments": util.PathEscapeSegments,
diff --git a/package-lock.json b/package-lock.json
index 945193538591..8e67363310a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -38,6 +38,7 @@
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.74.0",
+ "webpack-assets-manifest": "5.1.0",
"webpack-cli": "4.10.0",
"workbox-routing": "6.5.4",
"workbox-strategies": "6.5.4",
@@ -3354,7 +3355,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -4559,7 +4559,6 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9088,6 +9087,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lockfile": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz",
+ "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==",
+ "dependencies": {
+ "signal-exit": "^3.0.2"
+ }
+ },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -9101,8 +9108,12 @@
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
- "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
- "dev": true
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
+ },
+ "node_modules/lodash.has": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
+ "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -11002,8 +11013,7 @@
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/simple-eval": {
"version": "1.0.0",
@@ -11489,7 +11499,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -12362,6 +12371,71 @@
}
}
},
+ "node_modules/webpack-assets-manifest": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/webpack-assets-manifest/-/webpack-assets-manifest-5.1.0.tgz",
+ "integrity": "sha512-kPuTMEjBrqZQVJ5M6yXNBCEdFbQQn7p+loNXt8NOeDFaAbsNFWqqwR0YL1mfG5LbwhK5FLXWXpuK3GuIIZ46rg==",
+ "dependencies": {
+ "chalk": "^4.0",
+ "deepmerge": "^4.0",
+ "lockfile": "^1.0",
+ "lodash.get": "^4.0",
+ "lodash.has": "^4.0",
+ "schema-utils": "^3.0",
+ "tapable": "^2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "peerDependencies": {
+ "webpack": "^5.2.0"
+ }
+ },
+ "node_modules/webpack-assets-manifest/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/webpack-assets-manifest/node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/webpack-assets-manifest/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "node_modules/webpack-assets-manifest/node_modules/schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/webpack-cli": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
@@ -15525,7 +15599,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -16501,8 +16574,7 @@
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
- "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
- "dev": true
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"define-properties": {
"version": "1.1.4",
@@ -19907,6 +19979,14 @@
"p-locate": "^5.0.0"
}
},
+ "lockfile": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz",
+ "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==",
+ "requires": {
+ "signal-exit": "^3.0.2"
+ }
+ },
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -19920,8 +20000,12 @@
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
- "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
- "dev": true
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
+ },
+ "lodash.has": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
+ "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g=="
},
"lodash.merge": {
"version": "4.6.2",
@@ -21309,8 +21393,7 @@
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"simple-eval": {
"version": "1.0.0",
@@ -21709,7 +21792,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -22433,6 +22515,54 @@
}
}
},
+ "webpack-assets-manifest": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/webpack-assets-manifest/-/webpack-assets-manifest-5.1.0.tgz",
+ "integrity": "sha512-kPuTMEjBrqZQVJ5M6yXNBCEdFbQQn7p+loNXt8NOeDFaAbsNFWqqwR0YL1mfG5LbwhK5FLXWXpuK3GuIIZ46rg==",
+ "requires": {
+ "chalk": "^4.0",
+ "deepmerge": "^4.0",
+ "lockfile": "^1.0",
+ "lodash.get": "^4.0",
+ "lodash.has": "^4.0",
+ "schema-utils": "^3.0",
+ "tapable": "^2.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "requires": {}
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "requires": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ }
+ }
+ }
+ },
"webpack-cli": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
diff --git a/package.json b/package.json
index a3e13eb609bb..ee8ea4a0b415 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.74.0",
+ "webpack-assets-manifest": "5.1.0",
"webpack-cli": "4.10.0",
"workbox-routing": "6.5.4",
"workbox-strategies": "6.5.4",
diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl
index 9bf16f8aa5b5..43fe1c4407d6 100644
--- a/templates/base/footer.tmpl
+++ b/templates/base/footer.tmpl
@@ -22,7 +22,7 @@
{{end}}
{{end}}
-
+
{{template "custom/footer" .}}