Build JS with Hugo instead of Webpack
We are currently building website with Webpack 4, which is very outdated and requires Node 14, which is ancient. Migration to Webpack 5 is somewhere between non-trivial and hard. This problem has thwarted efforts to upgrade to newer versions of Node for quite a few years now (cf. #986, #1076, #1147, #1205).
Meanwhile, we have had plans to switch away from Webpack for even longer. In #701, @zner0L attempted to switch to Hugo's js.Build based on esbuild. That would mainly have two advantages: The configuration is much easier and more ergonomic than Webpack. And builds are much faster.
The #701 PR is now very outdated and has even been created before our major refactoring and TS conversion in 2022. As such, I don't think it makes sense to build on that PR. It will be much easier to start anew and copy still relevant bits from the PR.
So, I guess that's what I'm going to attempt now. :D
I'll start by documenting what (non-obvious things) Webpack currently does for us and thus what we'll need to replace/decide to live without:
- [x] Chunk splitting (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/webpack.common.js#L36-L59)
- [x] Adding a banner to emitted bundle files (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/webpack.common.js#L104-L120)
- [x] Setting
window.CODE_VERSIONto the version frompackage.jsonfor the error handler (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/webpack.common.js#L122-L125)- Though this has been
1.0.0since day one, so it currently isn't exactly useful. A commit hash would be, though.
- Though this has been
- [x] Doing random import resolving (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/webpack.common.js#L131-L132)
- This is a horrible anti-pattern and was a big mistake because we didn't really understand what it does back in the day. This has already been fixed in all files that have been migrated to TS. We definitely don't want to port this over.
- [x] Tooling for measuring the bundling speed (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/webpack.prod.js#L4-L8)
- We have occasionally used this in the past to optimize bundling speed. Might be less relevant in the future given how much faster esbuild is.
- [x] Source maps (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/webpack.prod.js#L12-L13)
- [x] Bundling a web worker (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/webpack.common.js#L69-L82)
- [x] Our custom translations loader that:
- [x] Backfills missing translations from English (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/scripts/webpack-i18n-loader.js#L8-L12)
- [x] Replaces macros in the translations (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/scripts/webpack-i18n-loader.js#L13-L21)
- [x] Extracts and prepares the translations for Hugo and saves them in the correct location (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/scripts/webpack-i18n-loader.js#L23-L54)
- [x] Removes translations from the bundles that aren't needed in the browser (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/scripts/webpack-i18n-loader.js#L55-L57)
- [x] Prepares the special requests translations files that contain all languages (https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/scripts/webpack-i18n-loader.js#L64-L82)
And, just to document it for future comparisons, here's how long yarn build currently takes and what it outputs:
❯ yarn build
yarn run v1.22.18
$ cross-env BABEL_ENV=production; webpack --mode=production --config webpack.prod.js
Browserslist: caniuse-lite is outdated. Please run:
npx browserslist@latest --update-db
Why you should do it regularly: https://github.com/browserslist/browserslist#browsers-data-updating
Browserslist: caniuse-lite is outdated. Please run:
npx browserslist@latest --update-db
Why you should do it regularly:
https://github.com/browserslist/browserslist#browsers-data-updating
The exported identifier "undefined" is not declared in Babel's scope tracker
as a JavaScript value binding, and "@babel/plugin-transform-typescript"
never encountered it as a TypeScript type declaration.
It will be treated as a JavaScript value.
This problem is likely caused by another plugin injecting
"undefined" without registering it in the scope tracker. If you are the author
of that plugin, please use "scope.registerDeclaration(declarationPath)".
Hash: 4a694ece95fd57248927
Version: webpack 4.44.1
Time: 25821ms
Built at: 10/24/2025 3:52:29 PM
Asset Size Chunks Chunk Names
../i18n/cs.json 19.8 KiB [emitted]
../i18n/de.json 17.1 KiB [emitted]
../i18n/en.json 15.5 KiB [emitted]
../i18n/es.json 20.7 KiB [emitted]
../i18n/fr.json 21.6 KiB [emitted]
../i18n/hr.json 19.2 KiB [emitted]
../i18n/nl.json 19.8 KiB [emitted]
../i18n/pt.json 20.6 KiB [emitted]
js/20.bundle.gen.js 54.9 KiB 20 [emitted]
js/20.bundle.gen.js.map 56.2 KiB 20 [emitted] [dev]
js/act-widget.bundle.gen.js 52.1 KiB 3 [emitted] act-widget
js/act-widget.bundle.gen.js.map 53.8 KiB 3 [emitted] [dev] act-widget
js/app.bundle.gen.js 288 KiB 4, 17 [emitted] [big] app
js/app.bundle.gen.js.map 294 KiB 4, 17 [emitted] [dev] app
js/bank-transfer-codes.bundle.gen.js 30.6 KiB 5 [emitted] bank-transfer-codes
js/bank-transfer-codes.bundle.gen.js.map 31.3 KiB 5 [emitted] [dev] bank-transfer-codes
js/commons.bundle.gen.js 1.35 MiB 0 [emitted] [big] commons
js/commons.bundle.gen.js.map 1.38 MiB 0 [emitted] [dev] commons
js/company-list.bundle.gen.js 15.4 KiB 6 [emitted] company-list
js/company-list.bundle.gen.js.map 16 KiB 6 [emitted] [dev] company-list
js/donation-widget.bundle.gen.js 9.93 KiB 7 [emitted] donation-widget
js/donation-widget.bundle.gen.js.map 10.7 KiB 7 [emitted] [dev] donation-widget
js/error-handler.bundle.gen.js 7.17 KiB 8 [emitted] error-handler
js/error-handler.bundle.gen.js.map 7.66 KiB 8 [emitted] [dev] error-handler
js/general.bundle.gen.js 17.9 KiB 9 [emitted] general
js/general.bundle.gen.js.map 18.7 KiB 9 [emitted] [dev] general
js/generator.bundle.gen.js 73.2 KiB 10 [emitted] generator
js/generator.bundle.gen.js.map 75.8 KiB 10 [emitted] [dev] generator
js/home.bundle.gen.js 3.68 KiB 11 [emitted] home
js/home.bundle.gen.js.map 4.03 KiB 11 [emitted] [dev] home
js/id-data-controls.bundle.gen.js 45.9 KiB 12 [emitted] id-data-controls
js/id-data-controls.bundle.gen.js.map 47.4 KiB 12 [emitted] [dev] id-data-controls
js/misconduct-reporter.bundle.gen.js 521 KiB 13 [emitted] [big] misconduct-reporter
js/misconduct-reporter.bundle.gen.js.map 526 KiB 13 [emitted] [dev] misconduct-reporter
js/my-requests.bundle.gen.js 16.4 KiB 14 [emitted] my-requests
js/my-requests.bundle.gen.js.map 17.3 KiB 14 [emitted] [dev] my-requests
js/pdf.worker.worker.gen.js 1.86 MiB [emitted] [big]
js/pdf.worker.worker.gen.js.map 1.89 MiB [emitted] [dev]
js/privacy-controls.bundle.gen.js 8.52 KiB 15 [emitted] privacy-controls
js/privacy-controls.bundle.gen.js.map 8.97 KiB 15 [emitted] [dev] privacy-controls
js/runtime.gen.js 3.18 KiB 1 [emitted] runtime
js/runtime.gen.js.map 3.42 KiB 1 [emitted] [dev] runtime
js/suggest-edit.bundle.gen.js 166 KiB 16 [emitted] suggest-edit
js/suggest-edit.bundle.gen.js.map 182 KiB 16 [emitted] [dev] suggest-edit
js/sva-finder.bundle.gen.js 13.5 KiB 17 [emitted] sva-finder
js/sva-finder.bundle.gen.js.map 14.4 KiB 17 [emitted] [dev] sva-finder
js/test-interface.bundle.gen.js 1.57 KiB 18 [emitted] test-interface
js/test-interface.bundle.gen.js.map 1.79 KiB 18 [emitted] [dev] test-interface
js/translations-cs.gen.js 57 KiB [emitted]
js/translations-de.gen.js 61.2 KiB [emitted]
js/translations-dummy.bundle.gen.js 475 KiB 19 [emitted] [big] translations-dummy
js/translations-dummy.bundle.gen.js.map 505 KiB 19 [emitted] [dev] translations-dummy
js/translations-en.gen.js 55.8 KiB [emitted]
js/translations-es.gen.js 60.8 KiB [emitted]
js/translations-fr.gen.js 65.3 KiB [emitted]
js/translations-hr.gen.js 56 KiB [emitted]
js/translations-nl.gen.js 57.3 KiB [emitted]
js/translations-pt.gen.js 59.6 KiB [emitted]
js/translations-requests.gen.js 13 KiB [emitted]
js/vendors.bundle.gen.js 87.8 KiB 2 [emitted] vendors
js/vendors.bundle.gen.js.map 89.9 KiB 2 [emitted] [dev] vendors
Entrypoint error-handler [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/error-handler.bundle.gen.js js/error-handler.bundle.gen.js.map
Entrypoint general [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/general.bundle.gen.js js/general.bundle.gen.js.map
Entrypoint home [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/home.bundle.gen.js js/home.bundle.gen.js.map
Entrypoint generator [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/vendors.bundle.gen.js js/vendors.bundle.gen.js.map js/generator.bundle.gen.js js/generator.bundle.gen.js.map
Entrypoint app [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/vendors.bundle.gen.js js/vendors.bundle.gen.js.map js/app.bundle.gen.js js/app.bundle.gen.js.map
Entrypoint company-list [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/vendors.bundle.gen.js js/vendors.bundle.gen.js.map js/company-list.bundle.gen.js js/company-list.bundle.gen.js.map
Entrypoint my-requests [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/my-requests.bundle.gen.js js/my-requests.bundle.gen.js.map
Entrypoint privacy-controls [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/privacy-controls.bundle.gen.js js/privacy-controls.bundle.gen.js.map
Entrypoint suggest-edit [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/vendors.bundle.gen.js js/vendors.bundle.gen.js.map js/suggest-edit.bundle.gen.js js/suggest-edit.bundle.gen.js.map
Entrypoint id-data-controls [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/vendors.bundle.gen.js js/vendors.bundle.gen.js.map js/id-data-controls.bundle.gen.js js/id-data-controls.bundle.gen.js.map
Entrypoint sva-finder [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/sva-finder.bundle.gen.js js/sva-finder.bundle.gen.js.map
Entrypoint act-widget [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/vendors.bundle.gen.js js/vendors.bundle.gen.js.map js/act-widget.bundle.gen.js js/act-widget.bundle.gen.js.map
Entrypoint donation-widget [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/donation-widget.bundle.gen.js js/donation-widget.bundle.gen.js.map
Entrypoint misconduct-reporter [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/misconduct-reporter.bundle.gen.js js/misconduct-reporter.bundle.gen.js.map
Entrypoint test-interface [big] = js/runtime.gen.js js/runtime.gen.js.map js/commons.bundle.gen.js js/commons.bundle.gen.js.map js/test-interface.bundle.gen.js js/test-interface.bundle.gen.js.map
Entrypoint translations-dummy [big] = js/runtime.gen.js js/runtime.gen.js.map js/translations-dummy.bundle.gen.js js/translations-dummy.bundle.gen.js.map
[91] ./src/Components/SvaFinder.tsx 10.9 KiB {4} {17} [built]
[171] ./src/error-handler.js 12.7 KiB {8} [built]
[173] ./src/general.tsx 15.7 KiB {9} [built]
[191] ./src/home.tsx 1.36 KiB {11} [built]
[192] ./src/generator.tsx 1.86 KiB {10} [built]
[243] ./src/app.tsx 692 bytes {4} [built]
[257] ./src/company-list.tsx 3.34 KiB {6} [built]
[262] ./src/my-requests.tsx 1.51 KiB {14} [built]
[263] ./src/privacy-controls.tsx 5.31 KiB {15} [built]
[264] ./src/suggest-edit.js 14.4 KiB {16} [built]
[266] ./src/id-data-controls.tsx 10.5 KiB {12} [built]
[267] ./src/Components/ActWidget.tsx 2.6 KiB {3} [built]
[268] ./src/Components/DonationWidget.tsx 14.7 KiB {7} [built]
[269] ./src/Components/MisconductReporter.tsx 4.94 KiB {13} [built]
[270] ./src/test-interface.tsx 1.02 KiB {18} [built]
+ 423 hidden modules
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
js/pdf.worker.worker.gen.js (1.86 MiB)
js/commons.bundle.gen.js (1.35 MiB)
js/app.bundle.gen.js (288 KiB)
js/misconduct-reporter.bundle.gen.js (521 KiB)
js/translations-dummy.bundle.gen.js (475 KiB)
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
error-handler (1.36 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/error-handler.bundle.gen.js
general (1.37 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/general.bundle.gen.js
home (1.36 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/home.bundle.gen.js
generator (1.51 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/vendors.bundle.gen.js
js/generator.bundle.gen.js
app (1.72 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/vendors.bundle.gen.js
js/app.bundle.gen.js
company-list (1.46 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/vendors.bundle.gen.js
js/company-list.bundle.gen.js
my-requests (1.37 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/my-requests.bundle.gen.js
privacy-controls (1.36 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/privacy-controls.bundle.gen.js
suggest-edit (1.6 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/vendors.bundle.gen.js
js/suggest-edit.bundle.gen.js
id-data-controls (1.48 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/vendors.bundle.gen.js
js/id-data-controls.bundle.gen.js
sva-finder (1.37 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/sva-finder.bundle.gen.js
act-widget (1.49 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/vendors.bundle.gen.js
js/act-widget.bundle.gen.js
donation-widget (1.36 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/donation-widget.bundle.gen.js
misconduct-reporter (1.86 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/misconduct-reporter.bundle.gen.js
test-interface (1.36 MiB)
js/runtime.gen.js
js/commons.bundle.gen.js
js/test-interface.bundle.gen.js
translations-dummy (478 KiB)
js/runtime.gen.js
js/translations-dummy.bundle.gen.js
Child worker-loader node_modules/babel-loader/lib/index.js!node_modules/babel-loader/lib/index.js!src/Utility/pdf.worker.ts:
Asset Size Chunks Chunk Names
js/pdf.worker.worker.gen.js 1.86 MiB 0 [emitted] [big] pdf.worker
js/pdf.worker.worker.gen.js.map 1.89 MiB 0 [emitted] [dev] pdf.worker
Entrypoint pdf.worker = js/pdf.worker.worker.gen.js js/pdf.worker.worker.gen.js.map
[0] ../letter-generator/dist/utility.js 2.24 KiB {0} [built]
[1] ../letter-generator/dist/index.js 1.96 KiB {0} [built]
[2] ./src/Utility/fonts.json 641 KiB {0} [built]
[3] ./node_modules/babel-loader/lib!./node_modules/babel-loader/lib!./src/Utility/pdf.worker.ts 772 bytes {0} [built]
[4] ../letter-generator/dist/Template.js 2.21 KiB {0} [built]
[5] ../letter-generator/dist/Letter.js 3.27 KiB {0} [built]
[6] ../letter-generator/dist/layouts/din-5008-a.js 1.83 KiB {0} [built]
[7] ../letter-generator/dist/PdfRenderer.js 1.04 KiB {0} [built]
+ 1 hidden module
Done in 26.66s.
Thank you for your investigations!
Just my 2 cents, feel free to ignore them:
-
The banner doesn't seem to be too useful.
-
I think we can survive without speed measurements and code version. We do not deploy different code versions on prod anyways :D
-
the translation loader feels like something that could be ported to
deploy.shpipeline. :D Maybe except the backfil.
- the translation loader feels like something that could be ported to
deploy.shpipeline. :D Maybe except the backfil.
Unfortunately, for development we do also need a watch mode for the translations. Having to run deploy.sh every time you change a translation would be very annoying.
I have opened a PR with my current state: https://github.com/datenanfragen/website/pull/1212
So far, I have already migrated over all the JS bundles. I was able to borrow a lot from #701. But a bunch of stuff has gotten easier since then:
- We don't need to move all code to
assetsanymore. We can now just mount thesrcdirectory there. - Thanks to https://github.com/gohugoio/hugo/pull/12119, we don't need to import the JSX stuff in every file anymore.
For some reason, changes made using cy.proceedingsStore() aren't immediately reflected on the website anymore, only after a reload.
I don't really have an idea on why that might be or how to fix it right now, so I'll add reloads to the tests for now, but we might want to investigate this in the future.
Turns out that there is also a timing component. If you reload immediately after adding a proceeding, sometimes it won't be there after the reload. sigh
The reason appears to be our previous chunking/code splitting:
https://github.com/datenanfragen/website/blob/d67464b029ad1dd0234876fcd13349515a7b2e06/webpack.common.js#L34-L60
Due to that, zustand itself was loaded once through vendors.bundle.gen.js and the store was created once through commons.bundle.gen.js. Both the page-specific bundle (e.g. my-requests.bundle.gen.js) and test-interface.bundle.gen.js then used those.
Now, there is no code sharing between the bundles anymore and both the page-specific bundle (e.g. my-requests.bundle.gen.js) and test-interface.bundle.gen.js include their own copy of zustand and create their own instance of the store.
Figuring out how to do code splitting using Hugo's new-ish js.Batch wasn't exactly intuitive (the docs could use some love), but I have got it working now and even better and more granular than it was in Webpack. One major change: esbuild only supports code splitting in ESM, so that's what we're using now. Personally, I like that and ESM support in browsers is older than our project.
Implementing this did indeed fix the problem with the test interface as expected. At least locally, I also only get one test failure now and that looks like Cypress flake rather than an actual problem.
Great. Now, apparently hugo server isn't working in CI anymore.
The problem seems to be that Hugo opens the port immediately after hugo server is started but before the site is built and actually ready.
In this PR, I've upgraded to Hugo 0.140.0, so I assumed that the change may have occurred in that release. From my testing, this behaviour didn't happen in Hugo 0.88.1, which we were using until recently. However, curiously, it does also happen in Hugo 0.139.0, which we adopted in #1202.
https://github.com/user-attachments/assets/d4c50267-4a3e-4a4b-9d75-f3cbb7ae548c
That would imply that we should have run into this problem earlier, but we haven't. I don't know why. My guess is that before, Cypress' timeout happened to just be long enough for Hugo to finish building. But since Hugo now also builds the JS, the build takes longer and might exceed the timeout?
yarn wait-on "http://localhost:1314/generator/" instead of yarn wait-on tcp:1314 should solve that.
Next problem: We intermittently get this error when building:
ERROR render of "/root/project/content/de/blog/gdpr-territorial-scope/index.md" failed: "/root/project/layouts/_default/baseof.html:106:7": execute of template failed: template: _default/single.html:106:7: executing "_default/single.html" at <partial "scripts" .>: error calling partial: "/root/project/layouts/partials/scripts.html:163:14": execute of template failed: template: partials/scripts.html:163:14: executing "partials/scripts.html" at <$batch.Build>: error calling Build: interface conversion: interface is nil, not resources.targetPathProvider
ERROR render of "/root/project/content/de/act/honey/index.md" failed: "/root/project/layouts/_default/baseof.html:106:7": execute of template failed: template: _default/single.html:106:7: executing "_default/single.html" at <partial "scripts" .>: error calling partial: "/root/project/layouts/partials/scripts.html:163:14": execute of template failed: template: partials/scripts.html:163:14: executing "partials/scripts.html" at <$batch.Build>: error calling Build: interface conversion: interface is nil, not resources.targetPathProvider
ERROR render of "/root/project/content/de/act/deutsche-wohnen/index.md" failed: "/root/project/layouts/_default/baseof.html:106:7": execute of template failed: template: _default/single.html:106:7: executing "_default/single.html" at <partial "scripts" .>: error calling partial: "/root/project/layouts/partials/scripts.html:163:14": execute of template failed: template: partials/scripts.html:163:14: executing "partials/scripts.html" at <$batch.Build>: error calling Build: interface conversion: interface is nil, not resources.targetPathProvider
ERROR render of "/root/project/content/de/blog/datenloeschung-bei-werbetreibenden/index.md" failed: "/root/project/layouts/_default/baseof.html:106:7": execute of template failed: template: _default/single.html:106:7: executing "_default/single.html" at <partial "scripts" .>: error calling partial: "/root/project/layouts/partials/scripts.html:163:14": execute of template failed: template: partials/scripts.html:163:14: executing "partials/scripts.html" at <$batch.Build>: error calling Build: interface conversion: interface is nil, not resources.targetPathProvider
Built in 19593 ms
Error: error building site: render: failed to render pages: render of "/root/project/content/de/blog/v2-release/index.md" failed: "/root/project/layouts/_default/baseof.html:106:7": execute of template failed: template: _default/single.html:106:7: executing "_default/single.html" at <partial "scripts" .>: error calling partial: "/root/project/layouts/partials/scripts.html:163:14": execute of template failed: template: partials/scripts.html:163:14: executing "partials/scripts.html" at <$batch.Build>: error calling Build: interface conversion: interface is nil, not resources.targetPathProvider
That seems like a Hugo bug to me (especially since it is only intermittent and the code usually works fine). The obvious test would be to upgrade to a Hugo release that is less than a year old but as @mal-tee tested in #1202, we have a bit of a circular dependency here. :|
Because of that, I guess for now I'll continue working on the missing elements and only test locally and ignore the CI failures.
Once we don't need Webpack anymore, upgrading Node, CI images, and Hugo should be a lot easier and we can tackle this problem then.
Given how much has happened in Hugo since we last tried (especially Hugo mounts), I was sort of hopeful that we might be able to have Hugo handle our i18n-loader after all. However, alas, as far as I can tell, it still isn't possible, so I wrote a small building and watching script.
One improvement, though: We don't need to do the "wrapping in { "other": "<translation>" } dance anymore for Hugo.
With that, we are pretty much done. Here's what's left:
Adding a banner to emitted bundle files
Esbuild does support this feature but as far as no way in Hugo to pass the option to esbuild. If we wanted this, we'd need to raise a PR in Hugo. But I agree with @mal-tee that there really is no reason to have this. Iirc, way back when, I was just going through all the Webpack options and thought it was funny to have this. :D
Setting window.CODE_VERSION to the version from package.json for the error handler
As I said, in its current form, this is entirely useless, so I have just removed it altogether for the time being.
If we wanted to make this useful, Hugo can extract current commit hash (or other Git info). However, I'm wary of doing something like this as I would assume that it would increase build times quite drastically.
Tooling for measuring the bundling speed
I agree with @mal-tee that we don't really need this. I haven't seen an option to implement this, either.