diff --git a/.gitignore b/.gitignore index 0e82613dfd..38453a6532 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ dex/testing/loadbot/loadbot bin/ bin-v*/ client/webserver/site/template-builder/template-builder +client/webserver/site/webpack-build-id.txt dex/testing/btc/harnesschain.tar.gz client/asset/btc/electrum/example/server/server client/asset/btc/electrum/example/wallet/wallet diff --git a/client/webserver/site/src/html/bodybuilder.tmpl b/client/webserver/site/src/html/bodybuilder.tmpl index ac9cad80ff..8fa851e01a 100644 --- a/client/webserver/site/src/html/bodybuilder.tmpl +++ b/client/webserver/site/src/html/bodybuilder.tmpl @@ -9,7 +9,7 @@ {{.Title}} - + - + {{end}} diff --git a/client/webserver/site/src/js/app.ts b/client/webserver/site/src/js/app.ts index 61c4140b0b..642af9a56d 100644 --- a/client/webserver/site/src/js/app.ts +++ b/client/webserver/site/src/js/app.ts @@ -161,6 +161,7 @@ export default class Application { authed: boolean user: User seedGenTime: number + webpackBuildID: string showPopups: boolean loggers: Record recorders: Record @@ -184,13 +185,14 @@ export default class Application { this.notes = [] this.pokes = [] this.seedGenTime = 0 + this.webpackBuildID = process.env.WEBPACK_BUILD_ID || '' this.noteReceivers = [] this.fiatRatesMap = {} this.showPopups = State.fetchLocal(State.popupsLK) === '1' this.txHistoryMap = {} this.requiredActions = {} - console.log('Bison Wallet') + console.log('Bison Wallet', 'Webpack Build Id:', this.webpackBuildID) // Set Bootstrap dark theme attribute if dark mode is enabled. if (State.isDark()) { @@ -259,7 +261,7 @@ export default class Application { // Don't fetch the user until we know what page we're on. await this.fetchUser() const ignoreCachedLocale = process.env.NODE_ENV === 'development' - await intl.loadLocale(this.lang, ignoreCachedLocale) + await intl.loadLocale(this.lang, this.webpackBuildID, ignoreCachedLocale) // The application is free to respond with a page that differs from the // one requested in the omnibox, e.g. routing though a login page. Set the // current URL state based on the actual page. diff --git a/client/webserver/site/src/js/locales.ts b/client/webserver/site/src/js/locales.ts index 561787eec1..47f3abf4fd 100644 --- a/client/webserver/site/src/js/locales.ts +++ b/client/webserver/site/src/js/locales.ts @@ -222,16 +222,16 @@ export const ID_DELETE_BOT = 'DELETE_BOT' let locale: Locale -export async function loadLocale (lang: string, skipCache: boolean) { +export async function loadLocale (lang: string, buildID: string, skipCache: boolean) { if (!skipCache) { const specs = State.fetchLocal(State.localeSpecsKey) - if (specs && specs.lang === lang) { + if (specs && specs.lang === lang && specs.webpackBuildID === buildID) { // not stale locale = State.fetchLocal(State.localeKey) return } } locale = await postJSON('/api/locale', lang) - State.storeLocal(State.localeSpecsKey, { lang }) + State.storeLocal(State.localeSpecsKey, { lang, buildID }) State.storeLocal(State.localeKey, locale) } diff --git a/client/webserver/site/src/js/registry.ts b/client/webserver/site/src/js/registry.ts index d4e9c5fcd9..bc4bcaa680 100644 --- a/client/webserver/site/src/js/registry.ts +++ b/client/webserver/site/src/js/registry.ts @@ -1275,6 +1275,7 @@ export interface Application { exchanges: Record fiatRatesMap: Record showPopups: boolean + webpackBuildID: string authed: boolean start (): Promise reconnected (): void diff --git a/client/webserver/site/webpack/analyze.js b/client/webserver/site/webpack/analyze.js index a87cdb414e..185e1d3251 100644 --- a/client/webserver/site/webpack/analyze.js +++ b/client/webserver/site/webpack/analyze.js @@ -8,7 +8,7 @@ module.exports = merge(common, { rules: [{ test: /\.ts$/, use: 'ts-loader', - exclude: /node_modules/, + exclude: /node_modules/ }] }, optimization: { diff --git a/client/webserver/site/webpack/common.js b/client/webserver/site/webpack/common.js index c279913ac8..608a477e20 100644 --- a/client/webserver/site/webpack/common.js +++ b/client/webserver/site/webpack/common.js @@ -5,10 +5,24 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin') const StyleLintPlugin = require('stylelint-webpack-plugin') const ESLintPlugin = require('eslint-webpack-plugin') -const child_process = require('child_process') +const fs = require('node:fs') +const buildIdFilename = 'webpack-build-id.txt' + +function randBuildId () { + const buildID = JSON.stringify(Math.floor(Math.random() * 1000000000)).trim() + console.log('WEBPACK_BUILD_ID:', buildID) + fs.writeFile(buildIdFilename, buildID, err => { + if (err) { + console.error(err) + } else { + console.log(' ', buildID, ' written to ', buildIdFilename) + } + }) + return buildID +} module.exports = { - target: "web", + target: 'web', module: { rules: [ { @@ -26,7 +40,7 @@ module.exports = { { loader: 'sass-loader', options: { - implementation: require("sass"), // dart-sass + implementation: require('sass'), // dart-sass sourceMap: true } } @@ -35,12 +49,15 @@ module.exports = { ] }, plugins: [ + new webpack.DefinePlugin({ + 'process.env.WEBPACK_BUILD_ID': randBuildId() + }), new CleanWebpackPlugin(), new MiniCssExtractPlugin({ filename: '../dist/style.css' }), new StyleLintPlugin({ - threads: true, + threads: true }), new ESLintPlugin({ extensions: ['ts'], @@ -53,7 +70,7 @@ module.exports = { publicPath: '/dist/' }, resolve: { - extensions: ['.ts', ".js"], + extensions: ['.ts', '.js'] }, // Fixes weird issue with watch script. See // https://github.com/webpack/webpack/issues/2297#issuecomment-289291324 diff --git a/client/webserver/site/webpack/dev.js b/client/webserver/site/webpack/dev.js index dbfa29db19..d4fdf226a6 100644 --- a/client/webserver/site/webpack/dev.js +++ b/client/webserver/site/webpack/dev.js @@ -7,7 +7,7 @@ module.exports = merge(common, { rules: [{ test: /\.ts$/, use: 'ts-loader', - exclude: /node_modules/, + exclude: /node_modules/ }] }, devtool: 'inline-source-map' diff --git a/client/webserver/site/webpack/prod.js b/client/webserver/site/webpack/prod.js index e64ddd5137..2e42708116 100644 --- a/client/webserver/site/webpack/prod.js +++ b/client/webserver/site/webpack/prod.js @@ -9,7 +9,7 @@ module.exports = merge(common, { usedExports: true, minimize: true, minimizer: [ - `...`, // extend webpack 5's TerserPlugin + '...', // extend webpack 5's TerserPlugin new CssMinimizerPlugin({}) ] }, diff --git a/client/webserver/template.go b/client/webserver/template.go index bd3eb7b50e..c7d12b3f38 100644 --- a/client/webserver/template.go +++ b/client/webserver/template.go @@ -9,6 +9,7 @@ import ( "html/template" "io/fs" "os" + "path/filepath" "strings" "decred.org/dcrdex/client/intl" @@ -17,6 +18,8 @@ import ( "golang.org/x/text/language" ) +const webpackBuildIdFile = "webpack-build-id.txt" + // pageTemplate holds the information necessary to process a template. Also // holds information necessary to reload the templates for development. type pageTemplate struct { @@ -184,6 +187,37 @@ func (t *templates) exec(name string, data any) (string, error) { return page.String(), err } +// webpackBuildIdQuery fetches latest webpack build from the webpack-build-id.txt +// file in app site directory and makes it available to append to the main css +// link and to the main script link in bodybuilder; this should cause no reload +// of the main css/js files if they are already cached by the browser. +// If webpackBuildIdFile is not found return a fallback query that will make the +// browser reload css/js. +var webpackBuildIdQuery = func() string { + var fallbackQueryStr = "?v=1" + d, _ := os.Getwd() + cwd := filepath.Base(d) + if cwd != "bisonw" { + return fallbackQueryStr + } + d = filepath.Dir(d) + cmd := filepath.Base(d) + if cmd != "cmd" { + return fallbackQueryStr + } + d = filepath.Dir(d) + client := filepath.Base(d) + if client != "client" { + return fallbackQueryStr + } + f := filepath.Join(d, "/webserver/site", webpackBuildIdFile) + wpB, err := os.ReadFile(f) + if err != nil { + return fallbackQueryStr + } + return "?v=" + string(wpB) +}() + // templateFuncs are able to be called during template execution. var templateFuncs = template.FuncMap{ "toUpper": strings.ToUpper, @@ -212,4 +246,7 @@ var templateFuncs = template.FuncMap{ } return parts[0] }, + "webpackBuildIdQuery": func() string { + return webpackBuildIdQuery + }, }