Skip to content

Instantly share code, notes, and snippets.

@xtr3me
Last active March 31, 2021 23:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xtr3me/cf99f6673ebcca83f111abd41e9676bb to your computer and use it in GitHub Desktop.
Save xtr3me/cf99f6673ebcca83f111abd41e9676bb to your computer and use it in GitHub Desktop.
Workaround for hotwired/turbo#186
// I use Rails and my Stimulus controllers are available in application.controllers.
// This adds a listener that is triggered when the turbo:before-cache event is emitted
document.addEventListener('turbo:before-cache', function () {
application.controllers.forEach(function (controller) {
try {
if (typeof controller.disconnect === 'function') {
controller.disconnect()
}
} catch (_) {}
})
})
// Alternative: Add this in the connect function of the stimulus controller.
document.addEventListener('turbo:before-cache', function () {
this.disconnect();
})
<!-- wrap your script tags in an element that includes the workaround controller -->
<div data-controller='turbo-issue-186-workaround'>
<script>console.warn('Executed by the workaround stimulus controller');</script>
</div>
<script>console.warn('Will not be executed...')</script>
import { Controller } from "stimulus"
export default class extends Controller {
connect() {
// Could be replaced by requestAnimationFrame()
setTimeout(() => {
[...this.element.children].map(el => this.activateScript(el));
}, 300);
}
// Trigger this method to remove the script elements when the turbo:before-cache event is emitted
// Add additional logic if you need to unmount your code properly.
// For example: ReactDOM.unmountComponentAtNode(this.element);
disconnect() {
[...this.element.children].map(el => this.removeScript(el));
}
removeScript(element) {
if (element.nodeName == 'SCRIPT') {
element.parentElement?.removeChild(element);
}
}
activateScript(element) {
if (element.nodeName == 'SCRIPT') {
const createdScriptElement = document.createElement("script");
createdScriptElement.textContent = element.textContent;
createdScriptElement.async = false;
this.copyElementAttributes(createdScriptElement, element);
this.replaceElementWithElement(element, createdScriptElement);
}
}
replaceElementWithElement(fromElement, toElement) {
const parentElement = fromElement.parentElement
if (parentElement) {
return parentElement.replaceChild(toElement, fromElement)
}
}
copyElementAttributes(destinationElement, sourceElement) {
for (const { name, value } of [ ...sourceElement.attributes ]) {
destinationElement.setAttribute(name, value)
}
}
}
@xtr3me
Copy link
Author

xtr3me commented Mar 31, 2021

I have updated the gist to apply a workaround for a bug that was mentioned in: hotwired/turbo#225

The script is loaded twice:

  1. When the turbo cache is restored (this is called Preview in Turbo)
  2. When the new content is loaded by Turbo

This change removes the script elements just before the cache is stored so on restore they are not there anymore preventing the bug to trigger.

Normally you would check if the preview is visible by using: https://turbo.hotwire.dev/handbook/building#detecting-when-a-preview-is-visible and not execute the activateScript method.

if (document.documentElement.hasAttribute("data-turbo-preview")) {
  // Turbo Drive is displaying a preview
}

But that is currently not available.

By all means, this gist is not perfect. But it helps bridging the time before the fix is merged into Turbo itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment