I have been struggling with progressive enhancement on a legacy project. The requirements are simple: there is some PHP-generated HTML that could be nicely enhanced.
Our approach has been using Vue 2 inline templates. This feature neatly separated HTML code (server generated) from JS and CSS code, that lived on the client.
No JS? No problem: the HTML works anyway.
Unfortunately, Vue 3 removed support for inline templates. This left us in a tough spot, since Vue 2 is unsupported and EOL.
Enter Alpine.js. You know Vue? Alpine is the same sauce. Minus lots of complcations. It is just perfect for this use case!
Migrating from Vue 2 to Alpine.js is really, really straightforward, and you gain a lot of benefits:
- reactivity is super easy
- reactivity and events bubble outwards, to the outermost Alpine.js enabled element. No need to have props and weird questions about communicating between parents and children!
- data sharing among different components is easy: you can have a global reactive store and bind to that
- all is inline! but you can have some separation of concens and move JS code to a separate file, so that the HTML becomes lighter
- error reporting is stellar: look at your console and you will see a specific and well described error!
From file.vue to Alpine.js
Template (HTML)
If you have a component, it will have its own template, somewhere.
If it is an inline-template, then replace inline-template
with x-data="customcomponent"
.
<div is="custom-component" inline-template>...</div>
becomes
<div x-data="customcomponent">...</div>
Otherwise, you probably have some <custom-component />
code in your HTML: copy and paste your HTML template that you have in your .vue file, and replace <custom-component />
with the entire HTML template. Then add and x-data="customcomponent"
to the outermost tag.
What is customcomponent
? It is the name of the JS component that we will create in the next step. The customcomponent
will be registered at the last step of this tutorial, and will allow Alpine.js to connect this block of HTML to the correct script that automates it.
Note that you can (and should) inline x-data too, and have all your logic in one place. But I prefer to separate it as soon as it becomes even slightly complex.
Now that your HTML is mostly in place, you will need to replace the v-
attributes with... x-
attributes. You can just rename most of them: they are equivalent.
One exception is x-if
and x-show
: x-if
works on a <template>
tag only, so you should probably start by renaming v-if
to x-show
, unless you have special requirements.
CSS
Your CSS code goes to the main CSS. No magic here, sorry.
JS
- Create a new
custom-component.js
file - Start like this:
export default () => ({
// Code here
});
- Paste the insides of your .vue
script
tag to where// Code here
is. - Promote all
data
to the outermost scope:
export default () => ({
reactiveVar1 : 'hi!',
});
- Promote all
computed
to the outermost scope:
export default () => ({
reactiveVar1 : 'hi!',
computeMe() : { return this.reactiveVar1; },
});
- Rename
mount()
toinit()
export default () => ({
reactiveVar1 : 'hi!',
computeMe() : { return this.reactiveVar1; },
init() : { reactiveVar1 = 'hello world'; }
});
- Have a
watch
block? Promote all the watching function to the main scope, and then connect them manually in your init() function, like this:
this.$watch('watchedVariable', (newValue) => this.watchedVariableChanged(newValue))
- Adjust special Vue features, if you have any
Bring it all together now
You will need to bootstrap Alpine.js somewhere in your JS code, at the top level of your app, the same as you did with Vue.
You would do something like this:
import Alpine from 'alpinejs/dist/module.cjs';
import collapse from '@alpinejs/collapse'
import customcomponent from './custom-components.js';
window.Alpine = Alpine
document.addEventListener("DOMContentLoaded", function () {
Alpine.plugin(collapse)
Alpine.data('customcomponent', customcomponent)
Alpine.start()
});
We have imported our JS, we introduced it to Alpine and we gave it a name: now we can use it.
Go to your HTML file and replace data='{ someReactiveVar = 1; }'
with data='customcomponent'
.
Reload and enjoy!
Top comments (1)
Protip: When using modules already, you don't need to do a document listener
Slap
type=module
on that script and you're golden.