DEV Community

DGMANN
DGMANN

Posted on • Edited on

Vue Application as a Wordpress Plugin

Vue and Wordpress

Intro

I read this article of Lisa Armstrong to use VueJS to create a plugin for Wordpress. My post will extend Lisa's way and show a solution with a little bit more VueJS.
This is just a really short and quick article. I will write more about this next time.

Step 1

First we have to create a Vue application with vue create wp-vue. Select the default configuration and run npm run build.

Step 2

Now we can follow the steps of Lisas' article.

The shorthand-version
  1. We create a file wp-vue.php in /wp-content/plugin/wp-vue/
  2. with the following code:
<?php
 /**
 * Plugin Name: WordPress Vue
 * Description: Vue-App in WordPress.
 */

 function func_load_vuescripts() {
     wp_register_script('wpvue_vuejs', plugin_dir_url( __FILE__ ).'dist/js/app.c8d5a15f.js', true);
     wp_register_script('wpvue_vuejs1', plugin_dir_url( __FILE__ ).'dist/js/chunk-vendors.5e0c61d5.js', true);
 }

 add_action('wp_enqueue_scripts', 'func_load_vuescripts');

 //Add shortscode
 function func_wp_vue(){
     wp_enqueue_script('wpvue_vuejs');
     wp_enqueue_script('wpvue_vuejs1');

     $str= "<div id='app'>"
           ."Message from Vue: "
           ."</div>";
     return $str;
 } // end function

  add_shortcode( 'wpvue', 'func_wp_vue' );
?>
Enter fullscreen mode Exit fullscreen mode
  1. one very important change is the id attribute of the div element. For the connection to our Vue-App
  2. a second important change is that we won't load the VueJS-Script with CDN. We load the chunk-vendors.js and the app.js from our Vue-App
  3. now we have to activate the plugIn in Wordpress

Step 3

To get the js files we have to upload the dist folder which we created in Step 1 to the plugin folder wp-vue

Step 4

If you already added the shortcode [wpvue] in one of your pages the whole Vue-App shows up. We only have to reduce the code in HelloWorld.vue a little bit:

<template>
      <div class="hello">

      </div>
    </template>

    <script>
    export default {
      name: 'HelloWorld',
      props: {
        msg: String
      }
    }
    </script>

    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
    </style>
Enter fullscreen mode Exit fullscreen mode

...and run the npm run build task again. After that we can upload the necessary files and delete the old ones. We also have to change the scripts in our wp-vue.php. Now we get a perfect start for a new Vue-App plugin.
The hash behind app. and chunk-vendors.should be delete to avoid editing the wp-vue.php script. We can solve this by adding a file vue.config.js next to our package.json with the following code:

module.exports = {
  filenameHashing: false,
}
Enter fullscreen mode Exit fullscreen mode

We just have to remove the hashs out of our wp-vue.php and it will work. But you have to be careful with that because the caching should be handled at any point. Look at Jony's suggestion

Development

To develope in the development mode (so that we are able to use all advantages of VueJS) we can run npm run serve and create a local Vue-App to show our plugin.
A huge advantage of this way is that we also can realize an atomic design workflow and are also able to scale this up by using the Vue-App for more than one plugin.

Top comments (28)

Collapse
 
jonyhayama profile image
Jony Hayama

Cool stuff!

I would like to contribute with a quick piece of code to handle the enqueue_script cache and filenameHashing issue.

We can use the filemtime on the func_load_vuescripts function a to handle files being cached:

function func_load_vuescripts(){
  $file = plugin_dir_url(__FILE__) . 'dist/js/app.js';
  wp_register_script('wpvue_vuejs', $file, ['wpvue_vuejs1'], filemtime($file));

  $file = plugin_dir_url(__FILE__) . 'dist/js/chunk-vendors.js';
  wp_register_script('wpvue_vuejs1', $file, [], filemtime($file));
}
Enter fullscreen mode Exit fullscreen mode

I've also added the chunck-vendors as a dependency on wpvue_vuejs so it gets enqueue automatically:

function func_wp_vue(){
     wp_enqueue_script('wpvue_vuejs');
     wp_enqueue_script('wpvue_vuejs1'); // <- no longer needed

     $str= "<div id='app'>"
           ."Message from Vue: "
           ."</div>";
     return $str;
 }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
angelinetran profile image
Angeline Tran

Nice! Thanks for this! I extended your code to use glob and regex so I could keep the hash.

function get_hashed_file($filename)
{
    $regex = '/\/[\w-]+\.[\w-]+.*/i';
    $fileWithHash = glob(dirname(__FILE__) . '/dist/js/' . $filename . '.*.js')[0];
    preg_match($regex, $fileWithHash, $matches);
    return $matches[0];
}

//Register scripts to use
function func_load_vuescripts()
{
    $file = plugin_dir_url(__FILE__) . 'dist/js' . get_hashed_file('app');
    wp_register_script('wpvue_vuejs', $file, ['wpvue_vuejs1'], filemtime($file));

    $file2 = plugin_dir_url(__FILE__) . 'dist/js' . get_hashed_file('chunk-vendors');
    wp_register_script('wpvue_vuejs1', $file2, [], filemtime($file2));
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jonyhayama profile image
Jony Hayama

Awesome!!

Although - if you allow me to share a small thought - if you are using the hash, you do not need filemtime. I mean, the purpose of the hash is to avoid whatever new code you create from being cached...I guess it's a way to make the browser "think" it's a new file... WordPress handles this is by adding ?v=XXX to the end of your path where XXX is whatever you pass as the last argument for the wp_register_script function.

So, to make your code just a smidge more performant, I would replace filemtime with null (or simply remove the argument) :D

Collapse
 
dgmann profile image
DGMANN

Thank you Jony! :)
That are two really good points! I added it to the article.

Collapse
 
dstearle profile image
Dallas Earle

Excellent article! I ended up using this to build a new map plugin which is coming along very nicely. Something I am struggling with is trying to figure out a way to get the proper paths for some things like images. Usually in WP I would use something like "<?php echo get_template_directory_uri(); ?>" to handle that but obviously I can not use a PHP function in my JS app.

I have read about wp_localize_script() which can allow you to pass the values of said PHP functions to JS but I have no idea how to get that to work with this type of set up since it is a plugin.

Any ideas?

Collapse
 
dgmann profile image
DGMANN • Edited

Hi Dallas Earle and thank you!
Is it possible for you to use the Wordpress API? If this is the case, maybe you can get your images from "demo.wp-api.org/wp-json/wp/v2/media"?

Collapse
 
dstearle profile image
Dallas Earle

Well I am currently using WP API to fetch data from custom posts but the images I need are stored in a folder in the Theme. Wouldnt the "/wp/v2/media" endpoint only be good to retrieve media from posts? I am pretty new to using WP so I am not entirely familar with how the WP API works yet.

Thread Thread
 
dgmann profile image
DGMANN

I'm sorry. I did not read that correctly.
If you know where the pictures are stored, can you access this folder in your Vue app?

Collapse
 
renaudham profile image
renaudham

Hi.
I'm still reallu unclear with the policies of Wordpress repository, about this, if they would allow to publish a plugin to WP, that would be done using NPM and compilation... even if we keep the full .vue files content into the plugin...

Any feedbacks on this ?
Isn't it something that would make our pluign rejected by WP team?

thanks

Collapse
 
cmgustin profile image
Chris Gustin

That's a good question. My understanding is that Vue, npm, etc. are just build tools, which means they shouldn't be included in the final production version of a plugin if it's going to be released to the public. Typically you would have the build files on your local copy, and when you run the build command, it will create a distribution folder with everything compiled down to plain PHP/HTML/CSS/JS. That distribution folder is what you want to include with the released version of your plugin, not the build files.

If you're using Git for version control, you can tell Git to ignore the build files (npm, node_modules, Vue files, SCSS files, etc.) when you push up versions of your plugin. Those files would only live on your local copy since their sole purpose is to make the development workflow easier/faster.

Hope that helps clarify!

Collapse
 
dgmann profile image
DGMANN

Thanks Chris for your response! And your totally right :)

Collapse
 
franckysmith profile image
franckysmith

Hi Paloma, thanks, you did exactly what I needed so thanks again ... and thanks to Lisa too
all work perfectly except the image and the style css!! strange no? do you have an idea?
the .json works fine. I add to have more page and I did single page I mean template/script/style in each one
each image are present in /img and in the page the link refer to /img
it's works perfect in local

Franck from Paris

Collapse
 
tcmartin24 profile image
Terry

franckysmith,

If you're saying you weren't able to get CSS working, I may be able to partially help. I've created a Vue/Quasar app and was able to load its CSS like this:

wp_register_style('wpvue_style1', plugin_dir_url( FILE ).'dist/spa/css/app.0e433876.css', true);

and

wp_enqueue_style('wpvue_style1');

per Paloma's guidance.

Collapse
 
franckysmith profile image
franckysmith

thanks Terry,
maybe you saw I found myself the same solution... or almost

Collapse
 
dgmann profile image
DGMANN

Hey Franck,

thank you very much!

I didn't get this one "I add to have more page and I did single page I mean template/script/style in each one" completly. Can you explain that a bit more? Maybe then I can say something about it. Are the read & write permissions of the folders in the server file system sufficient?

About the images:
The link for the images called up in the network tab (prod) ended in nothing? What would have to be changed so that the link to the image is correct?
Maybe the local link is not completely the same as the productive link?

Collapse
 
franckysmith profile image
franckysmith

Hi Paloma, and thanks for your response?
Finaly I fixed all:
for the style css I add in wp-vue.php: wp_register_style( 'stylesheet', plugins_url( 'dist/css/app.css' , FILE ) ); and
wp_enqueue_style( 'stylesheet' );
and for the image I put the img folder next to wp-content , wp-admin ...
of course it should be better if we could let it in the dist folder ... but for now it works fine
thanks again

Thread Thread
 
dgmann profile image
DGMANN

Happy to hear that! :)
And you are right. Maybe this solution helps you: stackoverflow.com/questions/522163...

Collapse
 
salvo2689 profile image
Salvo2689

hello I perfectly executed your tutorial and finally, thanks to you, I found one to use vue without tag script, which has involved me a great deal of performance on GTMAtrix. I got stuck in point 4, activating 'npm run build' I have to go to localhost: 8080 to view the page. But on the shortcode page I only see 'Message from Vue:'. Could you kindly be more specific on how to implement new vue code on that shortcode? How do I add new files as in the initial tutorial you refer to. I have another question, my shortcode will have an output parameter which identifies it 'id'. As it could be called multiple times on the same page. How can I change, according to this 'id', also the id (el) Vue, so that they do not conflict? one last thing. When I finish the plugin, just send the folder via ftp to the site remotely or do I have to take the distribution? if the second in what way. Forgive me for all these questions, but I'm very confused.

Collapse
 
dgmann profile image
DGMANN • Edited

Hi Salvo,
you have two parts. The first part is to run "npm run build" in your local Vue-App. Out of this vue creates an distribution folder. In this folder the files are stored, which should be uploaded in the second part. Second part: You can upload the dist folder into you plugin folder via ftp. Important point is to change the name of the scripts in your wp-vue.php with the correct hash value if filename-hashing is activated in your vue app.

It's probably the most rudimentary way, but you can easily exchange the files via ftp. Of course you can also build a pipeline and connect your ftp server to it, but for a little test this would be a bit of overkill.

I'm not sure I understood your intention correctly. You want to be able to place your Vue plugin on the website multiple times and the id of both the WP-shortcode and the initial Vue tag is the same? Unfortunately, this is not easy to do because the id of the Vue app is already fixed before the backend code runs.

Collapse
 
tcmartin24 profile image
Terry

Awesome article Paloma, really helped me get my basic Vue app working (mostly) in WP site. However, I've run into some tricky (for me at least) styling issues. I guess various styles of my WP site's theme are overriding my Vue app's styles. I'm loading the styles using 'wp_register_style' and 'wp_enqueue_style' using the pattern you show above. I've confirmed the styles are being loaded in my WP site, but apparently being overridden. I've experimented with trying to affect their load order by adding (and removing) the 'true' parameter on wp_register_style, as well as moving the order of my register and enqueue calls first and last. I also saw a suggestion to add a high index number parameter to the 'add_action' call like this:

add_action('wp_enqueue_scripts', 'func_load_vuescripts', 99999);

One conflict I found was with jquery which was included with my site's theme (I believe). When I removed it in my browser's web tools, my app's layout improved some.

Any ideas?

Collapse
 
dgmann profile image
DGMANN

Hi Terry,
thank you! :)
Several problems may have arisen here. Actually Vue.js scopes its styling if the scope attribute is used. Furthermore I recommend only styling using/via classes (preferably in BEM) so that nothing can be overwritten. The advantage of BEM is also the readability. The specificity of the CSS selectors is also an important indicator (css-tricks.com/specifics-on-css-sp...).

It could be that styles are reloaded using jQuery. By removing this, this may be omitted.

Collapse
 
oivinds profile image
oivinds • Edited

Hi, and thanks for this article!
I have a problem making it work though. The Vue example app will not display on the page. This is what I got:

  • chunk-vendors.js and app.js are loaded on the same page along with the shortcode generated div (#app). As confirmed by browser network tab and elements tab.

  • I have tested with yarn serve that a dev server version works and displays the application.

Any hints to what I am missing?

Edit: Now it works!
I had to add DomContentLoaded listener to delay the Vue instantiation in main.js. Without it it will not work for me with what is provided in this tutorial. I'd be happy to get a comment telling me I am wrong and why.

Collapse
 
dgmann profile image
DGMANN • Edited

Hey oivinds!

sorry for my late response and i'm happy to hear that you get it working now.
It is a bit difficult for me to debug this one but is it working with a new and clean wordpress installation?

Collapse
 
eingenetzt profile image
Dennis Redder

Hi, how would you handle the shortcode beeing used twice (or more often - after all its up to the wordpress user)? You would have the same element id twice. I am trying to find a solution in which I bind it to a class but can't get it running:
'''
let elements = document.getElementsByClassName('conversionform')
for(var i = 0; i < elements.length; i++){
new Vue({
store,
render: h => h(App)
}).$mount( ???? )
}

Collapse
 
dgmann profile image
DGMANN • Edited

Hey Dennis!
I didn't had time to expand this little tut and i didn't tested it with WP but a maybe better solution would be to create a shortcode with a component tag of the vue component. So that you would create a string like this:

$str= "<vue-component/>";
Enter fullscreen mode Exit fullscreen mode

To mount the vue-app to the whole page you could set an id to a global tag like the body tag. I think this could be even a better solution for the integration of vue.js in wordpress. I'm still working on this to create a simple solution for using vue tags. I hope i can put this way to an article soon.
In addition you have to handle the script imports, otherwise it could be that it would be imported every time the shortcode is used, but i think the browser is smart enough to handle that.

Collapse
 
odai86 profile image
Odai Assaf

great work i would like to contribute with a quick piece of code to handle the css after
run build

module.exports = {
filenameHashing: false,
css: { extract: false }
}

Collapse
 
coledcrawford profile image
Cole Crawford

Got this up and running pretty smoothly, thanks for the walkthrough.

I have a situation where a site is mostly Wordpress, but I'm using Vue + Wordpress REST API for a few features that are more dynamic. These Vue features are on ~6 different pages. They use some shared components, but have different base templates. Eg PostsPage.vue and PeoplePage.vue both use RadioFilter.vue and are on different WP pages.

I'm wondering how to deploy this on different pages, either with different shortcodes or passing a param through the shortcode. How can I structure my codebase so I can benefit from reusable components while also having essentially 5 or 6 different apps to be injected?

Collapse
 
dgmann profile image
DGMANN • Edited

Hey Cole!

You could create a globals folder, where you can store all your global vue-components, stylings, extra scripts and so on. On the same level of the globals folder you can set up your different Vue instances. These can access the necessary global stuff.
Each Vue instance get a shortcode so that all instances can work independently. This makes sense if they are used on different pages. Otherwise you may have duplicate code.