{"id":16408246,"url":"https://github.com/dsyer/spring-boot-js-demo","last_synced_at":"2025-03-21T03:31:06.464Z","repository":{"id":45776449,"uuid":"437907342","full_name":"dsyer/spring-boot-js-demo","owner":"dsyer","description":"These samples explore the different options that Spring Boot developers have for using Javascript and CSS on the client (browser) side of their application.","archived":false,"fork":false,"pushed_at":"2024-01-16T14:12:58.000Z","size":300,"stargazers_count":32,"open_issues_count":0,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-12T06:16:13.660Z","etag":null,"topics":["javascript","spring","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dsyer.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-12-13T14:36:03.000Z","updated_at":"2024-08-17T17:02:50.000Z","dependencies_parsed_at":"2024-01-12T14:54:41.404Z","dependency_job_id":null,"html_url":"https://github.com/dsyer/spring-boot-js-demo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsyer%2Fspring-boot-js-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsyer%2Fspring-boot-js-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsyer%2Fspring-boot-js-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsyer%2Fspring-boot-js-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dsyer","download_url":"https://codeload.github.com/dsyer/spring-boot-js-demo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221811376,"owners_count":16884305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","spring","spring-boot"],"created_at":"2024-10-11T06:16:13.943Z","updated_at":"2024-10-28T09:13:58.956Z","avatar_url":"https://github.com/dsyer.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":":toc: auto\n\nThese samples explore the different options that Spring Boot developers have for using Javascript and CSS on the client (browser) side of their application. Part of the plan is to explore some Javascript libraries that play well in the traditional server-side-rendered world of Spring web applications. Those libraries tend to have a light touch for the application developer, in the sense that they allow you to completely avoid Javascript, but still have nice a progressive \"modern\" UI. We also look at some more \"pure\" Javascript tools and frameworks. It's kind of a spectrum, so as a TL;DR here is a list of the sample apps, in rough order of low to high Javascript content:\n\n* `htmx`: https://htmx.org[HTMX] is a library that allows you to access modern browser features directly from HTML, rather than using javascript. It is very easy to use and well suited to server-side rendering because it works by replacing sections of the DOM directly from remote responses. It seems to be well used and appreciated by the https://www.python.org/[Python] community.\n\n* `turbo`: https://turbo.hotwired.dev/[Hotwired] (Turbo and Stimulus). Turbo is a bit like HTMX. It is widely used and supported well in https://rubyonrails.org/[Ruby on Rails]. Stimulus is a lightweight library that can be used to implement tiny bits of logic that prefer to live on the client.\n\n* `vue`: https://vuejs.org[Vue] is also very lightweight and describes itself as \"progressive\" and \"incrementally adoptable\". It is versatile in the sense that you can use a very small amount of Javascript to do something nice, or you can push on through and use it as a full-blown framework.\n\n* `react-webjars`: uses the https://reactjs.org[React] framework, but without a Javascript build or bundler. React is nice in that way because, like Vue, it allows you to just use it in a few small areas, without it taking over the whole source tree.\n\n* `nodejs`: like the `turbo` sample but using https://nodejs.org[Node.js] to build and bundle the scripts, instead of https://webjars.org[Webjars]. If you get serious about React, you will probably end up doing this, or something like it. The aim here is to use Maven to drive the build, at least optionally, so that the normal Spring Boot application development process works. Gradle would work the same.\n\n* `react`: is the `react-webjars` sample, but with the Javascript build steps from the `nodejs` sample.\n\nThere is another sample using Spring Boot and HTMX https://github.com/dsyer/todowebflux[here]. HTMX is a very powerful tool for building a UI composed of a number of backend services accessed through a gateway, and there is a sample of that pattern https://github.com/dsyer/frontend-microservices[here], implemented with Mustache and Thymeleaf. If you want to know more about React and Spring there is a https://spring.io/guides/tutorials/react-and-spring-data-rest/[tutorial on the Spring website]. There is also content on https://angular.io[Angular] via another https://spring.io/guides/tutorials/spring-security-and-angular-js/[tutorial on the Spring website] and the related getting started content https://github.com/dsyer/spring-boot-angular[here]. If you are interested in Angular and Spring Boot https://github.com/mraible[Matt Raible] has a https://www.infoq.com/minibooks/angular-mini-book/[Minibook]. The https://spring.io[spring.io] website (https://github.com/spring-io/sagan[source code]) is also a Node.js build and uses a completely different toolchain and set of libraries. Another source of alternative approaches is https://www.jhipster.tech/[JHipster] which also has support for a few of the libraries used here. Finally the https://github.com/spring-projects/spring-petclinic[Petclinic], while it has no Javascript, does have some client side code in the stylesheets and a build process driven from Maven.\n\n## Getting Started\n\nAll the samples can be built and run with standard Spring Boot processes (e.g. see https://spring.io/guides/gs/spring-boot/[this getting started guide]). The Maven wrapper is in the parent directory so from each sample on the command line you can `../mvnw spring-boot:run` to run the apps or `../mvnw package` to get an executable JAR. E.g.\n\n```\n$ cd htmx\n$ ../mvnw package\n$ java -jar target/js-demo-htmx-0.0.1.jar\n\n  .   ____          _            __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n  '  |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot ::            (v3.1.1)\n\n2021-12-08 08:50:30.517  INFO 2385363 --- [           main] com.example.jsdemo.JsDemoApplication     : Starting JsDemoApplication using Java 11.0.7 on tower with PID 2385363 (/home/dsyer/dev/demo/workspace-daily/js-demo/target/classes started by dsyer in /home/dsyer/dev/demo/workspace-daily/js-demo)\n2021-12-08 08:50:30.519  INFO 2385363 --- [           main] com.example.jsdemo.JsDemoApplication     : No active profile set, falling back to default profiles: default\n2021-12-08 08:50:31.501 DEBUG 2385363 --- [           main] s.w.r.r.m.a.RequestMappingHandlerMapping : 6 mappings in 'requestMappingHandlerMapping'\n2021-12-08 08:50:31.519 DEBUG 2385363 --- [           main] o.s.w.r.handler.SimpleUrlHandlerMapping  : Patterns [/webjars/**, /**, /node_modules/**] in 'resourceHandlerMapping'\n2021-12-08 08:50:31.641 DEBUG 2385363 --- [           main] o.s.w.r.r.m.a.ControllerMethodResolver   : ControllerAdvice beans: none\n2021-12-08 08:50:31.666 DEBUG 2385363 --- [           main] o.s.w.s.adapter.HttpWebHandlerAdapter    : enableLoggingRequestDetails='false': form data and headers will be masked to prevent unsafe logging of potentially sensitive data\n2021-12-08 08:50:31.829  INFO 2385363 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080\n2021-12-08 08:50:31.841  INFO 2385363 --- [           main] com.example.jsdemo.JsDemoApplication     : Started JsDemoApplication in 0.97 seconds (JVM running for 1.209)\n```\n\nThe project works well in https://docs.github.com/en/codespaces[Codespaces] and was developed mostly locally with https://code.visualstudio.com/docs/languages/java[VSCode]. Feel free to use whatever IDE you prefer though, they should all work fine.\n\n## Narrowing the Choices\n\nBrowser application development is a huge landscape of ever-changing options and choices. It would be impossible to present all those options in one coherent picture, so we have intentionally limited the scope of tools and frameworks we look at. We start with a bias of wanting to find something that works with a light touch, or is at least incrementally adoptable. There is also the previously mentioned bias towards libraries that work well with server-side renderers - those that deal with fragments and subtrees of HTML. Also, we have used Javascript https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules[ESM] wherever possible, since most browsers now support that. However, most libraries that publish a module to `import` also have an equivalent bundle you can `require`, so you can always stick to that if you prefer.\n\nMany of the samples use https://webjars.org[Webjars] to deliver the Javascript (and CSS) assets to the client. This is very easy and sensible for an application with a Java backend. Not all the samples use Webjars though, and it wouldn't be hard to convert the ones that do to either use a CDN (like https://unpkg.com[unpkg.com] or https://jsdeliver.com[jsdelivr.com]) or a build time Node.js bundler. The samples here that do have a bundler use https://rollupjs.org/guide/en/[Rollup], but you could just as well use https://webpack.js.org/[Webpack], for instance. They also use straight https://www.npmjs.com/[NPM] and not https://classic.yarnpkg.com/[Yarn] or https://gulpjs.com/[Gulp], which are both popular choices. All the samples use https://getbootstrap.com/[Bootstrap] for CSS, but other choices are available.\n\nThere are also choices that can be made on the server side. We have used https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#spring-webflux[Spring Webflux] but https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#spring-web[Spring MVC] would work identically. We have used Maven as a build tool, but using Gradle it would be easy to achieve the same goals. All the samples actually have a static home page (not even rendered as a template), but they all have some dynamic content, and we have chosen https://github.com/samskivert/jmustache[JMustache] for that. https://www.thymeleaf.org/[Thymeleaf] (and other templating engines) would work just as well. In fact Thymeleaf has built-in support for fragments and that can be quite useful when you are updating parts of a page dynamically, which is one of our goals. You could do that same with Mustache (probably) with a bit of work, but we didn't need it in these samples.\n\n## Create a New Application\n\nTo get started with Spring Boot and client-side development, let's start at the beginning, with an empty app from https://start.spring.io[Spring Initializr]. You can go to the website and download a project with web dependencies (select Webflux or WebMVC) and open it up in your IDE. Or to generate a project from the command line you can use `curl`, starting form an empty directory:\n\n```\n$ curl https://start.spring.io/starter.tgz -d dependencies=webflux -d name=js-demo | tar -xzvf -\n```\n\nWe can add a really basic static home page at `src/main/resources/static/index.html`:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\n\u003chead\u003e\n\t\u003cmeta charset=\"utf-8\" /\u003e\n\t\u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" /\u003e\n\t\u003ctitle\u003eDemo\u003c/title\u003e\n\t\u003cmeta name=\"description\" content=\"\" /\u003e\n\t\u003cmeta name=\"viewport\" content=\"width=device-width\" /\u003e\n\t\u003cbase href=\"/\" /\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n\t\u003cheader\u003e\n\t\t\u003ch1\u003eDemo\u003c/h1\u003e\n\t\u003c/header\u003e\n\t\u003cmain\u003e\n\t\t\u003cdiv class=\"container\"\u003e\n\t\t\t\u003cdiv id=\"greeting\"\u003eHello World\u003c/div\u003e\n\t\t\u003c/div\u003e\n\t\u003c/main\u003e\n\n\u003c/body\u003e\n\n\u003c/html\u003e\n```\n\nand then run the app:\n\n```\n$ ./mvnw package\n$ java target/js-demo-0.0.1-SNAPSHOT.jar\n```\n\nand you can see the result on http://localhost:8080[localhost:8080].\n\n### Webjars\n\nTo start building client-side features, let's add some CSS out of the box from Bootstrap. We could use a CDN, like this for example in `index.html`:\n\n```html\n...\n\u003chead\u003e\n\t...\n\t\u003clink rel=\"stylesheet\" type=\"text/css\" href=\"https://unpkg.com/bootstrap/dist/css/bootstrap.min.css\" /\u003e\n\u003c/head\u003e\n...\n```\n\nThat's really convenient, if you want to get started quickly. For some apps it might be all you need. Here we take a different approach that makes our app more self-contained, and aligns well with the Java tooling we are used to - that is to use a Webjar and package the Bootstrap libraries in our JAR file. To do that we need to add a couple of dependencies to the `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars\u003c/groupId\u003e\n\t\u003cartifactId\u003ewebjars-locator-core\u003c/artifactId\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003ebootstrap\u003c/artifactId\u003e\n\t\u003cversion\u003e5.1.3\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nand then in `index.html` instead of the CDN we use a resource path inside the application:\n\n```html\n...\n\u003chead\u003e\n\t...\n\t\u003clink rel=\"stylesheet\" type=\"text/css\" href=\"/webjars/bootstrap/dist/css/bootstrap.min.css\" /\u003e\n\u003c/head\u003e\n...\n```\n\nIf you rebuild and/or re-run the application you will see nice vanilla Bootstrap styles instead of the boring default browser versions. Spring Boot uses the `webjars-locator-core` to locate the version and exact location of the resource in the classpath, and the browser sucks that stylesheet into the page.\n\n### Show Me Some Javascript\n\nBootstrap is also a Javascript library, so we can start to use it more fully by taking advantage of that. We can add the Bootstrap library in `index.html` like this:\n\n```html\n...\n\u003chead\u003e\n...\n\t\u003cscript src=\"/webjars/bootstrap/dist/js/bootstrap.min.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n...\n```\n\nIt doesn't do anything visible yet, but you can verify that it is loaded by the browser using the devtools view (F12 in Chrome or Firefox).\n\nWe said in the introduction that we would use ESM modules where available, and Bootstrap has one, so let's get that working. Replace the `\u003cscript\u003e` tag in `index.html` with this:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t\"bootstrap\": \"/webjars/bootstrap/dist/js/bootstrap.esm.min.js\"\n\t\t}\n\t}\n\u003c/script\u003e\n\u003cscript type=\"module\"\u003e\n\timport 'bootstrap';\n\u003c/script\u003e\n```\n\nThere are two parts to this: an \"importmap\" and a \"module\". The import map is a feature of the browser allowing you to refer to ESM modules by name, mapping the name to a resource. If you run the app now and load it in the browser there should be an error in the console because the ESM bundle of Bootstrap has a dependency on https://popper.js.org/[PopperJS]:\n\n```\nUncaught TypeError: Failed to resolve module specifier \"@popperjs/core\". Relative references must start with either \"/\", \"./\", or \"../\".\n```\n\nPopperJS is not a mandatory transitive dependency of the Bootstrap Webjar, so we have to include it in our `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003epopperjs__core\u003c/artifactId\u003e\n\t\u003cversion\u003e2.10.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n(Webjars use the \"__\" infix instead of a \"@\" prefix for namespaced NPM module names.) Then it can be added to the import map:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t\"bootstrap\": \"/webjars/bootstrap/dist/js/bootstrap.esm.min.js\",\n\t\t\t\"@popperjs/core\": \"/webjars/popperjs__core/lib/index.js\"\n\t\t}\n\t}\n\u003c/script\u003e\n```\n\nand this will fix the console error.\n\n### Normalizing Resource Paths\n\nThe resource paths inside a Webjar (e.g. `/bootstrap/dist/js/bootstrap.esm.min.js`) are not standardized - there is no naming convention that allows you to guess the location of the ESM module inside a Webjar, or an NPM module which amounts to the same thing. But there are some conventions in NPM modules that make it possible to automate: most modules have a `package.json` with a \"module\" field. E.g. from Bootstrap you can find the version and the module resource path:\n\n```json\n{\n  \"name\": \"bootstrap\",\n  \"description\": \"The most popular front-end framework for developing responsive, mobile first projects on the web.\",\n  \"version\": \"5.1.3\",\n...\n  \"module\": \"dist/js/bootstrap.esm.js\",\n...\n}\n```\n\nCDNs like unpkg.com make use of this information, so you can use them when you know only the ESM module name. E.g. this should work:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t\"bootstrap\": \"https://unpkg.com/bootstrap\",\n\t\t\t\"@popperjs/core\": \"https://unpkg.com/@popperjs/core\"\n\t\t}\n\t}\n\u003c/script\u003e\n```\n\nIt would be nice to be able to do the same with `/webjars` resource paths. That's what the `NpmVersionResolver` in all the samples does. You don't need it if you don't use Webjars and you can use a CDN, and you don't need it if you don't mind manually opening up all the `package.json` files and looking for the module path. But it's nice to not have to think about that. There's a https://github.com/spring-projects/spring-boot/issues/28715[feature request] asking for this to be included in Spring Boot. Another feature of the `NpmVersionResolver` is that it knows about the Webjars metadata, so it can resolve the version of each Webjar from the classpath, and we don't need that `webjars-locator-core` dependency (there's an https://github.com/spring-projects/spring-framework/issues/27619[open issue in Spring Framework] to add this feature).\n\nSo in the sample the import map is like this:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t\"bootstrap\": \"/npm/bootstrap\",\n\t\t\t\"@popperjs/core\": \"/npm/@popperjs/core\"\n\t\t}\n\t}\n\u003c/script\u003e\n```\n\nAll you need to know is the NPM module name, and the resolver figures out how to find a resource that resolves to the ESM bundle. It uses a Webjar if there is one, and otherwise redirects to a CDN.\n\nNOTE: Most modern browsers support modules and module maps. Those that don't can be used in our app at the cost of adding a https://www.npmjs.com/package/es-module-shims[shim library]. It is already included in the samples.\n\n### Adding Tabs\n\nWe might as well use the Bootstrap styles now we have it all working. So how about some tabs with content and a button or two to press? Sounds good. First the `\u003cheader/\u003e` with the tab links in `index.html`:\n\n```html\n\u003cheader\u003e\n\t\u003ch1\u003eDemo\u003c/h1\u003e\n\t\u003cnav class=\"nav nav-tabs\"\u003e\n\t\t\u003ca class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#message\" href=\"#\"\u003eMessage\u003c/a\u003e\n\t\t\u003ca class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#stream\" href=\"#\"\u003eStream\u003c/a\u003e\n\t\u003c/nav\u003e\n\u003c/header\u003e\n```\n\nThe second (default inactive) tab is called \"stream\" because part of the samples will be exploring the use of Server Sent Event streams. The tab contents look like this in the `\u003cmain/\u003e` section:\n\n```html\n\u003cmain\u003e\n\t\u003cdiv class=\"tab-content\"\u003e\n\t\t\u003cdiv class=\"tab-pane fade show active\" id=\"message\" role=\"tabpanel\"\u003e\n\t\t\t\u003cdiv class=\"container\"\u003e\n\t\t\t\t\u003cdiv id=\"greeting\"\u003eHello World!\u003c/div\u003e\n\t\t\t\u003c/div\u003e\n\t\t\u003c/div\u003e\n\t\t\u003cdiv class=\"tab-pane fade\" id=\"stream\" role=\"tabpanel\"\u003e\n\t\t\t\u003cdiv class=\"container\"\u003e\n\t\t\t\t\u003cdiv id=\"load\"\u003eNothing here yet...\u003c/div\u003e\n\t\t\t\u003c/div\u003e\n\t\t\u003c/div\u003e\n\t\u003c/div\u003e\n\u003c/main\u003e\n```\n\nNote how one of the tabs is \"active\" and both have ids that match up with the `data-bs-target` attributes in the header. That's why we need some Javascript - to handle the click events on the tabs so that the correct content is revealed or hidden. The https://getbootstrap.com/docs/5.1/getting-started/introduction/[Bootstrap docs] have loads of examples of different tab styles and layouts. One nice thing about the basic features here is that they can automatically render as drop downs on a narrow device like a mobile phone (with some small changes to the class attributes in the `\u003cnav/\u003e` - you can look at the https://github.com/spring-projects/spring-petclinic[Petclinic] to see how). In a browser it looks like this:\n\nimage::images/tabs.png[]\n\nand of course if you click on the \"Stream\" tab it reveals some different content.\n\n## Dynamic Content with HTMX\n\nWe can add some dynamic content really quickly with HTMX. First we need the Javascript library, so we add it as a Webjar:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003ehtmx.org\u003c/artifactId\u003e\n\t\u003cversion\u003e1.6.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nand then import it in `index.html`:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t\"bootstrap\": \"/npm/bootstrap\",\n\t\t\t\"@popperjs/core\": \"/npm/@popperjs/core\",\n\t\t\t\"htmx\": \"/npm/htmx.org\"\n\t\t}\n\t}\n\u003c/script\u003e\n\u003cscript type=\"module\"\u003e\n\timport 'bootstrap';\n\timport 'htmx';\n\u003c/script\u003e\n```\n\nThen we can change the greeting from \"Hello World\" to something that comes from user input. Let's add an input field and a button to the main tab:\n\n```html\n\u003cdiv class=\"container\"\u003e\n\t\u003cdiv id=\"greeting\"\u003eHello World\u003c/div\u003e\n\t\u003cinput id=\"name\" name=\"value\" type=\"text\" /\u003e\n\t\u003cbutton hx-post=\"/greet\" hx-target=\"#greeting\" hx-include=\"#name\"\u003eGreet\u003c/button\u003e\n\u003c/div\u003e\n```\n\nThe input field is unadorned, and the button has some `hx-*` attributes that are grabbed by the HTMX library and used to enhance the page. These ones say \"when user clicks on this button, send a POST to `/greet`, including the 'name' in the request, and render the result by replacing the content of the 'greeting'\". If the user enters \"Foo\" in the input field, the POST has a form-encoded body of `value=Foo` because \"value\" is the name of the field identified by `#name`.\n\nThen all we need is a `/greet` resource in the backend:\n\n```java\n@SpringBootApplication\n@RestController\npublic class JsDemoApplication {\n\n\t@PostMapping(\"/greet\")\n\tpublic String greet(@ModelAttribute Greeting values) {\n\t\treturn \"Hello \" + values.getValue() + \"!\";\n\t}\n\n\t...\n\n\tstatic class Greeting {\n\t\tprivate String value;\n\n\t\tpublic String getValue() {\n\t\t\treturn value;\n\t\t}\n\n\t\tpublic void setValue(String value) {\n\t\t\tthis.value = value;\n\t\t}\n\t}\n}\n```\n\nSpring will bind the \"value\" parameter in the incoming request to the `Greeting` and we convert it to text which is then injected in the `\u003cdiv id=\"greeting\"/\u003e` on the page. You can use HTMX to inject plain text like this, or whole fragments of HTML. Or you can append (or prepend) to a list of existing elements, like rows in a table, or items in a list. You can also use a `\u003cform\u003e` in place of the container `\u003cdiv\u003e` above, and then you don't need `hx-include` (HTMX just sends all the form data).\n\nHere's another thing you can do:\n\n```html\n\u003cdiv class=\"container\"\u003e\n\t\u003cdiv id=\"auth\" hx-trigger=\"load\" hx-get=\"/user\"\u003e\n\t\tUnauthenticated\n\t\u003c/div\u003e\n\t...\n\u003c/div\u003e\n```\n\nThis does a GET to `/user` when the page loads and swaps the content of the element. The sample app has this endpoint and it returns \"Fred\" so you see it rendered like this:\n\nimage::images/user.png[]\n\n### HTMX Triggers\n\nIn addition to page event (like \"load\" in the example above), the backend can send a response header `Hx-Trigger` to fire additional events in the client. In this way you can add nice pop-up notifications, or update other parts of the page. For example, you might add this to the backend:\n\n```java\n@GetMapping(\"/notify\")\npublic ResponseEntity\u003cVoid\u003e notification() throws Exception {\n\treturn ResponseEntity.status(HttpStatus.CREATED)\n\t\t\t.header(\"HX-Trigger\", mapper.writeValueAsString(Map.of(\"notice\", \"Notification\"))).build();\n}\n```\n\nThe `mapper` is a Jackson `ObjectMapper` because the `HX-Trigger` header is encoded in JSON (or just a plain string which is the name of the event to fire). When you `hx-get=\"/notify\"` the client will fire an event which you can listen for and handle in Javascript. For example:\n\n```javascript\n\u003cscript type=\"module\"\u003e\n\timport { Toast } from 'bootstrap';\n\timport 'htmx';\n\thtmx.on(\"notice\", (e) =\u003e {\n\t\tdocument.getElementById(\"toast-body\").innerText = e.detail.value;\n\t\tnew Toast(document.getElementById(\"toast\"), { delay: 2000 }).show();\n\t});\n\u003c/script\u003e\n```\n\nThe example above used a `Void` response body, but more commonly you'll want to return some data, like a view to render and HTML template and replace some content in the page.\n\n### SSE Streams\n\nThere are many other neat things you can do with HTMX, and one of those is to render a https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format[Server Sent Event (SSE)] stream. First we'll add an endpoint to the backend app:\n\n```java\n@SpringBootApplication\n@RestController\npublic class JsDemoApplication {\n\n\t@GetMapping(path = \"/stream\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n\tpublic Flux\u003cString\u003e stream() {\n\t\treturn Flux.interval(Duration.ofSeconds(5)).map(\n\t\t\tvalue -\u003e value + \":\" + System.currentTimeMillis()\n\t\t);\n\t}\n\n\t...\n}\n```\n\nSo we have a stream of messages rendered by Spring by virtue of the `produces` attribute on the endpoint mapping:\n\n```\n$ curl localhost:8080/stream\ndata:0:1639472861461\n\ndata:1:1639472866461\n\ndata:2:1639472871461\n\n...\n```\n\nHTMX can inject those messages into our page. Here's how in `index.html` added to the \"stream\" tab:\n \n```html\n\u003cdiv class=\"container\"\u003e\n\t\u003cdiv id=\"load\" hx-sse=\"connect:/stream\"\u003e\n\t\t\u003cdiv id=\"load\" hx-sse=\"swap:message\"\u003e\u003c/div\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nWe connect to the `/stream` using the `connect:/stream` attribute and then pull event data out using `swap:message`. Actually \"message\" is the default event type, but SSE payloads can also specify other types by including a line starting with `event:`, and so you could have a stream that multiplexes many different event types and have them each affect the HTML in different ways.\n\nThe endpoint in our backend above is very simple: it just sends back plain strings, but it could do more. E.g. it could send back fragments of HTML and they would be injected into the page. The sample applications do it with a custom Spring Webflux component named `CompositeViewRenderer` (requested as a feature https://github.com/spring-projects/spring-framework/issues/27652[here] for the Framework), where `@Contoller` method can return a `Flux\u003cRendering\u003e` (in MVC it would be `Flux\u003cModelAndView\u003e`). It enables an endpoint to stream dynamic views:\n\n```java\n@GetMapping(path = \"/stream\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\npublic Flux\u003cRendering\u003e stream() {\n\treturn Flux.interval(Duration.ofSeconds(5)).map(value -\u003e Rendering.view(\"time\")\n\t\t\t.modelAttribute(\"value\", value)\n\t\t\t.modelAttribute(\"time\", System.currentTimeMillis()).build());\n}\n```\n\nThis is paired with a view named \"time\" and the normal Spring machinery renders the model:\n\n```\n$ curl localhost:8080/stream\ndata:\u003cdiv\u003eIndex: 0, Time: 1639474490435\u003c/div\u003e\n\ndata:\u003cdiv\u003eIndex: 1, Time: 1639474495435\u003c/div\u003e\n\ndata:\u003cdiv\u003eIndex: 2, Time: 1639474500435\u003c/div\u003e\n\n...\n```\n\nThe HTML comes from a template `time.mustache` in `src/main/resources/templates`:\n\n```html\n\u003cdiv\u003eIndex: {{value}}, Time: {{time}}\u003c/div\u003e\n```\n\nwhich in turn works automatically because we included JMustache on the classpath in `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n\t\u003cartifactId\u003espring-boot-starter-mustache\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n\n### Replacing and Enhancing HTML Dynamically\n\nHTMX can still do more. Instead of an SSE stream, an endpoint can return a regular HTTP response, but compose it as a set of elements to swap on the page. HTMX calls this an \"out of band\" swap because it involves enhancing content of elements on the page that are not the same as the one that triggered the download.\n\nTo see this work we can add another tab with some HTMX-enabled content:\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"test\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003cdiv id=\"hello\"\u003e\u003c/div\u003e\n\t\t\u003cdiv id=\"world\"\u003e\u003c/div\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" hx-get=\"/test\" hx-swap=\"none\"\u003eFetch\u003c/button\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\n\nDon't forget to add a nav link so the user can see this tab:\n\n```html\n\u003cnav class=\"nav nav-tabs\"\u003e\n\t...\n\t\u003ca class=\"nav-link\" data-bs-toggle=\"tab\" data-bs-target=\"#test\" href=\"#\"\u003eTest\u003c/a\u003e\n\u003c/nav\u003e\n...\n```\n\nThe new tab has a button that fetches dynamic content from `/test` and it also sets up 2 empty divs \"hello\" and \"world\" to receive the content. The `hx-swap=\"none\"` is important - it tells HTMX not to replace the content of the element that triggered the GET.\n\nIf we have an endpoint that returns this:\n\n```\n$ curl localhost:8080/test\n\u003cdiv id=\"hello\" hx-swap-oob=\"true\"\u003eHello\u003c/div\u003e\n\u003cdiv id=\"world\" hx-swap-oob=\"true\"\u003eWorld\u003c/div\u003e\n```\n\nthen the page renders like this (after the \"Fetch\" button is pressed):\n\nimage::images/test.png[]\n\nA simple implementation of this endpoint would be\n\n```java\n@GetMapping(path = \"/test\")\npublic String test() {\n\treturn \"\u003cdiv id=\\\"hello\\\" hx-swap-oob=\\\"true\\\"\u003eHello\u003c/div\u003e\\n\"\n\t\t+ \"\u003cdiv id=\\\"world\\\" hx-swap-oob=\\\"true\\\"\u003eWorld\u003c/div\u003e\";\n}\n```\n\nor (using the custom view renderer):\n\n```java\n@GetMapping(path = \"/test\")\npublic Flux\u003cRendering\u003e test() {\n\treturn Flux.just(\n\t\t\tRendering.view(\"test\").modelAttribute(\"id\", \"hello\")\n\t\t\t\t.modelAttribute(\"value\", \"Hello\").build(),\n\t\t\tRendering.view(\"test\").modelAttribute(\"id\", \"world\")\n\t\t\t\t.modelAttribute(\"value\", \"World\").build());\n}\n```\n\nwith a template \"test.mustache\":\n\n```html\n\u003cdiv id=\"{{id}}\" hx-swap-oob=\"true\"\u003e{{value}}\u003c/div\u003e\n```\n\n### Boosting Links and Forms\n\nAnother thing that HTMX does is \"boost\" all the links and form actions in your page, so that they automatically work using an XHR request instead of a full page refresh. That's a really simple way to segment your page by feature and update only the bits that you need. You can also easily do that in a \"progressive\" way - i.e. the application works with full page refreshes if Javascript is disabled, but is zippier and feels more \"modern\" if Javascript is enabled.\n\n## Dynamic Content with Hotwired\n\nHotwired is a little bit similar to HTMX, so let's replace the libraries an get the app working. Take out HTMX and add Hotwired (Turbo) to the application. In `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003ehotwired__turbo\u003c/artifactId\u003e\n\t\u003cversion\u003e7.1.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nThen we can import it into our page by adding an import map:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t...\n\t\t\t\"@hotwired/turbo\": \"/npm/@hotwired/turbo\"\n\t\t}\n\t}\n\u003c/script\u003e\n```\n\nand a script to import the library:\n\n```html\n\u003cscript type=\"module\"\u003e\n\timport * as Turbo from '@hotwired/turbo';\n\u003c/script\u003e\n```\n\n### Replacing and Enhancing HTML Dynamically\n\nThis lets us do the dynamic content stuff that we already did with HTMX with a few changes to the HTML. Here's the \"test\" tab in `index.html`:\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"test\" role=\"tabpanel\"\u003e\n\t\u003cturbo-frame id=\"turbo\"\u003e\n\t\t\u003cdiv class=\"container\" id=\"frame\"\u003e\n\t\t\t\u003cdiv id=\"hello\"\u003e\u003c/div\u003e\n\t\t\t\u003cdiv id=\"world\"\u003e\u003c/div\u003e\n\t\t\t\u003cform action=\"/test\" method=\"post\"\u003e\n\t\t\t\t\u003cbutton class=\"btn btn-primary\" type=\"submit\"\u003eFetch\u003c/button\u003e\n\t\t\t\u003c/form\u003e\n\t\t\u003c/div\u003e\n\t\u003c/turbo-frame\u003e\n\u003c/div\u003e\n```\n\nTurbo works a little differently than HTMX. The `\u003cturbo-frame/\u003e` tells Turbo that everything inside is enhanced (a bit like an HTMX boost). And to replace the \"hello\" and \"world\" elements on a button click, we need the button to send a POST through a form, not just a plain GET (Turbo is more opinionated about this than HTMX). The `/test` endpoint then sends back some `\u003cturbo-stream/\u003e` fragments containing templates with the content we want to replace:\n\n```html\n\u003cturbo-stream action=\"replace\" target=\"hello\"\u003e\n        \u003ctemplate\u003e\n                \u003cdiv id=\"hello\"\u003eHi Hello!\u003c/div\u003e\n        \u003c/template\u003e\n\u003c/turbo-frame\u003e\n\n\u003cturbo-stream action=\"replace\" target=\"world\"\u003e\n        \u003ctemplate\u003e\n                \u003cdiv id=\"world\"\u003eHi World!\u003c/div\u003e\n        \u003c/template\u003e\n\u003c/turbo-frame\u003e\n```\n\nTo make Turbo take notice of the incoming `\u003cturbo-stream/\u003e` we need the `/test` endpoint to return a custom `Content-Type: text/vnd.turbo-stream.html` so the implementation looks like this:\n\n```java\n@PostMapping(path = \"/test\", produces = \"text/vnd.turbo-stream.html\")\npublic Flux\u003cRendering\u003e test() {\n\treturn ...;\n}\n```\n\nTo serve the custom content type we need a custom view resolver:\n\n```java\n@Bean\n@ConditionalOnMissingBean\nMustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler, MustacheProperties mustache) {\n\tMustacheViewResolver resolver = new MustacheViewResolver(mustacheCompiler);\n\tresolver.setPrefix(mustache.getPrefix());\n\tresolver.setSuffix(mustache.getSuffix());\n\tresolver.setViewNames(mustache.getViewNames());\n\tresolver.setRequestContextAttribute(mustache.getRequestContextAttribute());\n\tresolver.setCharset(mustache.getCharsetName());\n\tresolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);\n\tresolver.setSupportedMediaTypes(\n\t\t\tArrays.asList(MediaType.TEXT_HTML, MediaType.valueOf(\"text/vnd.turbo-stream.html\")));\n\treturn resolver;\n}\n```\n\nThe above is a copy of the `@Bean` defined automatically by Spring Boot but with an additional supported media type. There is an open https://github.com/spring-projects/spring-boot/issues/28858[feature request] to allow this to be done via `application.properties`.\n\nThe result of clicking the \"Fetch\" button should be to render \"Hello\" and \"World\" as before.\n\n### Server Sent Events\n\nTurbo also has built in support for SSE rendering, but this time the event data has to have `\u003cturbo-stream/\u003e` elements in it. For example:\n\n```\n$ curl localhost:8080/stream\ndata:\u003cturbo-stream action=\"replace\" target=\"load\"\u003e\ndata:   \u003ctemplate\u003e\ndata:           \u003cdiv id=\"load\"\u003eIndex: 0, Time: 1639482422822\u003c/div\u003e\ndata:   \u003c/template\u003e\ndata:\u003c/turbo-stream\u003e\n\ndata:\u003cturbo-stream action=\"replace\" target=\"load\"\u003e\ndata:   \u003ctemplate\u003e\ndata:           \u003cdiv id=\"load\"\u003eIndex: 1, Time: 1639482427821\u003c/div\u003e\ndata:   \u003c/template\u003e\ndata:\u003c/turbo-stream\u003e\n```\n\nThen the \"stream\" tab just needs an empty `\u003cdiv id=\"load\"\u003e\u003c/div\u003e` and Turbo will do what it was asked (replace the element identified by \"load\"):\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"stream\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003cdiv id=\"load\"\u003e\u003c/div\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nBoth Turbo and HTMX allow you to target elements for dynamic content by id or by CSS style matcher, both for regular HTTP responses and SSE streams.\n\n### Stimulus\n\nThere is another library in Hotwired called https://stimulus.hotwired.dev[Stimulus] that lets you add more customized behaviour using small amounts of Javascript. It comes in handy if you have an endpoint in your backend service that returns JSON not HTML, for instance. We can get started with Stimulus by adding it as a dependency in `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003ehotwired__stimulus\u003c/artifactId\u003e\n\t\u003cversion\u003e3.0.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nand with an import map in `index.html`:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t...\n\t\t\t\"@hotwired/stimulus\": \"/npm/@hotwired/stimulus\"\n\t\t}\n\t}\n\u003c/script\u003e\n```\n\nThen we are in good shape to replace the piece of the main \"message\" tab that we did with HTMX before. Here's the tab content covering just the button and custom message:\n\n```html\n\u003cdiv class=\"tab-pane fade show active\" id=\"message\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\" data-controller=\"hello\"\u003e\n\t\t\u003cdiv id=\"greeting\" data-hello-target=\"output\"\u003eHello World\u003c/div\u003e\n\t\t\u003cinput id=\"name\" name=\"value\" type=\"text\" data-hello-target=\"name\" /\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" data-action=\"click-\u003ehello#greet\"\u003eGreet\u003c/button\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nNotice the `data-*` attributes. There is a `controller` (\"hello\") declared on the container `\u003cdiv\u003e` that we need to implement. Its action in the button element says \"when this button is clicked, call the function 'greet' on the 'hello' controller\". And there are some decorations that identify which elements have input and output for the controller (the `data-hello-target` attributes). The Javascript to implement the custom message renderer looks like this:\n\n```html\n\u003cscript type=\"module\"\u003e\n\timport { Application, Controller } from '@hotwired/stimulus';\n\twindow.Stimulus = Application.start();\n\n\tStimulus.register(\"hello\", class extends Controller {\n\t\tstatic targets = [\"name\", \"output\"]\n\t\tgreet() {\n\t\t\tthis.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`;\t\n\t\t};\n\t});\t\n\u003c/script\u003e\n```\n\nThe `Controller` is registered with the `data-controller` name from the HTML, and it has a `targets` field that enumerates all the ids of elements that it wants to target. It can then refer to them by a naming convention, e.g. \"output\" shows up in the controller as a reference to a DOM element called `outputTarget`.\n\nYou can do more or less anything you like in the `Controller`, so for example you could pull some content from the backend. The `turbo` sample does that by pulling a string from the `/user` endpoint and inserting it in an \"auth\" target element:\n\n```html\n\u003cdiv class=\"container\" data-controller=\"hello\"\u003e\n\t\u003cdiv id=\"auth\" data-hello-target=\"auth\"\u003e\u003c/div\u003e\n\t...\n\u003c/div\u003e\n```\n\nwith the complementary Javascript:\n\n```javascript\nStimulus.register(\"hello\", class extends Controller {\n\tstatic targets = [\"name\", \"output\", \"auth\"]\n\tinitialize() {\n\t\tlet hello = this;\n\t\tfetch(\"/user\").then(response =\u003e {\n\t\t\tresponse.json().then(data =\u003e {\n\t\t\t\thello.authTarget.textContent = `Logged in as: ${data.name}`;\n\t\t\t});\n\t\t});\n\t}\n\t...\n});\n```\n\n## Add Some Charts\n\nWe can have some fun adding other Javascript libraries, for instance some nice graphics. Here's a new tab in `index.html` (remember to add the `\u003cnav/\u003e` link as well):\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"chart\" role=\"tabpanel\" data-controller=\"chart\"\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003ccanvas data-chart-target=\"canvas\"\u003e\u003c/canvas\u003e\n\t\u003c/div\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" data-action=\"click-\u003echart#clear\"\u003eClear\u003c/button\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" data-action=\"click-\u003echart#bar\"\u003eBar\u003c/button\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nIt has an empty `\u003ccanvas/\u003e` that we can fill in with a bar chart using https://www.chartjs.org/[Chart.js]. In preparation for that we declared a controller called \"chart\" in the HTML above and labelled the target element for it with `data-*-target`. So let's start by adding Chart.js to the application. In `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003echart.js\u003c/artifactId\u003e\n\t\u003cversion\u003e3.6.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nand in `index.html` we add an import map and some Javascript to render the chart:\n\n```html\n\t\u003cscript type=\"importmap\"\u003e\n\t\t{\n\t\t\t\"imports\": {\n\t\t\t\t...\n\t\t\t\t\"chart.js\": \"/npm/chart.js\"\n\t\t\t}\n\t\t}\n\t\u003c/script\u003e\n```\n\nand the new controller implementing the \"bar\" and \"clear\" actions from the buttons in the HTML:\n\n```javascript\nimport { Chart, BarController, BarElement, LinearScale, CategoryScale, Title, Legend } from 'chart.js';\nChart.register(BarController, BarElement, LinearScale, CategoryScale, Title, Legend);\n\nStimulus.register(\"chart\", class extends Controller {\n\tstatic targets = [\"canvas\"]\n\tbar(type) {\n\t\tlet chart = this;\n\t\tthis.clear();\n\t\tfetch(\"/pops\").then(response =\u003e {\n\t\t\tresponse.json().then(data =\u003e {\n\t\t\t\tdata.type = \"bar\";\n\t\t\t\tchart.active = new Chart(chart.canvasTarget, data);\n\t\t\t});\n\t\t});;\n\t\tclear() {\n\t\t\tif (this.active) {\n\t\t\t\tthis.active.destroy();\n\t\t\t}\n\t\t};\n\t};\n});\n```\n\nTo service this we need a `/pops` endpoint with some chart data (estimated world population by continent according to Wikipedia):\n\n```\n$ curl localhost:8080/pops | jq .\n{\n  \"data\": {\n    \"labels\": [\n      \"Africa\",\n      \"Asia\",\n      \"Europe\",\n      \"Latin America\",\n      \"North America\"\n    ],\n    \"datasets\": [\n      {\n        \"backgroundColor\": [\n          \"#3e95cd\",\n          \"#8e5ea2\",\n          \"#3cba9f\",\n          \"#e8c3b9\",\n          \"#c45850\"\n        ],\n        \"label\": \"Population (millions)\",\n        \"data\": [\n          2478,\n          5267,\n          734,\n          784,\n          433\n        ]\n      }\n    ]\n  },\n  \"options\": {\n    \"plugins\": {\n      \"legend\": {\n        \"display\": false\n      },\n      \"title\": {\n        \"text\": \"Predicted world population (millions) in 2050\",\n        \"display\": true\n      }\n    }\n  }\n}\n```\n\nThe sample app has a few more charts, all showing the same data in different formats. They are all serviced by the same endpoint illustrated above:\n\n```java\n@GetMapping(\"/pops\")\n@ResponseBody\npublic Chart bar() {\n\treturn new Chart();\n}\n```\n\n## Code Block Hiding\n\nIn Spring guides and reference documentation we often see blocks of code segmented by \"type\" (e.g. Maven vs. Gradle, or XML vs. Java). They are shown with one option active and the rest hidden, and if the user clicks on another option, not just the closest code snippets, but all the snippets in the whole document that match the click are revealed. For example if the user clicks on \"Gradle\" all the code snippets that refer to \"Gradle\" are simultaneously activated. The Javascript that drives that feature exists in several forms, depending on which guide or project is using it, and one of those forms is as an NPM bundle https://www.npmjs.com/package/@springio/utils[@springio/utils]. It's not strictly an ESM module but we can still import it and see the feature working. Here's what it looks like in `index.html`:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t...\n\t\t\t\"@springio/utils\": \"/npm/@springio/utils\"\n\t\t}\n\t}\n\u003c/script\u003e\n\u003cscript type=\"module\"\u003e\n\t...\n\timport '@springio/utils';\n\u003c/script\u003e\n```\n\nand then we can add a new tab with some \"code snippets\" (just junk content in this case):\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"docs\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\" title=\"Content\"\u003e\n\t\t\u003cdiv class=\"content primary\"\u003e\u003cdiv class=\"title\"\u003eOne\u003c/div\u003e\u003cdiv class=\"content\"\u003eSome content\u003c/div\u003e\u003c/div\u003e\n\t\t\u003cdiv class=\"content secondary\"\u003e\u003cdiv class=\"title\"\u003eTwo\u003c/div\u003e\u003cdiv class=\"content\"\u003eSecondary\u003c/div\u003e\u003c/div\u003e\n\t\t\u003cdiv class=\"content secondary\"\u003e\u003cdiv class=\"title\"\u003eThree\u003c/div\u003e\u003cdiv class=\"content\"\u003eThird option\u003c/div\u003e\u003c/div\u003e\n\t\u003c/div\u003e\n\t\u003cdiv class=\"container\" title=\"Another\"\u003e\n\t\t\u003cdiv class=\"content primary\"\u003e\u003cdiv class=\"title\"\u003eOne\u003c/div\u003e\u003cdiv class=\"content\"\u003eSome more content\u003c/div\u003e\u003c/div\u003e\n\t\t\u003cdiv class=\"content secondary\"\u003e\u003cdiv class=\"title\"\u003eTwo\u003c/div\u003e\u003cdiv class=\"content\"\u003eSecondary stuff\u003c/div\u003e\u003c/div\u003e\n\t\t\u003cdiv class=\"content secondary\"\u003e\u003cdiv class=\"title\"\u003eThree\u003c/div\u003e\u003cdiv class=\"content\"\u003eThird option again\u003c/div\u003e\u003c/div\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nIt looks like this if the user selects the \"One\" block type:\n\nimage::images/one.png[]\n\nThe thing that drives the behaviour is the structure of the HTML, with one element labelled \"primary\" and alternatives as \"secondary\", then a nested `class=\"title\"` before the actual content. The title is pulled out into the buttons by the Javascript.\n\n## Dynamic Content With Vue\n\nVue is a lightweight Javascript library that you can use a little of or a lot. To get started with Webjars we would need the dependency in `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003evue\u003c/artifactId\u003e\n\t\u003cversion\u003e2.6.14\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nand add it to the import map in `index.html` (using a manual resource path because the \"module\" in the NPM bundle points to something that doesn't work in a browser):\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t...\n\t\t\t\"vue\": \"/npm/vue/dist/vue.esm.browser.js\"\n\t\t}\n\t}\n\u003c/script\u003e\n```\n\nThen we can write a component and \"mount\" it in a named element (it's an example from the Vue user guide):\n\n```html\n\u003cscript type=\"module\"\u003e\n\timport Vue from 'vue';\n\n\tconst EventHandling = {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tmessage: 'Hello Vue.js!'\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\treverseMessage() {\n\t\t\t\tthis.message = this.message\n\t\t\t\t\t.split('')\n\t\t\t\t\t.reverse()\n\t\t\t\t\t.join('')\n\t\t\t}\n\t\t}\n\t}\n\n\tnew Vue(EventHandling).$mount(\"#event-handling\");\n\u003c/script\u003e\n```\n\nTo receive the dynamic content we need an element that matches `#event-handling`, e.g.\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"test\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\" id=\"event-handling\"\u003e\n\t\t\u003cp\u003e{{ message }}\u003c/p\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" v-on:click=\"reverseMessage\"\u003eReverse Message\u003c/button\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nSo the templating happens on the client, and it is triggered by a click using `v-on` from Vue.\n\nIf we want to replace Hotwired with Vue we could start with the content on the main \"message\" tab. So we can replace the Stimulus controller bindings with this, for example:\n\n```html\n\u003cdiv class=\"tab-pane fade show active\" id=\"message\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003cdiv id=\"auth\"\u003e\n\t\t\t{{user}}\n\t\t\u003c/div\u003e\n\t\t\u003cdiv id=\"greeting\"\u003e{{greeting}}\u003c/div\u003e\n\t\t\u003cinput id=\"name\" name=\"value\" type=\"text\" v-model=\"name\" /\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" v-on:click=\"greet\"\u003eGreet\u003c/button\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nand then hook the `user` and `greeting` properties in through Vue:\n\n```javascript\nimport Vue from 'vue';\n\nconst EventHandling = {\n\tdata() {\n\t\treturn {\n\t\t\tgreeting: '',\n\t\t\tname: '',\n\t\t\tuser: 'Unauthenticated'\n\t\t}\n\t},\n\tcreated: function () {\n\t\tlet hello = this;\n\t\tfetch(\"/user\").then(response =\u003e {\n\t\t\tresponse.json().then(data =\u003e {\n\t\t\t\thello.user = `Logged in as: ${data.name}`;\n\t\t\t});\n\t\t});\n\t},\n\tmethods: {\n\t\tgreet() {\n\t\t\tthis.greeting = `Hello, ${this.name}!`;\n\t\t},\n\t}\n}\n\nnew Vue(EventHandling).$mount(\"#message\");\n```\n\nThe `created` hook is run as part of the Vue component lifecycle, so it's not necessarily going to be run precisely the same time as Stimulus did it, but it's close enough.\n\nWe can also replace the chart picker with a Vue, and then we can get rid of Stimulus, just to see what it looks like.  Here's the chart tab (basically the same as before but without the controller decorations):\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"chart\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003ccanvas id=\"canvas\"\u003e\u003c/canvas\u003e\n\t\u003c/div\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" v-on:click=\"clear\"\u003eClear\u003c/button\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" v-on:click=\"bar\"\u003eBar\u003c/button\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nand here's the Javascript code to render the chart:\n\n```html\n\u003cscript type=\"module\"\u003e\n\timport Vue from 'vue';\n\n\timport { Chart, BarController, BarElement, LinearScale, CategoryScale, Title, Legend } from 'chart.js';\n\tChart.register(BarController, BarElement, LinearScale, CategoryScale, Title, Legend);\n\t\n\tconst ChartHandling = {\n\t\tmethods: {\n\t\t\tclear() {\n\t\t\t\tif (this.active) {\n\t\t\t\t\tthis.active.destroy();\n\t\t\t\t}\n\t\t\t},\n\t\t\tbar() {\n\t\t\t\tlet chart = this;\n\t\t\t\tthis.clear();\n\t\t\t\tfetch(\"/pops\").then(response =\u003e {\n\t\t\t\t\tresponse.json().then(data =\u003e {\n\t\t\t\t\t\tdata.type = \"bar\";\n\t\t\t\t\t\tchart.active = new Chart(document.getElementById(\"canvas\"), data);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tnew Vue(ChartHandling).$mount(\"#chart\");\n\u003c/script\u003e\n```\n\nThe sample code also has \"pie\" and \"doughnut\" in addition to the \"bar\" chart type, and they work the same way.\n\n### Server Side Fragments\n\nVue can replace the entire inner HTML of an element using the `v-html` attribute, so we can start to re-implement the Turbo content with that. Here's the new \"test\" tab:\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"test\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\" id=\"frame\"\u003e\n\t\t\u003cdiv id=\"hi\" v-html=\"html\"\u003e\u003c/div\u003e\n\t\t\u003cbutton class=\"btn btn-primary\" v-on:click=\"hello\"\u003eFetch\u003c/button\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nIt has a click handler referring to a \"hello\" method, and a div that is waiting to receive content. We can attach the button to the \"hi\" container like this:\n\n```html\n\u003cscript type=\"module\"\u003e\n\timport Vue from 'vue';\n\n\tconst HelloHandling = {\n\t\tdata: {\n\t\t\thtml: ''\n\t\t},\n\t\tmethods: {\n\t\t\thello() {\n\t\t\t\tconst handler = this;\n\t\t\t\tfetch(\"/test\").then(response =\u003e {\n\t\t\t\t\tresponse.text().then(data =\u003e {\n\t\t\t\t\t\thandler.html = data;\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t},\n\t\t}\n\t}\n\n\tnew Vue(HelloHandling).$mount(\"#test\");\n\u003c/script\u003e\n```\n\nTo make it work we just need to remove the `\u003cturbo-frame/\u003e` elements from the server side template (reverting to what we had in the HTMX sample).\n\nIt is definitely possible to replace our Turbo (and HTMX) code with Vue (or another library or even plain Javscript), but we can see from the sample that it inevitably involves some boilerplate Javascript.\n\n## Plain Javascript with SSE Stream\n\nVue isn't really adding a lot of value in this simple HTML replacement use case, and it would add no value at all to the SSE example, so we will go ahead and implement that in vanilla Javascript. Here's a stream tab:\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"stream\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003cdiv id=\"load\"\u003e\u003c/div\u003e\n\t\u003c/div\u003e\n\u003c/div\u003e\n```\n\nand some Javascript to populate it:\n\n```html\n\u003cscript type=\"module\"\u003e\n\tvar events = new EventSource(\"/stream\");\n\tevents.onmessage = e =\u003e {\n\t\tdocument.getElementById(\"load\").innerHTML = e.data;\n\t}\n\u003c/script\u003e\n```\n\n## Dynamic Content with React\n\nMost people who use React probably do more than just a bit of logic and end up with all of the layout and rendering in Javascript. You don't have to do that, and it's quite easy to use just a bit of React to get a feel for it. You could leave it at that and use it as a utility library, or you could evolve to a full Javascript client-side component approach.\n\nWe can get started and try it out without changing too much. The sample code will end up looking like the `react-webjars` sample if you want to peek. First the dependencies in `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003ereact\u003c/artifactId\u003e\n\t\u003cversion\u003e17.0.2\u003c/version\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n\t\u003cgroupId\u003eorg.webjars.npm\u003c/groupId\u003e\n\t\u003cartifactId\u003ereact-dom\u003c/artifactId\u003e\n\t\u003cversion\u003e17.0.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nand the module map in `index.html`:\n\n```html\n\u003cscript type=\"importmap\"\u003e\n\t{\n\t\t\"imports\": {\n\t\t\t...\n\t\t\t\"react\": \"/npm/react/umd/react.development.js\",\n\t\t\t\"react-dom\": \"/npm/react-dom/umd/react-dom.development.js\"\n\t\t}\n\t}\n\u003c/script\u003e\n```\n\nReact is not packaged as an ESM bundle (yet, anyway), so there is no \"module\" metadata and we have to hard code the resource paths like this. The \"umd\" in the resource path refers to \"Universal Module Definition\" which is an older attempt at modular Javascript. It's close enough that if you squint you can use it in a similar way.\n\nWith those in place you can import the functions and objects they define:\n\n```html\n\u003cscript type=\"module\"\u003e\n\timport * as React from 'react';\n\timport * as ReactDOM from 'react-dom';\n\u003c/script\u003e\n```\n\nBecause they are not really ESM modules you can do this at the \"global\" level in a `\u003cscript/\u003e` in the HTML `\u003chead/\u003e`, e.g. where we import `bootstrap`. Then you can define some content by creating a `React.Component`. Here's a really basic static example:\n\n```html\n\u003cscript type=\"module\"\u003e\n\tconst e = React.createElement;\n\tclass RootComponent extends React.Component {\n\t\tconstructor(props) {\n\t\t\tsuper(props);\n\t\t}\n\t\trender() {\n\t\t\treturn e(\n\t\t\t\t'h1',\n\t\t\t\t{},\n\t\t\t\t'Hello, world!'\n\t\t\t);\n\t\t}\n\t}\n\tReactDOM.render(e(RootComponent), document.querySelector('#root'));\n\u003c/script\u003e\n```\n\nThe `render()` method returns a function that creates a new DOM element (an `\u003ch1/\u003e` with content \"Hello, world!\"). It is attached by `ReactDOM` to an element with `id=\"root\"`, so we'd better add one of those as well, for example in the \"test\" tab:\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"test\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\" id=\"root\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n```\n\nIf you run that it should work and it should say \"Hello World\" in that tab.\n\n### HTML in Javascript: XJS\n\nMost React apps use HTML embedded in the Javascript via a templating language called \"XJS\" (which can be used in other ways but is actually part of React now). The hello world sample above looks like this:\n\n```html\n\u003cscript type=\"text/babel\"\u003e\n\tclass Hello extends React.Component {\n\t\trender() {\n\t\t\treturn \u003ch1\u003eHello, {this.props.name}!\u003c/h1\u003e;\n\t\t}\n\t}\n\tReactDOM.render(\n\t\t\u003cHello name=\"World\"/\u003e,\n\t\tdocument.getElementById('root')\n\t);\n\u003c/script\u003e\n```\n\nThe component defines a custom element `\u003cHello/\u003e` that match the class name of the component, and conventionally starts with a capital letter. The `\u003cHello/\u003e` fragment is an XJS template, and the component also has a `render()` function that returns an XJS template. Braces are used for interpolation, and `props` is a map including all the attributes of the custom element (so \"name\" in this case). Finally there is that `\u003cscript type=\"text/babel\"\u003e` which is needed to transpile the XJS into actual Javascript that the browser will understand. The script above will do nothing until the browser is taught to recognize this script. We do that by importing another module:\n\n```html\n\t\u003cscript type=\"importmap\"\u003e\n\t\t{\n\t\t\t\"imports\": {\n\t\t\t\t...\n\t\t\t\t\"react\": \"/npm/react/umd/react.development.js\",\n\t\t\t\t\"react-dom\": \"/npm/react-dom/umd/react-dom.development.js\",\n\t\t\t\t\"@babel/standalone\": \"/npm/@babel/standalone\"\n\t\t\t}\n\t\t}\n\t\u003c/script\u003e\n\t\u003cscript type=\"module\"\u003e\n\t\t...\n\t\timport * as React from 'react';\n\t\timport * as ReactDOM from 'react-dom';\n\t\timport '@babel/standalone';\n\t\u003c/script\u003e\n```\n\nThe React user guide advises against using `@babel/standalone` in a large application because it has to do a lot of work in the browser, and the same work can be done once at build time which is more efficient. But it's good for trying stuff out, and for apps with small amounts of React code, like this one.\n\n### Basic Event and User Input Handling\n\nWe are now in a position where we can migrate the main \"message\" tab to React. So let's modify the `Hello` component and attach it to a different element. The message tab can be stripped down to an empty element ready to accept the React content:\n\n```html\n\u003cdiv class=\"tab-pane fade show active\" id=\"message\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\" id=\"hello\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n```\n\nWe can anticipate that we will need a second component to render the authenticated user name, so let's start with this to attach some code to the element in the tab above:\n\n```javascript\nReactDOM.render(\n\t\u003cdiv className=\"container\" id=\"hello\"\u003e\n\t\t\u003cAuth/\u003e\n\t\t\u003cHello/\u003e\n\t\u003c/div\u003e,\n\tdocument.getElementById('hello')\n);\n```\n\nThen we can define the `Auth` component like this:\n\n```javascript\nclass Auth extends React.Component {\n\tconstructor(props) {\n\t\tsuper(props);\n\t\tthis.state = { user: 'Unauthenticated' };\n\t};\n\tcomponentDidMount() {\n\t\tlet hello = this;\n\t\tfetch(\"/user\").then(response =\u003e {\n\t\t\tresponse.json().then(data =\u003e {\n\t\t\t\thello.setState({user: `Logged in as: ${data.name}`});\n\t\t\t});\n\t\t});\n\t};\n\trender() {\n\t\treturn \u003cdiv id=\"auth\"\u003e{this.state.user}\u003c/div\u003e;\n\t}\n};\n```\n\nThe lifecycle callback in this case is `componentDidMount` which is called by React when the component is activated, so that's where we put our initialization code.\n\nThe other component is the one that transfers the \"name\" input to a greeting:\n\n```javascript\nclass Hello extends React.Component {\n\tconstructor(props) {\n\t\tsuper(props);\n\t\tthis.state = { name: '', message: '' };\n\t\tthis.greet = this.greet.bind(this);\n\t\tthis.change = this.change.bind(this);\n\t};\n\tgreet() {\n\t\tthis.setState({message: `Hello ${this.state.name}!`})\n\t}\n\tchange(event) {\n\t\tconsole.log(event)\n\t\tthis.setState({name: event.target.value})\n\t}\n\trender() {\n\t\treturn \u003cdiv\u003e\n\t\t\t\u003cdiv id=\"greeting\"\u003e{this.state.message}\u003c/div\u003e\n\t\t\t\u003cinput id=\"name\" name=\"value\" type=\"text\" value={this.state.name} onChange={this.change}/\u003e\n\t\t\t\u003cbutton className=\"btn btn-primary\" onClick={this.greet}\u003eGreet\u003c/button\u003e\n\t\t\u003c/div\u003e;\n\t}\n}\n```\n\nA `render()` method has to return a single element, so we have to wrap the content in a `\u003cdiv\u003e`. The other thing that is worth pointing out is that the transfer of state from the HTML to the Javascript is not automtatic - there's no \"two-way model\" in React, and you have to add change listeners to inputs to explicitly update the state. Also we have to call `bind()` on all the component methods that we want to use as listeners (`greet` and `change` in this case).\n\n### Chart Chooser\n\nTo migrate the rest of the Stimulus content to React we need to write a new chart chooser. So we can start with an empty \"chart\" tab:\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"chart\" role=\"tabpanel\" data-controller=\"chart\"\u003e\n\t\u003cdiv class=\"container\"\u003e\n\t\t\u003ccanvas id=\"canvas\"\u003e\u003c/canvas\u003e\n\t\u003c/div\u003e\n\t\u003cdiv class=\"container\" id=\"chooser\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n```\n\nand attach a `ReactDOM` element to the \"chooser\":\n\n```javascript\nReactDOM.render(\n\t\u003cChartChooser/\u003e,\n\tdocument.getElementById('chooser')\n);\n```\n\n`ChartChooser` is a list of buttons encapsulated in a component:\n\n```javascript\nclass ChartChooser extends React.Component {\n\tconstructor(props) {\n\t\tsuper(props);\n\t\tthis.state = {};\n\t\tthis.clear = this.clear.bind(this);\n\t\tthis.bar = this.bar.bind(this);\n\t};\n\tbar() {\n\t\tlet chart = this;\n\t\tthis.clear();\n\t\tfetch(\"/pops\").then(response =\u003e {\n\t\t\tresponse.json().then(data =\u003e {\n\t\t\t\tdata.type = \"bar\";\n\t\t\t\tchart.setState({ active: new Chart(document.getElementById(\"canvas\"), data) });\n\t\t\t});\n\t\t});\n\t};\n\tclear() {\n\t\tif (this.state.active) {\n\t\t\tthis.state.active.destroy();\n\t\t}\n\t};\n\trender() {\n\t\treturn \u003cdiv\u003e\n\t\t\t\u003cbutton className=\"btn btn-primary\" onClick={this.clear}\u003eClear\u003c/button\u003e\n\t\t\t\u003cbutton className=\"btn btn-primary\" onClick={this.bar}\u003eBar\u003c/button\u003e\n\t\t\u003c/div\u003e;\n\t}\n}\n```\n\nWe also need the chart module setup from the Vue sample (it won't work in a `\u003cscript type=\"text/babel\"\u003e`):\n\n```html\n\u003cscript type=\"module\"\u003e\n\timport { Chart, BarController, BarElement, LinearScale, CategoryScale, Title, Legend } from 'chart.js';\n\tChart.register(BarController, BarElement, LinearScale, CategoryScale, Title, Legend);\n\twindow.Chart = Chart;\n\u003c/script\u003e\n```\n\nChart.js isn't shipped in a form you can import into a Babel script. We import it in a separate module, and `Chart` has to be defined as a global so we can still use it in our React component.\n\n### Server Side Fragments\n\nTo render the \"test\" tab with React we can start with the tab itself, empty again to accept content from React:\n\n```html\n\u003cdiv class=\"tab-pane fade\" id=\"test\" role=\"tabpanel\"\u003e\n\t\u003cdiv class=\"container\" id=\"root\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n```\n\nwith a binding to the \"root\" element in React:\n\n```javascript\nReactDOM.render(\n\t\u003cContent /\u003e,\n\tdocument.getElementById('root')\n);\n```\n\nThen we can implement the `\u003cContent/\u003e` as a component that fetches HTML from the `/test` endpoint:\n\n```javascript\nclass Content extends React.Component {\n\tconstructor(props) {\n\t\tsuper(props);\n\t\tthis.state = { html: '' };\n\t\tthis.fetch = this.fetch.bind(this);\n\t};\n\tfetch() {\n\t\tlet hello = this;\n\t\tfetch(\"/test\").then(response =\u003e {\n\t\t\tresponse.text().then(data =\u003e {\n\t\t\t\thello.setState({ html: data });\n\t\t\t});\n\t\t});\n\t}\n\trender() {\n\t\treturn \u003cdiv\u003e\n\t\t\t\u003cdiv dangerouslySetInnerHTML={{ __html: this.state.html }}\u003e\u003c/div\u003e\n\t\t\t\u003cbutton className=\"btn btn-primary\" onClick={this.fetch}\u003eFetch\u003c/button\u003e\n\t\t\u003c/div\u003e;\n\t}\n}\n```\n\nThe `dangerouslySetInnerHTML` attribute is delibrately named by React to discourage people from using it with content that is collected directly from users (XSS issues). But we get that content from the server so we can put our trust in the XSS protection there and ignore the warning.\n\nIf we use that `\u003cContent/\u003e` component and the SSE loader from the sample above then we can get rid of Hotwired altogether from this sample.\n\n## Building and Bundling with Node.js\n\nWebjars are great, but sometimes you need something closer to the Javascript. One problem with Webjars for some people is the size of the jars - the Bootstrap jar is nearly 2MB, most of which will never be used at runtime - and Javascript tooling has a strong focus on reducing that overhead, by not packaging the whole NPM module in your app, and also by bundling assets together so they can be downloaded efficiently. There are also some issues with Java tooling - regarding https://github.com/sass/dart-sass[Sass] in particular there is a lack of good tooling, as we found with the https://github.com/spring-projects/spring-petclinic/pull/868[Petclinic recently]. So maybe we should take a look at options for building with a Node.js toolchain.\n\nThe first thing you will need is Node.js. There are many ways of obtaining it, and you can use whatever tools you want. We will show how to do it with the https://github.com/eirslett/frontend-maven-plugin[Frontend Plugin].\n\n### Install Node.js\n\nLet's add the plugin to the `turbo` sample. (The final result is the `nodejs` sample if you want to peek) in `pom.xml`:\n\n```xml\n\u003cplugins\u003e\n\t\u003cplugin\u003e\n\t\t\u003cgroupId\u003ecom.github.eirslett\u003c/groupId\u003e\n\t\t\u003cartifactId\u003efrontend-maven-plugin\u003c/artifactId\u003e\n\t\t\u003cversion\u003e1.12.0\u003c/version\u003e\n\t\t\u003cexecutions\u003e\n\t\t\t\u003cexecution\u003e\n\t\t\t\t\u003cid\u003einstall-node-and-npm\u003c/id\u003e\n\t\t\t\t\u003cgoals\u003e\n\t\t\t\t\t\u003cgoal\u003einstall-node-and-npm\u003c/goal\u003e\n\t\t\t\t\u003c/goals\u003e\n\t\t\t\t\u003cconfiguration\u003e\n\t\t\t\t\t\u003cnodeVersion\u003ev16.13.1\u003c/nodeVersion\u003e\n\t\t\t\t\u003c/configuration\u003e\n\t\t\t\u003c/execution\u003e\n\t\t\t\u003cexecution\u003e\n\t\t\t\t\u003cid\u003enpm-install\u003c/id\u003e\n\t\t\t\t\u003cgoals\u003e\n\t\t\t\t\t\u003cgoal\u003enpm\u003c/goal\u003e\n\t\t\t\t\u003c/goals\u003e\n\t\t\t\t\u003cconfiguration\u003e\n\t\t\t\t\t\u003carguments\u003einstall\u003c/arguments\u003e\n\t\t\t\t\u003c/configuration\u003e\n\t\t\t\u003c/execution\u003e\n\t\t\t\u003cexecution\u003e\n\t\t\t\t\u003cid\u003enpm-build\u003c/id\u003e\n\t\t\t\t\u003cgoals\u003e\n\t\t\t\t\t\u003cgoal\u003enpm\u003c/goal\u003e\n\t\t\t\t\u003c/goals\u003e\n\t\t\t\t\u003cconfiguration\u003e\n\t\t\t\t\t\u003carguments\u003erun-script build\u003c/arguments\u003e\n\t\t\t\t\u003c/configuration\u003e\n\t\t\t\t\u003cphase\u003egenerate-resources\u003c/phase\u003e\n\t\t\t\u003c/execution\u003e\n\t\t\u003c/executions\u003e\n\t\u003c/plugin\u003e\n\t...\n\u003c/plugins\u003e\n```\n\nHere we have 3 executions: `install-node-and-npm` installs Node.js and NPM locally, `npm-install` runs `npm install` and `npm-build` runs a script to build the Javascript and possibly CSS. We will need a minimal `package.json` to run them all. If you have `npm` installed you could `npm init` to generate a new one, or just create it manually:\n\n```\n$ cat \u003e package.json\n{\n\t\"scripts\": { \"build\": \"echo Building\"}\n}\n```\n\nThen we can build\n\n```\n$ ./mvnw generate-resources\n...\n[INFO] Building\n[INFO] ------------------------------------------------------------------------\n[INFO] BUILD SUCCESS\n[INFO] ------------------------------------------------------------------------\n[INFO] Total time:  1.133 s\n[INFO] Finished at: 2021-12-16T07:46:42Z\n[INFO] ------------------------------------------------------------------------\n```\n\nYou will see the result is a new directory:\n\n```\n$ ls -d node*\nnode\n```\n\nIt is useful to have an quick way to run `npm` from the command line, when it is installed locally like this. So once you have Node.js you can make it easy by creating a script locally:\n\n```\n$ cat \u003e npm\n#!/bin/sh\ncd $(dirname $0)\nPATH=\"$PWD/node/\":$PATH\nnode \"node/node_modules/npm/bin/npm-cli.js\" \"$@\"\n```\n\nMake it executable and try it out:\n\n```\n$ chmod +x npm\n$ ./npm install\n\nup to date, audited 1 package in 211ms\n\nfound 0 vulnerabilities\n```\n\n### Adding NPM Packages\n\nNow we are ready to build something, let's set up `package.json` with all the dependencies that we had in Webjars until now:\n\n```json\n{\n    \"name\": \"js-demo\",\n    \"version\": \"0.0.1\",\n    \"dependencies\": {\n        \"@hotwired/stimulus\": \"^3.0.1\",\n        \"@hotwired/turbo\": \"^7.1.0\",\n        \"@popperjs/core\": \"^2.10.1\",\n        \"bootstrap\": \"^5.1.3\",\n        \"chart.js\": \"^3.6.0\",\n        \"@springio/utils\": \"^1.0.5\",\n        \"es-module-shims\": \"^1.3.0\"\n    },\n    \"scripts\": {\n        \"build\": \"echo Building\"\n    }\n}\n```\n\nRunning `./npm install` (or `./mvnw generate-resources`) will download those dependencies into `node_modules`:\n\n```\n$ ./npm install\n\nadded 7 packages, and audited 8 packages in 8s\n\n2 packages are looking for funding\n  run `npm fund` for details\n\nfound 0 vulnerabilities\n$ ls node_modules/\n@hotwired  @popperjs  @springio  bootstrap  chart.js  es-module-shims\n```\n\nIt's OK to add all the downloaded and generated code to your `.gitignore` (i.e. `node/`, `node_modules/`, and `package-lock.json`).\n\n### Building with Rollup\n\nThe Bootstrap maintainers use https://rollupjs.org/guide/en/[Rollup] to bundle their code, so that seems like a decent choice. One thing it does really well is \"tree shaking\" to reduce the amount of Javscript you need to ship with your application. Feel free to experiment with other tools. To get started with Rollup we will need some development dependencies in `package.json` and a new build script:\n\n```json\n{\n    ...\n    \"devDependencies\": {\n        \"rollup\": \"^2.60.2\",\n        \"rollup-plugin-node-resolve\": \"^2.0.0\"\n    },\n    \"scripts\": {\n        \"build\": \"rollup -c\"\n    }\n}\n```\n\nRollup has its own config file, so here's one that will bundle a local Javascript source into the app and serve the Javsacript up from `/index.js` at runtime. This is `rollup.config.js`:\n\n```json\nimport resolve from 'rollup-plugin-node-resolve';\n\nexport default {\n\tinput: 'src/main/js/index.js',\n\toutput: {\n\t  file: 'target/classes/static/index.js',\n\t  format: 'esm'\n\t},\n\tplugins: [\n\t\tresolve({\n\t\t\tesm: true,\n\t\t\tmain: true,\n\t\t\tbrowser: true\n\t\t  })\n\t]\n};\n```\n\nSo if we move all the Javascript into `src/main/js/index.js` we would have just one `\u003cscript\u003e` in `index.html`, for instance at the end of the `\u003cbody\u003e`:\n\n```html\n\u003cscript type=\"module\"\u003e\nimport '/index.js';\n\u003c/script\u003e\n```\n\nWe will keep the CSS for now, and we can deal with a local build for that later. So in `index.js` we have all the `\u003cscript\u003e` tag contents mushed together (or we could have split it up into modules and imported them):\n\n```javascript\nimport 'bootstrap';\nimport '@hotwired/turbo';\nimport '@springio/utils';\nimport { Application, Controller } from '@hotwired/stimulus';\nimport { Chart, BarController, BarElement, PieController, ArcElement, LinearScale, ategoryScale, Title, Legend } from 'chart.js';\n\nTurbo.connectStreamSource(new EventSource(\"/stream\"))\nwindow.Stimulus = Application.start();\n\nChart.register(BarController, BarElement, PieController, ArcElement, LinearScale, CategoryScale, itle, Legend);\n\nStimulus.register(\"hello\", class extends Controller {\n\t...\n});\n\nStimulus.register(\"chart\", class extends Controller {\n\t...\n});\n```\n\nIf we build and run the app it should all work, and Rollup creates a new `index.js` in `target/classes/static` where it will be picked up by the executable JAR. Because of the action of the \"resolve\" plugin in Rollup, the new `index.js` has all of the code that is needed to run our application. If any dependencies are packaged as a proper ESM bundle, Rollup will be able to shake the unused parts of them out. This works for Hotwired Stimulus at least, and most of the others get included wholesale, but the result is still only 750K (most of it Bootstrap):\n\n```\n$ ls -l target/classes/static/index.js \n-rw-r--r-- 1 dsyer dsyer 768778 Dec 14 09:34 target/classes/static/index.js\n```\n\nThe browser has to download this once, which is an advantage when the server is HTTP 1.1 (HTTP 2 changes things a bit), and it means the executable JAR isn't bloated with stuff that never gets used. There are other plugin options with Rollup to compress the Javascript, and we'll see some of those in the next section.\n\n### Building CSS with Sass\n\nSo far we have used plain CSS bundled in some NPM libraries. Most applications need their own stylesheets and developers prefer to work with some form of templating library and build time tooling to compile to CSS. The most prevalent such tool (but not the only one) is https://www.npmjs.com/package/sass[Sass]. Bootstrap uses it, and indeed packages its source files in the NPM bundle, so you can extend and adapt the Bootstrap styles to your own requirements.\n\nWe can see how that works by building the CSS for our application, even if we don't do much customization. Start with some tooling dependencies in NPM:\n\n```\n$ ./npm install --save-dev rollup-plugin-scss rollup-plugin-postcss sass\n```\n\nwhich leads to some new entries in `package.json`:\n\n```json\n{\n    ...\n    \"devDependencies\": {\n        \"rollup\": \"^2.60.2\",\n        \"rollup-plugin-node-resolve\": \"^2.0.0\",\n        \"rollup-plugin-postcss\": \"^0.2.0\",\n        \"rollup-plugin-scss\": \"^3.0.0\",\n        \"sass\": \"^1.44.0\"\n    },\n    ...\n}\n```\n\nThis means we can update our `rollup.config.js` to use the new tools:\n\n```javascript\nimport resolve from \"rollup-plugin-node-resolve\";\nimport scss from \"rollup-plugin-scss\";\nimport postcss from \"rollup-plugin-postcss\";\n\nexport default {\n  input: \"src/main/js/index.js\",\n  output: {\n    file: \"target/classes/static/index.js\",\n    format: \"esm\",\n  },\n  plugins: [\n    resolve({\n      esm: true,\n      main: true,\n      browser: true,\n    }),\n    scss(),\n    postcss(),\n  ],\n};\n```\n\nThe CSS processors look in the same place as the main input file, so we can just create a `style.scss` in `src/main/js` and import the Bootstrap code:\n\n```css\n@import 'bootstrap/scss/bootstrap';\n```\n\nCustomizations in SCSS would follow that if we were doing it for real. Then in `index.js` we add imports for this and the Spring utils library:\n\n```javascript\nimport './style.scss';\nimport '@springio/utils/style.css';\n...\n```\n\nand re-build. This will lead to a new `index.css` being created (the same file name as the main input Javascript) which we can then link to in the `\u003chead\u003e` of `index.html`:\n\n```html\n\u003chead\u003e\n\t...\n\t\u003clink rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" /\u003e\n\u003c/head\u003e\n```\n\nThat's it. We have one `index.js` script driving all the Javascript and CSS for our Turbo sample, and we can now remove all remaining Webjars dependencies in the `pom.xml`.\n\n## Bundling a React App with Node.js\n\nTo finish up we can apply the same ideas to the `react-webjars` sample, removing Webjars and extracting Javascript and CSS into separate source files. This way, we can also finally get rid of the slightly problematic `@babel/standalone`. We can start from the `react-webjars` sample and add the Frontend Plugin as above (or otherwise acquire Node.js), and create a `package.json` either manually or via the `npm` CLI. We will need the React dependencies, and also the build time tooling for Babel. Here's what we end up with:\n\n```json\n{\n    \"name\": \"js-demo\",\n    \"version\": \"0.0.1\",\n    \"dependencies\": {\n        \"@popperjs/core\": \"^2.10.1\",\n        \"@springio/utils\": \"^1.0.4\",\n        \"bootstrap\": \"^5.1.3\",\n        \"chart.js\": \"^3.6.0\",\n        \"react\": \"^17.0.2\",\n        \"react-dom\": \"^17.0.2\"\n    },\n    \"devDependencies\": {\n        \"@babel/core\": \"^7.16.0\",\n        \"@babel/preset-env\": \"^7.16.0\",\n        \"@babel/preset-react\": \"^7.16.0\",\n        \"@rollup/plugin-babel\": \"^5.3.0\",\n        \"@rollup/plugin-commonjs\": \"^21.0.1\",\n        \"@rollup/plugin-node-resolve\": \"^13.0.6\",\n        \"@rollup/plugin-replace\": \"^3.0.0\",\n        \"postcss\": \"^8.4.5\",\n        \"rollup\": \"^2.60.2\",\n        \"rollup-plugin-postcss\": \"^4.0.2\",\n        \"rollup-plugin-scss\": \"^3.0.0\",\n        \"sass\": \"^1.44.0\",\n        \"styled-jsx\": \"^4.0.1\"\n    },\n    \"scripts\": {\n        \"build\": \"rollup -c\"\n    }\n}\n```\n\nWe need the `commonjs` plugin because React is not packaged as an ESM and the imports will not work without doing some conversion. The Babel tooling comes with a config file `.babelrc` which we use to tell it to build the JSX and React components:\n\n```json\n{\n        \"presets\": [\"@babel/preset-env\", \"@babel/preset-react\"],\n        \"plugins\": [\"styled-jsx/babel\"]\n}\n```\n\nWith those build tools in place we can extract all the Javascript from `index.html` and put it in `src/main/resources/static/index.js`. It's almost a copy paste, but we will want to add the CSS imports:\n\n```javascript\nimport './style.scss';\nimport '@springio/utils/style.css';\n```\n\nand the imports from React look like this:\n\n```javascript\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n```\n\nYou can build that with `npm run build` (or `./mvnw generate-resources`) and it should work - all the tabs have some content and all the buttons generate some content.\n\nFinally we just need to tidy up the `index.html` so that it only imports the `index.js` and `index.css`, and then all the features from the Webjars project should be working as expected.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdsyer%2Fspring-boot-js-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdsyer%2Fspring-boot-js-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdsyer%2Fspring-boot-js-demo/lists"}