diceline-chartmagnifiermouse-upquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

How to declare path aliases in Typescript

Defining path aliases using webpack can save you a lot of headache when it comes to imports, but you must also let Typescript know about them.

Following my previous post on declaring path aliases using webpack, you can configure your tsconfig.json file to in order to be able to use those aliases in Typescript like so:

{
  ...
  "paths": {
    "@/*": ["./src/*"],
    "images/*": ["./assets/images/*"],
  },
  "include": [
    ...
  ],
}

Of course, all paths for defined aliases must be reachable by Typescript. You can check this post out if you are not sure how to do that.

Otherwise, we are going to get this error:

Cannot find module 'images/[your module]' or its corresponding type declarations.

How to import images in Typescript

Normally, when Typescript cannot find something we get this error:

Cannot find module [your module] or its corresponding type declarations ts(2307)

Now let's see how we can fix this, with a simple example. Given the following folder structure:

│── src
│   ├── resources
│   │   ├── ts
│   │   │   ├── **/*.ts
│   │   ├── images
│   │   │   ├── logo.svg
├── ...
├── tsconfig.json

In order to be able to achieve something like this:

import Logo from '[path]/images/logo.svg'

without any ** Typescript errors**, we need to follow these steps:

1.Make sure that the images folder is "reachable" by Typescript.

Adding this inside tsconfig.json will do the trick:

{
  "include": [
        ...
        "resources/**/*.ts",
   ],
}

A configuration like this one will not do:

{
  "include": [
      "resources/ts/**/*.ts",
   ],
}

Because images/ is not included in ts/ and we don't have any other folders declared, so Typescript can't "reach" it.

2.Let Typescript know about the .svg type in that folder.

In the root of the images folder create a file called index.d.ts:

...
├── resources
│   ├── ts
│   │   ├── **/*.ts
│   ├── images
│   │   ├── logo.svg
│   │   ├── index.d.ts
...

With the following contents:

declare module '*.svg' {
  const value: any;
  export = value;
}

Now you should be good to go.

Making only some state properties persistent inside a persistent Vuex module

Making data persistent in web applications is a common required feature. State management patterns and libraries like Vuex provide systems for storing data in the local storage for achieving the persistence of data, making the data survive over refresh and making it reusable without having to request the data again.

The local storage has a (configurable) memory limit. Overflowing can cause unwanted behaviour and make the app work unexpectedly. Vuex gives the opportunity to make only some of its modules persistent, which is great.

Step 1: We import the modules and group them together in our modules object:

import store as module1 from '../module1Location'
import store as module2 from '../module2Location'
import store as module3 from '../module3Location'

const modules = {
  module1,
  module2,
  module3,
};

Step 2: For choosing which modules should be persistent, we can use the vuex-persistedstate plugin. We have to define the plugins array that will be passed to the createStore method like this:

const plugins = [
  createPersistedState({
    storage: window.localStorage,
    key: 'yourkey',
    paths: [
      'module1',
      'module2',
      'module3',
    ],
  }),
];

Step 3: Having only some modules to be persistent is nice. But what if we need to only make some parts of a module persistent? What if one of our modules should have all its state properties persistent except for one, the one that causes the unwanted local storage overflow?

Lets pretend our module3 has a property named bigData that causes the overlow.

Solution: we can add the REDUCER FUNCTION to our payload for the createPersistedState() function. Here is how our plugins array should look like:

const plugins = [
  createPersistedState({
    reducer: (state: RootState) => {
      const { module3, ...restOfRootState } = state;
      const { bigData, ...restOfModule3State } = module3;
      return {
        ...restOfRootState,
        module3: { ...restOfModule3State },
      };
    },
    storage: window.localStorage,
    key: 'yourkey',
    paths: [
      'module1',
      'module2',
      'module3',
    ],
  }),
];

Explanation of the reducer function: The reducer function takes in a state and returns the new state that should be persisted. We can change the state that we receive initially to only contain the data that we wish. Lets see how we extract the problematic bigData out of our module.

  1. We get our RootState as a parameter. This is an object containing all our Vuex modules.
  2. We extract in a variable our problematic module3 out of this RootState object and keep all other (non-problematic) modules in another separate object using the rest operator.
  3. We extract our problematic bigData from our module3 variable (the one extracted at step2) and keep all other (non-problematic) state properties in another object using the rest operator.
  4. We return the new state object that we build by spreading out the object containing the non-problematic modules using the spread operator and by redefining our module3 that should now contain only the non-problematic state properties (all its previous properties except bigData).

Step 4: passing the modules and plugins objects to the createStore() function, through its payload.

export const store = createStore({
  modules,
  plugins,
});

After this, the local storage will only help persist our modules, without the module3's bigData object that was causing our local storage overflow.

How to add dynamic routes with VUE router

Dynamic routes are an important part. Let's consider we have a blog (for example) which has 10 posts, each of them with a unique URL. Creating a new component for each blog post isn't the best practice, so here intervenes a special feature of VUE: dynamic routes.

First, let's create a Post component which will serve as a template for displaying each unique blog post:

<template>
	<div>
		<h1>Welcome to Post page</h1>
		<p>This is the page for post number {{ $route.params.id }}</p>
	</div>
</template>

Now, we need to update the router/index.js with our new route. *Don't forget to include the import for the Post component.

Below, we will use :id parameter as a dynamic value in the route:

{
	path: '/post/:id',
	name: 'Post',
	component: Post
}

Nicely done, you have now dynamic routes for your blog.

Visual Studio Code Snippets

VS Code offers the possibility to create personalized code snippets. These Code snippets can be configured for a specific user, globally, for a certain project or programming language.

For example, if a user wants to avoid the repetitive writing of the basic "console.log({})", after configuring the snippets, it will be enough to write a prefix like: "cl" or "log" and VSC will autocomplete the code snippet. The braces {} will also show the variable name before the result.

In order to achieve this, we can run the command "Preferences: Configure User Snippets" in VSC and a popup will appear showing the types of of snippets to configure.

If we wish to configure a snippet as in the 'console.log' example above, we need to choose to modify the "javascript.json" file with the following content:

{
    "Print to console": {
        "prefix": "cl",
        "body": [
	    "console.log({$1});",
	    "$2"
	],
        "description": "Log output to console"
	}
}

As you can see, a certain snippet will have a prefix under it will be accessed, a body (the code that will be inserted) and the description of the snippet.

This is a powerful feature of VSC and can avoid repetitive code writing that can be quite tedious after a while.

More info about this feature of VSC can be found here:

https://code.visualstudio.com/docs/editor/userdefinedsnippets#_create-your-own-snippets

SQLite vs MySQL - foreign key checks

Sqlite does not check for foreign key integrity when creating tables, only when inserting records. However, MySQL does check in both cases.

To make this more clear, let's take a look at a simple example: given two tables - todos and users - created in this exact order:

 Schema::create('todos', function (Blueprint $table) {
      $table->id();
      $table->string('title');
      $table->unsignedBigInteger('user_id');
      $table->foreign('user_id')
          ->references('id')
          ->on('users');
  });
 Schema::create('users', function (Blueprint $table) {
      $table->id();
      $table->string('username');
      $table->string('password');
  });

If you run your migrations using the sqlite driver, everything works just fine.

However, if you run your migrations using the mysql driver, you get the following error:

SQLSTATE[HY000]: General error: 1005 Can't create table `[your-app]`.`todos` 
(errno: 150 "Foreign key constraint is incorrectly formed") 
(SQL: alter table `todos` add constraint `todos_user_id_foreign`
 foreign key (`user_id`) references `users` (`id`))

Folder aliases using webpack

In order to define a folder alias using webpack, all you have to do is write the following in webpack.config.js:

const path = require('path');

module.exports = {
  ...
  resolve: {
    alias: {
      '@': path.resolve('src'),
      images: path.resolve('assets/images'),
    },
  },
};

Now , if you want to import something from assets/images, all you need do to is

import image from 'images/[path]'

regardless of the current directory.

If the location of any of the folders defined with aliases ever changes, you no longer need to update all the imports - changing the path in webpack.config.js will take care of everything.

Extracting closures to their own classes

Laravel has a lot of functions that accept a closure as a callback. But sometimes the callback function is just too large to be done inline - so our becomes very unreadable.

Let's take a look at an example of using such a function:

class MyScope
{
    public function apply(Builder $query, Model $model)
    {
        return $query->whereHas(
            'relationship',
            function (Builder $query) {
                //really long function
            }
        );
    }
}

If you are used to writing Javascript you might first attempt something like this in order to refactor:

class MyScope
{

    public function callback(Builder $query)
    {
       //really long method
    }

    public function apply(Builder $query, Model $model)
    {
        return $query->whereHas(
            'relationship',
            $this->callback
        );
    }
}

Here we extracted the callback to a method and we attempt to pass it as a parameter instead of writing the closure inline.

Unfortunately, this would not work and we would get the following error:

Undefined property: App\Scopes\MyScope::$callback

One valid approach would be to have our method return the function we need instead:

class MyScope
{

    public function callback()
    {
       return function (Builder $query) {
          //really long function
       }

    }

    public function apply(Builder $query, Model $model)
    {
        return $query->whereHas(
            'relationship',
            $this->callback()
        );
    }
}

This would do just fine, but we can take this one step further by making use of a powerful PHP feature - invokable classes:

class InvokableClass
{
    //this method will automatically be called and
    //passed the query parameter when the time comes
    public function __invoke($query)
    {
        //really long method
    }
}

This approach is very beneficial because it also allows us to break up the long method into smaller, more understandable pieces (because an invokable class is still a class and we can take full advantage of it) so we are not just sweeping the unreadable code under the rug.

We can now use our freshly defined invokable class in the following way:

class MyScope
{
    public function apply(Builder $query, Model $model)
    {
        return $query->whereHas(
            'relationship',
            \Closure::fromCallable(new InvokableClass())
        );
    }
}

Please notice that we called Closure::fromCallable() first - this is necessary because we have to convert our class to a closure to avoid getting an error such as:

Argument 2 passed to Illuminate\Database\Eloquent\Builder::whereHas() must be an instance of Closure or null, instance of App\InvokableClass given

Conditionally loading packages based on environment.

Based on the environment of the project (production or development), there is a way to load packages conditionally. We may want to load packages conditionally to keep an easier to run development server, for instance.

In order to achieve this, we may use the following technique, if the used packages do not already feature a parameter to be used accordingly to the environment.

For instance we may provide an APP_ENVIRONMENT parameter in the .env file of the project. Based on the value of the parameter, we may load or not a package.

.env

APP_ENVIRONMENT=development

nuxt.config.js

let modules = {
  "@nuxtjs/axios",
}

if (process.env.APP_ENVIRONMENT !== "development")
  modules.push("@nuxtjs/component-cache")