Adding Vue.js to Sage 9: Dependencies and Approach

Here’s how to add Vue.js support to your Sage 9 project.

1. Add dependencies

yarn add vue
yarn add --dev babel-core babel-eslint babel-loader \
  babel-plugin-transform-runtime babel-preset-env \
  babel-preset-stage-2 babel-register \
  vue-loader vue-style-loader vue-template-compiler

2. Add a .babelrc file

{
  "presets": ["es2015", "stage-0"],
  "plugins": ["transform-runtime"]
}

3. Add stuff to webpack.config.js

To your webpack.config.js file (found in resources/assets/build/), add this to modules.rules:

{
  test: /\.vue$/,
  loader: 'vue'
},

And to force the runtime to include the full compiler (only needed if you want your root Vue element to be parsed as a Vue template), add this under resolve:

alias: {
  'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
},

4. Setup a directory for your .vue components

resources/assets/scripts/vue/ seems like a good spot for those .vue components.

Here’s an example:

The root vue element is here:

And here’s home.js hooking a Vue instance to parse that element and calling that sites.vue component:

26 Likes

Bravo, bravo :clap: :clap: :clap:

2 Likes

Or just use laravel mix and be done with it.

2 Likes

I really like the way Laravel Mix allows you to work with VueJS. However to integrate Laravel Mix into sage without compromising the way sage handles js/webpack is something I haven’t been able to accomplish yet.

So the way I make Vue possible within sage allows me to work with vue files quite similar as I would be working with Laravel Mix. Which is defining vue components in main.js that you store in a separate vue directory, and use them site-wide. This implementation is inspired by @pascallaliberte (thx!) and could definitively be improved as I’m not JS/webpack savvy but its working for my projects. I wanted to share it, in case someone else could benefit from it as well.

Step 1.
run from the theme folder: yarn add --dev vue vue-loader vue-template-compiler

Step2.
add the following blocks to resources/assets/build/webpack.config.js with the rules section

 {
    test: /\.vue$/,
    loader: 'vue-loader'
  }

Step 3.
Within resolve section of that same file:

alias: {
  vue: 'vue/dist/vue.js'
},

Step 4.
At the top of main.js file you add import Vue from 'vue'

Step 5.
Make sure you created the vue directory within the scripts directory

Step 6.
edit resources/config.js and add the following to the watch section "resources/scripts/vue/**/*.vue"

Step 7.
Define your components like Vue.component('your-element-name-goes-here', require('./vue/test.vue').default);

Step 8.
After your components add:

 new Vue({
  el: '#app',
});

Step 9.
Within views/layouts/app.blade.php make sure to embody @include(‘partials.header’) till @include(‘partials.footer) with <div id="app"></div> so you get something like

...
<div id="app">
@include(‘partials.header’)
...
@include(‘partials.footer)
</div>
...

Step 10.
Add your custom element to a page template and check it out with yarn run start / build

edit: Added an extra step (step 6) for watch support when running yarn run start

6 Likes

Updated instructions for buble instead of babel

Sage 9 is now packaged with buble, so a few tweaks are needed.
Also, vue-loader is now setup differently, so we need to update that too.

So here’s an updated setup (assuming you’re starting from scratch with a new Sage 9 install):

Caveat: vue-style-loader not configured!

I haven’t figured out how to setup vue-style-loader with Sage 9’s buble setup. I just style my components as part of my central sass styles and I remove the <style> block in my .vue templates to avoid compilation errors.

If you figure out the vue-style-loader integration (especially handy for including third-party vue components), please comment below and I’ll update my post with instructions and attribution. Thanks!

1. Add dependencies

yarn add vue
yarn add --dev vue-loader vue-template-compiler eslint-plugin-vue

vue-style-loader not installed, see caveat above.

2. Add stuff to webpack.config.js

To your webpack.config.js file (found in resources/assets/build/), include vue-loader:

const { VueLoaderPlugin } = require('vue-loader')

Add this to module.rules:

      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },

And to force the runtime to include the full compiler (only needed if you want your root Vue element to be parsed as a Vue template), add this under resolve:

    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    },

Now register VueLoaderPlugin under plugins.

    new VueLoaderPlugin(),

I placed mine after the debug watcher plugin (does it even matter?):

    new webpack.LoaderOptionsPlugin({
      minimize: config.enabled.optimize,
      debug: config.enabled.watcher,
      stats: { colors: true },
    }),
    new VueLoaderPlugin(), //here

3. Lint .vue files properly

In .eslintrc.js, replace the extends property with:

  "extends": [
    "eslint:recommended",
    "plugin:vue/essential",
  ],

4. Setup a directory for your .vue components

resources/assets/scripts/vue/ seems like a good spot for those .vue components.

See example reference above in the original post.


Hope this helps, and please share your suggestion for vue-style-loader integration.

13 Likes

I am using this at the moment:

{
    test: /\.css$/,
    exclude: config.paths.assets,
    use: [
      { loader: 'vue-style-loader' },
      { loader: 'css-loader' },
    ],
  },

placed right before sage’s CSS rules. You will need to add vue-style-loader to your list of packages too.

This works fine just running yarn run build but running the watcher I get this error in the console:

Uncaught SyntaxError: Unexpected token *

Which points to this line:

var content = __webpack_require__(/*! !../../../../node_modules/cache-loader/dist/cjs.js!../../../../node_modules/css-loader?{"sourceMap":true}!../../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../../node_modules/postcss-loader/lib?{"config":{"path":"/.../web/app/themes/.../resources/assets/build","ctx":{"open":false,"copy":"images/**/*","proxyUrl":"https://localhost:3000","cacheBusting":"[name]_[hash:8]","paths":{"root":"/.../web/app/themes/...","assets":"/.../web/app/themes/..../resources/assets","dist":"/.../web/app/themes/.../dist"},"enabled":{"sourceMaps":true,"optimize":false,"cacheBusting":false,"watcher":true},"watch":["app/**/*.php","config/**/*.php","resources/views/**/*.php"],"entry":{"main":["./scripts/main.js","./styles/main.css"],"customizer":["./scripts/customizer.js"]},"publicPath":"/app/themes/.../dist/","devUrl":"https://....test","env":{"production":false,"development":true},"manifest":{}}},"sourceMap":true}!../../../../node_modules/vue-loader/lib?{}!../../../../node_modules/import-glob!../../../../node_modules/import-glob!./ContactForm.vue?vue&type=style&index=0&lang=css */ 35);

So it’s a bit strange!

1 Like

Yeah, I think that’s how far I got too and reverted course. It looks like it’s because of babel’s own loader sequence, mixed in with sage is configured to pull all scss back into style sheets, and because of how vue-style-loader is setup. Some digging to do.

I’ve got around this now by setting output.pathinfo to false in the resources/assets/build/webpack.config.watch.js file.

My knowledge of webpack isn’t great, but it just seems like it adds in those comments for debugging help during development. I haven’t faced any side effects whilst using sage though, yet!

2 Likes

Is Sage not considering at any point adding an option at install for Vue / Angular / React such as it is with style frameworks (bootstrap etc.)? Would that be possible? Or is it not viable at all and why not?

1 Like

Yeah, maybe like an extra question in the sage-installer script, or as an installer script or preset in sage directly. For that we’d need to split out the webpack configs into different presets (default, vue, react, etc).

Either that or maybe there’s a library that exists to upgrade webpack configs post-install to inject the Vue configs in the right spots (reliably?).

Updated instructions for buble instead of babel, with vue-style-loader working

The previous solution above was missing vue-style-loader integration, and this one fixes that problem, permitting <style> sections in .vue single-file components.

Thanks to @kellymears for having published https://github.com/pixelcollective/SageJS, which includes these fixes.

Known issue:

In the final main.css, the styles from .vue components are included before the normal scss styles. Now sure how to fix that yet.

1. Add dependencies

yarn add vue
yarn add --dev vue-loader vue-template-compiler eslint-plugin-vue vue-style-loader

2. Add stuff to webpack.config.js

To your webpack.config.js file (found in resources/assets/build/ ), include vue-loader :

const { VueLoaderPlugin } = require('vue-loader')

Add this to module.rules :

      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },

And also insert this in module.rules, right before the other test: /\.css$/ entry, not replacing it:

      {
        test: /\.css$/,
        exclude: config.paths.assets,
        use: [
          { loader: 'vue-style-loader' },
          { loader: 'css-loader' },
        ],
      },

And to force the runtime to include the full compiler (only needed if you want your root Vue element to be parsed as a Vue template), add this under resolve :

    alias: {
      'vue$': 'vue/dist/vue.esm.js'

Now register VueLoaderPlugin under plugins .

    new VueLoaderPlugin(),

I placed mine after the debug watcher plugin (does it even matter?):

    new webpack.LoaderOptionsPlugin({
      minimize: config.enabled.optimize,
      debug: config.enabled.watcher,
      stats: { colors: true },
    }),
    new VueLoaderPlugin(), //here

3. Modify webpack.config.watch.js

Set pathinfo to false to fix “Unexpected token ‘*’” errors while running yarn start

  output: {
    pathinfo: false, // set to false
    publicPath: config.proxyUrl + config.publicPath,
  },

4. Lint .vue files properly

In .eslintrc.js , replace the extends property with:

  "extends": [
    "eslint:recommended",
    "plugin:vue/essential",
  ],

5. Setup a directory for your .vue components

resources/assets/scripts/vue/ seems like a good spot for those .vue components.

See example reference above in the original post.

6. Add the new directory to your watch list in resources/assets/config.json:

Under watch:, add:

    "resources/assets/scripts/vue/**/*.vue"
10 Likes

@pascallaliberte

Your solution works great except that the linter hangs forever with no indication of where the lint error is.

With yarn build I get:
12% building modules 22/29 modules 7 active ...es.vue?vue&type=template&id=6b81681d&...

With yarn start I get:
Webpack is watching the files…

I’ve followed your instructions very carefully. Any ideas?

Nevermind, I think previous customisations to my webpack config might have broken things.

Seems to be working for now. Thanks again.

This latest iteration is working for me as well. Thanks for the hard work.
For those who haven’t learned yet. Sitepoint’s “Jump Start Vue.js” book is a great place to start. Wasn’t overwhelming.

I am still having occasional trouble compiling .vue files after following these instructions.

For the most part it works but occasionally my code will not compile with either yarn build or yarn start, and there’s no indication of why.

For example, here is some .vue file code that does not compile or produce any error messages for me:

<div v-if="retrieving">
	<p>Retrieving...</p>
</div>

<div v-else>
	<p>Retrieved!.</p>
</div>

The usual error message I would receive from chrome developer tools would be something like:
Component template should contain exactly one root element
which I understand.

However with Sage and the Vue integration method outlined in this post I receive no indication or message explaining the error. The yarn build simply hangs forever.

I’m sure there are other occasions where similar errors are not indicated as I have had similar trouble before.

Any ideas?

Wish I could help Stuart, but I’m just getting started with the Roots stack, myself.

I’m also having a small issue. Until this point, everything has been compiling fine, until trying to get mixins working properly. When creating one and using it all within a single file component – no problem. Works great. But when importing a mixin from an external file, it breaks the component. The external JS is even in the same directory.

So the external file (slideBase.js) looks something like this:

export const slideBase = {
    // mixin here
};

I also tried:

export default {
  // mixin here
}

In the single file component:

import { slideBase } from './slideBase.js'
export default {
  name: 'slide-half-2',
  mixins: [slideBase],
  data() {
    ...

This should be correct, yes?

Anyone else running into mixin problems? Couldn’t find any info remotely related to this. On the discourse or otherwise.

Cheers,

@db12

I haven’t had much experience with vue mixins but is it possible to use require instead of import? Something similar to the way I am defining components below:

components: {
	'test-component-1': require('./TestComponent1.vue').default,
	'test-component-2': require('./TestComponent2.vue'').default,
},

So in your case it would be:

mixins: [require("./slideBase.js").default]

I don’t understand why the ‘.default’ part is needed for my components. Perhaps the webpack script can be updated to avoid this?

Didn’t end up solving the problem. However, I’m getting the same console error that one of my variables is undefined. If my logic is correct, that could mean that it is indeed importing when trying both import and require, and something else is going on. I’m on Vue forums to try to understand the big picture of loading order for a component – since I’m using a vuex store and such. Will bring up more here if I find webpack/vue-loader to be the issue.

@pascallaliberte

A different issue – I’m having trouble getting global sass variables with sass-loader to work properly. I combined the instructions here with your method of adding vue-style-loader for CSS. So the rule is placed above the other test: /\.scss$/ and looks like this:

      {
        test: /\.scss$/,
        exclude: config.paths.assets,
        use: [
          { loader: 'vue-style-loader' },
          { loader: 'css-loader' },
          { loader: 'sass-loader',
            options: {
              data: `@import "scripts/vue/Project/SCSS/test.scss";`
            },
          },
        ],
      },

When I load a variable into test.scss, I’m still getting a build error ‘undefined variable’ when trying to use it in the single file component:

<style lang="scss">
</style>

The test: /\.css$/ rule from the above instructions is still present.

Any ideas?
Thanks.

Yeah, the one problem that I think is remaining from this setup is that the scss extracted from .vue files ends up being parsed before the rest of the site’s scss. Because of that, your global sass variables aren’t available at the time the .vue styles are parsed.

I haven’t looked deeper into why this is happening this way. Maybe someone will fall on the solution.