Adventures of a Front-End Developer - Migration of Front-End Project

Mustafa Dalga
Mustafa Dalga

Table of Contents

In this blog post I will explain how I upgraded our Heybooster front-end project which is built on the vue.js framework and what motivated me to do so.

In our previous project we used the vue version of 2.5.11 and the webpack version of 3.6.0 and we had some problems with it.

Besides the above issues, I had the following goals:

  • To improve development environment
  • To improve web performanse
  • Desire to use Typescript
  • Desire to write text with vitest and cypress

I attempted to upgrade webpack and vue almost ten times after I joined Heybooster, but many problems prevented me from upgrading it. A year and a half later I decided to upgrade the project using a different approach: By creating a new vue project and moving the files from the old to the new.

Now I`m going to explain how I did the migration process.

First, I created a new vue.js project with typeScript, vite and Cypress options.I deleted all unnecessary settings and files after installing the project. I rewrote the index.html file of the old project in the index.html file of the new project.

In our new project, I rewrote webpack.config.js configurations in vite.config.js.

import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

interface IAssetFileNames {
  [key: string]: any;
}

interface IDefineConfig {
  command: string,
  mode: string
}

// https://vitejs.dev/config/
export default defineConfig(({ command, mode }: IDefineConfig) => {

  const configs = {
    plugins: [vue()],
    resolve: {
      alias: {
        "@": fileURLToPath(new URL("./src", import.meta.url)),
      },
    },
    server: {
      strictPort: true,
      port: 8080,
      host: "::", //'0.0.0.0',
    },
    build: {
      rollupOptions: {
        output: {
          assetFileNames: (assetInfo: IAssetFileNames) => {
            const extType: string = assetInfo.name.split(".")[1];
            const exceptionWords = [ "google", "facebook", "shopify", "amazon", "adword", "adwords", "pinterest", "slack" ];

            if (exceptionWords.some(word => assetInfo.name.includes(word))) {
              return `assets/${extType}/[hash][extname]`;
            }

            return `assets/${extType}/[name][extname]`;
          },
          chunkFileNames: "assets/js/[name]-[hash].js"

        }
      }
    },
    esbuild: {}
  }

  if (mode == "production") {
    configs.esbuild = {
      drop: [ "console", "debugger" ]
    };
  }

  return configs;
});
vite.config.js
var path = require('path');
var webpack = require('webpack');
const dotenv = require('dotenv');

function envVariables() {
  const env = dotenv.config().parsed;

  return Object.keys(env).reduce((newObject, key) => {

    newObject[key] = JSON.stringify(env[key]);

    return newObject;

  }, {});

}

module.exports = {
  entry: [ "core-js/modules/es.promise", "core-js/modules/es.array.iterator", './src/main.js' ],
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'build.js',
    chunkFilename: './js/[name].bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
      },
      {
        test: /\.scss$/,
        use: [ 'style-loader', 'css-loader', 'sass-loader' ]
      },
      {
        test: /\.sass$/,
        use: [ 'style-loader', 'css-loader', 'sass-loader?indentedSyntax' ]
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name(resourcePath,resourceQuery) {
            const exceptionWords = [ "google", "facebook", "shopify", "amazon", "adword", "adwords", "pinterest", "slack" ];
            const filename = path.parse(resourcePath).name.toLowerCase();

            if (exceptionWords.some(word => filename.includes(word))) {

              return 'images/[contenthash].[ext]';

            }

            return 'images/[name].[ext]';
          },
        }
      },
      {
        test: /\.(woff(2)?|ttf|eot)$/,
        loader: 'file-loader',
        options: {
          name: 'fonts/[name].[ext]?[hash]'
        }
      }
    ]
  },
  externals: {
    moment: 'moment'
  },
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js',
      '@': path.resolve('src')
    },
    extensions: [ '*', '.js', '.vue', '.json' ]
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true,
    overlay: true
  },
  performance: {
    hints: 'warning',
    maxEntrypointSize: 2500000,
    maxAssetSize: 2500000,
  },
  devtool: '#source-map',
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      children: true,
      // (use all children of the chunk)

      async: 'common',
      // (create an async commons chunk)

      minChunks: 3,
      // (3 children must share the module before it's separated)
    }),
    new webpack.DefinePlugin({
      'process.env': envVariables()
    }),
  ]
};

if (JSON.parse(envVariables().MODE) == "PRODUCTION") {

  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    //https://github.com/webpack/webpack/issues/3216#issuecomment-257347714

    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false,
        drop_console: true
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true,
    }),
  ]);
}
webpack.config.js

I have installed the new versions of npm packages, which we are using in the project . I tested the newer versions to see if they worked. While testing these packages, I took note of minor changes/breakings for refactoring our codes used within the project.

I moved the components, views, layouts, assets, plugins, store etc files from the old project to the new one.

Importing extension-less Vue components

In the previous project, we used the webpack , so we didn’t have to use the .vue extension when importing vue components. However, by starting to use vite.js , we have to use the .vue extension when importing vue components. As a result, I have added the .vue extension to all the components that were imported without the .vue extension

Importing extension-less Vue components

Env Variables

In the previous project, we could access env variables with process.env[VARIABLE_NAME]. But in the new project, by starting to use vite.js ,we access the env variables with import.meta.env[VITE_VARIABLE_NAME].

So, I have added the VITE_ prefix to the beginning of all variables in the .env file and have changed all the codes in the project starting with the process.env prefix to import.meta.env.VITE_

Importing SCSS Files

I was adding ~ alias to the path of SCSS files when I imported them. As new project does not require this, I have deleted all the aliases beginning with ~ in the project.

Fixing Deprecated Codes

  • Deprecated Hooks

In Vue 3, the destroyed hook has been changed to unmounted , and the beforeDestroy hook has been changed to beforeUnmount. I changed the deprecated hooks used in the project as follows.

beforeUnmount
unmounted
  • Deprecated Slash(/) as Division in SASS

With the upgrade of the SASS version, the slash as a division has been deprecated. As a solution, I changed the codes where slashes (/) are used for division.

  • Deprecated Deep Selectors

In Vue 2 , we use the /deep/ selector to apply scoped styles to child components. However, with Vue 3, the /deep/ selector has been deprecated and :deep() has replaced it.

:deep(inner selector)
  • Passing params that are not defined as part of the path in vue-router

We were sending some data as params on the router, but since params that are not defined in the path are not supported in the updated vue-router version, we refactored these codes and kept the data in vuex.

  • Dynamic Assets URL

In Vue 2, we used the Require module loader to load dynamic assets. With the introduction of vite.js in vue 3, this method has been deprecated.

I created a helper function to load dynamic assets with vite.js. I have replaced the require module loader with this helper function.

function getImageUrl(type, file) {

  let folder = "";
  if (type == "image") {
    folder = "images";
  } else if (type == "icon") {
    folder = "icons";
  }

  return new URL(`../../assets/${folder}/${file}`, import.meta.url).href;
}
get Image Url
  • Removed APIs — $children

Since the $children API was deprecated in vue 3, I refactored the code.

  • Removed APIs — $set

We were using the Vue $set api for dynamically created object keys to be reactive. But with Vue 3, They are no longer required with proxy-based change detection. So , I refactored code as follow.

Lazy loading components

Since lazy loading components have been changed in Vue 3, I used the defineAsyncComponent function to load component as a lazy loading component.

Fixing the codes of npm packages that are not working

In the new project, some codes were deprecated as a result of the upgrade of npm packages. I resolved the issue by replacing all code that used deprecated third party packages.

Rewrite of plugins

Our plugin we created with Vue 2 didn’t work with Vue 3, so I have re-written it with Vue 3.

import axios from "axios";
import store from "@/store";

const Constants = {
  install(Vue) {
    Vue.prototype.$constants = (key) => {

      const constantList = {
        $http: axios.create({
          baseURL: store.getters.getApiBaseUrl,
        }),
        axios: axios.create({
          baseURL: store.getters.getServerlessApiBaseUrl,
        }),
      }

      return constantList[key];
    }
  }
}

export default Constants;
Plugin in Vue 2
import axios from "axios";
import { store } from "@/store";


export default {
  install: (app) => {

    // constants list
    const constants = {
      $http: axios.create({
        baseURL: store.getters.getApiBaseUrl
      }),
      axios: axios.create({
        baseURL: store.getters.getServerlessApiBaseUrl
      })
    };


    app.config.globalProperties.$constants = (key) => {
      return constants[key];
    };

    app.provide("$constants", constants);
  }
};
Plugin in Vue 3

Configure of Store — Vuex

We use Vuex as the state management library. There have been some minor changes in the new version of Vuex.

To apply the changes:

  • I changed our store creation code with createStore.
  • I imported and installed the store.
  • In Vue 2, we needed to access the Vue instance in order to use the plugins in Vuex. In Vue 2, we were using the constants plugin in Vuex as follows.
  • This method has been changed with Vue 3. Therefore, I configured the plugin instance in the store and I changed the plugin codes we used in the store.
main.ts
store/actions.js

Project size(dist) — New project vs Previous Project

As a result of the migration process, the project size has been reduced by almost 54.88%.


Originally published here

heybooster tech

Mustafa Dalga Twitter

A passionate developer who wants to be a pioneer at what he is doing.