Migrating to 3.0

Installation & setup changes

Core/Vuejs repo and gem split

  • matestack-ui-core previously contained logic for

    • Ruby -> HTML conversion

    • Reactivity via prebuilt and custom Vue.js components

  • In order to have better seperation of concerns, we've moved the reactivity related things to its own repository/gem -> matestack-ui-vuejs

  • matestack-ui-core is now meant to be combined with any reactivity framework or none at all

If you've used reactivity features of matestack-ui-core 2.x you now have to install matestack-ui-vuejs (Ruby Gem & NPM Package) additionally:

Gemfile

gem 'matestack-ui-core', '~> 3.0.0'
gem 'matestack-ui-vuejs', '~> 3.1.0'

Remove matestack-ui-core JavaScript package

  • matestack-ui-core does not ship a JavaScript package anymore

  • please remove the package from your application and switch to matestack-ui-vuejs for the VueJs driven reactivity if required

yarn remove matestack-ui-core
  • and add matestack-ui-vuejs:

package.json

{
  "name": "my-app",
  "dependencies": {
    "matestack-ui-vuejs": "^3.1.0", // <-- new package name
    "..."
  }
}

IE 11 support dropped

  • vue3 dropped IE 11 support

  • when using babel alongside webpacker, please adjust your package.json or .browserslistrc config in order to exclude IE 11 support:

{
  "name": "my-app",
  "...": { },
  "browserslist": [
    "defaults",
    "not IE 11" // <-- important!
  ]
}

Otherwise you may encounter issues around regeneratorRuntime (especially when using Vuex)

Setup via webpacker

config/webpack/environment.js

const { environment } = require('@rails/webpacker')
const webpack = require('webpack');

const customWebpackConfig = {
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm-bundler',
      'matestack-ui-vuejs': 'matestack-ui-vuejs/lib/matestack/ui/vue_js/index.js' // in order not to use the esm package
    }
  },
  plugins: [
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false
    })
  ]
}

environment.config.merge(customWebpackConfig)

module.exports = environment

Don't forget to restart webpacker when changing this file!

and then just use import { whatever } from 'vue' instead of import { whatever } from 'vue/dist/vue.esm'

Optional: vue3 compat build usage

If you, or any of the libraries you're using, are consuming any vue2 APIs, you can use the vue3 compat build. This enables you to use both vue2 and vue3 APIs and migrate step by step.

Usage via webpack config when using webpacker 5.x and up:

config/webpack/environment.js

const { environment } = require('@rails/webpacker')
const webpack = require('webpack')

const customWebpackConfig = {
  resolve: {
    alias: {
      vue: '@vue/compat/dist/vue.esm-bundler',
      'matestack-ui-vuejs': 'matestack-ui-vuejs/lib/matestack/ui/vue_js/index.js' // in order not to use the esm package
    }
  },
  plugins: [
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false
    })
  ]
}

environment.config.merge(customWebpackConfig)

module.exports = environment

Matestack::Ui::App is now called Matestack::Ui::Layout

Matestack::Ui::App was always meant to be a layout wrapping pages, but was supercharged with some vuejs logic before splitting the core and vuejs repos. Now Matestack::Ui::App is only a layout, that's why it should be named like that: Matestack::Ui::Layout

Protip: Search & Replace

matestack_app method is renamed to matestack_layout

Following the above mentioned naming adjustment, the matestack_app method used on controller level is renamed to matestack_layout

app/controllers/demo_controller.rb

class DemoController < ActionController::Base
  include Matestack::Ui::Core::Helper

  layout "application" # root rails layout file
  
  # matestack_app DemoApp::Layout
  matestack_layout DemoApp::Layout # <-- renamed from matestack_app

  def foo
    render DemoApp::Pages::Foo
  end

end

Matestack::Ui::Layout Matestack::Ui::Page wrapping DOM structures

Previously, Matestack::Ui::App added some wrapping DOM structures around the whole layout and around it's yield. This enabled dynamic page transition and loading state animations.

Matestack::Ui::Layout now purely renders the layout and yields a page without anything in between. The wrapping DOM structures required for dynamic page transitions and loading state animations needs to be added via two new components if you want to use these features via matestack-ui-vuejs (see section below!)

matestack/some/app/layout.rb

class Some::App::Layout < Matestack::Ui::Layout
  def response
    h1 "Demo App"
    main do
      yield
    end
  end
end

matestack/some/app/pages/some_page.rb

class Some::App::Pages::SomePage < Matestack::Ui::Page
  def response
    h2 "Some Page"
  end
end

will just render:

<body> <!-- coming from rails layout if specified -->
  <!-- no wrapping DOM structure around the layout -->
  <h1>Demo App</<h1>
  <main>
    <!-- page markup without any wrapping DOM structure -->
    <h2>Some Page</h2>
  <main>
</body>

Matestack::Ui::Layout adjustments when using matestack-ui-vuejs

As seen in the above example, Matestack::Ui::Layout classes are no longer automatically wrapped by a component tag meant to mount the matestack-ui-core-app component on it.

This has to be done manually via the matestack_vue_js_app component, which is more explicit and provides more flexibility. Additionally, the page_switch component has to wrap the yield in order to support dynamic page transitions:

matestack/some/vue_js/app/layout.rb

class Some::VueJs::App::Layout < Matestack::Ui::Layout

  def response
    h1 "Demo VueJs App"
    matestack_vue_js_app do # <-- this one
      main do
        page_switch do # <-- and this one
          yield
        end
      end
    end
  end

end

Using these components will add the original wrapping DOM structures which enables loading state animations.

The new <matestack-component-template> will be rendered coming from these two new components

<body> <!-- coming from rails layout if specified -->
  <div id="matestack-ui"> <!-- coming from rails layout if specified -->
    <h1>Demo VueJs App</h1>
    <matestack-component-template> <!-- new tag rendered since 3.0, should not break anything -->
      <div class="matestack-app-wrapper">
        <main>
          <matestack-component-template> <!-- new tag rendered since 3.0, should not break anything -->
            <div class="matestack-page-container">
              <div class="matestack-page-wrapper">
                <div> <!--this div is necessary for conditonal switch to async template via v-if -->
                  <div class="matestack-page-root">
                    your page markup
                  </div>
                </div>
              </div>
            </div>
          </matestack-component-template>
        </main>
      </div>
    </matestack-component-template>
  </div>
</body>

MatestackUiCore is now MatestackUiVueJs

Following the repo/gem split, the Vue.js related libary is now called MatestackUiVueJs

// import MatestackUiCore from 'matestack-ui-core'
import MatestackUiVueJs from 'matestack-ui-vuejs' // Replace import statement

// MatestackUiCore.eventHub.$emit('some-event')
MatestackUiVueJs.eventHub.$emit('some-event') // -> Replace references

Protip: Search & Replace

App Definition and Mount

javascript/packs/application.js

import { createApp } from 'vue'
import MatestackUiVueJs from 'matestack-ui-vuejs'

const appInstance = createApp({})

document.addEventListener('DOMContentLoaded', () => {
  MatestackUiVueJs.mount(appInstance) // use this mount method
})

Custom Component Registration

some/component/file.js

import MatestackUiVueJs from 'matestack-ui-vuejs'

const myComponent = {
  mixins: [MatestackUiVueJs.componentMixin],
  template: MatestackUiVueJs.componentHelpers.inlineTemplate,
  data() {
    return {
      foo: "bar"
    };
  },
  mounted(){
    console.log("custom component mounted")
  }
};

export default myComponent

javascript/packs/application.js

import { createApp } from 'vue'
import MatestackUiVueJs from 'matestack-ui-vuejs'

import myComponent from 'some/component/file.js' // import component definition from source

const appInstance = createApp({})

appInstance.component('my-component', myComponent) // register at appInstance

document.addEventListener('DOMContentLoaded', () => {
  MatestackUiVueJs.mount(appInstance)
})

VueJs Component Template

  • For application components, apply template: MatestackUiVueJs.componentHelpers.inlineTemplate

some/component/file.js

import MatestackUiVueJs from 'matestack-ui-vuejs'

const myComponent = {
  mixins: [MatestackUiVueJs.componentMixin],
  template: MatestackUiVueJs.componentHelpers.inlineTemplate, // this one!
  data() {
    return {
      foo: "bar"
    };
  },
  mounted(){
    console.log("custom component mounted")
  }
};

export default myComponent
  • Only for core components

    • add import import componentHelpers from 'some/relative/path/to/helpers'

    • and then apply template: componentHelpers.inlineTemplate

Component Scope Prefix

Use vc. (short for vue component) in order to prefix all

  • Properties

  • References

  • Or method calls

within your vue.js component response.

some/component/file.js

import MatestackUiVueJs from 'matestack-ui-vuejs'

const myComponent = {
  mixins: [MatestackUiVueJs.componentMixin],
  template: MatestackUiVueJs.componentHelpers.inlineTemplate,
  data() {
    return {
      foo: "bar"
    };
  },
  mounted(){
    console.log(this.foo) // --> bar
    // or:
    console.log(vc.foo) // --> bar
  }
};

export default myComponent
class Components::MyComponent < Matestack::Ui::VueJsComponent
  vue_name "my-component"

  def response
    div do
      plain "{{foo}}" # --> undefined!
      plain "{{vc.foo}}" # --> bar
    end
  end
end

Component $refs

  • use this.getRefs() instead of this.$refs

  • use matestack_ui_vuejs_ref() when applying refs to your component template:

def response
    # ...
    div ref: "some-ref" # <-- not picked up by this.getRefs()
    # ...
    div "matestack-ui-vuejs-ref": matestack_ui_vuejs_ref('some-ref')
end

Component $el

Use this.getElement() instead of this.$el in order to get the root element defined in your response method.

Protip: Search & Replace

Additional Note:

Use this.getTemplateElement() in order to get the template element (matestack-component-template tag) wrapping the root element defined in your response method.

Component beforeDestroy Hook

beforeDestroy was renamed to beforeUnmount within vue3.

Protip: Search & Replace

Component destroyed Hook

destroyed was renamed to unmounted within vue3.

Protip: Search & Replace

$set and Vue.set

this.$set and Vue.set are removed in vue3 as they are no longer required for proper reactivity binding. If you use these methods, use plain JavaScript mutations instead.

Vuex store dependency removed

Previously a Vuex store was required and by default available. Now it's optional

You can add a store manually following the official Vuex 4.x docs

Last updated