VueDose https://vuedose.tips Learn the best tips and tricks about Vue.js, Nuxt and JavaScript and find the best articles about Web Development Sun, 23 Apr 2023 04:11:38 GMT http://blogs.law.harvard.edu/tech/rss https://github.com/nuxt-community/feed-module 3D Accessibility Advanced API Best Practices Components Composition API CSS Deploy Devtools DX Gaming Headless CMS i18n JAM Stack Library Nuxt SEO Testing UI Use Cases Video Vue 3 Vue.js Vuex Web Performance <![CDATA[The new Provide and Inject in Vue 3]]> https://vuedose.tips/the-new-provide-inject-in-vue-3/ https://vuedose.tips/the-new-provide-inject-in-vue-3/ Mon, 18 Jul 2022 02:00:00 GMT In Vue we use props to pass data between components.

However, in more complex applications, we may need to pass data across multiple layers to a lower level component. In such cases, components pass certain props down without needing them themselves. This pattern is known as prop drilling.

For developers of plugins or component libraries, problems also arise with providing data. These are even more severe because of the lack of access to the consuming application.

These problems can be bypassed by the two methods provide and inject. Through so-called dependency providers, data can be passed to any lower-level component, no matter how deep it is. Completely without prop drilling.

import { provide } from 'vue'

export default {
  setup() {
    provide(/* key */ 'count', /* value */ 0)
  }
}
<Parent>
  <Child>
    <Grandchild><Grandchild>
  </Child>
</Parent>
import { inject } from 'vue'

export default {
  setup() {
    const count = inject(/* key */ 'count')
    console.log(count) // 0
  }
}

However, this becomes particularly useful when working with reactive data and methods. For example, consider a table component whose columns can be defined by components rather than props:

<MyTable>
  <MyColumn key="id" label="Identifier" />
  <MyColumn key="name" label="First name" :sortable="true" />
  <MyColumn key="email" label="Email address" :sortable="true" />
</MyTable>

We could create such functionality by using provideand inject:

import { provide, ref } from 'vue'

export default {
  setup() {
    const columns = ref<Column[]>([])
    
    provide('TableKey', {
      columns,
    })
  }
}
import { inject } from 'vue'

export default {
  props: ['key', 'label', 'sortable']
  setup(props) {
    const table = inject('TableKey')
    table?.columns.push(props)
  }
}

If we take a closer look at this example, we can see several pitfalls that should be avoided right from the start. For this, Vue offers some recommendations and optimization options.

Symbol and relocation of keys

The key used is specified as an inline string and therefore does not scale well. We cannot ensure that nobody else uses this key or that we do not mistype. Therefore it is a good idea to store and export the keys in a separate file and make them more collision-proof by using a symbol declaration (see MDN).

// keys.ts
export const MY_TABLE_KEY = Symbol('MY_TABLE_KEY')

// in provider component
import { MY_TABLE_KEY } from '@/keys'
provide(MY_TABLE_KEY, {...})

// in injector component
import { MY_TABLE_KEY } from '@/keys'
const injected = inject(MY_TABLE_KEY)

Data typing

However, the use of constants does not help with proper typing. provide and inject are usually defined in different components, so we can never really be sure in both places that we are not mistyping and using the data correctly. Did we really name the variable in MyTable columns or was it cols after all?

Therefore the type InjectionKey should be used. This will ensure synchronization of the type between the provider and consumer.

Translated with www.DeepL.com/Translator (free version)

// keys.ts
import { InjectionKey } from 'vue';
import { TableConfig } from '@/types';
export const MY_TABLE_KEY: InjectionKey<TableConfig> = Symbol('MY_TABLE_KEY');

// in provider component... Type Error
provide(MY_TABLE_KEY, {});

// in injector component
const injected = inject(MY_TABLE_KEY) // typed as TableConfig | undefined
injected.columns // safe access through autocompletion

Default values and guaranteed provision of data

Since there is no guarantee that provide was actually called further up the tree, the return value of inject is always nullable. inject allows a second parameter, which makes it possible to specify a default value, but this does not help us to map the desired functionality and is only useful for static data.

Therefore it is recommended to write a helper function for inject which checks if the data was really provided and throws an exception otherwise.

function requireInjection<T>(key: InjectionKey<T>, defaultValue?: T) {
  const resolved = inject(key, defaultValue);
  if (!resolved) {
    throw new Error(`${key} was not provided.`);
  }
  return resolved;
}

If we now use this helper function in MyColumn, we can access the data completely safely.

import { inject } from 'vue'
import { MY_TABLE_KEY } from '@/keys'
import { requireInjection } from '@/utils'

export default {
  props: ['key', 'label', 'sortable']
  setup(props) {
    const table = requireInjection(MY_TABLE_KEY)
    // autocompletion and without optional chaining, since it is safe to use
    table.register(props) 
  }
}

Immutability

Finally, we turn our attention to the reactive property columns. The provided data should always be protected from direct manipulation to ensure traceability and a consistent data flow. For this we use the readonly function. Functions (see register) should be provided by which a controlled manipulation in the provider can be guaranteed.

import { provide, ref, readonly } from 'vue'
import { MY_TABLE_KEY } from '@/keys'

export default {
  setup() {
    const columns = ref([])

    function register(column: Column) {
      columns.value.push(column)
    }
    
    provide('TableKey', {
      columns: readonly(columns),
      register
    })
  }
}

Wrapping Up

At this point, you already know how you can make your components more flexible and independent by using provide/inject. Tell me, where do you plan to use them?

In Vue.js Conf we'll cover this topic in depth and many others, from Pinia by its author Eduardo (Posva) to testing and sharing all news on the Vue ecosystem.

Will I see you in Berlin? 😉 As a VueDose reader, grab your 20% discounted ticket if you don't have it yet!

]]>
<![CDATA[The 101 guide to Script Setup in Vue 3]]> https://vuedose.tips/the-101-guide-to-script-setup-in-vue-3/ https://vuedose.tips/the-101-guide-to-script-setup-in-vue-3/ Mon, 20 Jun 2022 08:01:00 GMT With the release of the Composition API, the new component option setup has also made its way into Vue. With this, we no longer need other options in most cases and write all our code within this function. The idea of <script setup is that you want to get rid of the unnecessary wrapper and the other old component options so you can write components simpler and focused.

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const count = ref<number>(0)

    function increment() {
      count.value++
    }

    return {
      count,
      increment
    }
  }
})
</script>
<script setup lang="ts">
import { ref } from 'vue'

const count = ref<number>(0)

function increment() {
  count.value++
}
</script>

With the <script setup> syntax we can write our component logic more compactly. Even a return statement is no longer needed. Every variable and method we define is automatically provided in the template.

We can also use imported data like functions and even components directly. Components don`t even have to be registered separately anymore:

<script setup lang="ts">
import { ref } from 'vue'
import { format } from '@/utils/currency'
import MyComponent from '@/components/MyComponent.vue'

const count = ref<number>(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">{{ format(count) }}</button>
  <MyComponent />
</template>

Props and Events

To declare options like props and emits we have to use so called compiler macros which are automatically available inside <script setup>.

<script setup lang="ts">
const props = defineProps({
  msg: String
})

const emit = defineEmits(['update', 'remove'])
</script>

The two functions do not need to be imported and are compiled away when <script setup> is preprocessed.

defineProps takes the same value as the props option, while defineEmits takes the same value as the emits option.

If we are already using TypeScript in our application anyway, then we can declare props and emits with a plain type syntax as well:

const props = defineProps<{
  msg: string
  count?: number
}>()

const emit = defineEmits<{
  (e: 'update', id: number): void
  (e: 'remove'): void
}>()

Both approaches cannot be combined, i.e. either we use the generic or the function brackets syntax, but never both at the same time. If we decide to go the TypeScript way, it is important to mention that we cannot currently use imported types or interfaces, but only type literals (see example) or types/interfaces defined in the same file.

In the case of events, we can specify the parameters more precisely ourselves and thus write type-safe code.

Default values for props

Above in the example we have marked the prop count as optional, but here we lack a possibility to specify a default value. For this reason there is another compiler macro called withDefaults.

interface Props {
  msg: string
  count?: number
}

const props = withDefaults(defineProps<Props>(), {
  count: 0
})

If withDefaults is not used, but a prop is marked as optional in the type, Vue treats this prop as required.

Using Slots and Attributes

Normally, slots and attributes in components are used directly in the template via $slots and $attrs and rarely if ever needed in script. If it should be necessary nevertheless, Vue provides appropriate functions with useSlots and useAttrs.

<script setup lang="ts">
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

Use additional options

In some cases it is necessary that we declare custom options in our components. Vuelidate's validations option is just one popular example. These other options, which include setting a component name via name, are not possible with <script setup>.

The solution to these problems is to use an additional script tag. The two blocks are then automatically merged when the *.vue file is compiled.

<script>
// executed only once
export const componentName = 'MyComponent';

export default {
	name: componentName
  inheritAttrs: false,
  customOptions: {}
}
</script>

<script setup>
// executed for each component instance
</script>

Access from the outside?

If we use <script setup> every variable and method we define is automatically provided in the template, but the component itself is closed to the outside. So we cannot access the properties and functions declared inside <script setup> via the instance of a component, for example when using $parent or template refs.

To explicitly expose something, we need to use another compiler macro: defineExpose.

<script setup lang="ts">
import { ref } from 'vue'

const count = ref<number>(0)

function increment() {
  count.value++
}

defineExpose({
  count,
  increment
})
</script>

Top-level await

In components we often need to request data asynchronously from a service. In <script setup> we have the await keyword available for this at the top level.

A component defined in this way must be used with the suspense component so that Vue can take care of resolving the asynchrony and load the component properly.

<script setup>
const user = await fetch(`/users/1`).then((d) => d.json())
</script>
<template>
  <Suspense>
    <AsyncComponent />
  </Suspense>
</template>

Warning: Suspense is currently still an experimental feature, which is not recommended for productive applications.

Availability outside of Vue 3

If you are not lucky enough to be working with Vue 3 already, you will be happy to know that there is a fantastic plugin that makes <script setup> available in Vue 2 as well as Nuxt, Vite or the Vue CLI: https://github.com/antfu/unplugin-vue2-script-setup

]]>
<![CDATA[Hybrid Rendering: the secret way to smoothly test Vue.js components]]> https://vuedose.tips/hybrid-rendering-the-secret-way-to-test-components-in-vuejs/ https://vuedose.tips/hybrid-rendering-the-secret-way-to-test-components-in-vuejs/ Mon, 07 Mar 2022 07:00:00 GMT In the article Deep vs Shallow Rendering in Vue.js Tests I showed you how deep and shallow rendering works to create Vue.js tests.

Each of them have their own use case and it could depend in your way of testing and architecting as well.

But… why not having the best of both worlds? Let’s get into Hybrid Rendering.

The Hybrid Rendering consists on stubbing only part of the child components on a test.

For instance, taking back the example from the tip, let's add another component called FoodList to the App component:

<template>
  <div>
    <h3>User List</h3>
    <UserList :users="['Rob Wesley']" />
    <FoodList :foods="['Tomatoes', 'Carrots']" />
  </div>
</template>

<script setup>
  import UserList from "./UserList";
  import FoodList from "./FoodList";
</script>

Assuming FoodList has a similar implementation to UserList, and we're using deep rendering, this test:

import { mount } from "@vue/test-utils";
import App from "@/App";

describe("App.vue", () => {
  it("Deep renders the App component", () => {
    const wrapper = mount(App);
    expect(wrapper.html()).toMatchSnapshot();
  });
});

will result in the following snapshot:

<div>
  <h3>User List</h3>
  <ul>
    <li>
      Rob Wesley
    </li>
  </ul>
  <ul>
    <li>
      Tomatoes
    </li>
    <li>
      Carrots
    </li>
  </ul>
</div>

Now, let's say that for whatsoever reason you want only to deep render UserList, but not FoodList.

In that case, you can use the stubs option of the mount method in order to indicate which components must be stubbed:

import { mount } from "@vue/test-utils";
import App from "@/App";

describe("App.vue", () => {
  it("Hybrid renders the app component", () => {
    const wrapper = mount(App, { stubs: ["FoodList"] });
    expect(wrapper.html()).toMatchSnapshot();
  });
});

Notice I'm stubbing FoodList. That's the way to apply hybrid rendering in a test, or partial shallow/deep rendering if you're more comfortable with that name.

The generated snapshot in this case will be like:

<div>
  <h3>User List</h3>
  <ul>
    <li>
      Rob Wesley
    </li>
  </ul>
  <foodlist-stub foods="Tomatoes,Carrots"></foodlist-stub>
</div>

Keep in mind that this won't work with shallowMount, since it already stubs all child components. So make sure to use mount. Learn more in the Stubs and Shallow Mount section on the official docs

Do you have any case in mind where this technique can be useful in your projects? Let me know on Twitter!

That's it for today's tip!

Stay cool 🦄

]]>
<![CDATA[How to use script setup in Nuxt 2]]> https://vuedose.tips/how-to-use-script-setup-in-nuxt-2/ https://vuedose.tips/how-to-use-script-setup-in-nuxt-2/ Mon, 14 Feb 2022 15:00:00 GMT If you’re like me and love ❤️ the new “script setup” syntax, you’ve probably been wondering when we could start using it on our current Nuxt (2) projects. Well, the wait is over! Thanks to the Nuxt team, now it’s possible. Let’s see how, but first...

What is script setup?

<script setup> refers to the latest Vue API for authoring components. It is the recommended syntax if you're using the Composition API in Single File Components (SFC). This is what it looks like:

<script setup>
const msg = 'Hello world'
</script>

<template>
  <p>{{ msg }}</p>
</template>

How to use it in a Nuxt 2 app:

If you want to use it in an existing Nuxt (v2) application, you have two options:

Option 1: Composition API Nuxt module

Starting in v0.28.0, the Composition API Nuxt module includes unplugin-vue2-script-setup which enables the <script setup> syntax out of the box.

First, you will need to install the Composition API Nuxt module if you don’t have it yet (or update it to v0.28.0 or higher). You can see the docs here.

npm i @nuxtjs/composition-api

Then, add it in nuxt.config.js, under buildModules:

{
  buildModules: [
    // https://composition-api.nuxtjs.org/getting-started
    '@nuxtjs/composition-api/module',
  ],
}

After that, you can start using <script setup> in Nuxt component and pages:

<template>
  <p>{{ awesome }}</p>
</template>

<script setup>
// *In Nuxt 3 apps you can omit importing Composition API methods*
import { ref } from '@nuxtjs/composition-api';

const awesome = ref('Look, ma, script setup!!')
</script>

That’s it!

Only downside I see is that adding a JavaScript library makes your JS bundle bigger. So, it needs to be justified. More on that later.

Option 2: Nuxt Bridge

Nuxt Bridge is a Nuxt module that allows you to use the Nuxt 3 features of tomorrow into your Nuxt 2 application today. It sits between Nuxt v2 and v3, backporting Nuxt v3 features into Nuxt v2. Straight from the docs:

Using Nuxt Bridge, you can make sure your project is (almost) ready for Nuxt 3 and have the best developer experience without needing a major rewrite or risk breaking changes.

Migrating your Nuxt app to Nuxt Bridge also requires changing your nuxt dependency into nuxt-edge@latest. Fortunately, the Nuxt team has put together an installation guide. I recommend you follow all the steps described in that link.

Regarding the current status of the different Nuxt builds –Nuxt 2, Nuxt Bridge, Nuxt 3– you can refer to this table for a comparison between them. At least, at the current state, the Nuxt team agrees that:

Nuxt Bridge is more stable than Nuxt 3 at the moment (– Feb 2022)

Conclusion

At least for me, <script setup> is *the way* of writing Vue components from now on.

  • For me, it’s more fun to follow function composition patterns. Basing your logic on pure functions makes the app easier to test and maintain.
  • Generally, you will need fewer lines of code to achieve the same results as we used to with the Options API.
  • Everything comes up nicely organised if you group your logic by features.
  • Script setup also brings a better developer experience because it has a better TypeScript IDE integration.
  • One of the biggest benefits for me is that I no longer need to remember to add things in the returned object of the setup() function with the traditional Composition API. 😅

Being able to use this modern syntax today in a Nuxt 2 app is a complete bliss. ✨🚀

Demo

I’ve prepared a live demo so you can play, watch, and even break the code all by yourself.

]]>
<![CDATA[When to use a Vue Render Function]]> https://vuedose.tips/when-to-use-a-vue-render-function/ https://vuedose.tips/when-to-use-a-vue-render-function/ Thu, 27 Jan 2022 12:25:00 GMT There are very few cases where you should be using a render function instead of regular templates to create components in Vue.js. And the Render Functions API got slimmer in Vue 3. In this article, we’re going to see a few cases where the use of render functions is justified. By the way, if you need an introduction to render functions, make sure you read my previous post on Render Functions.

This article is going to show you some advanced patterns, so strap in and bear with me!

TL;DR: In my opinion, you should only use render functions when you want to intercept the data passed to the component and change it, and only if you cannot do it with regular template functions and props or similar approaches.

So, please, if you can use a template, use it. You and anyone reading the code will thank me later!

Let’s see a few render functions use-cases. And stay until the end to see my favourite one!

Intercepting HTML attributes

One of the use-cases I’ve found while developing Design Systems based on CSS classes (ahem, Tailwind) is that you want to make certain classes appear alongside other classes in your DS. And if you want to validate that the classes that are passed are consistent with what you expect, you cannot do that with templates, unless you access the DOM or pass classes via props...

Let me show you the principle with a live example: <br/><br/> <iframe loading="lazy" style="border:0; border-radius: 4px; overflow:hidden;" src="https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiwgZGVmaW5lQ29tcG9uZW50LCBoLCB1c2VBdHRycywgdXNlU2xvdHMgfSBmcm9tICd2dWUnXG5cbmNvbnN0IE15Q29tcG9uZW50ID0gZGVmaW5lQ29tcG9uZW50KHtcbiAgcmVuZGVyKCkge1xuICAgIHJldHVybiBoKCdkaXYnLCB7fSwgdGhpcy4kc2xvdHMuZGVmYXVsdCgpKVxuICB9XG59KVxuY29uc3QgTXlDc3NDbGFzc0ludGVyY2VwdG9yQ29tcG9uZW50ID0gZGVmaW5lQ29tcG9uZW50KGZ1bmN0aW9uIE15Q3NzQ2xhc3NJbnRlcmNlcHRvckNvbXBvbmVudCgpIHtcbiBjb25zdCBhdHRycyA9ICB1c2VBdHRycygpXG4gY29uc3Qgc2xvdHMgPSB1c2VTbG90cygpXG4gXG4gcmV0dXJuICgpID0+IGgoXG4gICAnc3BhbicsXG4gICB7XG4gICAgIC4uLmF0dHJzLFxuICAgICBjbGFzczogYXR0cnMuY2xhc3MuaW5jbHVkZXMoJ3JlZCcpID8gJ2JsdWUnIDogJydcbiAgIH0sXG4gICBzbG90cy5kZWZhdWx0KClcbiApXG5cbn0pXG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8bXktY29tcG9uZW50IGNsYXNzPVwicmVkXCI+aGVsbG88L215LWNvbXBvbmVudD5cbiAgPE15Q3NzQ2xhc3NJbnRlcmNlcHRvckNvbXBvbmVudCBjbGFzcz1cImdyZWVuXCI+aGVsbG88L015Q3NzQ2xhc3NJbnRlcmNlcHRvckNvbXBvbmVudD5cbiAgPGJyIC8+XG4gIDxNeUNzc0NsYXNzSW50ZXJjZXB0b3JDb21wb25lbnQgY2xhc3M9XCJyZWRcIj5oZWxsbzwvTXlDc3NDbGFzc0ludGVyY2VwdG9yQ29tcG9uZW50PiAmbHQ7LS0gcGFzc2luZyBjbGFzcyByZWQ/IE5haCwgYWgsIGJldHRlciBtYWtlIGl0IGJsdWUgKHRoYW5rcyB0byB0aGUgY2FzY2FkZSkhXG4gIDxiciAvPlxuPC90ZW1wbGF0ZT5cblxuPHN0eWxlPlxuICAucmVkIHtcbiAgICBjb2xvcjogcmVkO1xuICB9XG4gIC5ncmVlbiB7XG4gICAgY29sb3I6IGdyZWVuO1xuICB9XG4gIC5ibHVlIHtcbiAgICBjb2xvcjogYmx1ZTtcbiAgfVxuPC9zdHlsZT4iLCJpbXBvcnQtbWFwLmpzb24iOiJ7XG4gIFwiaW1wb3J0c1wiOiB7XG4gICAgXCJ2dWVcIjogXCJodHRwczovL3VucGtnLmNvbS9AdnVlL3J1bnRpbWUtZG9tQDMuMi4yOS9kaXN0L3J1bnRpbWUtZG9tLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSJ9" width="100%" height="400px"></iframe> <br/><br/>

As you can see, we’re “reading" the classes passed to the component via attrs, and if the class passed is red, we make add blue. When would you want to do such a thing? To enforce consistency or to add your own logic. Imagine you want to have something like this:

  • Every time someone passes the .x class, you want to make sure .y class is also present.

So, this pattern is probably only interesting for components library authors or Design System authors, and I recognise it, it’s an edge-case probably you won’t even encounter.

Note: did you notice how we’re creating components inside <script setup> and defineComponent? Also, the second component is returning a function. This way, we can use useAttrs and useSlots inside of it (instead of this.slots). And then, by returning a function, it’s treated as a render function.

Unfortunately for us, in Vue 3, the class object is read-only, so you can only add classes and not remove existing ones. But it’s an example of what you can do with render functions. And the same way we have intercepted the class attribute, you could intercept other HTML attributes.

Validating slots for Accessibility

Again, let’s consider you’re creating you’re own Design System. And you want to provide a11y and usability hints when someone does something funky in the template. For example:

  • By spec, the a element cannot contain any interactive (clickable) elements.

So, what if we try to warn our library users of an incorrect markup, in development, before they even need to check it? <br/><br/> <iframe loading="lazy" style="border:0; border-radius: 4px; overflow:hidden;" src="https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiwgZGVmaW5lQ29tcG9uZW50LCBoLCB1c2VBdHRycywgdXNlU2xvdHMgfSBmcm9tICd2dWUnXG5cbmNvbnN0IE15Q29tcG9uZW50ID0gZGVmaW5lQ29tcG9uZW50KHtcbiAgcmVuZGVyKCkge1xuICAgIHJldHVybiBoKCdkaXYnLCB7fSwgdGhpcy4kc2xvdHMuZGVmYXVsdCgpKVxuICB9XG59KVxuY29uc3QgTXlDc3NDbGFzc0ludGVyY2VwdG9yQ29tcG9uZW50ID0gZGVmaW5lQ29tcG9uZW50KGZ1bmN0aW9uIE15Q3NzQ2xhc3NJbnRlcmNlcHRvckNvbXBvbmVudCgpIHtcbiBjb25zdCBhdHRycyA9ICB1c2VBdHRycygpXG4gY29uc3Qgc2xvdHMgPSB1c2VTbG90cygpXG4gXG4gcmV0dXJuICgpID0+IGgoXG4gICAnc3BhbicsXG4gICB7XG4gICAgIC4uLmF0dHJzLFxuICAgICBjbGFzczogYXR0cnMuY2xhc3MuaW5jbHVkZXMoJ3JlZCcpID8gJ2JsdWUnIDogJydcbiAgIH0sXG4gICBzbG90cy5kZWZhdWx0KClcbiApXG5cbn0pXG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8bXktY29tcG9uZW50IGNsYXNzPVwicmVkXCI+aGVsbG88L215LWNvbXBvbmVudD5cbiAgPE15Q3NzQ2xhc3NJbnRlcmNlcHRvckNvbXBvbmVudCBjbGFzcz1cImdyZWVuXCI+aGVsbG88L015Q3NzQ2xhc3NJbnRlcmNlcHRvckNvbXBvbmVudD5cbiAgPGJyIC8+XG4gIDxNeUNzc0NsYXNzSW50ZXJjZXB0b3JDb21wb25lbnQgY2xhc3M9XCJyZWRcIj5oZWxsbzwvTXlDc3NDbGFzc0ludGVyY2VwdG9yQ29tcG9uZW50PiAmbHQ7LS0gcGFzc2luZyBjbGFzcyByZWQ/IE5haCwgYWgsIGJldHRlciBtYWtlIGl0IGJsdWUgKHRoYW5rcyB0byB0aGUgY2FzY2FkZSkhXG4gIDxiciAvPlxuPC90ZW1wbGF0ZT5cblxuPHN0eWxlPlxuICAucmVkIHtcbiAgICBjb2xvcjogcmVkO1xuICB9XG4gIC5ncmVlbiB7XG4gICAgY29sb3I6IGdyZWVuO1xuICB9XG4gIC5ibHVlIHtcbiAgICBjb2xvcjogYmx1ZTtcbiAgfVxuPC9zdHlsZT4iLCJpbXBvcnQtbWFwLmpzb24iOiJ7XG4gIFwiaW1wb3J0c1wiOiB7XG4gICAgXCJ2dWVcIjogXCJodHRwczovL3VucGtnLmNvbS9AdnVlL3J1bnRpbWUtZG9tQDMuMi4yOS9kaXN0L3J1bnRpbWUtZG9tLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSJ9" width="100%" height="400px" ></iframe> <br/><br/> I can see how this kind of message could be useful in a big Design System that focuses on accessibility (and why shouldn’t you, right?).

Rendering dynamic templates

You’ve probably encountered this problem while developing an app. You receive a string from some Backend or API that contains a template or some HTML that needs to interpolate some data. For example, it’s commonly needed for internationalisation (i18n) or rendering dynamic dashboards.

Note: As you might have thought, it is an alternative API to Vue.compile. And for any compilation during runtime, you would need to load the Vue “build" that includes the compiler, so bear in mind it produces a heavier JS bundle and you’ll need to add it into your configuration manually.

For this example, I will showcase an example I had up my sleeve, using Vue 2: <br/><br/> <iframe loading="lazy" src="https://codesandbox.io/embed/vue-2-vue-compiled-templates-on-the-fly-rvbvj?file=/src/App.vue&fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:400px; border:0; border-radius: 4px; overflow:hidden;" title="vue-2-vue-compiled-templates-on-the-fly" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

As you can see, it could be a replacement for v-html if you need to transpile during runtime some expressions or if you need to compile at runtime components. For example, the <router-view> is also a very common case. Notice how in my example I'm not implementing methods/events. I just wanted to showcase the main idea.

This pattern is actually the principle behind Alex Jover's v-runtime-template package and many other similar ones. And now you know, it’s not magic at all 😉!

Mandatory note: ❗️ Be careful about rendering user inputs (avoid it or sanitize them), as it can lead to an XSS attack.

Conclusion

These are some of the use-cases of render functions. I hope you have seen something new or you have learned something! 😊 Let me know other use cases of render functions you know about!

Also, remember, when possible, use templates instead!

]]>
<![CDATA[Nuxt 3 + Storyblok for a Sushi Recipes Website]]> https://vuedose.tips/nuxt-3-storyblok-sushi-website/ https://vuedose.tips/nuxt-3-storyblok-sushi-website/ Wed, 15 Dec 2021 18:55:00 GMT What happens when you combine the versatility of the NuxtJS Framework with a top-notch headless CMS solution like Storyblok?

The result is a sparkling website with a ridiculously easy content editing experience that deploys seamlessly on your cloud infrastructure of choice. Sounds amazing right?

In this article, we are going to learn how to combine both technologies using the Storyblok Nuxt module. The format of this post is going to be different than usual, we will go section by section checking the content of my last video along with the repository here.

@video

What the... is headless?

"Off with their heads!" said the Queen of Hearts! This phrase could come to your mind when thinking of headless, but it has a slightly different meaning (kinda) when we are talking about CMS solutions.

Do you remember those old, monolithic, self-hosted websites solutions where the BE (Back-end), the FE (Front-end), and the content were tightly coupled?

A Headless CMS is when the presentational layer or Front-end (a.k.a the "head") and it's entirely decoupled from the content repository and the BE system (a.k.a the "body")

With a content-first approach, users can author their content through an API and deliver it to any client app, could be a website, a mobile app, you name it.

Check the complete section here 00:38

Storyblok

It's a leading technology in terms of headless CMS. What I personally like the most, is how easy is to change content, preview it and then deploy to production independently from tech stack you choose.

The developer and content experience are beautiful, something that is missing in most of the CMS out there.

https://res.cloudinary.com/alvarosaburido/image/upload/v1639302628/blog/Storyblok%20%2B%20Nuxt3/storyblok_sjjtz7.gif

For more info check here 01:22

Content structures

https://res.cloudinary.com/alvarosaburido/image/upload/v1639303735/blog/Storyblok%20%2B%20Nuxt3/storyblok-diagram_e2fzrd.png

Storyblok's way of creating content objects is quite cool. Have you ever played with Legos? They use bloks to create bigger pieces and each blok has its own set of properties (like color, width, height, specific form). This CMS uses bloks that represent components (hero, features section etc) to create bigger pieces (Stories), were ****each blok has their own set of fields (called Schema) holding your content.

https://res.cloudinary.com/alvarosaburido/image/upload/v1639303735/blog/Storyblok%20%2B%20Nuxt3/storyblok-diagram-2_qosd5n.png

There is also another important concept, Content Types, which is another type of component that represents templates for your stories. Examples of common Content Types are Post, Product, Page etc**.**

For more detailed explanation, check here 23:22

Installation

Let's create a new Nuxt project:

npx nuxi init name-of-your-project

Then we install the module:

yarn add -D @storyblok/nuxt@next axios

Check complete process here 05:08.

Setting up your Storyblok space

First you will need to create your user if you haven't already at Storyblok, then login to enter the dashboard and you will see a call to action next to the welcome hero. Click on it "Create a new space"

I will not cover the whole Dashboard explanation, if you are interested, you can check it on the video here 02:56.

https://res.cloudinary.com/alvarosaburido/image/upload/v1639307394/blog/Storyblok%20%2B%20Nuxt3/Setting_up_location_c9puvp.png

Before we continue, please go to Settings on the left sidebar, then General and where it says Location (default environment) add your localhost url, which on Nuxt is http://localhost:3000

https://res.cloudinary.com/alvarosaburido/image/upload/v1639306333/blog/Storyblok%20%2B%20Nuxt3/Screenshot_2021-12-12_at_11.50.06_wl3uvp.png

Now to link our module we will need a key, so click on API-Keys and copy your personal key from the list. ⚠️  Please be careful not to share or save this in a public place or in the repository.

Configuring the Nuxt module

Add the following code to the modules section of nuxt.config.ts and replace the accessToken with API token from Storyblok space.

import { defineNuxtConfig } from "nuxt3";

export default defineNuxtConfig({
  modules: [
    ["@storyblok/nuxt", { accessToken: "process.env.STORYBLOK_API_TOKEN" }]
    // ...
  ]
});

Safe tip here: you can create and .envfile and add there your key and the API Url

STORYBLOK_API_URL=https://api.storyblok.com/v2
STORYBLOK_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx

Make sure you don't commit the file to your repository.

As a final step, run yarn dev on the project to serve the Nuxt application and if all goes smooth you should see the <NuxtWelcome /> component.

For the complete explanation refer to this section of the video 10:18.

Creating our first page

Now is when the magic begins! Open Content on the sidebar and it will display a list of all the pages available. By default, there is a Home page already included. Do you notice that the Content Type is Page? We're going need this on a moment., but for now click on the Home item.

Do you remember when we add the default environment to http://localhost:3000? Here is why: Storyblok allows you to see the preview of your page directly for the CMS. On the right-hand sidebar you will see all the configuration for the current page, including the Bloks.

Page component on Nuxt

On your Nuxt application, create a component under /components named ThePage , which is responsible to render all our Stories and Bloks. This is connected to

<script lang="ts" setup>
const props = defineProps({
  blok: {
    type: Object,
    required: true,
  },
})
</script>

<template>
  <div v-editable="blok">
    <component
      :is="blok.component"
      v-for="blok in blok.body"
      :key="blok._id"
      :blok="blok"
    />
  </div>
</template>

Check here for the complete process 27:49

Nuxt home route

Since Nuxt has already a Router, just create an pages/index.vue to edit your Home page, this will correspond with the / path.

<script lang="ts" setup>
...
</script>
<template>
  <component
    :is="state.story.content.component"
    :key="state.story.content._uid"
    :blok="state.story.content"
  />
</template>

Since Storyblok uses a JSON data structure like this on the request

{
    "story": {
        "name": "Home",
        "slug": "home",
        "full_slug": "home",
        "id": 10718480,
        "uuid": "74a92c75-dbb6-4cb6-8c2f-b4582017c087",
        "content": {
            "_uid": "c0c5fd8d-6913-4ebf-bbed-a60d5e83dc1f",
            "component": "the-page",
            "body": [
                ...
            ],
            ...
        },
        ...
    }
}

We will use dynamic components from Vue3 a lot. Why? You see that story.content.component: 'the-page' on the response? We will pass exactly that field on our component tag <component :is="story.content.component" /> so Nuxt knows which components are associated to the story and be able to render it. Genius I know.

Then we need to fetch that JSON right? For that we're going to add some composable functions on our <script>.

import { useStoryApi, useStoryBridge } from '@storyblok/nuxt/composables'

const storyapi = useStoryApi()

const { data } = await storyapi.get('cdn/stories/home', {
  version: 'draft',
})

const state = reactive({
  story: data.story,
})

We use the useStoryApi to fetch directly to Storyblok cdn, and get the content of our Story (Home). For now let's fetch the content on draft instance (check content versions here)

Now we need to tell our Nuxt app to listen to changes made on the CMS dashboard and update the content, for that we are gonna use useStoryBridge on the mounted hook.

onMounted(() => {
  useStoryBridge(state.story.id, event => {
    state.story = event
  })
})

Check here 31:41

Creating Bloks (components)

On the left sidebar go to Components, you will see a list of available ones (Storyblok gives you some default ones). Remember that we talk about the Page component which is a Content Type on the previous section? If you click on it you will be able to check it's Schema, which contains a field called body (check template of /components/ThePage.vue 😜 ) .

I will rename it to the-page to align with the name of the component on Nuxt, but by default is page.

https://res.cloudinary.com/alvarosaburido/image/upload/v1639311385/blog/Storyblok%20%2B%20Nuxt3/Screenshot_2021-12-12_at_13.15.59_ge5ynr.png

Now click con New to create a Blok**,** give it a name (This name need to be kebab-case and the same as your Nuxt component to be able to render), I will call it the-hero 🦸🏻‍♀️ , make sure you check the Nestable checkbox before hitting Next.

On the tab Schema is where we are gonna define the properties to hold our content. since it's a Hero Component we will need:

  • Headline
  • Description
  • CTA's
  • Image

To add a new field just enter a key value and click the button + Add. By default it will create a simple Text Field, but if you click the field you can change its type (For more info check the Field Types docs).

https://res.cloudinary.com/alvarosaburido/image/upload/v1639323102/blog/Storyblok%20%2B%20Nuxt3/storyblok-field-types.png

Hero component on Nuxt

Similar to our ThePage, we are gonna create another component called TheHero with the following code.

<script lang="ts" setup>
const props = defineProps({
  blok: {
    type: Object,
    required: true,
  },
})
</script>
<template>
  <section class="hero w-full py-16">
    <h1 class="font-display text-shrimp-400 font-extrabold text-3xl mb-12">
       {{ blok.headline }}
    </h1>
    ...
	</section>
</template>

All components need to have a prop called blok which is going to contain the actual component Schema from the JSON response done at page level.

To see the complete explanation + markup check the video at minute 34:30.

Final result

Go back to your Home Story, on the right sidebar you will see a + Add block under the field label Body, click there and add your recently created component, once you do, click in it and add the content of each field you created before on the Schema.

If all went well (if not, you can always download the repo and that's it 😜) and you create your markup and style like in the video you should be able to see something like this:

https://res.cloudinary.com/alvarosaburido/image/upload/v1639324044/blog/Storyblok%20%2B%20Nuxt3/Screenshot_2021-12-12_at_16.47.16_cklinu.png

Try to edit something and see how it updates automatically on the Preview on the left. That's what I call real-time editing 😱.

Wrap up

Storyblok + Nuxt3 is a powerful combination to deliver a modern and flexible CMS experience to your clients and developers. Some people say it's impossible to have them both happy at the same time. Well, now you can 😉

I hope you enjoyed the article + video. See you around

Happy coding!

]]>
<![CDATA[Going 3D with Trois.js and Vue 3]]> https://vuedose.tips/going-3d-with-trois-js-and-vue-3/ https://vuedose.tips/going-3d-with-trois-js-and-vue-3/ Tue, 16 Nov 2021 11:00:00 GMT Trois.js allows you to bring the 3D magic of Three.js into the modern world of Vite.js ⚡️ and Vue3 💚.

In this article, we are going to learn the basics. I will assume you have some notion about 3D before continuing. You can find the complete repo here.

yarn create vite troisjs-example --template vue

Add Trois.js to the project:

yarn add three@0.127 troisjs

You can install it as a plugin:

import { createApp } from 'vue';
import { TroisJSVuePlugin } from 'troisjs';
const app = createApp(App);
app.use(TroisJSVuePlugin);

Or importing the resources directly into your component (This enable tree shaking and typescript support):

import { defineComponent } from 'vue';
import { Box, Camera, LambertMaterial, PointLight, Renderer, Scene } from 'troisjs';
export default defineComponent({
  components: { Box, Camera, Renderer, Scene, PointLight, LambertMaterial },
});

Core components

The more basic example const of 3 core components:

  • Render: enables to perform 3D rendering in an HTML canvas.
  • Camera: that uses perspective projection. This projection mode is designed to mimic the way the human eye sees.
  • Scene: Scenes allow you to set up what and where is to be rendered by three.js. This is where you place objects, lights and cameras.
<script lang="ts">
import {
  PointLight,
  Box,
  Camera,
  Renderer,
  Scene,
  LambertMaterial,
} from 'troisjs';
import { defineComponent } from 'vue';

export default defineComponent({
  components: { Box, Camera, Renderer, Scene, PointLight, LambertMaterial },
});
</script>

<template>
  <Renderer resize="window" orbit-ctrl ref="renderer">
    <Camera :position="{ z: 10 }" />
    <Scene background="#4DBA87">
      <PointLight :position="{ y: 50, z: 50 }" />
      <Box ref="box">
        <LambertMaterial />
      </Box>
    </Scene>
  </Renderer>
</template>

To break down a little bit the code above, we set a Renderer component as a wrapper, we add resize="window" to make the canvas responsive, orbit-ctrl allows you to use the mouse to change the camera position (Initially on z: 10) dynamically, so you can rotate the object as you wish.

Inside Scene, we have PointLight which is a component that enables a light that gets emitted from a single point in all directions (think of it as a bare lightbulb 💡) and a Box mesh with a LambertMaterial inside as a slot (non-shiny surface without specular highlights).

Use yarn dev to see the result on the browser:

https://res.cloudinary.com/alvarosaburido/image/upload/v1633253957/blog/Going%203d%20with%20Troisjs/troisjs-cube-example.png

Render loop

Trois.js uses the requestAnimationFrame loop under the hood to render the scene. Usually runs at 60fps.

Let's use it to give some life to the box once the scene is rendered, first of all, make sure you are referencing the renderer and the box with template refs:

<script lang="ts">
import {
  PointLight,
  Box,
  Camera,
  Renderer,
  Scene,
  LambertMaterial,
} from 'troisjs';
import { defineComponent, onMounted, ref } from 'vue';

export default defineComponent({
  setup() {
    const renderer = ref(null);
    const box = ref(null);

    onMounted(() => {
        renderer?.value?.onBeforeRender(() => {
            box.value.mesh.rotation.x += 0.01;
        });
    })

    return {
        renderer,
        box
    }
  }
};
</script>

Once we mount our host component, we will listen to the onBeforeRender event on the renderer object instance and wait for it to start rotating the box by accessing the rotation property inside the mesh.

Conclusion

Trois.js allows you to quickly set up a 3D project with top-notch modern frontend technologies. It's pretty performant and easy to use.

Happy coding!

]]>
<![CDATA[Introduction to Render Functions]]> https://vuedose.tips/introduction-to-render-functions/ https://vuedose.tips/introduction-to-render-functions/ Tue, 28 Sep 2021 00:00:00 GMT What is a Vue render function?

You're probably used to write Vue components in Single File Components (SFC) format:

<template>
   <div>Hello world</div>
</template>

…where you put all your HTML inside the template block.

Vue has an HTML-based template engine, and what it does is transform these templates into “render functions”.

Render functions are the JavaScript representation of a Vue template.

Just so you know, Vue compiles the previous example into something like:

function render( ) {
  with(this){
    return _c('div',[_v("Hello World")])
  }
}

But more generally, when we talk about render functions, we talk about the way of authoring templates with the following syntax:

<script>
// Vue 2 syntax
export default {
  render(h) { // <- Render Function
    return h('div', ['hello world']) // <- This is our template
  }
}
</script>

So, for components defined with render functions, there's no need to have a template block at all, because the template is defined in the render function.

To get to know this syntax better, please refer to the official docs: https://vuejs.org/v2/guide/render-function.html

Vue 3 syntax

Now let's write the previous example with Vue 3 syntax:

<script>
// Vue 3 syntax
import { h } from 'vue'
export default {
  render() {
    return h('div', ['hello world'])
  }
}
</script>

What changed is that now we need to import the h function from Vue manually. It's no longer passed as a parameter to the render function by default.

Here's a link to the Vue (3) SFC Playground to see this in action:

<iframe loading="lazy" src="https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdD5cbiAgaW1wb3J0IHsgaCB9IGZyb20gJ3Z1ZSdcbiAgZXhwb3J0IGRlZmF1bHQge1xuICAgIHJlbmRlcigpIHtcbiAgICAgIHJldHVybiBoKCdkaXYnLCBbJ2hlbGxvJ10pXG4gICAgfVxuICB9XG48L3NjcmlwdD5cbiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0ifQ==" width="100%" height="400px"></iframe>

Differences with regular templates

  • With render functions, performance is not increased or even degraded.
  • File size differences are marginal.
  • Readability is worse in render function components.
  • Therefore, maintainability is harder.
  • And it makes it more difficult for folks not used to JavaScript (or Vue render functions) to contribute.

So, if it makes them harder to maintain and the performance is not significantly improved, why do we want them, why do we need them? In the next article, we're going to see a few cases where render functions are actually needed and recommended. Stay tuned!

]]>
<![CDATA[Achieve Max Performance loading your images with v-lazy-image]]> https://vuedose.tips/achieve-max-performance-loading-your-images-with-v-lazy-image/ https://vuedose.tips/achieve-max-performance-loading-your-images-with-v-lazy-image/ Mon, 30 Aug 2021 09:00:00 GMT Most likely you already know how to use v-lazy-image, even using responsive images to load the best image size based on the viewport's size. If you don't, I suggest you to do it now.

Have you ever been reading a blog on Medium and see this cool loading image effect as you scroll down an article?

Img

That's Progressive Image Loading, a performance technique that Medium, Spotify and Netflix (among others) use to improve the user experience when lazy loading an image.

Note that it's a perceived performance technique, meaning that the image won't be loaded quicker. Instead, you'll load a tiny version of the image and make the switch to the real one.

That improves considerably the UX (User eXperience). The user will perceive the image load is smooth and you'll avoid the user to wait seeing a blank space until the image is loaded, accompanied by a CSS transition.

If you're more interested on the different kind of performances, I talk in more detail in my Vue Day's talk, but let's go to the point.

Progressive Image Loading in v-lazy-image

As simple as using the src-placeholder property to define an image that is shown until the src image is loaded.

Make sure the placeholder image is a tiny version of the original one. If you host your images in Storyblok, that's pretty easy to achieve with the Image Service since it allows you to resize images on the fly.

When the src image is loaded, a v-lazy-image-loaded class is added, so you can use it to perform animations. For example, a blur effect:

<template>
  <v-lazy-image
    src="https://cdn-images-1.medium.com/max/1600/1*xjGrvQSXvj72W4zD6IWzfg.jpeg"
    src-placeholder="https://cdn-images-1.medium.com/max/80/1*xjGrvQSXvj72W4zD6IWzfg.jpeg"
  />
</template>

<style scoped>
.v-lazy-image {
  filter: blur(10px);
  transition: filter 0.7s;
}
.v-lazy-image-loaded {
  filter: blur(0);
}
</style>

You can listen to the intersect and load events for more complex animations and state handling:

<template>
  <v-lazy-image
    src="https://cdn-images-1.medium.com/max/1600/1*xjGrvQSXvj72W4zD6IWzfg.jpeg"
    src-placeholder="https://cdn-images-1.medium.com/max/80/1*xjGrvQSXvj72W4zD6IWzfg.jpeg"
    @intersect="..."
    @load="..."
  />
</template>

Demo with CSS Animations

Look at this demo and see different CSS Animations you can apply. It's aim is that you can get inspired by playing with it.

<iframe src="https://codesandbox.io/embed/9l3n6j5944?fontsize=14&hidenavigation=1&module=%2Fsrc%2FApp.vue&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="v-lazy-image with placeholder" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" loading="lazy"

</iframe>

Follow up

Now you're ready to provide the best user experience using v-lazy-image! You can always go to its Gihub repo to see the full component API.

If you want to learn more about Progressive Image Loading, my friend @jmperezperez has written about the progressive loading technique on his blog with a deeper explanation.

]]>
<![CDATA[Use Responsive Images with v-lazy-image]]> https://vuedose.tips/use-responsive-images-with-v-lazy-image/ https://vuedose.tips/use-responsive-images-with-v-lazy-image/ Mon, 23 Aug 2021 09:00:00 GMT Yes I know. Web Performance is a must nowadays, and image loading plays a big part on it.

I've already told you how to lazy load an image easily. But... Can you image how the performance would improve if you can load different sizes of an image depending on the viewport?

v-lazy-image allows you to do that using Web Standards: srcset and the <picture> tag.

Let me show you the how-to.

Srcset

Using the srcset property you can set images for different resolutions:

<template>
  <v-lazy-image
    srcset="image.jpg 1x, image_2x.jpg 2x"
  />
</template>

When using the srcset attribute is recommended to use also src as a fallback for browsers that don't support the srcset and sizes attributes:

<template>
  <v-lazy-image
    srcset="image-320w.jpg 320w, image-480w.jpg 480w"
    sizes="(max-width: 320px) 280px, 440px"
    src="image-480w.jpg"
  />
</template>

The srcset prop is combinable with src-placeholder in order to apply progressive loading.

Picture

If you want to wrap the img in a <picture> tag, use the prop usePicture. You can then use slots to add additional elements above the img element`.

<v-lazy-image
  srcset="image-320w.jpg 320w, image-480w.jpg 480w"
  alt="Fallback"
  use-picture
>
  <source srcset="image-320w.jpg 320w, image-480w.jpg 480w" />
</v-lazy-image>

Renders as:

<picture>
  <source srcset="image-320w.jpg 320w, image-480w.jpg 480w" />
  <img srcset="image-320w.jpg 320w, image-480w.jpg 480w" alt="Fallback" />
</picture>

Note you can use the picture polyfill.

Demo

Better shown than said. See how it works in this demo and feel free to play with it:

<iframe src="https://codesandbox.io/embed/v-lazy-image-responsive-images-forked-kq48f?fontsize=14&hidenavigation=1&module=%2Fsrc%2FApp.vue&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="v-lazy-image responsive images (forked)" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"

</iframe>

Follow Up

I wanted to go straight, so now you know how to optimize image loading in your website and improve performance by loading different sizes of an image, improving the UX indirectly.

However, if you didn't know about srcset and <picture>, you probably need to understand that better. You can read a deeper explanation on the article Responsive Images Done Right: A Guide To <picture> And srcset from Smashing Magazine.

Do you want to go one step further on achieving max performance? Then you must learn Progressive Image Loading, one of the techniques I find more fascinating.

]]>
<![CDATA[How to test Web Workers with Jest]]> https://vuedose.tips/how-to-test-web-workers-with-jest/ https://vuedose.tips/how-to-test-web-workers-with-jest/ Tue, 20 Oct 2020 02:56:00 GMT As you could read in Alex's article, Use Web Workers in your Vue.js Components for Max Performance, you can use Web Workers to maximize performance in your Vue.js app instead of running heavy tasks in the main thread which is UI-blocking.

But how can we test Web Workers? Webpack-bundled Web Workers are not supported by Jest so we have to mock the worker in order to test it! Let's see how to do in 3 simple steps, starting from a simple Vue app to calculate the Fibonacci number, where fibonacci function is the heavy task performed by the Web worker (you can follow the code here)

<img src="https://github.com/astagi/webworker-test/blob/master/public/article/fibonacci_demo.gif?raw=true&v=1" style="width: 300px; height: auto;">

First of all we need to isolate the main functionality of our worker, in this case is really straightforward because it's just our fibonacci function (src/fibonacci.js)

let fibonacci = (num) => {
  if (num <= 1) return 1;
  return fibonacci(num - 1) + fibonacci(num - 2);
}

export default fibonacci

and keep the worker minimal (src/fibonacci.worker.js):

import fibonacci from "./fibonacci";

self.onmessage = async function (e) {
  self.postMessage(fibonacci(e.data));
};

This way we can mock just the Web Worker part of our implementation (src/__mocks__/fibonacci.worker.js)

import fibonacci from "../fibonacci";

export default class fibonacciWorker {
  constructor() {
    // Note that `onmessage` should be overwritten by the code using the worker.
    this.onmessage = () => { };
  }

  postMessage(data) {
    this.onmessage({ data: fibonacci(data) });
  }
}

and easily test the main functionality

import { shallowMount } from '@vue/test-utils'
import App from '@/App.vue'

jest.mock("@/fibonacci.worker")

describe('Fibonacci App.vue', () => {
  it('should calculate Fibonacci number', async () => {
    const wrapper = shallowMount(App)
    await wrapper.find('input').setValue('10')
    await wrapper.find('button').trigger('click')
    expect(wrapper.find('.result').element.innerHTML).toBe('Result: 89')
  })
})

I created workerloader-jest-transformer to generalize this solution so that all workers are mocked at once. This Jest transformer helps you to test Web Workers loaded with Webpack worker-loader module in Jest. It's easy to use, install it with

yarn add workerloader-jest-transformer --dev

and add the tranformation rule to your Jest configuration:

transform: {
  "^.+\\.worker.[t|j]sx?$": "workerloader-jest-transformer"
}

This transformer is inspired by jsdom-worker and implements Web Worker API for JSDOM, so you can remove any mocking code as you can see here.

Workerloader-jest-transformer is highly experimental and code is available on Github, any contribution and advice would be greatly appreciated!

]]>
<![CDATA[How to use the new Fetch in Nuxt.js]]> https://vuedose.tips/how-to-use-the-new-fetch-in-nuxt-js/ https://vuedose.tips/how-to-use-the-new-fetch-in-nuxt-js/ Mon, 07 Sep 2020 03:57:00 GMT It was back in the March (March 17th to be exact), when the Nuxt.js team introduced version 2.12 and the new Fetch hook. This step made it possible to create better architecture for our Nuxt projects.

You may wonder what I am talking about. The fetch was there the whole time... or was it? Yes it was, but in version 2.12 you can now define the fetch hook on the component, which is much cleaner than before. I will cover the difference between the old and new fetch in another article.

Imagine you have a simple component which should show multiple teasers of the articles. In the headless CMS (Storyblok in my case) you could select the articles using the multiselect, like in this example.

The articles are usually content types on their own. They used to live in a separate folder and have a pretty rich content. Because of this the JSON response of this story will not contain the content of the articles, but the IDs of the articles. You can see this in the following code sample, where the property articles is array of IDs.

{
  "_uid": "47d9a0eb-fccd-463a-bfd1-5393afce3ff2",
  "title": "Selected Articles",
  "articles": [
    "7a9efb98-f8e2-4482-b77e-b7d12fd7297c",
    "c3a03db7-b8f8-48ab-abaf-9d84c477a071",
    "b429a8b0-668c-42b3-b043-f4a3de4966c4"
  ],
  "component": "featured-articles",
  "_editable": "<!--#storyblok#{\"name\": \"featured-articles\", \"space\": \"81302\", \"uid\": \"47d9a0eb-fccd-463a-bfd1-5393afce3ff2\", \"id\": \"9879809\"}-->"
  }

You could also include whole content of the articles in the response, but that is another topic covered by this article from Storyblok.

This situation brings up a good question: Where should we request and load our data? Back in the old days, I would do it in the page (in the page folder of Nuxt). The new fetch allows us to do it directly in the component, which then will also render our data. This means, that if we don't use that component, these data will be not requested. That's awesome!

It also means that all of our logic and styling will live in one file. I named this component FeaturedArticles.vue and it looks like this (with a little bit of TailwindCSS styling):

<template>
  <div v-editable="blok">
    <h2 class="pt-2 pl-6 text-lg text-gray-700 italic">{{ blok.title }}</h2>
    <p v-if="$fetchState.error">
      Error occured during the article loading
    </p>
    <p v-else-if="$fetchState.pending">
      Loading articles...
    </p>
    <ul
      v-else
      class="flex py-6 mb-6">
      <li
        v-for="article in articles" :key="article._uid"
        class="flex-auto px-6" style="min-width: 33%">
        <article-teaser
          v-if="article.content"
          :article-content="article.content"/>
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  props: {
    blok: {
      type: Object,
      required: true
    }
  },
  async fetch() {
    this.articles = await this.$storyapi.get(`cdn/stories/`,
        {
          starts_with: 'articles/',
          version: 'draft',
          by_uuids: this.blok.articles
        }
      ).then((res) => {
        return res.data.stories
      }).catch((res) => {
        console.error(res)
      })
  },
  fetchOnServer: true,
  data() {
    return {
      articles: []
    }
  }
}
</script>

You can see in the template that we can end up with three different situations:

  • $fetchState.pending - If the request takes a long time we will inform the user that we are loading articles.
  • $fetchState.error - If we get an error from our end-point, we will let the user know about it. Then we could show a button to renew request.
  • !$fetchState.pending - Finally if the request was succesful, we will render the teasers on the page.

Why do it like this?

  • You can provide better UX to your user.
  • You don't have to use $store.
  • You will load data only when you need them.
  • Your logic lives in one place and is not spread through multiple files.
  • If you decide to remove the component, you will remove all of it's code with it.

You can read more about the new Fetch hook and its option in the official Nuxt.js documentation. This was just the tip of the iceberg. 😏

In the next tip I will compare the difference between the old fetch and the new fetch. Stay tuned and see you soon!

]]>
<![CDATA[Generate and deploy the blog as a full static Nuxt site]]> https://vuedose.tips/generate-and-deploy-the-blog-as-a-full-static-nuxt-site/ https://vuedose.tips/generate-and-deploy-the-blog-as-a-full-static-nuxt-site/ Tue, 25 Aug 2020 01:55:00 GMT Congrats if you reach this point because that means you should have now a fully running blog with articles, authors and topics, as well as search functionality thanks to Storyblok. All organized in UI components with TailwindCSS and basic SEO and social media sharing covered.

Now it's the time to show it to the world!

Generating a Nuxt.js full static site

Since version 2.14, you're able to generate full static sites with Nuxt.js. That was indeed one of the releases that I've been waiting for so badly.

If you go back to the first chapter where we set up a full static Nuxt.js site, remember that we prepared the Nuxt.js project already to be full static.

You can double check it by looking at the nuxt.config.js file:

export default async () => {
  const routes = await fetchSitemapRoutes()

  return {
    mode: 'universal',
    target: 'static',
    // ...
  }
}

The target: static part is the one in charge of making that happen.

Here's the cool part, to generate the full static project you just need to run npm run generate and Nuxt will create the HTML files for everything:

https://a.storyblok.com/f/83078/770x804/530d96422b/07-01-run-generate.png {.img-shadow}

As you can see, each route we created has its own folder with its index.html file, all pre-rendered.

You can also run npm run start after being generated to see that it's working correctly.

But in order to deploy it, in the way I'm going to show you, you don't need to run these commands locally.

Hang on a second and continue reading 😉.

Deploying the full static website

By nature, a static site, also known as a JAM Stack site, it's fast and secure because of the fact that it doesn't render on the server.

Just like ready-cooked meals, a static site is pre-rendered and there is no rendering nor processing effort on the server side.

At the end, it's all HTML, CSS and JavaScript, and to run that you just need a basic static server since all the rest happens on the client side.

Normally, a static site is served pre-rendered and from that point becomes an SPA when it reaches the browser. It'd perform API calls or whatever is needed to get the data for a page.

But when we specify that the website is a full-static site, it means that literally all API calls are performed only once at compilation.

In order to host and deploy the site, we have several options in the market and it might be hard to choose one.

I'm going to suggest to you my top 4, knowing that my preferences are:

  • As zero-config as possible
  • Good pricing with a free-tier option
  • Friendly for frontend devs

Hosting options

It comes without saying that probably Netlify is the top here, and the one we'll use. It's focused on static sites. It's performant with a generous free-tier and with many features covering most of the cases for static sites and SPA's.

I love using Surge for demos, because to deploy it you just need to literally run one command: surge. It's for frontend code as well and quite easy to use.

In the land of cloud servers that are not only-frontend, my favorite is by no doubt Vercel. It's zero-config, focused on DX and performance and very easy to use for one that is not used to deal with servers. It's probably one of the very few that has a free tier as well.

The last I'd suggest is Digital Ocean. You'll choose this one when you want to have more control on your server. The prices are very convenient and it's indeed a quality product. Alba Silvente has a nice guide on how to deploy a Nuxt static site.

Deploying the site to Netlify

Netlify is where VueDose is hosted, and I'm completely happy and satisfied by it.

I have no knowledge about DevOps or system administration, so configuring servers and stuff is always daunting for me.

But Netlify makes it very easy. As easy as pushing some buttons, so let's get started.

Assuming your project is hosted on Gihub or Gitlab, you can automatically connect it to Netlify.

For that, log into Netlify and you'll see this button to create a project from Git directly:

https://a.storyblok.com/f/83078/1688x690/f1d3c90ab8/07-02-netlify-new-site.png {.img-shadow}

You'll go through a step-by-step process where you'll connect to Github, Gitlab or Bitbucket and choose your repository.

At some point, you'll reach a step where you have to configure the build commands. Here's where you need to set npm run generate and the dist folder to be published:

https://a.storyblok.com/f/83078/1680x1182/1dd5477e9a/07-03-netlify-commands.png {.img-shadow}

You can also set the branch where you want to be "listened" instead of the master branch by default, so it can fit your personal workflow.

And... that's basically it! Let it run for a minute or two and you'll see...

https://a.storyblok.com/f/83078/1646x698/b094e89105/07-04-netlify-error.png {.img-shadow}

An error!!!??? WTF?

Wait, I just forgot something important: we haven't set the environment variables.

We have them locally in the .env file, but that's not published to the repository, and it shouldn't for security reasons.

Instead, we need to do it on Netlify itself. Go to the Site settings > Build and deploy > Environment page and set the environment variables as you need:

https://a.storyblok.com/f/83078/2468x1168/caa9a00918/07-05-netlify-env.png {.img-shadow}

:::blockquote

Don't copy this image values, but instead use the ones you need to.

:::

After fixing that, let's trigger a new deploy. Go to the deploy page and you'll find this button to do it:

https://a.storyblok.com/f/83078/1998x1188/93e9e187de/07-06-netlify-trigger-deploy.png {.img-shadow}

This time, all should work properly and your site will be live! Yay \o/

You'll see the default url that Netlify gives you on the overview page:

https://a.storyblok.com/f/83078/1712x782/a3d21a2aa4/07-07-netlify-success.png {.img-shadow}

What I love from Netlify is that you can have your custom domain, HTTPS, redirects and much more for free! Check out their docs to know-how!

Wrapping Up

Yes, this is the end.

It was an incredible journey to guide you through how to create, step-by-step, a full-static blog with Nuxt.js and Storyblok following the same process that we used for VueDose.

You've learnt how to create the project from scratch and how to define components based on a design system, how to build articles overview and detail pages.

We dug deep into the Storyblok and learn how to transform our/your design into the components, how to organize your content and create queries to get it. At the end we learnt how to generate and deploy it statically into the world wide web.

I feel very proud to say this was done thanks to the help and collaboration of Storyblok, a beloved sponsor of ours 💚.

If you have any questions, feel free to reach VueDose or Storyblok on Twitter and we'll answer everything for you!

Stay awesome.

]]>
<![CDATA[Optimize SEO and Social Media Sharing in a Nuxt blog]]> https://vuedose.tips/optimize-seo-and-social-media-sharing-in-a-nuxt-blog/ https://vuedose.tips/optimize-seo-and-social-media-sharing-in-a-nuxt-blog/ Tue, 18 Aug 2020 00:01:00 GMT So far in this guide, the blog is full-featured since we added tags and search functionality. But still, SEO is important in a blog so we cannot overlook it.

SEO is a big thing and there are people working almost exclusively in SEO, especially those working on copywriting.

That's why I'm not covering everything about SEO here. I'm not an expert on it, but at least we can make sure the blog has the minimum stuff for having the basics sorted.

We'll make sure the pages have the correct metadata for search engines and social sharing, as well as generating a sitemap so we make easier the job for search engines.

Meta Tags and Social Media

One of the basic things we can do is to set up the meta tags for our blog pages.

They're important for giving some extra info to the search engines, so they can show better information on the SERP (Search Engine Result Pages).

For instance, if you search "vuedose" on Google, you'll see this result thanks to the meta tags and the sitemap:

https://a.storyblok.com/f/83078/1638x842/fe5b30cf36/06-01-serp-explanation.png {.img-shadow}

I'll cover the sitemap topic later, but for now you can see at least we have a title and description meta tags.

First, set them in nuxt.config.js using the head property, just like this:

export default {
  // ...
  head: {
    title: "NarutoDose",
    meta: [
      {
        hid: "description",
        name: "description",
        content: "Get to know all about Naruto and its characters in tiny bits of info.",
      },
      // ...
    ],
  },
};

That will be translated to <meta name="description" content="Get to know..."> and inserted by Nuxt within the <head> tag. It's necessary that we set hid, otherwise the tags will be duplicated.

Since later we'll add more tags in different pages, I'll suggest to create a util function for it. Create the file utils/seo.js with the following function:

export const createSEOMeta = (data) => [
  { hid: 'description', name: 'description', content: data.description },
]

And update it on nuxt.config.js:

import { createSEOMeta } from "./utils/seo";

export default {
  // ...
  head: {
    title: "NarutoDose",
    meta: [
      ...createSEOMeta({
        description: "Get to know all about Naruto and its characters in tiny bits of info.",
      }),
      // ...
    ],
  },
};

Now we need to do it on each page as well. If a tag is not set on a page, Nuxt will fallback to the one in nuxt.config.js if it exists there, so for the home page located at pages/index.vue.

Let's go to pages/_slug.vue and set it there. In this case, it's a dynamic page, but that's no problem because the head method can access the component instance properties:

export default {
  // ...
  head() {
    const { title, description } = this.article.content

    return {
      title,
      meta: createSEOMeta({ description }),
    }
  },
}

:::blockquote In case you're wondering where this.article comes from, I explained in another article how to show the blog content in Nuxt using Storyblok API. :::

In pages/topics/_slug.vue can be more simple, since I'd just set a title and let take the rest from the default one from nuxt.config.js.

Since it's a category, I'd like it to show up in the SERP and the browser tab like CATEGORY_NAME - NarutoDose:

head() {
  return {
    title: `${this.topic.name} - NarutoDose`,
  }
},

Social Media Sharing

Imagine you have your blog published and you want to share the link of an article on Twitter, Facebook or LinkedIn.

By default, that'd be shown as just a link. Wouldn't be cool that instead of showing just a link, it can appear in a more visual way, like this?

https://a.storyblok.com/f/83078/1192x1246/116d779bc0/06-02-social-media-tweet.png {.img-shadow}

They are called Social Cards, and they don't show up automatically. We need to add some extra tags. In particular, we need the ones for the Open Graph, a protocol coined first by Facebook but now also Twitter and LinkedIn (I can imagine Instagram as well).

We need at least the tags:

  • og:title
  • og:description
  • og:url: the absolute URL that points to the article.
  • og:image: make sure it has the right resolution
  • twitter:card: the type of card you want to show on Twitter. We'll use summary_large, give it a look at Twitter docs.

You could use some extra ones if you want to polish it even more, but like these we're ok.

Let's add these tags to the createSEOTags util function we defined before:

export const createSEOMeta = (data) => [
  { hid: 'og:title', property: 'og:title', content: data.title },
  { hid: 'description', name: 'description', content: data.description },
  {
    hid: 'og:description',
    property: 'og:description',
    content: data.description,
  },
  { hid: 'og:image', property: 'og:image', content: data.image },
  {
    hid: 'og:url',
    property: 'og:url',
    content: process.env.HOST_NAME + '/' + data.url,
  },
  {
    hid: 'twitter:card',
    name: 'twitter:card',
    content: data.cardType || 'summary_large_image',
  },
]

Since the url must be absolute, probably it's a good time for you to add a HOST_NAME variable on you .env file.

We have all the data we need to set these tags, except the image. So first of all let's go to our Storyblok space and add an asset field to the Article schema.

https://a.storyblok.com/f/83078/1280x1164/a910dc55b2/06-03-storyblok-asset.png {.img-shadow}

:::blockquote If you don't know how to do it, take a look at how to set up your blog structure on Storyblok, I've explained how to add fields to a schema there 😉. :::

Then, go through each article and update them by adding an image to each one.

Once you do that, go to pages/_slug.vue and update the head function:

head() {
  const url = this.article.slug
  const { title, description, image } = this.article.content

  return {
    title,
    meta: createSEOMeta({ title, description, image: image.filename, url }),
  }
},

Notice the image url is located on image.filename.

Do the same in nuxt.config.js:

import { createSEOMeta } from "./utils/seo";

export default async () => {
  const routes = await fetchSitemapRoutes();

  return {
    head: {
      title: "NarutoDose",
      meta: [
        ...createSEOMeta({
          title: "NarutoDose",
          description: "Get to know all about Naruto and its characters in tiny bits of info.",
          image: "[Insert_NarutoDose_Image_URL]",
          url: process.env.HOST_NAME,
        }),
      ],
    },
  };
};

That'd be basically it. If you need to set specific tags on pages/topics/_slug.vue you're free to do it.

Sitemap

A sitemap tells search engines how's the page structured. That not only makes your pages indexable, but it makes the job easier to search engines and will be able to show the different sections of your site as you saw on the first image above.

To generate a sitemap, let's use @nuxtjs/sitemap: a Nuxt module that makes the job easy. Just install it and add it to the modules section on nuxt.config.js. Then, in the same file, you need to add this minimal configuration:

sitemap: {
  hostname: process.env.HOST_NAME,
  routes: [] // all the dynamic routes
},

The module will pick up by default the static routes, but it's not able to pick up the dynamic routes. So we have to fetch them and add it to it.

I'll suggest to do that in a utility function, so create a utils/sitemap.js file with the following content:

import StoryblokClient from 'storyblok-js-client'
import kebabCase from 'lodash/kebabCase'

export const fetchSitemapRoutes = async () => {
  const routes = []
  const client = new StoryblokClient({ accessToken: process.env.STORYBLOK_KEY })

  const { data: articlesData } = await client.get('cdn/links', {
    starts_with: 'articles/',
  })
  const { data: tagsData } = await client.get('cdn/tags')

  Object.values(articlesData.links).forEach((article) =>
    routes.push(`/${article.slug}`)
  )
  tagsData.tags.forEach((tag) => routes.push(`/topics/${kebabCase(tag.name)}`))

  return routes
}

We use storyblok-js-client to fetch the data. You don't need to install it since it comes by default with storyblok-nuxt module.

Then we fetch all the articles and tags and form an array of strings with all the routes. Notice that for the articles we use the Links Endpoint, which is much faster and convenient for cases when you don't need the stories content. You don't even need to paginate them.

Finally, we need to use that function to fill up the routes. By default, we're exporting an object from nuxt.config.js, but for cases like this where we need to perform an async operation like fetching the routes, we can export a function instead:

import { fetchSitemapRoutes } from "./utils/sitemap";

export default async () => {
  const routes = await fetchSitemapRoutes();

  return {
    // ...
    sitemap: {
      hostname: process.env.HOST_NAME,
      routes,
    },
    // ...
  };
};

That's it. To test if it works, make sure you have a generate script in your package.json and run npm run generate.

A dist folder must've been generated, containing the right sitemap.xml file on it:

https://a.storyblok.com/f/83078/1766x1114/ef6d98be96/06-04-sitemap.png {.img-shadow}

Wrapping Up

It's important not to have just a blog, but to make sure your blog it's minimally prepared for SEO and being positioned.

It's true that you can do much more to improve SEO, mostly dependent on the quality of the content you write. But at least with the right meta tags and a sitemap you cover the basics of it.

Now that the blog it's ready, what's next?

The last step: deploy it 😉

Coming next week!

]]>
<![CDATA[Advanced i18n in Nuxt using Interpolations]]> https://vuedose.tips/advanced-i18n-in-nuxt-using-interpolations/ https://vuedose.tips/advanced-i18n-in-nuxt-using-interpolations/ Mon, 17 Aug 2020 00:57:00 GMT In NuxtJS on our home page we have a title which has the letters JS as a green color, a different color to the rest of the text. Obviously this is done by adding a span with a color of light-green. This span is then added to all the other languages we have. We then import this using v-html. So far so good. It works. Why change this?

title: 'The Intuitive <span class="text-nuxt-lightgreen">Vue</span> Framework',
<p v-html="$t('title')"></p>

So recently we added eslint to our main website. Obviously this didn't pass the rule where v-html is considered a bad practice. But with eslint we can just turn off the rule. Problem solved. OK but what happens if we need to change the light-green to a dark-green. Well we need to change it in the i18n file of each language. Tedious? Well we could just do a find and replace. Problem solved.

So basically it means people can translate our text and change the text and color to anything they like. But surely they wouldn't do that. But that's not the point. Should we really have this in our translations? Is it really necessary? Isn't there a better way?

That's where i18n interpolation comes into practice. With i18n, instead of using v-html we can instead use interpolation. How?

i18n gives us an <i18n> component that we can use on any of our pages or inside our components. In this component we can set up the tag to be any html element we want such as h2, div, a or whatever we want to use for that particular text.

<i18n tag="h1"></i18n>  

We then allocate it to our translation. If for example in our en-EN.js file we have homepage.title:

module.exports = {
  homepage: {
    title:
      'The Intuitive <span class="text-nuxt-lightgreen">Vue</span> Framework'
  }
}

Then this is what we will use as the path in our <i18n> component.

<i18n
    tag="h1"
    path="homepage.title"
>
</i18n>          

We can also add classes to our component just like any other component

<i18n
    tag="h1"
    path="homepage.welcome.title"
    class="title"
>
</i18n>

We can now use slots inside our component to render what we want, which in our case is our span class. We give the slot a name so we can then refer to it in our translation file. And then we add our span.

<i18n
    tag="h1"
    path="homepage.welcome.title"
    class="title"
>
	 <template v-slot:frameworkType>
      <span class="text-nuxt-lightgreen">
          Vue
      </span>
   </template>
</i18n>  

Now in our translation file we can add the name of our slot enclosed in curly brackets where we want the actual span to appear.

module.exports = {
  homepage: {
    title: 'The Intuitive {frameworkType} Framework'
  }
}

This will now give us the translated text with a placeholder for our span class. And in our fr-FR.js file we can add the French translation where our placeholder will go at the end of the sentence instead of after the word Intuitive like in English.

module.exports = {
  homepage: {
    title: 'Le Framework Intuitif basĂŠ sur {frameworkType}'
  }
}

Now if we want to modify our span to be a different colour or add more styling we only have to do it in one place, and our translators only have to worry about translating the texts without having to deal with unnecessary html.

It is also possible to add more than one slot to your <i18n> component. For example, for styling purposes, you might want to add a break in the sentence. Just make sure you give them unique names and then you can use the placeholders in your translation files.

<i18n
    tag="h1"
    path="homepage.welcome.title"
    class="title"
>
	<template v-slot:br>
	    <br />
	</template>
	 <template v-slot:frameworkType>
      <span class="text-nuxt-lightgreen">
          Vue
      </span>
   </template>
</i18n> 
title: 'The Intuitive {br} {frameworkType} Framework',
title: 'Le Framework Intuitif {br} basĂŠ sur {frameworkType}',

As you an see it is much better to use i18n interpolation rather than v-html in your code as it allows you to keep your styling and html separate from your translations. This not only makes it easier for translators but it also makes it much easier to modify as the html code is not repeated throughout the translation files.

If you have never worked with i18n then check out these links to help you get started:

]]>
<![CDATA[Tags and Search Functionality in Nuxt Using Storyblok API]]> https://vuedose.tips/tags-and-search-functionality-in-nuxt-using-storyblok-api/ https://vuedose.tips/tags-and-search-functionality-in-nuxt-using-storyblok-api/ Tue, 11 Aug 2020 01:57:00 GMT I want you to think for a moment: when you get into the role of a blog reader, what do you really want?

In most cases, you probably want to easily find content relevant and interesting to you.

You might be interested in apples, and I am only interested in pineapples. Maybe you're just interested in sweet fruit, or you want to find only the purple fruit.

What you're looking to do is to narrow down the content and navigate through it. We can do this in the following two ways:

  • Categorization: use of tags, topics, taxonomies or any type of category.
  • Search: unstructured way to find content more easily

Let's see how to implement this in our blog.

Content Topics in the Nuxt Blog

In the article Setting up the blog content structure in Storyblok we've already added some tags to the articles we've created.

At this point I encourage you to create some more articles so you'll better understand this section.

The categorization strategy we're going to implement is simple: an article can belong to different topics and we want to have a page for each topic, listing all the articles related to that topic.

You don't need to do anything in Storyblok. As you've seen before, we'll be using tags for it, and the rest of the functionality is there for us.

Let's start by creating the routing for the topics pages.

They will have a url following the form of /topics/:slug so we have to create the page at pages/topics/_slug.vue. It'll have a similar shape to the home page, so for now copy/paste the template from pages/index.vue:

<template>
  <div class="mt-4">
    <section>
      <div class="container mx-auto px-4">
        <h1 class="text-4xl font-bold">Articles</h1>
        <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 mt-4">
          <ArticleCard
            v-for="article in articles"
            :key="article.content.title"
            :slug="`/${article.slug}`"
            :title="article.content.title"
            :description="article.content.description"
            :author="article.content.author"
            :date="article.content.date.toLocaleDateString()"
          />
        </div>
      </div>
    </section>
  </div>
</template>

We'll link to the topic page from the article detail, located at pages/_slug.vue. A story in Storyblok includes a tag_list field:

{
	id: 17434028,
        // ...
	tag_list: ['Beast', 'Hokage']
}

Let's use it in order to show the tags just below the <header> tag in pages/_slug.vue:

<header>...</header>
<div class="mt-4">
  <nuxt-link
    v-for="tag in article.tag_list"
    :key="tag"
    :to="`/topics/${tagSlug(tag)}`"
    class="rounded-full text-white bg-main uppercase text-sm mr-2 px-2 py-1"
    >{{ tag }}</nuxt-link>
</div>
<div v-html="$md.render(article.content.content)" class="prose mt-8"></div>

Since the tags can have any string shape, you have to make sure it gets converted to a slug so it can be a valid link. That's why you see tagSlug(tag) in there.

You can implement it with lodash easily:

import kebabCase from 'lodash/kebabCase'

export default {
  // ...
  methods: {
    tagSlug(tag) {
      return kebabCase(tag)
    },
  }
}

The result should look like this:

https://a.storyblok.com/f/83078/1558x822/f69586ee71/05-01-tags.png {.img-shadow}

Note: Don't worry about the search box, I'll get into that later 😉.

Going back to pages/topics/slug.vue, we have the opposite problem here: we have access to the **topic slug, but not to the topic itself.

What we can do is to fetch the topics using the tags resource and find the tag from the response.

That call returns an object with the following shape:

{
  "tags": [
    { "name": "Beasts", "taggings_count": 2 },
    { "name": "Techniques", "taggings_count": 1 }
  ]
}

Let's use the asyncData function on pages/topics/slug.vue to perform the call and search for the right topic name*:*

import kebabCase from 'lodash/kebabCase'

export default {
  async asyncData({ app, params }) {
    // Find tag based on the slug
    const { data: tagsData } = await app.$storyapi.get('cdn/tags/')
    const topic = tagsData.tags.find((t) => kebabCase(t.name) === params.slug)
  }
}

Notice that we're calling cdn/tags/ and the kebabCase operation to compare the slugs. Using params.slug you're accessing the slug from the route url parameter.

Now that we have the right topic, let's retrieve the articles for that topic. You need to use the with_tag stories option:

async asyncData({ app, params }) {
  // Find tag based on the slug
  const { data: tagsData } = await app.$storyapi.get('cdn/tags/')
  const topic = tagsData.tags.find((t) => kebabCase(t.name) === params.slug)

  // Fetch articles
  const { data: articlesData } = await app.$storyapi.get('cdn/stories', {
    starts_with: 'articles/',
    resolve_relations: 'author',
    with_tag: topic.name,
  })
  const articles = articlesData.stories.map((story) => {
    story.content.date = new Date(story.content.date)
    return story
  })

  return { topic, articles }
},

Finally, to differentiate it from the home page, update the page <h1> tag to show as well the topic name and count:

<h1 class="text-4xl font-bold">
  {{ topic.taggings_count }} articles on <i>#{{ topic.name }}</i>
</h1>

The page will look similar to this:

https://a.storyblok.com/f/83078/1692x910/f85bef9975/05-02-topics-page.png {.img-shadow}

Search Content Using Storyblok API

Having a way to navigate through different topics is great, but sometimes we want to search for something specific more easily.

That's where search comes into place, and you will rarely see a decent blog or content-based website without a search box.

Let's start by creating a SearchBox component. It must have a search input, and when the user types in it, it will perform a call to get the stories that match the criteria.

An example of that SearchBox could be:

<template>
  <div class="relative">
		<!-- Magnifying glass icon -->
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 512 512"
      class="block absolute text-gray-600 right-0 z-10 h-4 fill-current mr-3"
      style="top: 9px;"
    >
      <path
        d="M508.5 468.9L387.1 347.5c-2.3-2.3-5.3-3.5-8.5-3.5h-13.2c31.5-36.5 50.6-84 50.6-136C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c52 0 99.5-19.1 136-50.6v13.2c0 3.2 1.3 6.2 3.5 8.5l121.4 121.4c4.7 4.7 12.3 4.7 17 0l22.6-22.6c4.7-4.7 4.7-12.3 0-17zM208 368c-88.4 0-160-71.6-160-160S119.6 48 208 48s160 71.6 160 160-71.6 160-160 160z"
      ></path>
    </svg>
    <!-- Search Input -->
    <input
      v-model="searchInput"
      @input="onInputChange"
      @blur="onInputBlur"
      placeholder="Search..."
      class="w-full bg-white text-gray-700 rounded border-2 border-transparent outline-none focus:border-purple-500 px-4 py-1"
    />
    <!-- Suggestions list -->
    <div class="relative">
      <div
        class="absolute z-30 bg-white top-0 inset-x-0 rounded shadow-lg mt-1"
      >
        <nuxt-link
          v-for="suggestion in suggestions"
          :key="suggestion.id"
          :to="`/${suggestion.slug}`"
          class="block truncate text-gray-700 hover:text-main hover:bg-gray-100 px-4 py-2"
        >
          {{ suggestion.content.title }}
        </nuxt-link>
      </div>
    </div>
  </div>
</template>

<script>
import debounce from 'lodash/debounce'

export default {
  props: {
    search: {
      type: Function,
      required: true,
    },
  },
  data: () => ({
    searchInput: '',
    suggestions: [],
  }),
  methods: {
    onInputChange: debounce(async function () {
      this.suggestions = await this.search(this.searchInput)
    }, 300),
    onInputBlur() {
      setTimeout(() => (this.suggestions = []), 300)
    },
  },
}
</script>

I won't explain this component in detail, since it's not relevant. You're free to code your own or to use any existing libraries like the finely done vue-multiselect.

What's important on this SearchBox component is:

  • It keeps a searchInput and suggestions state.
  • When the search input changes, it performs a call to retrieve the results. We do this by listening to the @input event and delegating the responsibility for how the call must be done to the parent component by receiving a search property. It's convenient as well to debounce the function, so it doesn't perform a call for every keystroke, but instead it'll do that when the user stops typing for 300ms.
  • When the user blurs the input, we empty the suggestions to close the suggestion list. But we do it 300ms later, otherwise it won't be possible to click on a suggestion since the list closes immediately on blur.

Now you can use it on components/layout/AppHeader.vue to show the SearchBox in the header. But before you do that, let me explain something.

When we talk about a full-text search, it means that given a registry of data (in this case the article stories) the system performs a search on all of the fields you need. Usually, this can be quite complex, and you have technologies specifically built for it (like ElasticSearch) and services (like Algolia).

Here comes one of my favorite parts of Storyblok: it gives you a free full-text search, and it's actually quite easy to use.

All you need to do is to use the search_term parameter on your API query. Use it on your AppHeader.vue component and make use of the SearchBox:

<template>
  <header class="w-full bg-main py-2">
    <div class="container mx-auto px-4">
      <div class="flex">
        <nuxt-link to="/" class="text-2xl text-white font-semibold">
          NarutoDose
        </nuxt-link>
        <SearchBox :search="fetchSuggestions" class="flex-1 max-w-sm ml-12" />
      </div>
    </div>
  </header>
</template>

<script>
export default {
  methods: {
    async fetchSuggestions(searchInput) {
      const { data } = await this.$storyapi.get('cdn/stories', {
        starts_with: 'articles/',
        resolve_relations: 'author',
        search_term: searchInput,
        per_page: 5,
      })

      return data.stories
    },
  },
}
</script>

I've also added a per_page so we don't have a bunch of results, but the 5 most relevant.

When you run it, you should see it working like this:

https://a.storyblok.com/f/83078/1634x910/feeb466f9a/05-03-search.png {.img-shadow}

To Sum Up

Making it easy for your readers to find the content that they want doesn't need to be complicated nor complex. Providing them with categorization, tagging and search functionality is a good way to achieve it.

You've seen how easy it is to do that in a Nuxt blog thanks to Storyblok and all the great capabilities its API has.

But the job is not done...

Is the blog discoverable by the search engines yet?

Probably we still have a lot to do about SEO... stay tuned and we'll solve that next week in part 6!

Do you want to play with the code? Find it on Github.

]]>
<![CDATA[Creating UI components based on a Design System in Vue.js]]> https://vuedose.tips/creating-ui-components-based-on-a-design-system-in-vue-js/ https://vuedose.tips/creating-ui-components-based-on-a-design-system-in-vue-js/ Tue, 21 Jul 2020 01:52:00 GMT In the previous part of this guide you created a basic Nuxt project, full-static ready including TailwindCSS. In this one you'll take it from that to start designing some components, but before you get to that, I want to tell you how we structured VueDose Design system.

Organizing Vue.js components can be quite difficult if you're not very experienced with it. It took me a while to learn how to do it in a decent way, and that was through a lot of try and fail approaches.

Nowadays, frontend technologies, paradigms and ecosystem are changing so fast, that many best practices have not yet been established.

What's even worse is that often designers and frontend developers don't understand each other very well!

Have you felt the pain of any of these points?

I want to make this easier for you by sharing this article about how AarĂłn Garcia (UI designer) and I worked together to build VueDose 2.0. We created a Design System using TailwindCSS to easily implement the design system and structuring in Vue.js components.

Then, you'll apply these concepts to Naruto Dose, the project we're building to better understand the things you learn.

Note: I'll focus more on the implementation side, but expect in the future an article where AarĂłn Garcia describes in depth the creative process of designing VueDose Design System.

Designing a Design System

When you decide to create a software product with a top quality design, you should consider creating a Design System.

Why? Because it gives you the design structure, patterns and elements to make sure your design is consistent, harmonious and balanced throughout the product and people working on it. It's the language all designers and frontend devs are going to use to talk about design.

We structured the VueDose design system by separating the elements into 4 different layers: style guide, components, modules and pages.

In fact, this is similar to how Atomic Design structures elements.

:::blockquote success

Give a read to the amazing article "How to structure a Vue.js app using Atomic Design and TailwindCSS" by Alba Silvente to learn more about this.

:::

1. Style Guide

The style guide contains the basic rules, constraints and elements that your design will be built upon.

In VueDose style guide we define:

  • Screen breakpoints (mobile, tablet, desktop...)
  • Colors
  • Typography
  • Spacing
  • Grid and Container
  • Icons
  • Shapes
  • Borders
  • Shadows
  • ...

AarĂłn designed it in Figma, and looks like this:

https://a.storyblok.com/f/83078/1797x1359/fa3e63ec33/02-style-guide.png

The question still remains, why is TailwindCSS a good fit for creating a style guide and how does it work?

Easy: TailwindCSS is style guide oriented. In fact, by default it gives you a standard and well-thought out style guide.

Being a utility based CSS framework, it gives you the flexibility to define your own styling rules and uses them as classes.

You can better understand it with this comparison:

If you don't follow a style guide, and you have to set a margin top, you probably just play with some sizes, like 12px, 15px, 18px... until it fits the design, supposing the design is not following a style guide.

But when you have a style guide, your elements are calculated coherently without guessing. For example, we can apply an 8-pt spacing scale in the VueDose style guide:

https://a.storyblok.com/f/83078/1451x860/15e704d339/02-sizing.png

The spacing is always multiples of 8, giving you a consistent, calibrated and aligned design. The end result is that you'll have an optimized vertical rythm and better reading experience.

Same for typography, colors, borders, icons... They're all calculated to bring balance to the design.

2. Components

Once you have a style guide defined, you use it to build components.

A component is considered an atomic element. This means that a component, in general, doesn't include other components.

Some examples of components are buttons, links, form elements, alerts, logos...

This is how we defined some components in the design system:

https://a.storyblok.com/f/83078/1394x1497/4a07723232/02-components.png

3. Modules

We consider a module a combination of components to create a "bigger component". For example, for the article cards we use a button, an avatar, a title and tags, which you can see in this image:

https://a.storyblok.com/f/83078/1644x1358/9c4037b510/02-modules.png

Examples of modules are your website header and footer, a card or a form.

4. Pages

Finally, we use a combination of modules and components that follow our style guide to create the pages.

This is an example of the Home page in VueDose:

https://a.storyblok.com/f/83078/1847x1335/2bda3c8b80/02-pages.png

Creating your own design system

You probably realize that building a design system makes design much easier, since you start building the smaller elements that you later use for bigger ones.

That not only improves the gap between a designer and a developer, since they now have a common language measured and calculated to work on. But, it also makes your design more consistent.

Is this all that complex to implement?

Not at all. Lucky for us, today you have good libraries to make this easily happen.

TailwindCSS covers very well the style guide part, while Vue.js, by itself, is component oriented. The combination of both: extremely powerful.

Let's see how to apply this kind of design to NarutoDose.

Style Guide

To create a style guide you start by defining the colors, spacing and typography. You can create your fully customized style guide as we did in VueDose.

In this case, let's start from the tailwind default utilities set which is very well thought out and has everything you need:

First of all, open the file tailwind.config.js in your Nuxt project.

Let's add a main color that we want to use in our application, based on the color Teal 500. For that, let's extend the default colors:

const { theme } = require('tailwindcss/defaultConfig')

module.exports = {
  theme: {
    extend: {
      colors: {
        main: theme.colors.teal['500'], // #38b2ac
      },
    },
  },
  // ...
}

You could have written main: '#38b2ac' directly, but you can reuse the default TailwindCSS theme like that to build yours.

I'm adding the main color under the extend key. That allows you to add your customizations while keeping TailwindCSS defaults, and apply that to all theme customizations. In case you want to fully override TailwindCSS colors, you have to write your settings just below the theme key without extend.

You will use the theme key to override or extend any configuration from TailwindCSS.

Creating Components

First of all, I want to tell you how I organize components in the world of Vue.js where everything is a component.

I do that by categorizing them depending on their responsibilities. In particular, I organize it by creating these folders under components:

  • layout: components that are related to the main application layout, such as the navbar, main header, footer, etc.
  • ui: visual components that have no application logic and are usually reusable across the app. The obvious example are any components from a UI library like Vuetify.
  • app: they have application logic and they usually use ui components and are generally a bit more complex. They can either be reusable or not. Some examples could be: a login form, a user table, a filtered product list... or anything that has some business logic
  • page-sections: page components can sometimes get too big. In those cases, I start splitting a page component into page section components.

Combined with the special layout and pages directories, this structure is complete.

Layout

First of all, let's define a simple layout with a navbar. Create the file components/layout/AppHeader with the following markup:

<template>
  <header class="w-full bg-main py-2">
    <div class="container mx-auto px-4">
      <span class="text-2xl text-white font-semibold">NarutoDose</span>
    </div>
  </header>
</template>

Notice we use bg-main. That's the custom color we added before, and we can use that for background, text and borders. The cool part is that just by changing its value in the tailwind configuration, we change how the whole app looks 😉.

Then add it to layouts/default.vue:

<template>
  <div>
    <AppHeader />
    <main>
      <Nuxt />
    </main>
  </div>
</template>

In case you find it difficult to understand TailwindCSS and its classes, I suggest you take a look at these beautiful docs and play around a bit. It's pretty easy once you have some practice.

Design the ArticleCard component

You already created components/ArticleCard.vue. That's a good example of a UI component, so let's follow the structure proposed above and move it into components/ui/ArticleCard.vue.

Then add some tailwind magic to make it look nice:

<template>
  <nuxt-link
    class="block text-gray-800 rounded-lg shadow-lg p-6"
    :to="`/${slug}`"
  >
    <header class="text-2xl font-bold">{{ title }}</header>
    <p class="mt-4">{{ description }}</p>
    <footer class="flex items-center mt-6">
      <img
        class="w-20 rounded-full border-4 border-main"
        :src="author.image"
        :alt="author.name"
      />
      <div class="ml-6">
        <p class="text-xl font-bold">{{ author.name }}</p>
        <p class="text-sm mt-1">{{ date }}</p>
      </div>
    </footer>
  </nuxt-link>
</template>

<script>
export default {
  props: {
    title: String,
    slug: String,
    description: String,
    author: Object,
    date: String,
  },
}
</script>

Notice I've also added a date prop.

Now let's add some styling to the home page, located under pages/index.vue:

<template>
  <div class="mt-4">
    <section>
      <div class="container mx-auto px-4">
        <h1 class="text-4xl font-bold">Articles</h1>
        <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 mt-4">
          <ArticleCard
            v-for="article in articles"
            :key="article.title"
            :title="article.title"
            :description="article.description"
            :author="article.author"
            :date="article.date.toLocaleDateString()"
          />
        </div>
      </div>
    </section>
  </div>
</template>

I'm using a grid to show the articles in 1, 2 or 3 columns, depending on the window size.

For now, translate the date simply by using date.toLocaleDateString().

In order to see several article cards displayed, let's repeat the article object that we have on asyncData several times, and add the date property as well:

export default {
  asyncData() {
    const articles = [
      {
        title: 'How to make your articles Vuetiful',
        description:
          'This article guides you through all the steps to make an article shine with your favourite framework, Vue',
        author: {
          name: 'Naruto Uzumaki',
          image:
            'https://a.storyblok.com/f/83078/500x500/cb27fcd15a/naruto-avatar.jpg',
        },
        date: new Date(),
      },
      { ... }, // more articles repeated
      { ... },
   ]

    return { articles }
  },
}

That's it. If you followed the steps correctly, you should see an interface like this:

https://a.storyblok.com/f/83078/1355x873/c4290c8129/02-final-result.png

Layout key points

There are some layout techniques and best practices I've applied that might have gone unnoticed. Let me emphasize them:

  • Use of container. You can see in AppHeader and the home page that I've used the classes container mx-auto px-4. A container is used to limit the width of the content, and we use it inside the AppHeader or the sections (instead of outside) because that way we can have a background color in full width (like AppHeader) while having the inner content width adjusted. You can center it and add a padding by default in tailwind config. Then you only need the container class, without mx-auto px-4.
  • Set vertical separation with margin top. For consistency, always use margin-top to separate elements, and no margin-bottom or padding-top. Margin is for separation, and padding is for inner spacing.
  • Margins and widths are set outside the elements. If you check ArticleCard, it doesn't have any width or margins because it's not its job to know how it's going to be displayed in different places. ArticleCard has its inner dimensions (padding, and separation between inner elements), but how it is displayed is decided in pages/index.vue using the grid classes.

Recap

This is the end of the article, but not the end of the guide!

In this article I focused on showing you the way AarĂłn and I decided to architecture VueDose 2.0 design, explaining all the techniques and methodologies we used to ensure a consistent design for the best viewing and reading experience.

In particular, you've seen how to create a design system and to structure it in different layers.

You've also learned how to apply these concepts to NarutoDose, the pet project you're creating to integrate all of the lessons, and how TailwindCSS fits perfectly for this way of styling web apps.

Did you like it so far? Great, because the best is yet to come.

In the next article, you'll bring the articles to life! We'll set up the content structure in Storyblok and use its API to take the data from there.

Are you ready for it? 😏

Pssst: find this lesson's code on Github.

]]>
<![CDATA[Setting up a full static Nuxt site]]> https://vuedose.tips/setting-up-a-full-static-nuxt-site/ https://vuedose.tips/setting-up-a-full-static-nuxt-site/ Tue, 14 Jul 2020 03:56:00 GMT When I start a project these days I usually choose Nuxt for it. Why? Because I can do all the things I do with plain Vue.js in less time, thanks to its conventions and the amazing Nuxt modules.

Some people think because of these conventions you lose flexibility. That's far from the truth: I've been using Nuxt for the last 2 years and I haven't found a case where I can't do what I wanted to do.

I've been using it even in Sprinter, one of the biggest Spanish sports E-Commerce. I spoke about the challenges we faced in this talk on Vue Day 2019.

But what I love the most about Nuxt is that you can produce 3 types of applications mostly with the same code:

  • SSR (Server Side Rendered): they're rendered on the server for every request. Sprinter works like that.
  • SPA (Single Page Application): client-only javascript based apps. Nowadays, they're quite well-known.
  • Static Generated: a.k.a. Jam Stack. Similar to SSR, but the html is rendered at compile time, so they don't need a server while also being SEO friendly.

VueDose is built as a static generated site, for different reasons:

  • SEO friendly
  • Very fast: HTML is already rendered
  • Saving on resources: it doesn't need a server, so you're saving money and the planet

Since Nuxt 2.13, we can create full static websites. In other words: you can generate a pure HTML + CSS + JavaScript site. All the Ajax calls to any API's are made at compile time, and then stored in json files locally.

What does that mean? Easy: huge performance boost.

But hey that's too much introduction, right? Let's start building NarutoDose!

The Stack

Nuxt.js

I think I showed enough love to Nuxt, right? Ok fine let's show Nuxt love once more 💚💚💚

Storyblok (vs Nuxt Content Module)

Nuxt's recent release, @nuxt/content: an amazing module for creating your contents under a content/ folder by default. Your contents can be in Markdown, Yaml, JSON and other formats and it exposes an API where you can query those contents. Honestly, this was the missing part in the Nuxt ecosystem for indie tech bloggers.

You might think, couldn't you create VueDose with @nuxt/content? Yeah, I definitely could. But I didn't.

Why? Because, even though they both have things in common such as:

  • A flexible API to query contents, including full-text search
  • Ability to schedule content
  • Ability to use rich text format, such Markdown

There are definitely different:

  • @nuxt/content: it acts as a git-based CMS, meaning that people need to be familiar with git. That's ok for most tech blog cases, just something to be aware of.
  • Storyblok: it's much more than that. Although easy to use, it's a fully cloud-based CMS product that has more features for working with content such as, being able to define a schema, relationships and even a visual editor. It can be extended with plugins, although by default you also have roles, on-the-fly image resizing service, assets managements, and more. It can be managed by a non-dev user.

Since I wanted to leverage the power of Storyblok on VueDose, that was an easy decision to make. No worries, soon you'll know how to do it too 😉.

TailwindCSS

You know that there are many CSS frameworks out there... the well-known Vuetify, the new exciting Chakra UI for Vue, Buefy, Bootstrap Vue and of course the marvelous [INSERT YOUR NEW UI FRAMEWORK HERE].

But AarĂłn and I decided to design VueDose from scratch, creating our own Design System, carefully thought out for giving the best reading experience and a beautiful UI.

TailwindCSS is not opinionated and is perfect for this case. Check out the next article to find out all the whys and how-tos 😏

Create a Nuxt project

The easiest way is to use npx (install it if you haven't) to run create-nuxt-app:

npx create-nuxt-app mini-vuedose

That will open a prompt where you can select the features you want. For this project we'll choose TailwindCSS and it's important to choose Universal (SSR / SSG) as a rendering mode and Static (Static/JAMStack hosting) as a deployment target:

https://a.storyblok.com/f/83078/837x438/878133bee7/01-npx-create-nuxt.png

Now let's clean up some things:

  • Remove the folders store and middleware. You don't need them for now.
  • Remove components/Logo.vue
  • In pages/index.vue, remove the <style> tag and empty the <template>.

Starting clean, now is a good time to create the ArticleCard.vue component. Let's temporarily place it under the components folder.

As you can guess by the name, the ArticleCard component is used for displaying the list (or grid) of articles in the home page. Create it with the following structure:

<template>
  <div>
    <header>
      <nuxt-link :to="`/${slug}`">{{ title }}</nuxt-link>
    </header>
    <p>{{ description }}</p>
    <footer>
      <img :src="author.image" :alt="author.name" />
      <span>{{ author.name }}</span>
    </footer>
  </div>
</template>

<script>
export default {
  props: {
    title: String,
    slug: String,
    description: String,
    author: Object,
  },
}
</script>

It's simple: it has a title, description, slug and an author.

Now go to pages/index.vue and create an articles array in the asyncData function. For now it will be static, but later we'll use Storyblok to get the real data. Use the ArticleCard component we created to display the article data:

<template>
  <div>
    <h1>Articles</h1>
    <div>
      <ArticleCard
        v-for="article in articles"
        :key="article.title"
        :title="article.title"
        :description="article.description"
        :author="article.author"
      />
    </div>
  </div>
</template>

<script>
export default {
  asyncData() {
    const articles = [
      {
        title: 'How to make your articles Vuetiful',
        description:
          'This article guides you through all the steps to make an article shine with your favourite framework, Vue',
        author: {
          name: 'Naruto Uzumaki',
          image: 'https://wallpaperaccess.com/full/2866246.jpg',
        },
      },
    ]

    return { articles }
  },
}
</script>

:::blockquote info Do you see the ArticleCard being imported? No, because from Nuxt 2.13 the @nuxt/components modules is built-in Nuxt and activated by default, meaning... no more component imports! :::

Let's make sure things work. Run npm run dev in your terminal and open http://localhost:3000. The page should look like this:

https://a.storyblok.com/f/83078/1270x874/6d7d5309f8/01-first-article.png

Don't worry too much about the card design right now, you will see that in the next article!

Recap

Up to this point, you have set up Nuxt with TailwindCSS in full static mode. Easy right? Yes, Nuxt does a lot of things for you out of the box, and based on the choices you made using the installer, things are configured out of the box. Later in this guide you'll see how to generate the production page but hold on for now.

This was just an introduction to get you started, but things are about to get fun 😎

Hold on there! In the next article you'll learn to design the app UI based on a Design System while following a simple yet solid component structure that I use on my projects.

You'll also learn why TailwindCSS fits so well for that purpose.

Pssst: you can find this article's code on Github.

]]>
<![CDATA[Use Composition API to easily handle API requests in Vue.js]]> https://vuedose.tips/use-composition-api-to-easily-handle-api-requests-in-vue-js/ https://vuedose.tips/use-composition-api-to-easily-handle-api-requests-in-vue-js/ Mon, 06 Jul 2020 01:04:00 GMT When building a web-app most of the times we need to fetch from the server or execute some action.

Handling the request status can be tedious and we often write the same code over and over.

With the composition API we can write a composable that handles the request status and exposes reactive objects

Let start by defining some requirements:

  • Needs to provide reactive isLoading, result, error and execute
    • isLoading: true is we are waiting for a request
    • result: Success response from the server
    • error: Error object form the server
    • execute: Execute the request
  • Need to provide the base request and the execute should allow sending params or body to the request.

The expected usage will be like:

setup(){
  const userList =  useApi(()=>({url:`https://swapi.dev/api/people/1`}), (r)=> r.json());
  userList.execute(); // we can await for result
  // ...
  return {  ...userList }
}

This factory based usage allows great flexibility, the first parameter is a factory that returns the fetchRequest, the second parameter is just a transformer of the fetch result value.

The return execute() will pass all the parameters to the factory, allowing to greater customization on building requests. Allowing you to do:

setup(){
  const userList =  useApi((page: number)=>({url:`https://swapi.dev/api/people/${page}`}), (r)=> r.json());
  userList.execute(1); // we can await for result
  // ...
  return { ...userList }
}

Allowing greater composability on having automagically having a paginated list:

setup(){
  // ...
  const page = ref(1);
  // watch for `page` changes
  watch(page, p=> userList.execute(p));
  // ...
  return {
    ...userList,
    page 
  }
}

Simplifying the pagination to changing variable page.value instead of calling the userList.execute(page.value).

Implementation

A simple javascript implementation (with typings)

function useApi(
  factory,
  handleResponse
){
  const isLoading = ref(false);
  const result = ref(null);
  const error = ref(null);
  const execute = async (...args) => {
    const request = factory(...args);

    isLoading.value = true;
    error.value = null;
    try {
      const response = await fetch(request);
      const valueResponse = await handleResponse(response)

      result.value = valueResponse;
      return valueResponse;
    } catch (e) {
      error.value = e;
      result.value = null;
    } finally {
      isLoading.value = false;
    }
  };

  return {
    isLoading,
    result,
    error,
    execute,
  };
}

This implementation might not yield correct results, if the execute is called many times before the first finishes, because the result will always be the latest server response.

I'm the creator of a composable library compatible with vue2 + composition-api and vue3 called vue-composable, where you can do something similar by using usePromise, using it will make sure the result.value will hold the value of the last valid exec() response value.

// pass `true` on the second argument to make it lazy
const userList = usePromise((id)=>fetch(`https://swapi.dev/api/people/${id}`).then(r=>r.json(), true)
userList.exec(1)

I've been using this pattern on my projects, from action buttons to fetching the next paginated page, and has been working great so far. It makes it trivial to disable a button if a request is happening or to compose it into other composables.

]]>
<![CDATA[Build a Game in Vuejs with Phaser]]> https://vuedose.tips/build-a-game-in-vuejs-with-phaser/ https://vuedose.tips/build-a-game-in-vuejs-with-phaser/ Mon, 22 Jun 2020 00:00:00 GMT Nowadays you can create almost everything with JavaScript, also games 🎮!!! The cool thing about this is that you don’t need to be an expert making games to start, and there are a lot of tools, for example Phaser.

Phaser let you create games with only JavaScript, I find this exciting and is the reason why I’m playing with it in some of my Twitch sessions.

One of the weaknesses that you can find in this kind of libraries is that are not in the same level of evolution than others like Vue and React (if we talk about UI elements). To create a button in Phaser is more complex than in Vue.

This is why some developers started to use React and Vue in combination with Phaser, in this tip you will learn how to integrate Phaser and Vue.

First create a new project with the CLI:

vue create game

Navigate to the project and install phaser and ion-phaser

npm install --save phaser @ion-phaser/core

This package will help ups to easily integrate Phaser with libraries and frameworks like Angular, React or Vue, you will find more information in their website.

The next step is to add ion-phaser to our main.js file:

import { defineCustomElements as defineIonPhaser } from '@ion-phaser/core/loader';
import Vue from 'vue';
import App from './App.vue';
 
Vue.config.productionTip = false;
Vue.config.ignoredElements = [/ion-\w*/];
 
defineIonPhaser(window);
 
new Vue({
  render: (h) => h(App),
}).$mount('#app');

Ok, now its time to open our example component, ‘HelloWorld.vue’ and add the ion-phaser component.

The layout will look like:

<template>
  <div class='hello'>
    <div  @click="initializeGame"  class="flex" >
      <a  href="#1"  class="btn">Initialize</a>
    </div>
    <ion-phaser
      v-bind:game.prop='game'
      v-bind:initialize.prop='initialize'
    />
  </div>
</template>

And in the code we simply add the game object, for this example a really simple game object:

import Phaser from "phaser";
 
export default {
  data() {
    return {
      initialize: false,
      game: {
        width: "100%",
        height: "100%",
        type: Phaser.AUTO,
        scene: {
          init() {
            this.cameras.main.setBackgroundColor("#24252A");
          },
          create() {
            this.helloWorld = this.add.text(
              this.cameras.main.centerX,
              this.cameras.main.centerY,
              "Hello World",
              { font: "40px Arial",  fill: "#ffffff" }
            );
            this.helloWorld.setOrigin(0.5);
          },
          update() {
            this.helloWorld.angle += 1;
          }
        }
      }
    };
  },
  methods: {
    initializeGame() {
      this.initialize = true;
    }
  }
}; 

And yeah! both are integrated!

In a real world example what I recommend to you is to have a separated file game.js to have the game configuration and split the login in different scenes.

I also like to have a Vue component called ‘<Hud>’ where I show the data about my character.

I’m working in a game using this kind of technologies, just to train myself and improve my coding skills.

Do you want to start to practice too? C’mon lets play and make games 🤗🤩

(Image of my game :D)

]]>
<![CDATA[Create Dynamic Titles and Favicons with Nuxt]]> https://vuedose.tips/create-dynamic-titles-and-favicons-with-nuxt/ https://vuedose.tips/create-dynamic-titles-and-favicons-with-nuxt/ Thu, 18 Jun 2020 01:55:00 GMT According to a study by the University of Myself, 98.87% of developers don't pay attention to the title or the favicon of their webapps. That browser tab is where your app is going to live since the user opens it, goes to Twitter on another tab, spends 30 minutes liking dog memes and then comes back to you.

There are a lot of states that happen in your application in the background: Incoming messages, Notifications, Deploys, you name it... Sometimes the best place to put an indicator to that state is just the browser tab.

In our case, with remate.io, we have Projects the team can work on. A project has a unique color that identifies it. You can hop from one project to another, go to github, open issues, review that extra semicolon and then get back to the app (or not). When we are tracking our network tab looks like

<div style="max-width: 300px;" class="mx-auto"> <img src="http://g.recordit.co/5ZAKShtsiq.gif" alt="Counter" /> </div>

This seems rocket science, but in fact is quite easy with Nuxt and its head method.

Nuxt.js uses vue-meta under the hood to update the headers and html attributes of your application. So we can do something like

export default {
  head () {
    return {
      title: 'Vue Dose is awesome!',
    }
  }
}

The fun fact is that head function acts like a computed, so it's reevaluated each time a dependency inside of it changes!

With that in mind, let's create a simple example: A dynamic page that gets a slug by route param with some categories in it

// _slug.vue
export default {
  data() {
    return {
      slug: this.$route.params.slug,
      categories: [
        { slug: 'geography', name: 'Geography', color: 'rgb(66, 153, 225)' },
        { slug: 'history', name: 'History', color: 'rgb(236, 201, 75)' },
        { slug: 'sports', name: 'Sports', color: 'rgb(237, 137, 54)' },
      ]
    }
  }
}

Now, lets update the title of the page using the name of the category

export default {
  data() {
    return {
      slug: this.$route.params.slug,
      categories: [
        { slug: 'geography', name: 'Geography', color: 'rgb(66, 153, 225)' },
        { slug: 'history', name: 'History', color: 'rgb(236, 201, 75)' },
        { slug: 'sports', name: 'Sports', color: 'rgb(237, 137, 54)' },
      ]
    }
  },
  
  computed: {
    category() {
      return this.categories.find(c => c.slug === this.slug) || {}
    }
  },
  
  head() {
    return {
      title: this.category.name,
    }
  }
}

Easy right? Let's go crazy and update the favicon too! As you might know, you can use SVGs as favicons. This feature allows you to be more dynamic. For this example let's just use an inline SVG with a circle.

export default {
  data() {
    return {
      slug: this.$route.params.slug,
      categories: [
        { slug: 'geography', name: 'Geography', color: 'rgb(66, 153, 225)' },
        { slug: 'history', name: 'History', color: 'rgb(236, 201, 75)' },
        { slug: 'sports', name: 'Sports', color: 'rgb(237, 137, 54)' },
      ]
    }
  },
    
  computed: {
    category() {
      return this.categories.find(c => c.slug === this.slug) || {}
    }
  },
  
  head() {
    return {
      title: this.category.name,
      link: [
        {
          rel: 'icon',
          type: 'image/svg+xml',
          href: `data:image/svg+xml,
                <svg
                  width="32"
                  height="32"
                  viewBox="0 0 32 32"
                  fill="none"
                  style="color: ${this.category.color};"
                  xmlns="http://www.w3.org/2000/svg"
                ><circle cx="16" cy="16" r="15.0049" fill="currentColor" /></svg>`,
        },
      ],
    }
  }
}

What we are doing here is simple. We are drawing an SVG and styling it with the color of the category. Inside, there is a circle that takes the currentColor and uses it in the fill property. The result for the Sport category will be an orange circle!

image

You can checkout this repo and fork it out if you want to see it in action.

]]>
<![CDATA[Auto Load Vuejs Components in Nuxt]]> https://vuedose.tips/auto-load-vuejs-components-in-nuxt/ https://vuedose.tips/auto-load-vuejs-components-in-nuxt/ Wed, 17 Jun 2020 01:55:00 GMT With Nuxt components you can auto import your components really easily and even comes with support for dynamic imports otherwise known as lazy loading. That means you can just add your component in the template without having to add it to the script tag. This makes development much faster.

How does it work?

This module parses your template and automatically includes the component in the file where you are using it such as a page, layout or even a component. Because Nuxt.js uses automatic code splitting to split your pages by default this module works perfect as it will only contain the components that are used on that page. Also, if you use a component in more than 2 pages, Nuxt.js will automatically create a shared chunk for them thanks to the magic of webpack.

@video

Installation

If you are using Nuxt v2.13+ then this module is included by default but it is not activated by default so you don't have to install it but you do have to set components to true in your nuxt config file.

export default {
  components: true
}

OR

If you are using Nuxt 2.10+ then you will need to install the module.

yarn add --dev @nuxt/components # or npm install --save-dev @nuxt/components

Then you need to add the @nuxt/components to your buildModules section of your nuxt.config.js file.

export default {
  buildModules: [
    '@nuxt/components'
  ]
}

Auto Import your components

Once you have the module activated or installed you can now simply add your components in your template. The module will read the components directory and automatically look for the component in there.

  1. Create your components
components/
  TheHeader.vue
  TheFooter.vue
  1. Use your components in any .vue file (components, pages or layouts) without having to add a script tag or an import.
<template>
  <TheHeader /> <!-- or <the-header /> -->
  <TheFooter /> <!--  or <the-footer /> -->
</template>

Dynamic Imports

You can also dynamically import your components or lazy load your components by prefixing the world Lazy in your templates.

<template>
  <TheHeader />
  <LazyTheFooter />  <!-- lazy loaded -->
</template>

This is great for when you want to load a component only on a click event for example. Here we have a component called MountainsList in our components folder and we add it to our template. We only show it if show is true. The button will appear if show is false and on click it will call the showList method which will change the value of show to true and this will therefore lazy load your component.

<template>
  <div>
    <h1>Mountains</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="showList">Show List</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      show: false
    }
  },
  methods: {
    showList() {
      this.show = true
    },
  }
}
</script>

You can check how this works by opening your browsers network tab and clicking on JS. Then if you refresh the page you will see the javaScripts that are loaded and when you click the button you will then see the MountainsList.js is loaded.

For more options or to see a live CodeSandBox check out this link

ℹ️ Nuxt components will not work with dynamic components. For these components you will have to use the script tag to import your component.

<component :is="componentName" />
]]>
<![CDATA[How to structure a Vue.js app using Atomic Design and TailwindCSS]]> https://vuedose.tips/how-to-structure-a-vue-js-app-using-atomic-design-and-tailwindcss/ https://vuedose.tips/how-to-structure-a-vue-js-app-using-atomic-design-and-tailwindcss/ Tue, 16 Jun 2020 01:40:00 GMT In this article I will explain what the Atomic Design methodology is about and how we can apply it to our Vue.js project.

To give style to the example I created in this codesandbox, I have used TailwindCSS with its default configuration.

Img

What is Atomic Design

:::blockquote All the theory we are going to see below is in depth in the book Atomic Design by Brad Frost :::

As Brad Frost says ‘Atomic design is like mental model to help us think of our user interfaces as both a cohesive whole and a collection of parts at the same time’.

Atomic design is a methodology for creating design systems chemistry-based and there are five distinct levels in atomic design: Atoms, Molecules, Organisms, Templates and Pages. Let’s see them in depth.

Atoms

It is the smallest unit that composes our application, it is not useful by itself but it allows us to have more control over the application elements.

Imagine that you want to create a basic layout, in it there will be a header, a body and a footer. But, in addition, each of them is made up of smaller elements such as the links you can see in the header and also in the footer.

Well, we’re talking about the atoms being the HTML tags that will be reused throughout the application, as link, heading and svg, among others.

For this example we have defined five atoms that will be useful to us:

AtomButton.vue

As you can see, it's simply a button-style link that expects a property that will contain an object with the name and url of our link.

<a
    :href="link.url"
    class="block text-center py-1 px-4 bg-blue-100 text-blue-500 font-semibold rounded"
>{{ link.name }}</a>

AtomLink.vue

The atom that represents our link will be the same as the previous one but with the base style.

<a :href="link.url">{{ link.name }}</a>

AtomLogo.vue & AtomText.vue

For the logo we will add the SVG that represents it and for the text we will add the tag <p>.

<!-- AtomLogo -->
<svg width="48" height="48" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
  …
</svg>

<!-- AtomText -->
<p class="pb-6">{{ text }}</p>

AtomTitle.vue

In this case, as the title can have more than one type of tag, h1, h2, ..., we have created a dynamic component to be able to represent the corresponding title introduced by the prop: tag.

<template>
  <component :is="tag" class="text-3xl font-serif pb-2">{{ content }}</component>
</template>

<script>
export default {
  name: "AtomTitle",
  props: ["tag", "content"]
};
</script>

Now that we have reviewed all the atoms needed for our template, let's get on with the molecules!

Molecules

Molecules, as in nature, are groups of atoms linked together. In our application, molecules are the smallest components composed of one or more repeated atoms, always simple combinations built for reuse.

Having composed a molecule by several atoms already made, make us work with "do one thing and do it well" mentality. Both atoms and molecules encourage the creation of independent and reusable components.

Now, let's apply it to a case study. Imagine what we could do with the atoms we have created, what combinations without too much complexity we can create and reuse? Well, a list of links would be good for both the header and the footer and, also, we can create a card combining title and text atoms.

Let's take a look at the examples we have created:

MoleculeLinks.vue

In this case, we have created a list with the tags ul, li and, to represent the link, we have imported the AtomLink and used it as a component.

<template>
  <ul>
    <li v-for="(link, index) in links" :key="link.name + index">
      <AtomLink :link="link"/>
    </li>
  </ul>
</template>

<script>
import AtomLink from "@/components/AtomLink";

export default {
  name: "MoleculeLinks",
  props: ["links"],
  components: {
    AtomLink
  }
};
</script>

MoleculeCard.vue

In this molecule, to form the card, we have used the AtomTitle and AtomText, both added to the div that gives the style to the card.

<template>
   <div
     class="p-6 border border-blue-200 hover:shadow-lg rounded-sm bg-blue-100 text-blue-800 transition ease-in-out duration-500"
   >
     <AtomTitle tag="h2" :content="card.title"/>
     <AtomText :text="card.text"/>
   </div>
</template>

<script>
import AtomTitle from "@/components/AtomTitle";
import AtomText from "@/components/AtomText";

export default {
   name: "MoleculeCard",
   props: ["card"],
   components: {
     AtomTitle,
     AtomText
   }
};
</script>

MoleculeBanner.vue

For the Banner that will appear under the header we have needed both AtomTitle and AtomText, and also the AtomButton.

<template>
   <div class="px-4 pt-40 pb-12 md:py-32 bg-blue-900 text-gray-100">
     <header class="max-w-sm md:max-w-xl mx-auto">
       <AtomTitle tag="h1" content="Atomic Design in Vue.js"/>
       <AtomText
         text="Etiam in arcu lectus. Nulla facilisi. Nunc viverra vehicula nunc eu tristique. Sed gravida libero sem, sed ornare arcu venenatis at."
       />
       <AtomButton class="md:max-w-xs" :link="{ name: 'Button', url: '#' }"/>
     </header>
   </div>
</template>
 
<script>
import AtomTitle from "@/components/AtomTitle";
import AtomText from "@/components/AtomText";
import AtomButton from "@/components/AtomButton";
 
export default {
   name: "MoleculeBanner",
   components: {
     AtomTitle,
     AtomText,
     AtomButton
   }
};
</script>

Organisms

Organisms are groups of molecules joined together to form a relatively complex, distinct section of an interface, as the header and the footer.

Now, we can see the final interface beginning to take shape. As in molecules with atoms, organisms can consist of similar and/or different molecule types.

As in the previous sections, we will see the possibilities we have to create the organisms. Let's have a look directly on each one:

OrganismHeader.vue

In order to create the Header organism we need the logo, a list of links, to navigate through the application, and a button for registration, for example. In that case we need the Logo and Button atoms and the Links molecule.

 <template>
     <header class="absolute inset-x-0 text-gray-100 py-4 flex flex-wrap items-center">
       <div class="w-full md:w-3/4 px-4 pb-4 md:pb-0 flex items-center justify-between">
         <AtomLogo/>
         <MoleculeLinks class="flex" :links="links"/>
       </div>
       <div class="w-full md:w-1/4 px-4">
         <AtomButton :link="{ name: 'Button', url: '#' }"/>
       </div>
     </header>
 </template>

 <script>
 import AtomLogo from "@/components/AtomLogo";
 import AtomButton from "@/components/AtomButton";
 import MoleculeLinks from "@/components/MoleculeLinks";

 export default {
     name: “OrganismHeader",
     props: ["links"],
     components: {
       AtomLogo,
       AtomButton,
       MoleculeLinks
     }
 };
 </script>

OrganismGrid.vue

To represent several cards on the same grid we create the OrganismGrid, which will contain MoleculeCard inside a v-for and that will be shape it with flex. Let’s check the code:

<template>
   <div class="flex flex-wrap items-stretch md:-mr-4">
     <div class="w-full md:w-1/3 md:pr-4 pb-4" v-for="card in cards" :key="card.name">
       <MoleculeCard class="h-full" :card="card"/>
     </div>
   </div>
</template>

<script>
import MoleculeCard from "@/components/MoleculeCard";

export default {
   name: “OrganismGrid",
   props: ["cards"],
   components: {
     MoleculeCard
   }
};
</script>

OrganismFooter.vue

As the previous cases, we just need the molecule we want to use. But, in this case, applying a class to the molecule we are using we can represent it vertically, now MoleculeLinks is using flex-col (flex-direction: column).

<template>
   <footer class="flex flex-wrap text-white">
     <div class="w-full md:w-1/4 bg-gray-900 p-4 md:p-12">
       <MoleculeLinks class="flex flex-col" :links="columnOneLinks"/>
     </div>
     <div class="w-full md:w-3/4 bg-blue-900 p-4 md:p-12">
       <MoleculeLinks class="flex flex-col" :links="columnTwoLinks"/>
     </div>
   </footer>
</template>

<script>
import MoleculeLinks from "@/components/MoleculeLinks";

export default {
   name: “OrganismFooter",
   props: ["columnOneLinks", "columnTwoLinks"],
   components: {
     MoleculeLinks
   }
};
</script>

Templates

Now, we left behind the chemistry-based theory to get into common web language. Templates consist mostly of groups of organisms and/or molecules to form the common structure of a page, what we used to call layout.

At this stage, we already have created every piece of our template, so let's add them together to see how it looks.

We are hardcoding the data in the Template component, but the section below explains that we need to do it in the page stage.

<template>
   <section class="flex flex-col min-h-screen">
     <OrganismHeader :links="links"/>
     <div class="flex-auto flex flex-wrap bg-white">
       <MoleculeBanner class="w-full md:w-3/4 text-center md:text-left"/>
       <div class="container mx-auto px-4 py-8 md:py-16">
         <OrganismGrid :cards="cards"/>
       </div>
     </div>
     <OrganismFooter :columnOneLinks="columnOneLinks" :columnTwoLinks="columnTwoLinks"/>
   </section>
</template>

<script>
import OrganismHeader from "@/components/OrganismHeader";
import OrganismFooter from "@/components/OrganismFooter";
import MoleculeBanner from "@/components/MoleculeBanner";
import OrganismGrid from "@/components/OrganismGrid";

export default {
   name: "TemplateLanding",
   components: {
     MoleculeBanner,
     OrganismHeader,
     OrganismFooter,
     OrganismGrid
   },
   data: () => {
     return {
       links: […],
       cards: […],
       columnOneLinks: […],
       columnTwoLinks: […]
     };
   }
};
</script>

Pages

Pages are specific instances of templates, where our components are replaced with real data to give an accurate depiction of what a user will see.

This stage help us a lot testing the effectiveness of the design system we've created and check that everything in context is built as we expect. To better understand this part, imagine that you have a heading with three lines instead of one, in this stage you will decide how to change your atom to look as you want in this specific case.

Conclusion

Now that we know the possibilities offered by structuring our design system with this methodology, we can be sure that the changes will not be a headache and that we will be able to build huge applications.

]]>
<![CDATA[Go async in Vue 3 with Suspense]]> https://vuedose.tips/go-async-in-vue-3-with-suspense/ https://vuedose.tips/go-async-in-vue-3-with-suspense/ Mon, 15 Jun 2020 01:55:00 GMT Vue 3 is coming with a some exciting new features. Composition API is the hottest one at the moment but there are others that excite me as much as it.

One of those new features is called Suspense and it really excites me about the benefits it brings. You might have heard about it already but I will try to show some examples of the usage of Suspense and where it can be beneficial.

What is Suspense?

::: blockquote Suspense is a state of mental uncertainty, anxiety, of being undecided, or of being doubtful. In a dramatic work, suspense is the anticipation of the outcome of a plot or of the solution to an uncertainty, puzzle, or mystery, particularly as it affects a character for whom one has sympathy.

- Wikipedia :::

Back to Vue, Suspense is a component, that you don't need to import or do any kind of setup, with two <slot> that allows you to render a #fallback while the main component you want to load is not ready.

Ok, it seems vague. I will try to give you an example of how it is used. I also recommend you to take a look into its test cases, especially the first one to get familiar with it.

<Suspense>
  <template #default>
    <!-- The component I want to render -->
  </template>
  <template #fallback>
    <!-- Fallback component shown while my component is not ready -->
  </template>
</Suspense>

That is the basic blueprint of it and it tackles a really common use case: the v-if loading condition.

I consider it the first benefit of Suspense, as now we've some standard way of dealing with this scenario. Before Suspense each developer could choose the way they want to implement it, they still can, and it was kind of a nightmare in situations where multiple components were loaded, so you would have loadingHeader, loadingFooter, loadingMain, and so on.

At the beginning I wrote "while the main component you want to load is not ready", what it means is that the main component has some kind of async work, which plays nicely with an async setup() from the new Composition API.

Let's say we have the following component with some async work to be done in setup:

<template>
  <h1>Ive some async work to do before I can render</h1>
</template>

<script>
export default {
  name: 'MyAsyncComponent',
  async setup() {
    await someAsyncWork();
  }
 }
 </script>

Now we want to use this component somewhere but we want to have a proper loading while it is not ready.

Suspense makes it more intuitive how it works and it really helps readability, check it:

<template>
 <Suspense>
   <template #default>
     <MyAsyncComponent />
   </template>
   <template #fallback>
     <span>Loading... Please wait.</span>
   </template>
 </Suspense>
</template>

<script>
import MyAsyncComponent from '@/components/MyAsyncComponent.vue';

export default {
 name: 'App',
 components: { MyAsyncComponent }
}
</script>

Another cool thing about it is that you can have multiple Suspense components defined and have different fallbacks for each of them.

How do I handle errors?

Imagine the following: the call to someAsyncWork threw an exception. How do we handle it with Suspense?

We can use the errorCapture hook to listen to errors and conditionally render our Suspense. The component will look like the following:

<template>
  // Here we conditionally render based on error
  <h1 v-if="error">I failed to load</h1>
  <Suspense v-else>
    <template #default>
      <MyAsyncComponent />
    </template>
    <template #fallback>
      <span>Loading... Please wait.</span>
    </template>
  </Suspense>
</template>

<script>
import { ref, onErrorCaptured } from 'vue'
import MyAsyncComponent from '@/components/MyAsyncComponent.vue';

export default {
  name: 'App',
  components: { MyAsyncComponent },
  setup() {
    const error = ref(null);

    onErrorCaptured((e) => {
      error.value = e

      return true;
    });
    
    return { error };
  }
}
</script>

To be honest it is quite a boilerplate if you're doing it in multiple places and can be a bit cumbersome if you've multiple Suspenses.

I do encourage you to wrap this logic, and even improve it to your use case, in a new component. The following example shows a simple wrapper on top of it:

<template>
  <slot v-if="error" name="error"></slot>
  <Suspense v-else>
    <template #default>
      <slot name="default"></slot>
    </template>
    <template #fallback>
      <slot name="fallback"></slot>
    </template>
  </Suspense>
</template>

<script>
import { ref, onErrorCaptured } from 'vue'

export default {
  name: 'SuspenseWithError',
  setup() {
    const error = ref(null);

    onErrorCaptured((e) => {
      error.value = e

      return true;
    });
    
    return { error };
  }
}
</script>

So you can use it like this:

<template>
  <SuspenseWithError>
    <template #default>
      <MyAsyncComponent />
    </template>
    <template #fallback>
      <span>Loading... Please wait.</span>
    </template>
    <template #error>
      <h1>I failed to load</h1>
    </template>
  </SuspenseWithError>
</template>

<script>
import MyAsyncComponent from '@/components/MyAsyncComponent.vue';
import SuspenseWithError from '@/components/SuspenseWithError.vue';

export default {
  name: 'App',
  components: { MyAsyncComponent, SuspenseWithError },
}
</script>

Bear in mind that it is a simple and compact implementation that has not been tested in a real application. It also does not distinguished errors which might not be the ideal scenario for you.

Suspense with Vue Router

The main goal of this Dose is to show how to use the Suspense with Vue Router. All the other examples above were made to introduce Suspense and its power.

Suspense plays nicely with Vue Router. What I mean is that you can Suspense your <router-view> and in case the view has an async setup, you can show a fallback.

To be more specific: You can create your loading component that will be shown whenever your view is not ready due some async work that has to be performed.

You can achieve the same behavior with the navigation guards but for most of the cases, which do not involve a complex setup, the combination <router-view>, Suspense and async setup do a nice job!

The example below shows how it can be implemented:

<Suspense>
  <template #default>
    <router-view />
  </template>
  <template #fallback>
    <span>I'm a loading screen, I'm waiting the view to be ready!</span>
  </template>
</Suspense>

All in all

  • Suspense can be used to show a fallback element when an async work is needed in the main component
  • One component can have multiple suspended components inside
  • Error handling can be done with onErrorCaptured hook
  • A wrapper can be created to extract the error logic
  • Suspense plays nicely with Vue Router once we want to show a loading screen

The final result is shown below and you can also check the sample code here: vue-3-suspense.

Final example

]]>
<![CDATA[Use Web Workers in your Vue.js Components for Max Performance]]> https://vuedose.tips/use-web-workers-in-your-vuejs-component-for-max-performance/ https://vuedose.tips/use-web-workers-in-your-vuejs-component-for-max-performance/ Tue, 31 Mar 2020 00:00:00 GMT In the first vuedose VueDose tip ever I revealed a trick to improve performance on large lists. Yay, that was a great start!.

However not all comes into that case.

Sometimes you need to use a component that is heavy to create and render, usually because they perform complex tasks. I came up into that case yesterday.

I was creating a page using StoryBlok. They have the amazing feature of creating a rich-text field that content managers can use in order to enter any kind of formatted text such as lists, images, block quotes, bold, italics...

When you get the rich-text content from the StoryBlok API, it has its own structure. In order to render that data into HTML, you must use the richTextResolver.render(content) method of storyblok-js-client.

We can encapsulated this functionality into a RichText.vue component. A basic implementation would be:

<template>
  <div v-html="contentHtml"></div>
</template>

<script>
  export default {
    props: ["content"],
    computed: {
      contentHtml() {
        return this.$storyapi.richTextResolver.render(content);
      }
    }
  };
</script>

Note: $storyapi it's an instance of the StoryBlok Client that comes from StoryBlok Nuxt.js module, which is what I'm using, but that's not relevant for the purpose of this article

Nothing too fancy so far. Just... here comes the surprise.

It seems like that render it's a heavy task, which was starting to be noticable when rendering several of this components with a decent amount of content.

Now imagine this situation: you have a list of text-rich components in your page, and a dropdown to filter by. When you change your dropdown filter, you re-fetch all content given that filter, and the list re-renders.

Here's where you could notice the heavyness of richTextResolver.render: the dropdown was lagging to close after selecting it's value.

The reason is that the default JavaScript execution runs in the main thread, which is UI-blocking.

Problem understood. So... how can we fix it?

Easy: using a Web Worker for the rich-text rendering task.

Note: I'm not diving into Web Workers, but check its docs for more info.

Web Workers run in a separate thread and they're not UI-blocking, perfect for our case. We're not diving into web workers, but check its docs for more info.

Keep in mind that web workers run in their own context, and by default we cannot access external contexts. But we need to access the storyblok-js-client npm module. For that, Webpack comes to the rescue with worker-loader.

First, install it by running `npm install -D worker-loader. Then you'd need to configure it. In Nuxt.js you'd do it in nuxt.config.js in this way:

build: {
  extend(config, { isDev, isClient }) {
    config.module.rules.push({
      test: /\.worker\.js$/,
      use: { loader: "worker-loader" }
    });
  }
}

With this configuration, all *.worker.js files will be processed by worker-loader.

Let's create a render-html.worker.js:

import StoryblokClient from "storyblok-js-client";

let storyClient = new StoryblokClient({});

// When the parent theard requires it, render the HTML
self.addEventListener("message", ({ data }) => {
  const result = storyClient.richTextResolver.render(data);
  self.postMessage(result);
});

That's a basic implementation of a worker. You need to listen to message event, which is the way you'll communicate to it from your Vue.js app. Then you can get the data of the event, render it with storyblok-js-client and send up the result self.postMessage.

Let's update the RichText.vue component to use the service worker:

<template>
  <div v-html="contentHtml"></div>
</template>

<script>
  import Worker from "./render-html.worker.js";

  // Create the worker instance
  const worker = new Worker();

  export default {
    props: ["content"],
    data: () => ({
      contentHtml: ""
    }),
    mounted() {
      // Update the state with the processed HTML content
      worker.onmessage = ({ data }) => {
        this.contentHtml = data;
      };
      // Call the worker to render the content
      worker.postMessage(this.content);
    }
  };
</script>

The result

Do you want to know how much performance improvement we achieved by this? Sure, Web Performance doesn't make sense if you don't measure it.

In fact, I have an article for you to learn and undestand how to meassure performance in Vue.js components. So make sure to read it to better understand the following test.

I've meassured this component with a medium-size on my mac, 6x throttle.

The results are: 20.65x faster on component's render and 1.39x on patch.

If you don't know what render and patch mean, it's explained in this article

That's it for today's tip!

]]>
<![CDATA[Deep vs Shallow Rendering in Vue.js Tests]]> https://vuedose.tips/deep-vs-shallow-rendering-in-vuejs-tests/ https://vuedose.tips/deep-vs-shallow-rendering-in-vuejs-tests/ Tue, 03 Mar 2020 00:00:00 GMT What a nice feeling to get to write a tip right after the Vue.js Amsterdam! It was a boost, thanks to everyone that were so kind, friendly and curious about VueDose. Just so you know, if you'd like to write a tip here, just let me know!

To give you an impression on how Vue Amsterdam went, just see this picture taken by my friend GustoJS and his amazing camera.

Alex Adrian VueAmsterdam

Now let's get serious and start with the tip!

Testing is still one of the most controversial dev topics, and deep vs shallow rendering is no exception. In this tip I want to make my point on why and when to use each of them.

Deep Rendering

Deep rendering, as the name states, renders all component tree given a root component.

In order to illustrate it, given this UserList.vue component:

<template>
  <ul>
    <li v-for="user in users" :key="user">
      {{ user }}
    </li>
  </ul>
</template>

<script>
  export default {
    props: {
      users: Array
    }
  };
</script>

That you use in an App.vue component like this:

<template>
  <div>
    <h3>User List</h3>
    <UserList :users="['Rob Wesley']" />
  </div>
</template>

<script>
  import UserList from "./UserList";

  export default {
    components: { UserList }
  };
</script>

Then it will give us the combined DOM Tree of both components:

<div>
  <h3>User List</h3>
  <ul>
    <li>
      Rob Wesley
    </li>
  </ul>
</div>

In order to check that, you can use Jest Snapshots. If you don't know much about Snapshots, check the article The power of Snapshot Testing that goes deeper into them.

When using @vue/test-utils, the official Vue.js testing library, you can perform deep rendering by using the mount method.

With these clues, you can imagine the previous snapshot was taken from a test with a shape like this:

import { mount } from "@vue/test-utils";
import App from "@/App";

describe("App.vue", () => {
  it("Deeply renders the App component", () => {
    const wrapper = mount(App);
    expect(wrapper.html()).toMatchSnapshot();
  });
});

Shallow Rendering

Opposite to deep rendering, shallow rendering only renders the component that you're testing without going into deeper levels.

To implemente shallow rendering, you can use the shallowMount method from @vue/test-utils.

If you rewrite the previous test to use shallow rendering:

import { shallowMount } from "@vue/test-utils";
import App from "@/App";

describe("App.vue", () => {
  it("Shallow renders the App component", () => {
    const wrapper = shallowMount(App);
    expect(wrapper.html()).toMatchSnapshot();
  });
});

You'll see that the generated snapshot is:

<div>
  <h3>User List</h3>
  <userlist-stub users="Rob Wesley"></userlist-stub>
</div>

What happened? Basically Jest has created the <userlist-stub> tag automatically instead of creating and rendering the UserList component.

Notice one point: the UserList component wasn't even created, meaning the component isn't used at all. Not their props, not their lifecycle hooks... nothing. You'll see now why that's important.

What to Use and When

Taking back some testing theory, a test should be:

  • Independant from others
  • Focus on testing one thing
  • Easy to maintain and stable over the time
  • Valuable

And as you may see already, when you use deep rendering you're already violating the first three points, since every time a child component changes, you'll test will fail.

Additionally, imagine a child component performs side effects, like interacting with the store or calling an API. That could be even worse since the results might be unexpected.

That's why, my take on this is: Use shallowMount most of the time.

The question is: When not to use it?. I'll use mount just in those cases that you're testing a group of components as a unit, treating them as molecules.

Think of UserList and UserListItem, or Tab, TabGroup and TabItem. For those cases, it makes sense to use deep rendering when you want to test the interaction of the whole group working together.

Follow Up

To end up I wanted to share that I'll be giving the workshop Vue.js Testing for Everyone in Vue Day Italy, Verona, on 4th April.

In that workshop you'll be able to learn all the techniques and best practices to test a Vue.js App, step by step and in the most practical way. Definitely we'll cover this content in a much deeper way.

That's it for today's tip!

]]>
<![CDATA[Create a i18n Plugin with Composition API in Vue.js 3]]> https://vuedose.tips/create-a-i18n-plugin-with-composition-api-in-vuejs-3/ https://vuedose.tips/create-a-i18n-plugin-with-composition-api-in-vuejs-3/ Mon, 17 Feb 2020 03:00:00 GMT The way plugins are coded in Vue.js 3 with Composition API differ from traditional plugins. Traditional are used via a install function and added using Vue.use(plugin). They usually manipulate/extend the Vue prototype.

However, in Composition API plugins are non-manipulative and coded using an inject-provide pattern. For instance, you can create a i18n plugin like this:

// i18nPlugin.js
import { ref, provide, inject } from "@vue/composition-api";

const createI18n = config => ({
  locale: ref(config.locale),
  messages: config.messages,
  $t(key) {
    return this.messages[this.locale.value][key];
  }
});

const i18nSymbol = Symbol();

export function provideI18n(i18nConfig) {
  const i18n = createI18n(i18nConfig);
  provide(i18nSymbol, i18n);
}

export function useI18n() {
  const i18n = inject(i18nSymbol);
  if (!i18n) throw new Error("No i18n provided!!!");

  return i18n;
}

As you can see, the functions provide and inject are used to create the plugin instance and hold it in a dependency injection mechanism.

Check that we use ref for the locales, since we need the to be reactive.

If you're not very familiar yet with Composition API, please read the tip to easily migrate to Composition API and how to use old instance properties to get a bit more in detail about it.

Then, once in the app you must initialize the plugin with the right configuration by using the provideI18n function. That's usually done in the root App.vue component:

<script>
  import { provideI18n } from "./i18nPlugin";
  import HelloWorld from "./HelloWorld";

  export default {
    components: { HelloWorld },
    setup() {
      provideI18n({
        locale: "en",
        messages: {
          en: {
            hello_world: "Hello world"
          },
          es: {
            hello_world: "Hola mundo"
          }
        }
      });
    }
  };
</script>

Finally, in any component we want to use the plugin, we must inject it by using the useI18n function in the setup function. Create a HelloWorld.vue component with:

<template>
  <div>
    <h2>{{ i18n.$t('hello_world') }}</h2>
  </div>
</template>

<script>
  import { useI18n } from "./i18nPlugin";

  export default {
    setup() {
      const i18n = useI18n();

      return { i18n };
    }
  };
</script>

But hey... what's the fun of it if we cannot change the language? Let's add to the previous code the functionality to change the language:

<template>
  <div>
    <h2>{{ i18n.$t('hello_world') }}</h2>
    <button @click="switchLanguage">Switch language</button>
  </div>
</template>

<script>
  import { useI18n } from "./i18nPlugin";

  export default {
    setup() {
      const i18n = useI18n();

      const switchLanguage = () => {
        const locale = i18n.locale.value === "en" ? "es" : "en";
        i18n.locale.value = locale;
      };

      return {
        i18n,
        switchLanguage
      };
    }
  };
</script>

Just by adding a button and the switchLanguage function we already have the feature.

That's all. What I like the most about Composition API is the easiness to develop code that is predictable and maintainable by providing clean patterns.

How do you like it?

If you want to see with your own eyes that this code truly works, go and check it in this CodeSandbox!

That's it for today's tip!

]]>
<![CDATA[Access template refs in Composition API in Vue.js 3]]> https://vuedose.tips/access-template-refs-in-composition-api-in-vuejs-3/ https://vuedose.tips/access-template-refs-in-composition-api-in-vuejs-3/ Mon, 09 Dec 2019 00:00:00 GMT You've seen in the last tip "Use old instance properties in Composition API in Vue.js 3" how to access instance properties in the new syntax.

However, our beloved this.$refs wasn't included in the setup context object as you realized if you read the tip.

So, how can you work with template refs in Composition API?

It might be more simple than you think! The thing is, Vue.js unifies the concept of refs, meaning that you just need to use the ref() function you already know for declaring reactive state variables in order to declare a template ref as well.

Only keep in mind that the ref name must be the same as the variable's one. Let me illustrate it. For the template:

<template>
  <div>
    <h2 ref="titleRef">{{ formattedMoney }}</h2>
    <input v-model="delta" type="number" />
    <button @click="add">Add</button>
  </div>
</template>

I've set titleRef on the <h2> tag. That's all in the template level. Now in the setup function, you need to declare a ref with the same titleRef name, initialized to null for instance:

export default {
  setup(props) {
    // Refs
    const titleRef = ref(null);

    // Hooks
    onMounted(() => {
      console.log("titleRef", titleRef.value);
    });

    return {
      titleRef
      // ...
    };
  }
};

You access the ref value just like any other reactive ref, by accessing the .value property. If you do so as shown in the example you should see in the console the result titleRef <h2>10.00</h2>

Don't believe me? Check it out with your own eyes this CodeSandbox!

That's it for today's tip!

]]>
<![CDATA[Use old instance properties in Composition API in Vue.js 3]]> https://vuedose.tips/use-old-instance-properties-in-composition-api-in-vuejs-3/ https://vuedose.tips/use-old-instance-properties-in-composition-api-in-vuejs-3/ Tue, 26 Nov 2019 00:00:00 GMT In the last tip "Easily switch to Composition API in Vue.js 3" I explained how to migrate the basic parts of a Vue.js Object API component to the new Composition API.

However, that's not all. What happens with all instance properties we used to have, such as this.$emit, this.$slots, this.$attrs and so? They were on the this component instance, but there is no this in Composition API.

In the same line, in the last tip I didn't use props, and you used to access them via this in the component instance. How the heck can you access it now?

The thing is, I haven't explained the arguments of the setup function when using Composition API.

Effectively, the first parameter of the setup function receives all the component properties. Following the example from the last tip, let's add two properties to use as the initial values for the money and delta local state variables:

export default {
  props: {
    money: {
      type: Number,
      default: 10
    },
    delta: {
      type: Number,
      default: 1
    }
  },
  setup(props) {
    const money = ref(props.money);
    const delta = ref(props.delta);

    // ...
  }
};

Easy-peasy. Nothing else changes about props management aside from this in Vue.js components.

Now, what about all other instance properties and methods, such as $emit? You can find them in the second argument of the setup function: the setup context object.

The setup context has the following shape:

interface SetupContext {
  readonly attrs: Record<string, string>;
  readonly slots: { [key: string]: (...args: any[]) => VNode[] };
  readonly parent: ComponentInstance | null;
  readonly root: ComponentInstance;
  readonly listeners: { [key: string]: Function };
  emit(event: string, ...args: any[]): void;
}

And what's even cooler, it's that we can use object destructuring on the setup context and the reactivity is not lost!

To illustrate it, I'm going to modify the last example and add some logging in the onMounted hook as well as emitting a money-changed event when that changes:

setup(props, { emit, attrs, slots }) {
    // State
    const money = ref(props.money);
    const delta = ref(props.delta);

    // Hooks
    onMounted(() => {
      console.log("Money Counter (attrs): ", attrs);
      console.log("Money Counter (slots): ", slots);
    });

    // Watchers
    const moneyWatch = watch(money, (newVal, oldVal) =>
      emit("money-changed", newVal)
    );
}

That's it! Now you're already able to use most of Vue.js component instance properties and methods.

But probably you've realized not all are in the render context... What about this.$refs? And plugins that inject stuff such as this.$store?

No worries, I'll cover that in the next tips! No spoilers, stay with me and you'll stay cool!

By the way, if you're a live-coding peep you should check the code from this tip working in this CodeSandbox!

]]>
<![CDATA[Easily switch to Composition API in Vue.js 3]]> https://vuedose.tips/easily-switch-to-composition-api-in-vuejs-3/ https://vuedose.tips/easily-switch-to-composition-api-in-vuejs-3/ Tue, 19 Nov 2019 00:00:00 GMT The fact that Vue.js 3 already reached the alpha version made me think that... it's time already for some Vue.js 3 tips!

The idea is to give you some tips related to new features you can find on Vue.js 3 as they get available. For now, we'll focus on Composition API, one of the most game-changing features!

This first one is specially focused on showing you a basic step-by-step guide or cheatsheet to migrate object-api Vue.js components to use Composition API. In future tips you'll also see how to apply certain new techniques this API allow us to do as well 😉.

I'll do that by showing you how to convert an Object-API-based component to use Composition API.

For that, let's create a MoneyCounter.vue component that basically shows a money amount and allow us to add/substract quantities to it, implemented using the following code:

<template>
  <div>
    <h2>{{ formattedMoney }}</h2>
    <input v-model="delta" type="number">
    <button @click="add">Add</button>
  </div>
</template>

<script>
export default {
  data: () => ({
    money: 10,
    delta: 1
  }),
  computed: {
    formattedMoney() {
      return this.money.toFixed(2);
    }
  },
  mounted() {
    console.log("Clock Object mounted!");
  },
  methods: {
    add() {
      this.money += Number(this.delta);
    }
  },
  watch: {
    money(newVal, oldVal) {
      console.log("Money changed", newVal, oldVal);
    }
  }
};
</script>

This component has a money state which holds the quantity, the delta that is bound using v-model to the input and later used in the add method to add that quantity to money. The computed property formattedMoney correctly displays the decimal values of money. Finally, I also included a dummy watch and mounted with a console.log statement for the purpose of showing how to migrate it to Composition API.

Take some time to understand this component if you need it.

Right away, create a MoneyCounterComposition.vue component. This component shares the same <template> than the previous, since template is not affected by API's, so copy it from the previous component.

The change is in the <script> part. Let's check first how it'd be all code:

// In Vue 3, you'll import from "vue"
import { ref, computed, watch, onMounted } from "@vue/composition-api";

export default {
  setup() {
    // State
    const money = ref(10);
    const delta = ref(1);

    // Computed props
    const formattedMoney = computed(() => money.value.toFixed(2));

    // Hooks
    onMounted(() => console.log("Clock Object mounted"));

    // Methods
    const add = () => (money.value += Number(delta.value));

    // Watchers
    const moneyWatch = watch(money, (newVal, oldVal) =>
      console.log("Money changed", newVal, oldVal)
    );

    return {
      delta,
      money,
      formattedMoney,
      add,
      moneyWatch
    };
  }
};

First of all, we're exporting an object with the setup function. This is required and here's where all happens. Now let's focus part by part:

State: it's implemented using ref, and as you can see you can have as many as you want. To access them you don't need to access "this", since it's just a variable not a "magic" instance thing (yay!), although to change its value you need to access the .value property. You could use reactive as well, but I encourage use to read this article from Jason Yu and use ref as a convention.

Computed: you need to use computed for it. It's basic: it takes a function as its first argument.

Hooks: every hook has its own utility. In the case of mounted hook its onMounted. Same shape as computed: they take a function as their first argument.

Methods: they're just functions, like any other. Nothing special here.

Watch: there are different signatures. That's the one equivalent to watch money, but you can check all shapes in the RFC docs.

Finally, the setup function must return an object containing everything you want to use in the template. Anything that is not there, it won't be accesible from the template.

A little note: you may notice that I'm importing from "@vue/composition-api". That's because I'm using a plugin since Vue 3 is not there yet, but with the plugin we can already use it.

I hope the following image helps you understand how to migrate this component by this side-by-side perspective:

Side to side components

If you want to see with your own eyes that this code truly works, go and check it in this CodeSandbox!

That's it for today's tip!

]]>
<![CDATA[Theming using CSS Custom Properties in Vue.js Components]]> https://vuedose.tips/theming-using-custom-properties-in-vuejs-components/ https://vuedose.tips/theming-using-custom-properties-in-vuejs-components/ Tue, 05 Nov 2019 00:00:00 GMT Hello there! The topic of this VueDose tip is to create super generic, flexible and yet robust components. Ready? Let's go!

Let's start this tip with a simple button:

<template>
  <button><slot/></button>
</template>

<script>
export default {
  name: "AppButton",
};
</script>

<style scoped>
button {
  border: 4px solid #41b883;
  padding: 12px 24px;
  transition: 0.25s ease-in-out all;
}

button:hover {
  color: white;
  background-color: #41b883;
}
</style>

We want a component to be as generic as possible, to the point that we could reuse it in different websites with different brands. But how can we do that?

Well, first of all, we are not going to assume that we'll always get text as content. What if we want to pass different markup like <strong></strong> or pass an icon? The best way to overcome this problem? Make use of slots.

Let's work on the styling now. As always, a good rule of thumb is to use scoped which makes our CSS local. However, we want some things global, right? For instance the brand colors. Right now we are breaking the DRY principle by duplicating the concept of a primary color. If we needed to change the primary color we'll have to change it in every place it's hardcoded. The solution? Use custom properties.

<style scoped>
:root {
  --primary-color: #41b883;
  --on-primary-color: white;

  --small-spacing: 12px;
  --normal-spacing: calc(var(--small-spacing) * 2);
}

button {
  border: 4px solid var(--primary-color);
  padding: var(--small-spacing) var(--normal-spacing);
  transition: 0.25s ease-in-out all;
}

button:hover {
  color: var(--on-primary-color);
  background-color: var(--primary-color);
}
</style>

We would normally define custom properties in another file and target the :root selector and not inside the component, but for the sake of this example we are going to do it inside the component.

Colors and spacing is usually what changes the most in web design, so we have to make sure we protect ourselves from these changes.

This makes the component very flexible as we can change these properties and it will affect all the elements that make use of them.

However, how could we tackle theming? Well we could use non-declared custom properties. Wait. What? Let me explain with code:

<style scoped>
button {
  border: 4px solid var(--button-border-color, var(--primary-color));
  padding: var(--small-spacing) var(--normal-spacing);
  transition: 0.25s ease-in-out all;
}

button:hover {
  color: var(--button-hover-text-color, var(--on-primary-color));
  background-color: var(--button-hover-background-color, var(--primary-color));
}
</style>

Where are we declaring --button-border-color, --button-hover-text-color and --button-hover-background-color? That's the trick, we are not.

We are using an undefined custom property but giving it a default value. So, if one of these properties does not exist at runtime it will fallback to the default value.

This means that from the outside we can do something as follows:

<template>
  <AppButton class="custom-theme">Hello VueDose!</AppButton>
</template>

<style scoped>
.custom-theme {
  --button-border-color: pink;
  --button-hover-background-color: rgb(206, 161, 195);
}
</style>

And this is super flexible, but maybe too flexible. We don't want to expose so much knowledge to the client. Maybe the client just wants to set instead of a primary colored button a secondary theme. And the button should be able to know what things it has to set in order to have the look and feel of a secondary button. So let's mix custom properties and themes!

<template>
  <button :style="getTheme"><slot/></button>
</template>

<script>
export default {
  name: "AppButton6",
  props: {
    theme: String,
    validator: (theme) => ['primary', 'secondary'].includes(theme)
  },
  computed: {
    getTheme() {
      const createButtonTheme = ({
        borderColor,
        hoverTextColor,
        hoverBackgroundColor
      }) => ({
        '--button-border-color': borderColor,
        '--button-hover-text-color': hoverTextColor,
        '--button-hover-background-color': hoverBackgroundColor
      })

      const primary = createButtonTheme({
        borderColor: 'var(--primary-color)',
        hoverTextColor: 'var(--on-primary-color)',
        hoverBackgroundColor: 'var(--primary-color)'
      })

      const secondary = createButtonTheme({
        borderColor: 'var(--secondary-color)',
        hoverTextColor: 'var(--on-secondary-color)',
        hoverBackgroundColor: 'var(--secondary-color)'
      })
      
      const themes = {
        primary,
        secondary
      }
      
      return themes[this.theme]
    }
  }
};
</script>

So we can do this easily:

<AppButton theme="secondary">Hello VueDose!</AppButton>

And finally. All the cool kids nowadays are doing dark themes right?

<template>
  <main class="wrapper" :class="mode">
    <AppButton @click.native="toggleTheme" theme="secondary">
      Click me to change to dark mode!
    </AppButton>
  </main>
</template>

<script>
import AppButton from "./components/AppButton";

export default {
  name: "App",
  data: () => ({
    mode: 'light'
  }),
  components: {
    AppButton
  },
  methods: {
    toggleTheme() {
      this.mode = this.mode === 'light' ? 'dark' : 'light'
    }
  }
};
</script>
<style scoped>
.light {
  --background-color: white;
  --on-background-color: #222;
}

.dark {
  --background-color: #222;
  --on-background-color: white;
}

.wrapper {
  transition: 1s ease-in-out background-color;
  background-color: var(--background-color);
  color: var(--on-background-color);
}
</style>

We can switch --background-color and --on-background-color values to create new themes! And that will be all. Thanks for reading through all!

Just like that, it works! Check it out yourself in this CodeSandbox!

Here it goes today's tip!

]]>
<![CDATA[The most modern Pie Chart component using CSS Conic Gradient and Vue.js]]> https://vuedose.tips/the-most-modern-pie-chart-component-using-css-conic-gradient-and-vue-js/ https://vuedose.tips/the-most-modern-pie-chart-component-using-css-conic-gradient-and-vue-js/ Mon, 21 Oct 2019 22:00:00 GMT In the last tip a show you how to build The most modern Carousel component using CSS Scroll Snap. This time we'll follow the same line to build a Pie Chart!

But before starting, just wanted to mention that if you're a Packt user, this week my book on Testing Vue.js components with Jest has been published there too!

Back to the topic, this time I'm showing you another new CSS feature: Conic Gradient.

With it, we can easily build a Pie Chart component. In fact, it'd be very easy to build a color palette component, but that's another topic!

In order to construct a pie chart, we need to define every section of the conic gradient, which is used on the background css property.

For instance, the following pie chart:

<img style="max-width: 270px" src="/tips/the-most-modern-pie-chart-component-using-css-conic-gradient-and-vue-js/piechart.png" alt="Pie Chart" />

Is defined with the following CSS rule:

background: conic-gradient( 
  #00A37A 0 40%,
  #365164 0 70%,
  #A54f93 0 100% 
);

As you see, in each section you must define the color, the position from the center (in this case always 0) and how much angle that section must take.

The angle of that section must be absolute: they act like different layers that overlap each other in a Z index. However you probably want to pass to the Pie Chart component the "piece" of the cake that a section must take. Something like:

{
  pieData: [
    { color: "#00A37A", value: 40 },
    { color: "#365164", value: 30 },
    { color: "#a54f93", value: 30 }
  ]
}

So that when you sum up all pieData values, they total 100.

Given this introduction, let's build a PieChart.vue component. This component must take the above pieData structure and build the right background: conic-gradient(...) given that data:

<template>
  <div class="pie-chart" :style="pieStyles"></div>
</template>

<script>
export default {
  props: ["pieData"],
  computed: {
    pieStyles() {
      let acum = 0;
      let styles = this.pieData.map(
        segment => `${segment.color} 0 ${(acum += segment.value)}%`
      );

      return {
        background: `conic-gradient( ${styles.join(",")} )`
      };
    }
  }
};
</script>

<style scoped>
.pie-chart {
  border-radius: 50%;
}
</style>

First, notice we gave it a 50% border-radius in order to make it rounded.

The main part of this component is in the pieStyles computed prop. Basically there we map the pieData property to build an array of conic sections with the shape of "#aeaeae 0 30%" for instance, and finally build it in the background CSS property.

In that way, for the following data:

{
  pieData: [
    { color: "#00A37A", value: 40 },
    { color: "#365164", value: 30 },
    { color: "#a54f93", value: 30 }
  ]
}

The pieStyles computed property will return:

background: conic-gradient(
  #00A37A 0 40%,
  #365164 0 70%,
  #a54f93 0 100%
);

Just like that, it works! Don't trust me? Check it out yourself in this CodeSandbox!

Here it goes today's tip!

]]>
<![CDATA[The most modern Carousel component using CSS Scroll Snap and Vue.js]]> https://vuedose.tips/the-most-modern-carousel-component-using-css-scroll-snap-and-vue-js/ https://vuedose.tips/the-most-modern-carousel-component-using-css-scroll-snap-and-vue-js/ Sun, 06 Oct 2019 22:00:00 GMT Last week I went to Codemotion Madrid with my beloved friend Laura Bonmati. It's one of the biggest conferences about coding in Spain.

One of our favourite talks was "The latest features in CSS" (in Spanish) by Sonia Ruiz, and this tip comes from some ideas I took from her talk, so let's give her a shoutout on twitter for the inspiration!

Among the features she showed, I was amazed by scroll-snap and how easy is to create a carousel by using it, so I thought... let's make it a component!

First, create a Carousel.vue component with the following structure:

<template>
  <div class="carousel-wrapper">
    <div class="carousel" :style="{ width, height }">
      <slot/>
    </div>
  </div>
</template>

<script>
export default {
  props: ["width", "height"]
};
</script>

<style scoped>
.carousel {
  display: flex;
  overflow: scroll;
  scroll-snap-type: x mandatory;
}

.carousel > * {
  flex: 1 0 100%;
  scroll-snap-align: start;
}
</style>

The important parts here are the use of scroll-snap-type. Using it we're forcing the scroll to snap to every item inside the .carousel element, in the x axis.

Then you need to make every item in the carousel to take the full space of the container. You do that using flex: 1 0 100% and defining the items alignment with scroll-snap-align: start.

Notice that I'm also passing by a width and height properties so you can easily set the carousel dimensions from outside the component.

By the way, if you're not sure about how <slot/> work, you can check the articles Using scoped slots in Vue.js and New v-slot directive in Vue.js 2.6.0 and know more about them (including scoped slots).

Just like that, you have a carousel component that works smoothly using the mouse scroll!

But hey... let's add some functionality right? What if you want to change the carousel slides manually?

As you might have thought, the carousel "sliding" works according to the scroll, so you must play with the scroll position.

In fact, each slide scroll position is exactly the width of the carousel multiplied per the slide position in the carousel.

Let's add a couple of buttons that allow us to go back and forth in the carousel, so we can navigate through every item:

<template>
  <div class="carousel-wrapper">
    <div class="carousel" :style="{ width, height }">
      <slot/>
    </div>
    <button @click="changeSlide(-1)">Prev Slide</button>
    <button @click="changeSlide(1)">Next Slide</button>
  </div>
</template>

<script>
export default {
  props: ["width", "height"],
  methods: {
    changeSlide(delta) {
      const carousel = this.$el.querySelector(".carousel");
      const width = carousel.offsetWidth;
      carousel.scrollTo(carousel.scrollLeft + width * delta, 0);
    }
  }
};
</script>

These buttons call the changeSlide method passing by the number of positions you want to navigate back or forth.

The method basically takes the carousel current width, and calculates where should scroll by checking the current scroll position and adding the amount of pixels where it should navigate. That calculation is defined by carousel.scrollLeft + width * delta.

If you try the component now, you'll see that it works as expected, but the scroll is not smooth. Instead, it changes the slide immediately.

Luckily, modern CSS comes to the rescue again!

You can use the property scroll-behavior: smooth on the carousel element and the scrolling will be smooth again!

.carousel {
  display: flex;
  overflow: scroll;
  scroll-behavior: smooth;
  scroll-snap-type: x mandatory;
}

Finally, the way to use the component is as simple as this:

<template>
  <Carousel width="400px" height="250px">
    <div class="blue">Blue</div>
    <div class="green">Green</div>
    <div class="red">Red</div>
  </Carousel>
</template>

Do you realize how easy was to build this component? Modern CSS is rad!

I'm pretty sure you're now thinking on more ways to improve it: change slides automatically every X seconds, loop scrolling, go to a specific slide... please, tell me what you come up with and show it to me! I'll be more than happy to see what you accomplish!

Of course, keep in mind that this component uses very new CSS properties, so it's only supported in the most modern browsers.

But hey, you must be building the Carousel of the future!

Do you want to see this carousel working? Go to this CodeSandbox!

]]>
<![CDATA[Data Provider component in Vue.js]]> https://vuedose.tips/data-provider-component-in-vue-js/ https://vuedose.tips/data-provider-component-in-vue-js/ Tue, 24 Sep 2019 00:00:00 GMT By using component-based technologies such as Vue.js, doesn't mean that all components must be UI based.

In fact, my favourite way to apply advanced reusability in large applications is by using component composition.

With scoped slots, as you haven seen in "Using scoped slots in Vue.js", you can encapsulate logic in another component and pass it to a slot via props. That allows you to compose the UI and behaviour of a new component by combining multiple of them.

Today, I'm going to show you a Data provider Component example.

Data Provider is a renderless component, meaning that it doesn't need to render anything.

The base to create a renderless component is to create a scoped slot from a render function and pass any prop to it:

render() {
  return this.$scopedSlots.default({
    loading: !this.loaded,
    data: this.data
  });
}

Due to an inconsistency on scoped slots (fixed by version 2.6), you can better do it like this to make it work in any case:

render() {
  const slot = this.$scopedSlots.default({
    loading: !this.loaded,
    data: this.data
  });

  return Array.isArray(slot) ? slot[0] : slot;
}

Knowing that, to create a Data Provider component you must perform an ajax/fetch call when the component is created, and then pass that data to the scoped slot.

A final version of DataProvider.js could be:

import axios from "axios";

export default {
  props: ["url"],
  data: () => ({
    data: null,
    loaded: false
  }),
  created() {
    axios.get(this.url).then(({ data }) => {
      this.data = data;
      this.loaded = true;
    });
  },
  render() {
    const slot = this.$scopedSlots.default({
      loading: !this.loaded,
      data: this.data
    });

    return Array.isArray(slot) ? slot[0] : slot;
  }
};

Notice that it's a .js file. Since it just has the script part of the component, it doesn't need to be a .vue file.

The Data Provider component also is holding a loading state, so you can use that to render different UI depending on that state. An example could be:

<template>
  <DataProvider url="https://jsonplaceholder.typicode.com/users">
    <div v-slot="{ data, loading }">
      <div v-if="loading">Loading...</div>
      <div v-else>
        <h2>Result: {{ data.length }} users</h2>
        <p v-for="user in data" :key="user.id">{{ user.name }}</p>
      </div>
    </div>
  </DataProvider>
</template>

That's it!

If you want to check this example running, go to this CodeSandbox!

Don't forget to share VueDose with your colleagues, so they also know about these tips as well!

See you next week.

Alex

]]>
<![CDATA[Using Scoped Slots in Vue.js]]> https://vuedose.tips/using-scoped-slots-in-vue-js/ https://vuedose.tips/using-scoped-slots-in-vue-js/ Sun, 15 Sep 2019 00:00:00 GMT Multiple times I get to know companies that want to improve their productivity when coding in Vue.js.

That's a too open question, but at least some part can be achieved that by identifying what functionality they build pretty often in their apps, and then having a toolkit of reusable components that allow you to put that common logic in there, while being flexible enough to adapt to other apps.

Vue.js has slots in order to make components have a redefinable structure, but by themselves they're pretty limited. Sometimes you need some data or state in order to define how a component should render.

If you don't know slots, I suggest you learn them first on the Vue.js docs.

Scoped slots is an advanced feature in Vue.js that allows you to do that. They're like normal slots, but they can receive properties as well.

Let's build a Clock.vue component in order to illustrate that. Basically it must be a time counter:

<template>
  <div class="clock">
    <slot :time="time">
      <p>Default slot</p>
      <p>Time: {{ time.toLocaleTimeString() }}</p>
    </slot>
  </div>
</template>

<script>
  export default {
    data: () => ({
      time: new Date()
    }),
    created() {
      setInterval(() => {
        this.time = new Date(this.time.getTime() + 1000);
      }, 1000);
    }
  };
</script>

You might have noticed the <slot :time="time"> line, that's the slot receiving the time property. That's the way this Clock.vue component can send the time data to the component that uses it, while encapsulating the timer logic itself.

You also might have realised that this component already renders the time by itself, as it has some default content within the slot.

But what if we want to redefine what it renders, while holding the timer logic in it?

Well, since we're passing the time property to the slot, we can do that simply by using the v-slot directive on the root element of the Clock slot. So, wherever you want to render the Clock component, you'll write something like:

<template>
  <Clock>
    <template v-slot="{ time }">
      <p><b>Slot override!</b></p>
      <p>Date: {{ time.toLocaleDateString() }}</p>
      <p>Time: {{ time.toLocaleTimeString() }}</p>
    </template>
  </Clock>
</template>

v-slot receives all the props passed from within the Clock component. Since that's JavaScript object, we can use the Object Spread Operator to already grab the time prop as { time }.

Do you see the power of Scoped Slots? You can do more powerful stuff, like render-less components, that you'll see in a future tip 😜.

If you want to run the code yourself, you can find it in this CodeSandbox!

]]>
<![CDATA[Quick Content Testing using Snapshots in Vue.js]]> https://vuedose.tips/quick-content-testing-using-snapshots-in-vue-js/ https://vuedose.tips/quick-content-testing-using-snapshots-in-vue-js/ Tue, 28 May 2019 11:00:00 GMT Have you ever heard about Snapshots being evil? About how fragile they are and how you should avoid them? It's true! You should be extremely careful about them because they do exact comparison of content as text, that is if you are snapshoting a component, you are actually snapshoting its HTML content, so anything changing the HTML will break the snapshot and if this is repeated too often, you may end up accidentally accepting snapshots updates and missing regressions in your application 😱.

But you don't have to snapshot the whole HTML! You can even provide a hint to recognize the snapshot and this can be used to generate tests fixtures on the flight in a very convening way, specially for very large content sets

Imagine you have a very big table and you want to test that given some props, the table renders the right content:

<table>
  <thead>
    <tr>
      <th v-for="column in columns">{{ column.name }}</th>
    </tr>
  </thead>
  <tbody>
    <tr v-for="item in items">
      <td v-for="column in columns">
        <span class="label">{{ colum.name }}: </span>
        <span class="value">{{ item[colum.key] }}</span>
      </td>
    </tr>
  </tbody>
</table>

Here columns is an array of all the columns in the table and items in an array of all the rows displayed. You could say both are props. If you want to test the content of the table given those props, you will have to test each row:

test('contains the right information', () => {
  // columns and items are defined above
  const wrapper = shallowMount(MyTable { props: { columns, items }})
  // first cell in the header
  expect(wrapper.find('thead th:nth-of-type(1)').text()).toBe('Product')
  // first row in the tbody
  expect(wrapper.find('tbody tr:nth-of-type(1) .value').text()).toBe('Dinner plates set of 8')
  // repeat for EVERY row 🤯
})

There are multiple approaches to select the table cell like using a data-test attribute but that's not the issue here. Can we go faster when writing this kind of test? What if we wrote the component, check manually and then add a test that snapshots the current state?

test('contains the right information', () => {
  // columns and items are defined above
  const wrapper = shallowMount(MyTable { props: { columns, items }})

  const cells = wrapper.findAll("td");
  for (let i = 0; i < cells.length; ++i) {
    const cell = cells.at(i);
    // use label as the hint for the snapshot
    const label = cell.find(".label");
    if (!label.exists()) continue;    // filter out cells that do not have a label
    expect(cell.find(".value").text()).toMatchSnapshot(label.text());
  }
})

Writing this test will generate a snapshot the first time it is run:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MyTable contains the right information: Product Name 1`] = `"Dinner plates set of 8"`;

exports[`MyTable contains the right information: Sells 1`] = `"23"`;

exports[`MyTable contains the right information: Stock 1`] = `"3"`;

// more and more cells

The advantages of this solution is that adding a new column will create a single new snapshots without invalidating the others while removing existing columns will make some snapshot obsoletes and changing any of the cells .value content will make the snapshot test fail.

If you don't like the idea of creating dozens of snapshots like this, you can create some custom text value and create one single snapshot:

test('contains the right information', () => {
  // columns and items are defined above
  const wrapper = shallowMount(MyTable { props: { columns, items }})

  const cells = wrapper.findAll("td");
  let content = ''
  for (let i = 0; i < cells.length; ++i) {
    const cell = cells.at(i);
    // use label as the hint for the snapshot
    const label = cell.find(".label");
    if (!label.exists()) continue; // filter out cells that do not have a label
    content += `${label.text()}: ${cell.find(".value").text()} \n`
  }

  expect(content).toMatchSnapshot()
})

You will end up with one single snapshot:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MyTable contains the right information 1`] = `
Product Name: Dinner plates set of 8
Sells: 23
Stock: 3
etc.
`;

So remember: Snapshots can also be used to generate tests fixtures with text!

Happy Testing!

]]>
<![CDATA[Style inner elements in scoped CSS using /deep/ selector in Vue.js]]> https://vuedose.tips/style-inner-elements-in-scoped-css-using-deep-selector-in-vue-js/ https://vuedose.tips/style-inner-elements-in-scoped-css-using-deep-selector-in-vue-js/ Sun, 12 May 2019 20:00:00 GMT In the The importance of scoped CSS in Vue.js you saw why scoped CSS is important when we want to achieve style encapsulation in components. If you haven't read that tip, I strongly suggest you to do so to understand this one.

But when we tried to turn the style of that example into scoped CSS, the styles were missing.

Here's the thing: when you use scoped CSS, you can modify the root element of the component you want to customise.

In other words, from BlueList.vue and RedList.vue of the example we can only modify the .list class of BaseList.vue since it's the root element of that component.

But what about inner elements? We want to style the .list-item class to change the color of the items.

For that, we have the /deep/ selector, and you can use it to access inner elements of components as follows:

<style scoped>
.list /deep/ .list-item {
  color: white;
  background: #42a5f5;
}
</style>

Take a look at the updated example and see how now it works as expected and each of them have a different color.

]]>
<![CDATA[The importance of scoped CSS in Vue.js]]> https://vuedose.tips/the-importance-of-scoped-css-in-vue-js/ https://vuedose.tips/the-importance-of-scoped-css-in-vue-js/ Tue, 07 May 2019 22:00:00 GMT Sometimes I see new devs coming to Vue.js getting confused about scoped CSS in Vue.js. Some using it without really knowing how does it work.

If you are there, I hope this tip helps you understand why and when to use them (and when not) 😉.

I'm not going to dive deep into the theory as you have it already in the vue-loader docs. Just to know, it's a vue-loader's feature to avoid style collisions and encapsulate styles by emulating Shadow DOM functionality.

Let's better see an example of a problematic situation when you don't use scoped CSS.

Imagine we have a BaseList.vue component with the following structure:

<template>
  <ul class="list">
    <li class="list-item" v-for="item in items" :key="item">
      {{ item }}
    </li>
  </ul>
</template>

Then create two components with the same code. Call them RedList.vue and BlueList.vue:

<template>
  <BaseList :items="items" />
</template>

<script>
import BaseList from "./BaseList";

export default {
  props: ["items"],
  components: {
    BaseList
  }
};
</script>

Now add two different styles to each of them, according to the colors. For instance, for BlueList.vue:

<style>
.list-item {
  color: white;
  background: #42a5f5;
}
</style>

Put them together like I did in this CodeSandbox, and... surprise! You'll see that even though both components have a different color defined, they show the same color:

That's because when we don't use scoped CSS, they're global, even though they are in different components. So, in this case one of the styles is overriding the other.

That's why scoped CSS are that important: they avoid these collisions to happen out of the box.

Here's the joke: if you try to put the scoped word into the style tag in the example above, you'll see that the styles of the list item won't apply 😅.

But that's normal, next week you'll see how to style inner elements when using scoped CSS, so stay tuned!

To end up, I want to mention that you can mix scoped (thus local) styles with global styles (not scoped). You want to use global styles in these situations:

  • When applying cross-component styles (utility styles).
  • When writing a third party library. If you apply scoped CSS, you'd be making your library impossible to customise styles.

Keep in mind scoped CSS is not the only solution. You might be comfortable as well using CSS modules or methodologies such as BEM.

]]>
<![CDATA[How to create a Geolocated Currency with MaxMind in Vue.js]]> https://vuedose.tips/geolocated-currency-with-max-mind/ https://vuedose.tips/geolocated-currency-with-max-mind/ Mon, 22 Apr 2019 21:00:00 GMT Sometimes you need to integrate a geolocated content on your website. In this little tip I will show you how easy could it be using VueJS, MaxMind GeoIP and Autonumeric. This sample is written in NuxtJS for the special case of a geolocated currency, but the same principles apply on the vanilla VueJS applications.

The geolocated currency means, that even if you have a page only in the one language you want to show a different currency according to the country of the visitor. The visitor from the USA will see the dollar sign $10,000 and the visitor from the Germany would see in the same sentence Euro sign 10.000€. You could also notice the different punctuation in these two cases.

Ok, I assume your content is reachable trough some API of headless CMS like Storyblok, like in my case. Remember, the currency could be use in any string. That's why I defined unique pattern that could be used in any string to mark the geolocated currency. I used this pattern #{number}#, which means that string Price: #{10000}# will be replaced in the USA with string Price: $10,000 and in the Germany with Price: 10.000€.

// API returns:
{
  description: "Price: #{10000}#";
}

First you have to find and transform your patterns into HTML tags. I wanted to do it using the global filter, but then I found out that the filter is forbidden in v-html and v-text. So, I did it with the global mixin, which will transform string #{number}# into string <span data-currency>number</span>.

// mixins.js
import Vue from "vue";
// Adds global currency method to transfor #{}# string to span
Vue.mixin({
  methods: {
    extractCurrency(string) {
      if (!string.includes("#{")) {
        return string;
      }
      const openTag = `<span data-currency>`;
      const closeTag = `</span>`;
      const openRegex = /#{/g;
      const closeRegex = /}#/g;
      string = string.replace(openRegex, openTag);
      string = string.replace(closeRegex, closeTag);
      return string;
    }
  }
});

As this is the global mixin you can use the function extractCurrency in every component. Eg.

<template>
  <p v-html="extractCurrency(description)" />
</template>

If you will generate static page using nuxt generate as I did, you will end with a static page containing this:

<!-- static-page.html -->
<p>Price: <span data-currency>10000</span></p>

Now you have to find out the country of the visitor using GeoIP of the MaxMind and then call Autonumeric function to add correct currency. You will do this in the mounted() life cycle of the VueJS as this should be run only at the client side. The best place to do this in the NuxtJS is in your layout file.

// layouts/default.vue
export default {
  mounted() {
    geoip2.country(
      success => {
        const currencyType =
          success.country.iso_code === "US" ? "NorthAmerican" : "French";
        AutoNumeric.multiple(
          "[data-currency]",
          AutoNumeric.getPredefinedOptions()[currencyType]
        );
      },
      error => {
        console.warn("Error occured by getting geolocation.");
      }
    );
  }
};

If the GeoIP is not working or taking to long time to return location, you can show the number without the currency or show the default currency using CSS :before pseudo-element.

// fallback for currency
span[data-currency]:not([value]) {
  &::after {
    content: " €";
  }
}

Don't forget that you could use also the global event bus in the NuxtJS to trigger method on component according to the geolocation. You would add this line this.$nuxt.$emit("geolocationFound", success.country.iso_code) into your mounted cycle.

Pretty easy or?

]]>
<![CDATA[Debugging Templates in Vue.js Components]]> https://vuedose.tips/debugging-templates-in-vue-js/ https://vuedose.tips/debugging-templates-in-vue-js/ Thu, 18 Apr 2019 19:00:00 GMT The Vue.js DevTools are awesome to inspect your Vue app but sometimes you might want to debug things that are happening in your template and as you heard that you can place JavaScript expressions there maybe you've tried to place a console.log and find this situation:

This is due that everything that you placed in the template Vue try to find it in the instance so a quick solution could be put a log method in the component to allow Vue to find it:

and just use {{ log(message) }}.

But this is something that you occasionally want in some different components and it's a little boring to repeat this code all the time so what can we do is to add an instance property and use the Vue.prototype to place our custom log method in the main.js:

so now we can use $log in every component template and if we don't want to interfere with the rendering just use the JS lazy evaluation with an OR operator 😊

awesome, right? :D but... what if we want to place a breakpoint in order to quietly debug some variables around the template?

If we place a debugger in the template we might find this:

it is not even compiling the template! 😱 and if we apply the same strategy than before we will find us alone in the prototype function instead of the template that we want to debug​:

​ so in order to place a breakpoint in the middle of the template we can use a little trick wrapping the debugger in an IIFE (Immediately Invoked Function Expression) like this:​

and we will find ourselves in the middle of the compiled render function​:

where in this case the "_vm" variable

means ViewModel

and is the instance of our component :). This is also interesting to check the compiled template but for some reason the variables are not available in the function scope of the chrome devtools until we place them after the debugger:​

and now you can enjoy inspect everything around!

That's all! I hope that with these tips you will find yourself more confident debugging Vue templates and also enjoy inspecting the insides of the compiled render functions just for curiosity​.

]]>
<![CDATA[Simple transition effect between pages and layouts in Nuxt.js]]> https://vuedose.tips/simple-transition-effect-between-pages-and-layouts-in-nuxt-js/ https://vuedose.tips/simple-transition-effect-between-pages-and-layouts-in-nuxt-js/ Wed, 17 Apr 2019 19:00:00 GMT Vue.js makes the animations and transitions incredibly easy to implement. So you should really use this opportunity to give a little spark to your application/website to shine.

Nuxt.js already builds on the provided capabilities of Vue.js. It gives you a possibility to create a very simple transitions between the pages very fast and almost for no effort.

All you need to specify in your page.vue file is name of the transition. This name will be used to generate 6 transition classes, which can be use for transition effect between pages.

export default {
  transition: "default"
};

NuxtJS will interpret this as <transition name="default"> and it will look for following classes in you css code, which will define the transition between the pages.

.default-enter {
}
.default-enter-active {
}
.default-enter-to {
}
.default-leave {
}
.default-leave-active {
}
.default-leave-to {
}

You should definitely check Vue.js documentation to understand, when are these classes used and what are transition modes. But lets define very simple transition between pages using the opacity.

.page-enter-active,
.page-leave-active {
  transition-property: opacity;
  transition-timing-function: ease-in-out;
  transition-duration: 500ms;
}
.page-enter,
.page-leave-to {
  opacity: 0;
}

After integration of this code into your Nuxt.js application/website, the default transition between pages will take 1000ms and content of the page will fade out and then the new one will fade in. You even don't have to define transition name as the page is the default transition name. You can check this code in the CodeSandbox.

If you want create a special transition for one of your pages, then you can specify a name of the transition in page.vue file as I did in this CodeSandbox for intro.vue. As you can see, if you visit intro.vue page the transition is change to two black rectangles.

https://codesandbox.io/embed/2xovlqpv9n

Be aware, that all transitions between pages are working only if you are navigating between the pages with the same layout. If you are navigating between the pages with two different layouts you have to use the layout transition of Nuxt.js. You can see this in our CodeSandbox, if you visit other.vue page.

NuxtJS is already setting few defaults on the page transitions and the layout transitions. These defaults can be override directly in nuxt.config.js:

module.exports = {
  /* Layout Transitions */
  layoutTransition: {
    name: "layout",
    mode: ""
  },
  /* Page Transitions */
  pageTransition: {
    name: "default",
    mode: ""
  }
};

These are only the basics of transitioning between the pages in Nuxt.js. Under the hood of the Vue.js is hidden much more power, which can be used to create crazy animations and transitions. So keep digging in the documentation and check this sample from Sarah Drasner.

]]>
<![CDATA[Lazy load images using v-lazy-image Vue.js component]]> https://vuedose.tips/lazy-loading-images-with-v-lazy-image/ https://vuedose.tips/lazy-loading-images-with-v-lazy-image/ Tue, 16 Apr 2019 19:00:00 GMT Some time ago I wanted to apply lazy loading in images in order to load them only when they enter the viewport. Researching I found different ways to do it, but they were a bit more cumbersome than what I wanted.

I wanted something as simple as having the same <img> tag that gets lazy loaded. That's why I created v-lazy-image, a Vue.js component that imitates the <img> tag API and applies lazy loading.

It also makes easy for you to achieve max performance by using responsive images and applying progressive image loading effortlessly, but let me show you the most simple case.

v-lazy-image supports Vue 3+, but it has a compatible Vue 2 version.

To use it, once you install it by running npm install v-lazy-image, just import it like a component:

<script setup>
import VLazyImage from "v-lazy-image";
</script>

In Vue 2, import it from v-lazy-image/v2:

import VLazyImage from "v-lazy-image/v2";

export default {
  components: {
    VLazyImage
  }
};

And then is as simple to use as using an <img>:

<template>
  <v-lazy-image src="http://lorempixel.com/400/200/" />
</template>

See it in action

Here's the most simple demo. It shows you how the image is lazy loaded just by using <v-lazy-image> instead of <img>.

You can check that works by opening the Devtools Network tab, check the Img filter and clear the output. Then, when you scroll down in the demo until the image shows, you'll see the image is loaded when it enters the viewport.

Psst! See that little bar on the left of that embed? If you scroll right you'll see the demo code

<iframe src="https://codesandbox.io/embed/r5wmj970wm?fontsize=14&hidenavigation=1&module=%2Fsrc%2FApp.vue&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="v-lazy-image" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"

</iframe>

Here's what you should see in the Devtools:

Img

Follow up

Do you think that's too basic? Yeah, the idea was to keep it basic, but I understand you want something more advanced.

In a real project you want to achieve max performance, and for that, v-lazy-image allows you to use responsive images as well as progressive image loading.

Check them out and see your web apps flying!

]]>
<![CDATA[Dynamic Imports in Vue.js for better performance]]> https://vuedose.tips/dynamic-imports-in-vue-js-for-better-performance/ https://vuedose.tips/dynamic-imports-in-vue-js-for-better-performance/ Mon, 15 Apr 2019 19:00:00 GMT I bet you are already familiar with the terms "code splitting" and "lazy loading". Let's take the latter definition from Webpack's docs:

Lazy, or "on demand", loading is a great way to optimize your site or application. This practice essentially involves splitting your code at logical breakpoints, and then loading it once the user has done something that requires, or will require, a new block of code. This speeds up the initial load of the application and lightens its overall weight as some blocks may never even be loaded.

This kind of feature should be done by default by the frameworks we use, as some people have suggested. (Also in the React ecosystem)

The meat:

Whenever it's possible, I'd recommend to use dynamic imports to import components. They will be lazily loaded (by Webpack) when needed.

// Instead of a usual import
import MyComponent from "~/components/MyComponent.js";

// do this
const MyComponent = () => import("~/components/MyComponent.js");

The explanation:

When using Webpack to bundle your application, you may use different ways to work with modules (ES Modules, CJS, AMD...). If you choose the ESM way (which is the recommended), you will have this kind of syntax:

import MyComponent from "~/components/MyComponent.js";

Notice that there are several use cases where we would like to use asyncronous components. As explained by Alex Jover in this article:

  • In component importing
  • In Vue Router, for components mapping
  • In Vuex modules

Let's take a look at the syntax and focus on the import part.

If you are using Webpack (or Parcel!), that syntax is going to be transformed on compilation time and these tools are going to usePromises to load asynchronously your assets/modules/components.

Why the need of an arrow function, you might be wondering: As Alex explained, we need to wrap the import with an arrow function to be resolved (remember, promises...) only when executed.

To demonstrate that they are fully lazy loaded I've prepared a repository (using Nuxt.js). It has 2 pages, each of them use different techniques (With and Without dynamic imports) to import 2 components (component "A" and component "B").

We will see how, when loading the page with dynamic imports, webpack loads 2 separate files after the navigation. But, the page component itself (/without) using regular imports, is heavier because it loads everything at once.

Image showing network waterfall when navigating to both pages. And the differences between both techniques (with and without dynamic imports)

Yes, by using this technique, Webpack will create separate files ("chunks") to load them when needed (lazily). Custom chunk naming can be done with Magic comments but that will be the subject of another article 😉.

Image showing the result of nuxt build. See how different chunks are created for components A and B when dynamic imports are used!

That's it!

For a deeper exploration of code splitting techniques check:

PS: For this example repo I have used webpack@4.29.6 and Nuxt@2.4.0 which uses Vue@2.5.22.

]]>
<![CDATA[Testing logic inside a Vue.js watcher]]> https://vuedose.tips/testing-logic-inside-a-vue-js-watcher/ https://vuedose.tips/testing-logic-inside-a-vue-js-watcher/ Sun, 14 Apr 2019 19:00:00 GMT Even though the most of the time we use computed properties to react to data changes, sometimes we have to use a watcher in order to perform an expensive operation or an asynchronous call to our API. Let's keep it simple for this example.

Imagine that you have a component that emits an input event when an internal value changes:

<template>
  <input v-model="internalValue" />
</template>
<script>
  export default {
    data() {
      return {
        internalValue: ""
      };
    },
    watch: {
      internalValue(value) {
        this.$emit("input", value);
      }
    }
  };
</script>

When its time to test this feature, the first thing that comes to our minds is that we have to mount the component, change the internalValue data and expect that the watcher has been fired.

But you know what? That feature is already tested by Vue core members. Don't do it again. Evan You is pretty confident that a watcher is fired when its associated value changes.

You can do a test like this instead:

test("emits input event when interalValue changes", () => {
  const wrapper = shallowMount(YourComponent);

  wrapper.vm.$options.watch.internalValue.call(wrapper.vm, 15);

  expect(wrapper.emitted("input")[0][0]).toBe(15);
});

Vue attaches to the $options.watch object each watcher that we define in our component so what we are doing here is invoking the watcher directly using call().

First parameter of call is the context of this inside the function (the component instance). Then we can pass more parameters (the value).

So wrapping up: "Don't test the framework"

Sometimes is hard to identify the code that you own from the features that have already been tested thousands of times by the library you are using. I guarantee you that a watcher is going to be fired when the data is changed.

Test the logic inside of it, don't waste your precious time trying to re-create the scenario and the conditions that would fire the watcher up.

]]>
<![CDATA[Handle and redirect 404 responses in Nuxt.js]]> https://vuedose.tips/redirect-404-not-found-in-nuxt-js/ https://vuedose.tips/redirect-404-not-found-in-nuxt-js/ Sun, 07 Apr 2019 14:00:00 GMT For me, Nuxt is one of the best pieces of software I've ever used. It makes me be so productive building web apps, no matter if they're SPA, Sever Side Rendered (SSR) apps, or static generated sites, following the JAM Stack trend.

Pss, in fact VueDose website is built on Nuxt as a static site 😉

But still, a lot of times find very interesting tricks that are not documented... that needs to stop! Let's share the first hot Nuxt tip!

If you're familiar with Nuxt.js, you should know the concept of pages. You should also know that there is a special Error page (well, it goes into the Layouts folder, but it's a page).

You can override the default error page and customize it to your needs... but what if we want a different behaviour?

In some cases, we might want that when a user visits a non existing page, we redirect him to the home page.

Here's the trick: you can do that easily by creating this pages/*.vue component:

<!-- pages/*.vue -->
<script>
export default {
  asyncData ({ redirect }) {
    return redirect('/')
  }
}
</script>

In Nuxt, routes are defined by the file naming convention. So when we create a *.vue file, we're actually using a wildcard route on Vue Router.

Then, we use the redirect method from the Nuxt context to perform the redirection, whether it's on the client or server.

We do that on the asyncData method since we have the context there, but it would perfectly work with the fetch method as well:

<!-- pages/*.vue -->
<script>
export default {
  fetch({ redirect }) {
    return redirect("/");
  }
};
</script> 

Go and try it out by typing any non-existent url. You should see how it is be redirected. It'd be helpful if you also share VueDose with your colleagues, so they also know about these tips!

See you next week.

]]>
<![CDATA[Run watchers when a Vue.js component is created]]> https://vuedose.tips/run-watchers-when-a-vue-js-component-is-created/ https://vuedose.tips/run-watchers-when-a-vue-js-component-is-created/ Sun, 31 Mar 2019 14:00:00 GMT Although Vue.js provides us with computed properties which are very useful for most of the cases, in some situations you might need to use watchers.

Watchers by default are run only when the value of the property that is watched changes, and that totally makes sense.

Here's how you define a watcher for a dog property:

export default {
  data: () => ({
    dog: ""
  }),
  watch: {
    dog(newVal, oldVal) {
      console.log(`Dog changed: ${newVal}`);
    }
  }
};

So far so good. If you try that code, the dog watch function would be called as soon as its value changes.

However, in some situations you need the watcher to be run as soon as the component is created. You could move the logic within to a method, and then call it from both the watcher and the created hook, but there is a simpler way.

You can use the long-hand version of the watcher in order to pass the immediate: true option. That will make it run instantly on component's creation.

export default {
  data: () => ({
    dog: ""
  }),
  watch: {
    dog: {
      handler(newVal, oldVal) {
        console.log(`Dog changed: ${newVal}`);
      },
      immediate: true
    }
  }
};

As you can see, in the long-hand way you need to set the watch callback in the handler key of the object.

Do you want to see it in action? Check it out yourself in this CodeSandbox!

]]>
<![CDATA[Simple and performant functional Vue.js components]]> https://vuedose.tips/simple-and-performant-functional-vue-js-components/ https://vuedose.tips/simple-and-performant-functional-vue-js-components/ Sun, 24 Mar 2019 14:00:00 GMT Sometimes we don't need complex components, and in some cases we don't even need them to have a state on its own. That can be the case when building UI components that don't have much logic in it.

For cases like that, functional components can be very appropriated. They're stateless and instanceless, which means that it doesn't even have an instance on its own (so no way to call this.$emit and such).

That makes them simple to reason about, faster and more lightweight.

The question is, when can I use a functional component? Easy: when they depend only on props.

As an example, the following component:

<template>
  <div>
    <p v-for="item in items" @click="$emit('item-clicked', item)">
      {{ item }}
    </p>
  </div>
</template>

<script>
  export default {
    props: ["items"]
  };
</script>

Could be written in a functional way as follows:

<template functional>
  <div>
    <p v-for="item in props.items" @click="props.itemClick(item);">
      {{ item }}
    </p>
  </div>
</template>

Pay attention to the things that changed:

  • Write functional in the template tag
  • Props are accessible via props
  • Since we don't have access to $emit, we can use a function as a prop. That's how the React community has done it the whole time and it worked pretty well.
  • No need for the script part

Do you want to see it in action? Check it out yourself in this CodeSandbox!

]]>
<![CDATA[Listen to lifecycle hooks on third-party Vue.js components]]> https://vuedose.tips/listen-to-lifecycle-hooks-on-third-party-vue-js-components/ https://vuedose.tips/listen-to-lifecycle-hooks-on-third-party-vue-js-components/ Sun, 17 Mar 2019 14:00:00 GMT Here's a very useful tip I learnt once from my friend Damian Dulisz, the Vue.js core team member that created the official Vue newsletter and vue-multiselect.

In some cases I needed to know when a component has been created, mounted or updated from a parent component, specially when building components for vanilla JS libraries.

You've probably already something like that in your own components, for example, by emitting an event in a lifecycle hook from the child component, like this:

mounted() {
  this.$emit("mounted");
}

So then you can listen to it from the parent component like <Child @mounted="doSomething"/>.

Let me tell you this: there is no need for that, and in fact you won't be able to do it on third party components.

Instead, the solution is as simple as listening to an event with the lifecycle hook name, prefixed by @hook:.

For instance, if you want to do something when the third-party component v-runtime-template renders, you can listen to it's updated lifecycle hook:

<v-runtime-template @hook:updated="doSomething" :template="template" />

Still don't trust me? Check it yourself in this CodeSandbox!

]]>
<![CDATA[The power of Snapshot Testing in Vue.js]]> https://vuedose.tips/the-power-of-snapshot-testing/ https://vuedose.tips/the-power-of-snapshot-testing/ Sun, 10 Mar 2019 11:00:00 GMT If you are into testing, you probably have used Jest: the all-in-one testing framework created by Facebook. Right now is the most popular tool out there and I've been using it since the first time I've tried it.

To test Vue.js components, you also have vue-test-utils written by Edd Yerburg, the official helper library that makes testing Vue.js apps easier.

This is an example of tests using both Jest and vue-test-utils:

it('has a message list of 3 elements', () => {
  const cmp = createCmp({ messages: ['msg-1', 'msg-2', 'msg-3'] })
  expect(cmp.is('ul')).toBe(true)
  expect(cmp.find('.message-list').isEmpty()).toBe(false)
  expect(cmp.find('.message-list').length).toBe(3)
})

it('has a message prop rendered as a data-message attribute', () => {
  const cmp = createCmp({ message: 'Cat' })
  expect(cmp.contains('.message')).toBe(true)
  expect(cmp.find('.message').props('message').toBe('Cat')
  expect(cmp.find('.message').attributes('data-message').toBe('Cat')

  // Change the prop message
  cmp.setProps({ message: 'Dog' })
  expect(cmp.find('.message').props('message').toBe('Dog')
  expect(cmp.find('.message').attributes('data-message').toBe('Dog')
})

As you can see, vue-test-utils gives you many methods you can use to check props, classes, content, search, etc. It gives you methods to change stuff, like setProps, which is pretty cool.

This test has very specific assertions in the form of "Find an element with a 'message' class and check if it has a 'data-message' set to 'Cat'".

But what if I tell you that... you don't need to do that with Snapshot Testing?

Basically, you can rewrite the previous tests by using snapshots in the following way:

it("has a message list of 4 elements", () => {
  const cmp = createCmp({ messages: ["msg-1", "msg-2", "msg-3"] });
  expect(cmp.element).toMatchSnapshot();
});

it("has a message prop rendered as a data-message attribute", () => {
  const cmp = createCmp({ message: "Cat" });
  expect(cmp.element).toMatchSnapshot();

  cmp.setProps({ message: "Dog" });
  expect(cmp.element).toMatchSnapshot();
});

The value of the tests will remain the same, or it can have even more since sometimes you find non-related regressions in snapshots.

Notice that in this test I've just used toMatchSnapshot for the assertions, and that's all. That makes testing much easier and quicker since you don't need to check every specific thing.

The dynamics of testing changed: instead of writing specific assertions, you set the component state in any way you need and take snapshots of it.

Snapshots are meant to assert the rendering state: they describe how the component is rendered given a specific state, and in later runs the snapshots are compared to check its validity.

Keep in mind that not always snapshot testing is what you need. It depends on what you need to test.

I can't fit more in a tip, but if you want more information you have the book Testing Vue.js Components with Jest where I added last week a whole section on snapshot testing, including:

  • Rethinking in Snapshots
  • Why, how and when to use snapshots
  • When not use them
  • Code examples

Please tell me what you think about it! You can find me on twitter 🙂

]]>
<![CDATA[Two less known facts about Vuex]]> https://vuedose.tips/two-less-known-facts-about-vuex/ https://vuedose.tips/two-less-known-facts-about-vuex/ Sun, 03 Mar 2019 11:00:00 GMT When using Vuex in our Vue.js components we tend to forget the amazing API that it exposes beside the mapping functions.

Let's see what we can do with it, but first let's create a basic store for our examples:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    getCountPlusOne: state => state.count + 1
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  }
});

Watch

The watch method is the most useful to integrate Vuex with external code, be it in your awesomeService or in your catchAllAuthUtils.

This is how to use it:

const unsubscribe = store.watch(
  (state, getters) => {
    return [state.count, getters.getCountPlusOne];
  },
  watched => {
    console.log("Count is:", watched[0]);
    console.log("Count plus one is:", watched[1]);
  },
  {}
);

// To unsubscribe:
unsubscribe();

What we are doing is to call the watch method with two functions, one to return what part of the state and/or getters we want to keep an eye on and the other with the function that we want to invoke when state.count or getCountPlusOne change.

This is extremely useful to integrate with react code or angular or even... JQuery!

See the example in this CodeSandbox.

SubscribeAction

Sometimes instead of watching a store property change it's more useful to react to a specific action, login and logout come in mind, vuex has us covered with subscribeAction.

Calling subscribe adds a 'callback' that is run at every action and that we can use to call custom code.

Let's use it to start and stop a global spinner before and after every action!

const unsubscribe = store.subscribeAction({
  before: (action, state) => {
    startBigLoadingSpinner();
  },
  after: (action, state) => {
    stoptBigLoadingSpinner();
  }
});

// To unsubscribe:
unsubscribe();
]]>
<![CDATA[Create an ImageSelect component on top of vue-multiselect]]> https://vuedose.tips/create-an-image-select-component-on-top-of-vue-multiselect/ https://vuedose.tips/create-an-image-select-component-on-top-of-vue-multiselect/ Sun, 24 Feb 2019 17:00:00 GMT Two tips before you learnt the concept of Adaptive Components and how you can build the base of it by proxifying props and events using v-bind and v-on.

Now it's time to show it in action. By any chance, do you know about vue-multiselect? It's an amazing select component built by Damian Dulisz. vue-multiselect can be used in many different ways given how flexible and customisable it is. That's really how a fine third-party component should be in order to be reusable.

Based on this example from its documentation, let's build an ImageSelect component. To do that, the example redefines some scoped slots that vue-multiselect exposes:

<multiselect v-model="value" :options="options">
  <template slot="singleLabel" slot-scope="{ option }">
    <img class="option__image" :src="option.img" alt="Sth" />
    <span class="option__desc">
      <span class="option__title">{{ option.title }}</span>
    </span>
  </template>

  <template slot="option" slot-scope="{ option }">
    <img class="option__image" :src="option.img" alt="Sth" />
    <span class="option__desc">
      <span class="option__title">{{ option.title }}</span>
      <span class="option__small">{{ option.desc }}</span>
    </span>
  </template>
</multiselect>

I'm not getting into scoped slots, just assume that code works in case you don't know about them. The thing here is that I want to build an ImageSelect component on top of that code.

From the last tip, you probably know already that you need to use v-bind="$props" and v-on="$listeners" in order to make that proxifying of props and events happen.

You also need to redeclare the props from the original vue-multiselect component, and you can take them from the MultiselectMixin of its source code:

<template>
  <multiselect v-bind="$props" v-on="$listeners">
    <template slot="singleLabel" slot-scope="{ option }">
      <img class="option__image" :src="option.img" alt="No Man’s Sky" />
      <span class="option__desc">
        <span class="option__title">{{ option.title }}</span>
      </span>
    </template>

    <template slot="option" slot-scope="{ option }">
      <img class="option__image" :src="option.img" alt="No Man’s Sky" />
      <span class="option__desc">
        <span class="option__title">{{ option.title }}</span>
        <span class="option__small">{{ option.desc }}</span>
      </span>
    </template>
  </multiselect>
</template>

<script>
  import Multiselect from "vue-multiselect";
  import MultiselectMixin from "vue-multiselect/src/multiselectMixin";

  export default {
    components: {
      Multiselect
    },
    props: MultiselectMixin.props
  };
</script>

Here's how you can use this ImageSelect component, passing by the minimal properties to make it work:

<template>
  <ImageSelect
    v-model="imageValue"
    :options="imageOptions"
    label="title"
    track-by="title"
    :show-labels="false"
  />
</template>

<script>
  import ImageSelect from "./ImageSelect";

  export default {
    components: {
      ImageSelect
    },
    data: () => ({
      imageValue: null,
      imageOptions: [
        { title: "Random img", img: "https://picsum.photos/300/150" },
        { title: "Cool image", img: "https://picsum.photos/300/151" }
      ]
    })
  };
</script>

If you run this code you will notice that there is something not working properly. In particular the show-labels prop. The thing is that it's not a prop, but an attribute! And they're accessible through the $attrs component instance option.

Basically we need to proxify not only the props, but also the attributes to make it work.

To do that, I'm going to use a computed property to merge both $props and $attrs into the same object:

<template>
  <multiselect v-bind="allBindings" v-on="$listeners">
    <!-- ... -->
  </multiselect>
</template>

<script>
  import Multiselect from "vue-multiselect";
  import MultiselectMixin from "vue-multiselect/src/multiselectMixin";

  export default {
    components: {
      Multiselect
    },
    props: MultiselectMixin.props,
    computed: {
      allBindings() {
        // Need to proxify both props and attrs, for example for showLabels
        return { ...this.$props, ...this.$attrs };
      }
    }
  };
</script>

You can try yourself and run the code on this Codesandbox example I've prepared for you. You'll see it has some additional Adaptive Components, such as SingleSelect and MultiSelect.

Pss: they have some CSS tricks we'll cover on the next tips

]]>
<![CDATA[Creating a Store without Vuex in Vue.js 2.6]]> https://vuedose.tips/creating-a-store-without-vuex-in-vue-js-2-6/ https://vuedose.tips/creating-a-store-without-vuex-in-vue-js-2-6/ Sun, 17 Feb 2019 22:30:00 GMT Vue.js 2.6 introduced some new features, and one I really like is the new global observable API.

Now you can create reactive objects outside the Vue.js components scope. And, when you use them in the components, it will trigger render updates appropriately.

In that way, you can create very simple stores without the need of Vuex, perfect for simple scenarios like those cases where you need to share some external state across components.

For this tip example, you're going to build a simple count functionality where you externalise the state to our own store.

First create store.js:

import Vue from "vue";

export const store = Vue.observable({
  count: 0
});

If you feel comfortable with the idea of mutations and actions, you can use that pattern just by creating plain functions to update the data:

import Vue from "vue";

export const store = Vue.observable({
  count: 0
});

export const mutations = {
  setCount(count) {
    store.count = count;
  }
};

Now you just need to use it in a component. To access the state, just like in Vuex, we'll use computed properties, and methods for the mutations:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="setCount(count + 1);">+ 1</button>
    <button @click="setCount(count - 1);">- 1</button>
  </div>
</template>

<script>
  import { store, mutations } from "./store";

  export default {
    computed: {
      count() {
        return store.count;
      }
    },
    methods: {
      setCount: mutations.setCount
    }
  };
</script>

If you want to try this example yourself, I've compiled for you in this CodeSandbox, go check it out!

Remember you can read this tip online (with copy/pasteable code), and don't forget to share VueDose with your colleagues, so they also know about these tips as well!

]]>
<![CDATA[Adaptive components using v-bind and v-on]]> https://vuedose.tips/adaptive-components-using-v-bind-and-v-on/ https://vuedose.tips/adaptive-components-using-v-bind-and-v-on/ Sun, 10 Feb 2019 22:00:00 GMT When you're working with component-based technologies, as soon as an application starts to grow, you need to structure and categorise your components in order to keep them as simple and maintainable as possible.

Component composition it's a powerful pattern popular that helps reusing and structuring code in component-based technologies... But what the heck is component composition? Although being a generic concept, let's say that when you do component composition you're creating a component by the combination of one or more.

One case could be when we have a base component, say BaseList, and you want to create a similar component with some additional functionality on top of it, like SortableList. I call them Adaptive Components (also proxy or wrapper components).

When building an Adaptive Component, you usually want SortableList to keep the same API that the original BaseList in order to keep the components consistent. Which means that from SortableList you need to pass down all the props and listen to all events of BaseList.

Here's the trick: you can do that by using v-bind and v-on:

<!-- SortableList  -->
<template>
  <AppList v-bind="$props" v-on="$listeners"> <!-- ... --> </AppList>
</template>

<script>
  import AppList from "./AppList";

  export default {
    props: AppList.props,
    components: {
      AppList
    }
  };
</script>

The way v-bind works is basically the same than passing one by one all the properties to AppList, but instead passed all at once in an object. $props is the object in the component instance that contains all the properties of that component.

As you can imagine, v-on="$listeners" works exactly the same way, but for events.

This would work for two-way bound components using v-model as well. In case you didn't know, v-model is a shorthand for passing a value property and listening to a input event.

Finally, remember that in Vue.js we have to explicitly declare the props of a component in order to be interpreted as such. A quick way to do that when building an Adaptive Component is to use the base component props to define them, like I do in props: AppList.props.

That's it! Maybe you didn't see the practical use of the Adaptive Components? No worries, in the next tip I'll build a real case of an Adaptive Component so you can see it in action. Stay tuned!

]]>
<![CDATA[How to use the new v-slot directive in Vue.js]]> https://vuedose.tips/new-v-slot-directive-in-vue-js-2-6-0/ https://vuedose.tips/new-v-slot-directive-in-vue-js-2-6-0/ Sun, 03 Feb 2019 20:00:00 GMT I'm so excited to see how's people liking VueDose! I received amazing feedback about the performance tips published so far. I'm so grateful about the support and the compliments 🤗.

Last week the version 2.6.0-beta.3 of Vue.js was released, enabling a new feature to simplify scoped slots even more.

It introduces the new v-slot directive and its shorthand, as described in the RFC-0001 and RFC-0002.

In order to understand how it does simplify the syntax, let's see how's it in current scoped slots. Imagine you have a List the component that exposes a filtered list as its scope.

The way you'd use that List scoped slot would be similar to:

<template>
  <List :items="items">
    <template slot-scope="{ filteredItems }">
      <p v-for="item in filteredItems" :key="item">{{ item }}</p>
    </template>
  </List>
</template>

The implementation of the List component is not too relevant, but in this Codesandbox you can check an example of it.

With v-slot, you can write the scope of that slot directly on the component tag, avoiding an extra layer:

<template>
  <List v-slot="{ filteredItems }" :items="items">
    <p v-for="item in filteredItems" :key="item">{{ item }}</p>
  </List>
</template>

Keep in mind v-slot can only be used on components and template tags, but not in plain HTML tags

This makes the code more readable specially when you have nested scoped slots, which can be difficult to reason where a scope comes from.

The v-slot directive also introduces a way to combine the slot and the scoped-slots directive, but by separating them with a colon :.

For example, this example taken from vue-promised:

<template>
  <Promised :promise="usersPromise">
    <p slot="pending">Loading...</p>

    <ul slot-scope="users">
      <li v-for="user in users">{{ user.name }}</li>
    </ul>

    <p slot="rejected" slot-scope="error">Error: {{ error.message }}</p>
  </Promised>
</template>

Could be written with v-slot as follows:

<template>
  <Promised :promise="usersPromise">
    <template v-slot:pending>
      <p>Loading...</p>
    </template>

    <template v-slot="users">
      <ul>
        <li v-for="user in users">{{ user.name }}</li>
      </ul>
    </template>

    <template v-slot:rejected="error">
      <p>Error: {{ error.message }}</p>
    </template>
  </Promised>
</template>

And to end up, v-slot has the symbol # as a shorthand, so the example before could be written as:

<template>
  <Promised :promise="usersPromise">
    <template #pending>
      <p>Loading...</p>
    </template>

    <template #default="users">
      <ul>
        <li v-for="user in users">{{ user.name }}</li>
      </ul>
    </template>

    <template #rejected="error">
      <p>Error: {{ error.message }}</p>
    </template>
  </Promised>
</template>

Just keep in mind that the shorthand for the default v-slot is #default.

Are you excited about this new slot syntax?

]]>
<![CDATA[Remove unused CSS with PurgeCSS]]> https://vuedose.tips/remove-unused-css-with-purge-css/ https://vuedose.tips/remove-unused-css-with-purge-css/ Sun, 27 Jan 2019 22:00:00 GMT We can tackle web performance in different ways, and one of them is removing all JS and CSS we don't use in our apps.

In the case of CSS, it's even more important when we use frameworks such as Bootstrap, Bulma or Tailwind, as well as when we're facing large or legacy applications.

PurgeCSS is a tool that removes unused CSS by applying string comparison. That has some advantages, but also some caveats, so please keep attention to the white-listing part later.

As an example, VueDose's website is built on Nuxt (static generated) using Tailwind, and it uses PurgeCSS to optimize the generated CSS.

If I disable PurgeCSS, you can see that the tailwind css is 485 KB:

While if I activate it, it goes down to 16 KB:

PurgeCSS configuration can be different in each project. It can be set as a webpack plugin or a postcss plugin.

In the case of VueDose, I'm using it as a postcss plugin. So I have a postcss.config.js file with this content:

const purgecss = require("@fullhuman/postcss-purgecss");

const plugins = [...];

if (process.env.NODE_ENV === "production") {
  plugins.push(
    purgecss({
      content: [
        "./layouts/**/*.vue",
        "./components/**/*.vue",
        "./pages/**/*.vue"
      ],
      whitelist: ["html", "body"],
      whitelistPatternsChildren: [/^token/, /^pre/, /^code/],
    })
  );
}

module.exports = { plugins };

Basically, all you need is to tell where to look for the matching classes using the content property.

Also, you'd like to whitelist some classes or tags to don't be removed. You'll need to do that at least in html and body, as well as any dynamic classes.

In my case, I use prismjs in order to highlight the code blocks, and it adds several token classes, as well as styles in the pre and code tags. In order to exclude them, I need to use the whitelistPatternsChildren property.

Tailwind, additionally, needs a custom extractor in order to work properly with PurgeCSS. All in all, the whole postcss.config.js file for VueDose has the following content:

const join = require("path").join;
const tailwindJS = join(__dirname, "tailwind.js");
const purgecss = require("@fullhuman/postcss-purgecss");

class TailwindExtractor {
  static extract(content) {
    return content.match(/[A-Za-z0-9-_:\/]+/g) || [];
  }
}

const plugins = [require("tailwindcss")(tailwindJS), require("autoprefixer")];

if (process.env.NODE_ENV === "production") {
  plugins.push(
    purgecss({
      content: [
        "./layouts/**/*.vue",
        "./components/**/*.vue",
        "./pages/**/*.vue"
      ],
      whitelist: ["html", "body"],
      whitelistPatternsChildren: [/^token/, /^pre/, /^code/],
      extractors: [
        {
          extractor: TailwindExtractor,
          extensions: ["html", "vue"]
        }
      ]
    })
  );
}

module.exports = {
  plugins
};

That's it for today!

]]>
<![CDATA[Measure runtime performance in Vue.js apps]]> https://vuedose.tips/measure-runtime-performance-in-vue-js-apps/ https://vuedose.tips/measure-runtime-performance-in-vue-js-apps/ Sun, 20 Jan 2019 22:00:00 GMT In the last tip we talked about how to improve performance in large lists. But still we haven't measure how much it really improved.

We can do so by using the Performance tab in Chrome DevTools. But in order to have accurate data, we must activate performance mode on our Vue app.

We can do that by setting the global, in our main.js file or in a plugin in the case of Nuxt:

Vue.config.performance = true;

Or if you have your NODE_ENV env variable set correctly, you could use it to set it in non-production environments:

const isDev = process.env.NODE_ENV !== "production";
Vue.config.performance = isDev;

That will activate the User Timing API that Vue uses internally to mark the components performance.

From the last tip, I've created this codesandbox. Open it and hit the reload button from the performance tab on Chrome DevTools:

That will record the page load performance. And thanks to the Vue.config.performance setting that you can see set on main.js you'll be able to see a User Timing section on the profiling:

In there, you'll find 3 metrics:

  • Init: time it takes to create the component instance
  • Render: time to create the VDom structure
  • Patch: time to apply the VDom structure to the real DOM

Back to the curiosity, the results of the previous tip are the following: the normal component takes 417ms to initalize:

While the non-reactive one using Object.freeze takes 3.9ms:

Of course this can vary a little from run to run, but still the difference must be quite huge. Since the reactivity issue happens when the component is created, you'll see that difference in the init part of the Reactive and NoReactive components.

That's it!

Remember you can read this tip online (with copy/pasteable code) and please share VueDose with all your colleagues if you liked it!

See you next week.

]]>
<![CDATA[Improve performance on large lists in Vue.js]]> https://vuedose.tips/improve-performance-on-large-lists-in-vue-js/ https://vuedose.tips/improve-performance-on-large-lists-in-vue-js/ Sun, 13 Jan 2019 21:00:00 GMT Hi there! And welcome to the very first VueDose tip! I'm really stocked to start this adventure on VueDose and help devs like you learn some badass tricks.

VueDose's tips are going to be very concise, and that's the format I believe people find more useful. So let's go straight to the point.

Usually we have the need of fetching a list of objects, say users, items, articles, whatever...

And sometimes, we don't even need to modify them, just to display them or having them in our global state (a.k.a. Vuex). A simple code for fetching that list would be something like:

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = users;
  }
};

Vue by default makes reactive every first-level property for each object in the array.

That can be expensive for large arrays of objects. Yeah, sometimes those lists are paginated, but others you just might have that list in the frontend.

That's usually the case with Google Maps markers, which in fact are huge objects.

So, in these cases, we can gain some performance if we prevent Vue from making reactive that list. And we can do that by using Object.freeze to the list before adding it to the component:

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

Keep in mind that the same applies to Vuex:

const mutations = {
  setUsers(state, users) {
    state.users = Object.freeze(users);
  }
};

By the way, if you need to modify the array, you can still do it by creating a new array. For instance, in order to add an item, you can do it in this way:

state.users = Object.freeze([...state.users, user]);

Are you wondering how much is the performance improvement? We'll see it in the next tip, so stay tuned!

That's it for today! I hope you like this first tip 😛.

Remember you can read this tip online (with copy/pasteable code) and please share VueDose with all your colleagues if you liked it!

]]>
<![CDATA[How we built our blog as a full-static site with Nuxt, Storyblok and TailwindCSS]]> https://vuedose.tips/guides/how-we-built-our-blog-as-a-full-static-site-with-nuxt-storyblok-and-tailwindcss/ https://vuedose.tips/guides/how-we-built-our-blog-as-a-full-static-site-with-nuxt-storyblok-and-tailwindcss/ Tue, 25 Aug 2020 15:25:16 GMT This is not just a guide for learning how to create a blog with modern technologies such as Nuxt, Storyblok and TailwindCSS. It's also not another tutorial where the author creates a random pet project to show concepts out of the blue.

This is a story. The story that tells how we built VueDose 2.0, this very site you're reading on. In other words, a production-ready and finely thought out website from design to development.

You'll go through this story yourself by creating step by step NarutoDose: a smaller and funnier version of VueDose but themed with Naruto characters. You'll learn to:

  • Use Nuxt in it's brand new full-static mode for best SEO and max performance
  • Organize UI components in Vue based on a Design System
  • Structure and work with the blog articles, authors and data using Storyblok
  • And much more!

Psst: this story wouldn't have been written without the help of Storyblok, a beloved VueDose sponsor 💚

]]>