-
Notifications
You must be signed in to change notification settings - Fork 211
Migrate from Turbolinks 5 to Hotwire Turbo 8 #2087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
This will require more testing, and likely would need a new release with upgrade notes (to update the JS files projects call on Peek.2025-07-08.17-39.mp4But so far it's working 🤯 Seems to also fix #2063 that triggered this experiment 😄 |
|
Auto refresh is also working. Peek.2025-07-08.18-59.mp4 |
|
I'm trying to check the SSC following https://ihp.digitallyinduced.com/Guide/server-side-components.html#serverside-components But I'm getting this error. My What else might I be missing? p.s. Is anyone using SSC? Is this a feature we might want to drop at a certain point? |
|
@mpscholten this can already benefit from a review/ testing locally, as it's a big change. |
|
I've fixed ihp-scc not working, maybe it's not needed if running non-local IHP 🤷 haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
+ p.ihp-ssc |
|
SSC is now included in the compile, but it complains about the code from the guide. So maybe someone that has ever used it, should give it a try 😄 |
|
@unhammer care to check your scenario with the back button? You can do it by changing in your
- <script src={assetPath "/vendor/morphdom-umd.min.js"}></script>
+ <script src={assetPath "/vendor/turbo.js"}></script>
- <script src={assetPath "/vendor/turbolinks.js"}></script>
- <script src={assetPath "/vendor/turbolinksInstantClick.js"}></script>
- <script src={assetPath "/vendor/turbolinksMorphdom.js"}></script> |
|
I'm pretty sure it's because of this change to var isModalOpen = document.body.classList.contains('modal-open');
- morphdom(
- document.body,
- newHtml.tagName === 'BODY' ? newHtml : newHtml.body,
- {
- childrenOnly: false,
- onBeforeElUpdated: function (from, to) {
- if (
- newHtml.body &&
- newHtml.body.classList.contains('modal-open') &&
- from.id === 'main-row'
- ) {
- return false;
- } else if (isModalOpen && from.id === 'main-row') {
- return false;
- } else if (
- from.classList.contains('flatpickr-input') &&
- from._flatpickr
- ) {
- unsafeSetTimeout(
- function (from, to) {
- console.log(
- 'FROM',
- from,
- to.getAttribute('value'),
- to.value
- );
- from.value = to.value;
- // from.setAttribute('value', to.getAttribute('value'));
- },
- 0,
- from,
- to
- );
- }
- },
- getNodeKey: function (el) {
- var key = el.id;
- if (el.id) {
- key = el.id;
- } else if (el instanceof HTMLScriptElement) {
- key = el.src;
- }
- return key;
- },
- }
- );
+ var newBodyHasModal = newHtml.body && newHtml.body.classList.contains('modal-open');
+ if (isModalOpen && !newBodyHasModal) {
+ // Modal is currently open but new content doesn't have modal - preserve modal state
+ // Only update non-modal content areas
+ var mainRow = document.getElementById('main-row');
+ if (mainRow && newHtml.body) {
+ var newMainRow = newHtml.body.querySelector('#main-row');
+ if (newMainRow) {
+ mainRow.innerHTML = newMainRow.innerHTML;
+ }
+ }
+ } else {
+ // Normal page update - replace entire body content
+ if (newHtml.tagName === 'BODY') {
+ document.body.innerHTML = newHtml.innerHTML;
+ } else if (newHtml.body) {
+ document.body.innerHTML = newHtml.body.innerHTML;
+ }
+ }So turbo doesn't get a chance to run any morphing here. |
|
If I simply disable the --- ../ihp/ihp/data/static/helpers.js 2025-09-22 11:25:01.897260252 +0200
+++ static/helpers.js 2025-09-22 12:40:17.399831586 +0200
@@ -149,28 +149,20 @@
function initDisableButtonsOnSubmit() {
if (window.initDisableButtonsOnSubmitRun) {
return;
}
window.initDisableButtonsOnSubmitRun = true;
- var lastClicked = null;
- document.addEventListener('submit', function (event) {
- event.preventDefault();
-
- var form = event.target;
- window.submitForm(form, lastClicked);
- });
-
document.addEventListener('mouseup', function (event) {
lastClicked = event.target;
});
}then turbo.js seems to pick up the POST results and morph them on its own, no need for explicit calls to
So one way forward would be to let turbo do the form submissions, and we hook into turbo's form submission events: https://turbo.hotwired.dev/handbook/drive#form-submissions where I guess we could do most of the stuff that window.submitForm currently does, and Also, turbo.js seems to require an |
|
@unhammer it's very possible stuff are wrong. It was a session of me and Claude AI, and it's for sure not perfect. I wouldn't mind if you want to take it from here 😄 |
|
👍 I don't think there's that much missing for it to be functional, apart from that window.submitForm (which I think is mostly about removing code). I'll see if I find some time to make an attempt at it after Potato Holiday. |



Migrates IHP from legacy Turbolinks 5.1.1 to modern Hotwire Turbo 8.0.13, replacing morphdom with Turbo's built-in morphing.
Changes
• Replace Turbolinks with Turbo 8.0.13 bundle
• Remove morphdom dependency - use Turbo's Idiomorph instead
• Enable morphing with
<meta name="turbo-refresh-method" content="morph">• Update auto-refresh to use Turbo Streams
• Migrate
transitionToNewPage()toTurbo.renderPage()API• Update events:
turbolinks:load→turbo:loadBenefits
🚀 Better performance with Idiomorph vs morphdom
📦 Single library instead of multiple dependencies
🔧 Actively maintained vs discontinued Turbolinks
Test plan
js-delete🤖 Generated with Claude Code