VueDose https://vuedose.tips Learn the best tips and tricks about Vue.js, Nuxt and JavaScript and find the best articles about Web Development Thu, 21 Jan 2021 05:13:41 GMT http://blogs.law.harvard.edu/tech/rss https://github.com/nuxt-community/feed-module API Best Practices Components Composition API CSS Deploy Devtools Gaming i18n JAM Stack Library Nuxt SEO Testing UI Use Cases Vue 3 Vue.js Vue Router Vuex Web Performance <![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 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`) ~~~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`): ~~~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`) ~~~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 ~~~js 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 ~~~sh yarn add workerloader-jest-transformer --dev ~~~ and add the tranformation rule to your Jest configuration: ~~~js transform: { "^.+\\.worker.[t|j]sx?$": "workerloader-jest-transformer" } ~~~ This transformer is inspired by [jsdom-worker](https://github.com/developit/jsdom-worker) and implements Web Worker API for JSDOM, so you can remove any mocking code as you can see [here](https://github.com/astagi/webworker-test/tree/feature/jesttransformer). Workerloader-jest-transformer is highly experimental and code is available on [Github](https://github.com/astagi/workerloader-jest-transformer), 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 " } ``` 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): ```vue ``` 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 { 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](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](https://jamstack.org/) 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](https://www.netlify.com/)** 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](https://surge.sh/) 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](https://vercel.com/). 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](https://dev.to/dawntraoz/how-to-deploy-a-nuxt-full-static-site-in-digitalocean-4l73). ### 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](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](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](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](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](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](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](https://docs.netlify.com/domains-https/custom-domains/) 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](https://www.storyblok.com/developers?utm_source=newsletter&utm_medium=logo&utm_campaign=vuedose), a beloved sponsor of ours 💚. If you have any questions, feel free to reach [VueDose](https://twitter.com/vuedose) or [Storyblok](https://twitter.com/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 ` and inserted by Nuxt within the `` 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: ```js export const createSEOMeta = (data) => [ { hid: 'description', name: 'description', content: data.description }, ] ``` And update it on *nuxt.config.js:* ```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: ```js 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](https://www.storyblok.com/tp/show-blog-content-in-nuxt). ::: 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`: ```js 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](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](https://ogp.me/), 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](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image). 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: ```js 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](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](https://www.storyblok.com/tp/setting-up-blog-content-structure), 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: ```js 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*: ```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](https://www.npmjs.com/package/@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: ```js 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: ```js 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](https://www.storyblok.com/docs/api/content-delivery#core-resources/links/retrieve-multiple-links), 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: ```js 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](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 Vue Framework', ~~~ ~~~vue

~~~ So recently we added [eslint](https://eslint.org/) 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](https://kazupon.github.io/vue-i18n/guide/interpolation.html) comes into practice. With i18n, instead of using `v-html` we can instead use interpolation. How? i18n gives us an `` 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. ~~~vue ~~~ We then allocate it to our translation. If for example in our *en-EN.js* file we have `homepage.title`: ~~~js module.exports = { homepage: { title: 'The Intuitive Vue Framework' } } ~~~ Then this is what we will use as the path in our `` component. ~~~vue ~~~ We can also add classes to our component just like any other component ~~~vue ~~~ 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. ~~~vue ~~~ 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. ~~~js 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. ~~~js 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 `` 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. ~~~vue ~~~ ~~~js title: 'The Intuitive {br} {frameworkType} Framework', ~~~ ~~~js 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: - For more info on using i18n with Nuxt.js check out the [nuxt-i18n module](https://i18n.nuxtjs.org/) - For more info on component interpolation checkout the [vue-i18n docs](http://kazupon.github.io/vue-i18n/guide/interpolation.html) - To learn more about i18n checkout the [i18n course on Vue School](https://vueschool.io/courses/internationalization-with-vue-i18n)]]>
<![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

Articles

``` 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: ```js { id: 17434028, // ... tag_list: ['Beast', 'Hokage'] } ``` Let's use it in order to show the tags just below the `
` tag in *pages/_slug.vue*: ```vue
...
{{ tag }}
``` 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](https://lodash.com/) easily: ```js 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](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](https://www.storyblok.com/docs/api/content-delivery#core-resources/tags/tags) and find the tag from the response. That call returns an object with the following shape: ```json { "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*:* ```js 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](https://www.storyblok.com/docs/api/content-delivery#core-resources/stories/stories): ```js 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 `

` tag to show as well the topic name and count: ```vue

{{ topic.taggings_count }} articles on #{{ topic.name }}

``` The page will look similar to this: ![https://a.storyblok.com/f/83078/1692x910/f85bef9975/05-02-topics-page.png](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: ```html ``` 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](https://vue-multiselect.js.org/). 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](https://www.elastic.co/start)) and services (like [Algolia](https://www.algolia.com/)). 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*: ```html ``` 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](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](https://nuxtjs.org/) blog thanks to [Storyblok](https://www.storyblok.com/) 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](https://github.com/alexjoverm/narutodose/tree/05-tags-and-search-functionality-in-nuxt-using-storyblok-api).]]> <![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
NarutoDose
``` 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`: ```vue ``` In case you find it difficult to understand TailwindCSS and its classes, I suggest you take a look at these [beautiful docs](https://tailwindcss.com/docs/installation/) 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: ```vue ``` Notice I've also added a `date` prop. Now let's add some styling to the home page, located under `pages/index.vue`: ```vue ``` I'm using a [grid](https://tailwindcss.com/docs/grid-template-columns/) 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: ```js 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](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](https://tailwindcss.com/docs/container/#app)**. 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](https://tailwindcss.com/docs/container/#centering-by-default). 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](https://twitter.com/aarongarciah) 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](https://www.storyblok.com/developers?utm_source=newsletter&utm_medium=logo&utm_campaign=vuedose) and use its API to take the data from there. Are you ready for it? 😏 **Pssst**: find [this lesson's code on Github](https://github.com/alexjoverm/narutodose/tree/02-creating-ui-components-based-on-a-design-system-in-vuejs).]]>
<![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 ` tag and empty the ` ``` ### Dynamic Imports You can also dynamically import your components or lazy load your components by prefixing the world Lazy in your templates. [//]: # "auto-load-vuejs__lazy-loading-components" ```vue ``` 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. [//]: # "auto-load-vuejs__conditional-component" ```vue ``` 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](https://github.com/nuxt/components#features) ℹ️ Nuxt components will not work with dynamic components. For these components you will have to use the script tag to import your component. [//]: # "auto-load-vuejs__dynamic-component" ```vue ```]]> <![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 {{ link.name }} ``` **AtomLink.vue** The atom that represents our link will be the same as the previous one but with the base style. ```vue {{ link.name }} ``` **AtomLogo.vue** & **AtomText.vue** For the logo we will add the SVG that represents it and for the text we will add the tag

. ```vue

{{ text }}

``` **AtomTitle.vue** In this case, as the title can have more than one type of tag, h1, h2, ..., we have created a [dynamic component](https://vuejs.org/v2/guide/components-dynamic-async.html) to be able to represent the corresponding title introduced by the prop: tag. ```vue ``` 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. ```vue ``` **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. ```vue ``` **MoleculeBanner.vue** For the Banner that will appear under the header we have needed both **AtomTitle** and **AtomText**, and also the **AtomButton**. ```vue ``` ### 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. ```vue ``` **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: ```vue ``` **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). ```vue ``` ### 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. ```vue ``` ### 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 ` 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](https://github.com/vuejs/vue-next/blob/8b85aaeea9b2ed343e2ae19958abbd9e5d223a77/packages/runtime-core/__tests__/components/Suspense.spec.ts#L45-L69) to get familiar with it. [//]: # "go-async-in-vue3-suspense__test-case" ```vue ``` 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`: [//]: # "go-async-in-vue3-suspense__setup-function" ```vue ``` 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: [//]: # "go-async-in-vue3-suspense__first-example" ~~~vue ~~~ 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: [//]: # "go-async-in-vue3-suspense__error-capture" ~~~vue ~~~ 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: [//]: # "go-async-in-vue3-suspense__error-capture" ~~~vue ~~~ So you can use it like this: [//]: # "go-async-in-vue3-suspense__handling-error" ~~~vue ~~~ 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 `` 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 ``, `Suspense` and async setup do a nice job! The example below shows how it can be implemented: [//]: # "go-async-in-vue3-suspense__with-vue-router" ~~~vue ~~~ ## 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](https://github.com/viniciuskneves/vue-3-suspense). ![Final example](https://a.storyblok.com/f/83078/896x428/6e7b1515e8/go-async-with-suspense__demo.gif)]]> <![CDATA[Naming Webpack Chunks on Lazy Loaded Routes in Vue.js]]> https://vuedose.tips/naming-webpack-chunks-on-lazy-loaded-routes-in-vuejs/ https://vuedose.tips/naming-webpack-chunks-on-lazy-loaded-routes-in-vuejs/ Sun, 19 Apr 2020 00:00:00 GMT import(/* webpackChunkName: "Home" */ '../views/Home.vue') }, { path: '/about', name: 'About', component: () => import(/* webpackChunkName: "About" */ '../views/About.vue') }, { path: '/login', name: 'Login', component: () => import(/* webpackChunkName: "Login" */ '../views/Login.vue') } ] ``` This way it's really ok and it has nothing wrong. However, for most cases, I prefer to use a different system instead of using the "normal" approximation. As you can see, there are some repetitive patterns. Since you are an awesome developer, you can use an `array` with the *route options* and then iterate with a `map()` function to avoid doing repetitive tasks. Something like this: [//]: # "routes_v2" ```javascript const routeOptions = [ { path: '/', name: 'Home' }, { path: '/about', name: 'About' }, { path: '/login', name: 'Login' } ] const routes = routeOptions.map(route => { return { ...route, component: () => import(`@/views/${route.name}.vue`) } }) const router = new VueRouter({ routes }) ``` 🤩 Looks nice, isn't it? We have reduced the use of the `component` key by using the route `name` as param in the `import` function. But.. what happens if you want set the *chunk name*? I'm not a scientist, but as far as my knowledge reaches, you can't have dynamic comments in javascript. So, we are sacrificing comments (`webpackChunkName`) in favor of having to write less code. It's up to you. Joke. **Webpack to the rescue!** As of webpack `2.6.0` , the placeholders `[index]` and `[request]` are supported, meaning you can set the name of the generated chunk thus: [//]: # "routes_v3" ```javascript const routeOptions = [ { path: '/', name: 'Home' }, { path: '/about', name: 'About' }, { path: '/login', name: 'Login' } ] const routes = routeOptions.map(route => { return { ...route, component: () => import(/* webpackChunkName: "[request]" */ `../views/${route.name}.vue`) } }) const router = new VueRouter({ routes }) ``` 👌 Very nice! Now you have all the power, dynamically loaded routes with named chunks. You can check it out by running `npm run build` in your terminal: ![build](/tips/naming-webpack-chunks-on-lazy-loaded-routes-in-vuejs/build.png) As you see, it's extremely easy implement this in your vue.js applications. Now it's your turn. Start to improve your `router.js` file by using this awesome tip. ]]> <![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
``` _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](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) 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: [//]: # "nuxt-config" ```js 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`: [//]: # "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: [//]: # "basic-richtext-component" ```vue ``` ### 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](/tips/measure-runtime-performance-in-vue-js-apps). 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_. ![](https://i.ibb.co/sVgt3Pj/featured.png) ![](https://i.ibb.co/YNmqrM3/patch.png) If you don't know what _render_ and _patch_ mean, it's explained in [this article](/tips/measure-runtime-performance-in-vue-js-apps) 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
  • {{ user }}
``` That you use in an `App.vue` component like this: [//]: # "app-component" ```vue ``` Then it will give us the combined DOM Tree of both components: [//]: # "mount-snap" ```vue

User List

  • Rob Wesley
``` 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](/tips/the-power-of-snapshot-testing/) that goes deeper into them. When using [@vue/test-utils](https://vue-test-utils.vuejs.org/), 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: [//]: # "mount-test" ```js 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: [//]: # "shallow-test" ```js 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: [//]: # "shallow-snap" ```vue

User List

``` What happened? Basically Jest has created the `` 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](https://www.eventbrite.it/e/vueday-2020-tickets-60309252598) 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 ({ 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](https://vuedose.tips/tips/easily-switch-to-composition-api-in-vuejs-3) and how to [use old instance properties](https://vuedose.tips/tips/use-old-instance-properties-in-composition-api-in-vuejs-3/) 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: [//]: # "provide" ```vue ``` 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: [//]: # "inject" ```vue ``` 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: [//]: # "change_language" ```vue ``` 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](https://codesandbox.io/s/composition-api-simple-demo-lp0z5) [CodeSandbox](https://codesandbox.io/s/i18n-plugin-composition-api-mbe0b)! 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

{{ formattedMoney }}

``` I've set `titleRef` on the `

` 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: [//]: # "onMounted" ```js 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

10.00

_ Don't believe me? Check it out with your own eyes [this CodeSandbox](https://codesandbox.io/s/template-refs-in-composition-api-w8rux)! 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 ; 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: ```js 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](https://codesandbox.io/s/composition-context-yq2s8)! ]]> <![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

{{ formattedMoney }}

``` 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 ` ``` 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 `` work, you can check the articles [Using scoped slots in Vue.js](https://vuedose.tips/tips/using-scoped-slots-in-vue-js/) and [New v-slot directive in Vue.js 2.6.0](https://vuedose.tips/tips/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: ```vue ``` 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! ```css .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: ```vue ``` 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](https://codesandbox.io/s/carousel-component-5zkbe)! ]]>
<![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 ({ 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: ```html ``` That's it! If you want to check this example running, go to this [CodeSandbox](https://codesandbox.io/s/2w6zp30kjy)! Don't forget to share [VueDose](https://vuedose.tips) 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

Default slot

Time: {{ time.toLocaleTimeString() }}

``` You might have noticed the `` 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: ```vue ``` `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](https://codesandbox.io/s/yjjq04vn91)! ]]>
<![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 {{ column.name }} {{ colum.name }}: {{ item[colum.key] }} ``` 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: ```js 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](https://github.com/LinusBorg/vue-cli-plugin-test-attrs) 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? ```js 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: ```js // 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**: ```js 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: ```js // 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 .list /deep/ .list-item { color: white; background: #42a5f5; } ``` Take a look at the [updated example](https://codesandbox.io/s/40y6v5w3w0) 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
  • {{ item }}
``` Then create two components with the same code. Call them `RedList.vue` and `BlueList.vue`: ```vue ``` Now add two different styles to each of them, according to the colors. For instance, for `BlueList.vue`: ```vue ``` Put them together like I did in [this CodeSandbox](https://codesandbox.io/s/zwkj000z7p), and... surprise! You'll see that even though both components have a different color defined, they show the same color: [](/tips/the-importance-of-scoped-css-in-vue-js/ListExample.png) 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 number`. ```js // 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 = ``; const closeTag = ``; 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. ```vue ``` If you will generate static page using `nuxt generate` as I did, you will end with a static page containing this: ```vue

Price: 10000

``` 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. ```js // 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. ```scss // 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 <![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 ` and it will look for following classes in you css code, which will define the transition between the pages. ```css .default-enter { } .default-enter-active { } .default-enter-to { } .default-leave { } .default-leave-active { } .default-leave-to { } ``` You should definitely check [Vue.js documentation](https://vuejs.org/v2/guide/transitions.html#Transition-Classes) to understand, when are these classes used and what are transition modes. But lets define very simple transition between pages using the opacity. ```css .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](https://codesandbox.io/embed/2xovlqpv9n). 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](https://codesandbox.io/embed/2xovlqpv9n) 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](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](https://nuxtjs.org/api/configuration-transition#the-layouttransition-property) 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](https://nuxtjs.org/api/pages-transition/) and the [layout transitions](https://nuxtjs.org/api/configuration-transition#the-layouttransition-property). These defaults can be override directly in `nuxt.config.js`: ```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](https://github.com/sdras/page-transitions-travelapp) 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 ` tag that gets lazy loaded. That's why I created [v-lazy-image](https://github.com/alexjoverm/v-lazy-image), a Vue.js component that extends the `` tag API and applies lazy loading. To use it, once you install it by running `npm install v-lazy-image`, you can add it globally to your project: ```js // main.js import Vue from "vue"; import { VLazyImagePlugin } from "v-lazy-image"; Vue.use(VLazyImagePlugin); ``` And then is as simple to use as using an ``: ```vue ``` You can even use the **progressive image loading** technique by setting a `src-placeholder` property with a tiny image (usually a small version of the image), even by applying your own transition effects using CSS: ```vue ``` You can checkout [this demo](https://codesandbox.io/s/9l3n6j5944) made by [@aarongarciah](https://twitter.com/aarongarciah) where you can see it in action with many different CSS effects!]]> <![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 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](https://twitter.com/slightlylate/status/1018880523446337536). (Also in the [React ecosystem](https://twitter.com/slightlylate/status/1031934342132461568)) ### 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. ~~~javascript // 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: ~~~javascript 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](https://alexjover.com/blog/lazy-load-in-vue-using-webpack-s-code-splitting/): * 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](https://parceljs.org/)!), that syntax is going to be transformed on *compilation time* and these tools are going to use`Promise`s 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](https://github.com/gangsthub/dynamic-imports-example) (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 `import`s, is heavier because it loads everything at once. ![](/tips/dynamic-imports-in-vue-js-for-better-performance/network.png) 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](https://webpack.js.org/api/module-methods/#magic-comments) but that will be the subject of another article 😉. ![](/tips/dynamic-imports-in-vue-js-for-better-performance/featured.png) 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: * De facto linked article by Anthony Gore: [https://vuejsdevelopers.com/2017/07/03/vue-js-code-splitting-webpack/](https://vuejsdevelopers.com/2017/07/03/vue-js-code-splitting-webpack/) * Google's web fundamentals article by Addy Osmani and Jeremy Wagner about code splitting: [https://developers.google.com/web/fundamentals/performance/optimizing-javascript/code-splitting/](https://developers.google.com/web/fundamentals/performance/optimizing-javascript/code-splitting/) * Webpack docs: [https://webpack.js.org/guides/code-splitting/](https://webpack.js.org/guides/code-splitting/) *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 ``` 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: ```javascript 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 ``` 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: ```vue ``` 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](https://vuedose.tips) 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 ({ 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. ```js 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](https://codesandbox.io/s/rwxp7pnklo)!]]> <![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

{{ item }}

``` Could be written in a functional way as follows: ```vue ``` 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](https://codesandbox.io/s/rwxp7pnklo)! ]]>
<![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 `. 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](https://github.com/alexjoverm/v-runtime-template) renders, you can listen to it's `updated` lifecycle hook: ```vue ``` Still don't trust me? Check it yourself in this [CodeSandbox](https://codesandbox.io/s/18r05pkmn7)! ]]> <![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 { 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: ```js 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](https://leanpub.com/testingvuejscomponentswithjest)_ 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](https://twitter.com/alexjoverm) 🙂 ]]> <![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 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: ```javascript 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](https://codesandbox.io/s/vm6r05qjq0). ## 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! ```javascript 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 ``` 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: ```vue ``` Here's how you can use this `ImageSelect` component, passing by the minimal properties to make it work: ```vue ``` 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: ```vue ``` You can try yourself and run the code on [this Codesandbox example](https://codesandbox.io/s/n71oq781r0) 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

Count: {{ count }}

``` If you want to try this example yourself, I've compiled for you in [this CodeSandbox](https://codesandbox.io/s/k3kpqz2wz7), go check it out! Remember you can read this tip [online](https://vuedose.tips/tips/creating-a-store-without-vuex-in-vue-js-2-6 ) (with copy/pasteable code), and don't forget to share [VueDose](https://vuedose.tips) 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 ``` 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 ``` The implementation of the List component is not too relevant, but in [this Codesandbox](https://codesandbox.io/s/wwzx6zw47w) 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: ```vue ``` _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](https://github.com/posva/vue-promised): ```vue ``` Could be written with `v-slot` as follows: ```vue ``` And to end up, `v-slot` has the symbol `#` as a shorthand, so the example before could be written as: ```vue ``` 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 <![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 <![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 ({ 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: ```javascript 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: ```javascript 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: ```javascript 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](https://vuedose.tips/tips/improve-performance-on-large-lists-in-vue-js) (with copy/pasteable code) and please share [VueDose](https://vuedose.tips/) 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