Build webapp Tailwind via Vite instead of the in-browser Play CDN - #40
Conversation
Introduces an npm + Vite + Vitest frontend toolchain in transitclockWebapp, wired into Maven via frontend-maven-plugin so mvn package downloads a pinned Node, runs npm ci, and builds the Tailwind bundle automatically. Output lands in target/frontend-dist/ and is folded into the WAR at /dist by maven-war-plugin's webResources. includes.jsp now links the bundled stylesheet instead of loading @tailwindcss/browser at every page load. A new JspStylesheetAuditTest pins the migration: it asserts no JSP or tag still references the Play CDN and that includes.jsp links the bundled CSS. Modeled on JspTaglibAuditTest, same silent-failure shape (page renders 200, classes silently don't apply). Node is installed under ~/.m2/node so mvn clean doesn't redownload the ~30 MB tarball every full rebuild. The dev-compose override gains a third bind mount for target/frontend-dist so npm run dev output reaches Tomcat without a docker rebuild. Out of scope: vendoring jQuery / Leaflet / Stimulus / Select2 via npm (still on CDNs); converting the existing 38 KLoC of classic JS to ES modules; manifest-driven hashed-filename JSP wiring; Vite dev server with HMR. All deferred to follow-up PRs that can adopt this same toolchain incrementally.
📝 WalkthroughWalkthroughThis PR introduces a Vite-based frontend build system with Tailwind CSS v4 integration for the transitclockWebapp. It includes Maven plugin configuration to automate frontend dependency installation and builds, updates Docker setup for development iteration, adds frontend utilities and tests, and switches CSS delivery from CDN to precompiled Vite output. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
transitclockWebapp/vite.config.js (1)
8-13: Add Node version constraint or use compatibility-safe dirname.The code uses
import.meta.dirname(lines 8, 12) which requires Node v18.17.0+. Thepackage.jsonlacks an"engines.node"declaration, so developers runningnpm run devdirectly with older Node versions will fail. Either add"engines": { "node": ">=18.17.0" }topackage.jsonor adopt the compatibility-safe pattern usingfileURLToPathandpath.dirname.🔧 Compatibility-safe alternative
import { defineConfig } from 'vite'; import tailwindcss from '@tailwindcss/vite'; import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ plugins: [tailwindcss()], build: { - outDir: path.resolve(import.meta.dirname, 'target/frontend-dist'), + outDir: path.resolve(__dirname, 'target/frontend-dist'), emptyOutDir: true, rollupOptions: { input: { - tailwind: path.resolve(import.meta.dirname, 'frontend/tailwind.css'), + tailwind: path.resolve(__dirname, 'frontend/tailwind.css'), },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@transitclockWebapp/vite.config.js` around lines 8 - 13, The project uses import.meta.dirname in vite.config.js (used for outDir and rollupOptions.input tailwind paths) which requires Node >=18.17.0; either add an engines.node entry to package.json (e.g., "engines": { "node": ">=18.17.0" }) or make the config Node-version-safe by deriving a dirname: import { fileURLToPath } from 'url' and const __dirname = path.dirname(fileURLToPath(import.meta.url)), then replace import.meta.dirname usages with __dirname so outDir and rollupOptions.input (tailwind) resolve correctly on older Node versions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@transitclockWebapp/vite.config.js`:
- Around line 8-13: The project uses import.meta.dirname in vite.config.js (used
for outDir and rollupOptions.input tailwind paths) which requires Node
>=18.17.0; either add an engines.node entry to package.json (e.g., "engines": {
"node": ">=18.17.0" }) or make the config Node-version-safe by deriving a
dirname: import { fileURLToPath } from 'url' and const __dirname =
path.dirname(fileURLToPath(import.meta.url)), then replace import.meta.dirname
usages with __dirname so outDir and rollupOptions.input (tailwind) resolve
correctly on older Node versions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e4003d56-89db-4638-b8f1-405fb25f73fa
⛔ Files ignored due to path filters (1)
transitclockWebapp/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (12)
.dockerignore.gitignoreCLAUDE.mddocker-compose.dev.ymltransitclockWebapp/frontend/tailwind.csstransitclockWebapp/frontend/utils.jstransitclockWebapp/frontend/utils.test.jstransitclockWebapp/package.jsontransitclockWebapp/pom.xmltransitclockWebapp/src/main/webapp/template/includes.jsptransitclockWebapp/src/test/java/org/transitclock/webapp/jsp/JspStylesheetAuditTest.javatransitclockWebapp/vite.config.js
Summary
transitclockWebappviafrontend-maven-plugin.mvn packagenow downloads a pinned Node, runsnpm ci, and builds Tailwind intotarget/frontend-dist/;maven-war-plugin's<webResources>folds that into the WAR at/dist. Single command, no extra build steps for Docker or CI.@tailwindcss/browser@4) —includes.jspnow links the Vite-built/dist/tailwind.cssinstead of compiling utility classes in-browser at every page load. Play CDN is explicitly not for production.JspStylesheetAuditTest, modeled on the existingJspTaglibAuditTest, as a regression guard. It asserts no JSP or tag references the Play CDN and thatincludes.jsplinks the bundled stylesheet — same silent-failure shape (page returns 200, classes don't apply) the taglib test was written for.Out of scope
Explicitly deferred to follow-up PRs that can adopt this toolchain incrementally:
manifest.jsonnot yet emitted;assetFileNames: '[name][extname]'keeps the stylesheet at a literal path the JSP can hardcode).npm run dev(watch mode) alongside the existingdocker-compose.dev.ymlbind-mount.Notable choices
${user.home}/.m2/noderather thantarget/nodesomvn cleandoesn't redownload the ~30 MB tarball every full rebuild.frontend/utils.js(a tinyclassNameshelper) plus its Vitest test is intentional scaffolding so the test runner exercises a real module — gets replaced when the first real frontend module lands.@sourcedirectives infrontend/tailwind.cssscan*.jsp,*.tag,*.html, and*.jsunder the webapp; the*.tagglob is required becauseWEB-INF/tags/layout.tag(page chrome) uses utility classes.Test plan
mvn -pl transitclockWebapp -am clean package -DskipTestsproducestarget/web.warcontainingdist/tailwind.css(verified viaunzip -l).mvn -pl transitclockWebapp -am testrunsJspStylesheetAuditTest(2 tests),JspTaglibAuditTest(2 tests), and Vitest (2 tests). All pass.cd transitclockWebapp && npm ci && npm run build && npm testworks standalone.docker compose build tomcat && docker compose up -d tomcat(no.devoverride) — Tomcat unpacksweb.warand serves/web/dist/tailwind.css(HTTP 200, 23 107 bytes) directly from the unpacked WAR contents at/usr/local/tomcat/webapps/web/dist/tailwind.css.http://localhost:8080/web/dist/tailwind.csswith HTTP 200 and real Tailwind v4 output./web/,/web/reports/?a=1, and/web/maps/map.jsp?a=1all render with full Tailwind styling and zero console errors. Verified via Playwright./web/dist/tailwind.cssand do not reference@tailwindcss/browser.