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" .}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index e0d2b26f2cdb..2772b4ccce9b 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -21,7 +21,7 @@ {{end}} - + {{template "base/head_script" .}}