Sunday 30 January 2022

local mock data for SPA web service

Whenever I build SPA's in vue I use a poor mans IOC pattern, which in principle accomplishes leveraging a mock version of my service that instead of fetching data from a web api leverages local json files, however in practice it has very little to do with Inversion of control, though it could adhere to IOC principles I find my pragmatic approach less hassle. 

I start with creating env files: moq, dev, prod

.env.dev

VUE_APP_ENV=dev
VUE_APP_HEADER_COLOR=#9F1F36

.env.moq

VUE_APP_ENV=moq
VUE_APP_HEADER_COLOR=#FFD200

.env.prod

VUE_APP_ENV=prod
VUE_APP_HEADER_COLOR=

as you can see I also like to add a a color code so that i can easily tell which environment I am currently working on.

next I create a services folder where I keep all my logic that communicates outside my app and a mock_data folder where I keep json files that will represent the responses that I would get from my backend api.


so let's create a simple json file for our mock data, here i have created a openings.json file that contains three chess openings

[
  {
    "description""Sacrifice a pawn for a head start",
    "id""55d86e6f-b043-4544-ae6b-b88ee4ee134b",
    "name""King's gambit",
    "tags": ["kingsPawn""gambit"]
  },
  {
    "description""trade a pawn for central occupation",
    "id""52366eb7-fcad-4b58-9ad3-f73c1585c16a",
    "name""Queens's gambit",
    "tags": ["queensPawn""gambit"]
  },
  {
    "description""Win by oversight",
    "id""e5f7f895-073c-4a24-9df0-d9d65b6ad46a",
    "name""Scholar's mate",
    "tags": ["kingsPawn""dubious"]
  }
]

this would be the payload i would expect from some web api get openings, but for now we will just use local mock data.

**in your tsconfig.json and make sure to add "resolveJsonModule": true to the compilerOptions

next create a openingsService.ts file in which you should define an interface and two services a private mock service and a default prod/dev service. however rather than creating a context and pass that into the service service what i do is use the object.assign function to override my dev/prod services with a mock counter part.

import { IOpening } from "@/models/opening";

export interface IOpeningService {
  getOpeningsAsync: () => Promise<IOpening[]>
}

class MoqOpeningService implements IOpeningService {
  getOpeningsAsync: () => Promise<IOpening[]> = 
    async () => {
      return require<IOpening[]>("@/mock_data/openings.json");
    };
}

export default class OpeningService implements IOpeningService {
  constructor() {
    if(process.env.VUE_APP_ENV === "moq")
      Object.assign(thisnew MoqOpeningService());
  }

  getOpeningsAsync: () => Promise<IOpening[]> = 
    async () => {
      throw new Error("getOpeningsAsync not implemented");
    }
}

notice that in the constructor of the default class there is a check of which environment is running and if it is the "moq" version a new instance of the MoqOpeningService is assigned to the current opening service overriding everything defined in the interface. 

now that is fine and dandy however we have one final caveat, all of your mock data is going to be packaged in your dist folder when you build your project.

To exclude mock files from your build 

firstly you have to add a reference to the webpack npm package 

i used the 4.45.0 version because I was getting exceptions with the latest major 5+ version

npm install webpack@^4.45.0 --save-dev

with webpack install if you haven't already created a vue.config.js file go a head and do that at the root of your project, refer to the project structure screen capture at the start of this post.

//vue.config.js

// eslint-disable-next-line @typescript-eslint/no-var-requires
const webpack = require('webpack')

module.exports = {  
  configureWebpack: {
    plugins: process.env.VUE_APP_ENV == "prod" ? [
      new webpack.IgnorePlugin({
        resourceRegExp: /mock_data\/.*\.json$/,
        contextRegExp: /services$/
      })
    ] : []
  }
}

IgnorePlugin | webpack

now when you build your project your mock data will not be included in your dist folder.


Monday 24 January 2022

Vue.js 3 include webconfig in build

So you pushed your SPA build with Vue.js to Azure and if you refresh anything deeper than your index page you are getting a "resource not available" or something along those lines. 

well my dear friend you need to include a webconfig file in your build, now the problem is that if you include a webconfig in your project that file is ignored in your build. 

before we tackle that problem, let's start with the actual web config.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
    <rewrite>
      <rules>
        <rule name="Main Rule" stopProcessing="true">
                <match url=".*" />
                <conditions logicalGrouping="MatchAll">
                    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                </conditions>
                <action type="Rewrite" url="/" />
            </rule>
        </rules>
    </rewrite>
</system.webServer>
</configuration>

Create a file in your project somewhere and call it web.config

Now let's focus on copying it over to your build result.

to pull this off you will need to install the copy webpack plugin

    "copy-webpack-plugin": "^6.4.1",

ensure that it's version 6.x or at least at the time of this blog post

npm install copy-webpack-plugin@6 --save-dev

next open your vue.config.js file and use your webpack copy plugin to copy your webconfig to your dist folder.

// eslint-disable-next-line @typescript-eslint/no-var-requires
const webpack = require('webpack');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const CopyPlugin = require('copy-webpack-plugin');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');

module.exports = {  
  chainWebpack: config => {
    config
    .plugin('html')
    .tap(args => {
      args[0].title = 'My app name'
      return args
    })
   
   
  },
  configureWebpack: {
    plugins: [
      new CopyPlugin({
        patterns: [
          {
            from: path.resolve(__dirname, "src", "web.config"),
            to: path.resolve(__dirname, "dist",),
          }
        ]
      })
    ]
  }
}

and voila, npm run build and you should see your web.config file in your dist folder.