Friday 15 January 2021

Guid for frontend

 So you are working on a front end app and you need a guid; try the following in a powershell terminal


new-guid | set-clipboard

this creates a new guid and copies it to your clipboard; now all you have to do is click ctrl-v in your app and voila you have your guid.

Sunday 10 January 2021

Use json Data in a typescript project

Let's say that you have some json data that you would like to leverage in your project, could be something like config files. well you can import the module like so

    import('@/mockData/fingerRational.json')
      .then(module => this.fingerRational = (module.default as unknownas IFingerRational[]);

However you will get an error saying something like

Module '@/mockData/fingerRational.json' was resolved to 'd:/dev/eDoc_Panels_Frontend/trunk/src/mockData/fingerRational.json', but '--resolveJsonModule' is not used.ts(7042)

the solution is simple you have to modify your tsconfig.json file

{
  "compilerOptions": {
    "target""esnext",
    "module""esnext",
    "strict"true,
    "jsx""preserve",
    "importHelpers"true,
    "resolveJsonModule"true//<----- added for mock data
    "moduleResolution""node",
    "experimentalDecorators"true,
    "skipLibCheck"true,
    "esModuleInterop"true,
    "allowSyntheticDefaultImports"true,
    "sourceMap"true,
    "baseUrl"".",
    "types": [
      "webpack-env",
      "jest",
      "webpack",
      "w3c-image-capture"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

and voilia all should be well

Thursday 7 January 2021

Excel VBA Macro

Recently I had to design a user interface for one of our clients and wasn't certain that we were on the same page; I decided that the absolute best way to ensure we were both speaking kiwis to kiwis and not apples to oranges I decided that mocking a large dataset in excel and use conditional formatting to really demonstrate which records would be filtered and what proportion of the data would be used in the data visualization. 

to get started I first had to enable the developer tab in the ribbon, I did this this by going to the file tab and hitting the options button.


with the Developer tab in place, I created a generate data macro


Now I already have my macro created, but what if i didn't then the create button would be enabled


once you enter in a name for your macro and hit the create button, the VBA environment straight from 2003 will pop right up

Try not to fear this relic; it brings me back to days of Visual basic 6... 

this will be your IDE... under the view dropdown menu you can open your Immediate window were you can do console logs or in this case Debug.Print statements and the locals for debugging variables.

now the thing to remember is that this is a slightly different paradigm than OOP, this is more functional; you have the concept of modules which is sorta kinda like classes, but not really. 

anyway the way I set my macro up is that I create sub procedures that will do things for me, however sub procedures do not return values, there are functions, but I Find that declaring globals that sub procedures manipulate just seems to work better than functions.

so lets get started,

Option Explicit

Dim recordCount As Integer
Dim dimensions As Variant
Dim communities As Variant
Dim radars As Variant

Sub GenerateData()

End Sub

Above I created my main macro "GenerateData" (I realize in the screenshot is says GenderateData, that my dear friends was a typo) and i set up some global variables that my subprocedures will populate,

firstly lets create a private sub procedure that will get the number of records we want to collect and store it in our recordCount global variable. the rational for it being private is that it is useless by itself so as not to pollute the macro list we mark it as private so that it doesn't appear in the list of macros.

Private Sub GetRecordCount()
    recordCount = Application.InputBox("How many records would you like to generate?")
    Debug.Print "Records to create: " & recordCount
End Sub

Here we simply popup a input box and take in an import, this input comes in as a string so in theory we should do some type checking, but this is a tool for developers not idiots business analysts. 


then in our immediate window we will see our debug text


this is a great way of debugging our script.

next let's create a sub Procedure to generate the header for our data.

Sub GenderateHeader()
    Dim i As Integer
    Dim Headers As Variant
    Headers = Array("Id", "Community", "Radar", "Gender", "Age", "Household Size", "Education", "Dimension", "Subdimension", "Value")
    
    For i = 1 To UBound(Headers) + 1
        Cells(1, i).Value = Headers(i - 1)
        Cells(1, i).Font.Bold = True
        Debug.Print "Created '" & Headers(i - 1) & "' Header"
    Next i
End Sub

The above code creates a variable i which we will use to iterate over our array with, and a headers array containing all of the header names we want to print out.

now the for loop; the syntax is a bit quirky but in essence it just takes the variable i and iterates it in this case from 1 to the upperbound index of our header + 1
the reasons we start from 1 and not 0 is because the cells function takes in two numbers, the first is the row and the second is the column; as depicted in the table below

1,1 1,2 1,3
2,1 2,2 2,3
3,1 3,2 3,3

this is why we start with 1 and just subtract one since our array is 0 based;

now lets add these two procedures to our main procedure

Option Explicit

Dim recordCount As Integer
Dim dimensions As Variant
Dim communities As Variant
Dim radars As Variant

Sub GenerateData()
    Call GetRecordCount
    Call GenderateHeader
End Sub

And that is it now if we call our macro we will get the number of records to create and generate a header; we still have no data but at least we know how much data to create. however before we create our data let's populate our global variables, since we will need them to generate our data records. We'll start with radars and communities since they are much easier.

Private Sub PopulateCommunities()
    communities = Array("Bell River", "Lasalle", "Leamington", "Tecumseh", "Windsor")
End Sub

Private Sub PopulateRadars()
    radars = Array("Baseline", "Midline", "Endline")
End Sub

like I said simple, we just create arrays with some static values, no big deal, 

Now for the hard part, now we don't have objects in VBA but we do have modules which can sort of be used in the same way. What we need is a simple object with a name and a list of subdimensions, you could think of it as a jagged array, which may have been an easier to implement this concept however i opted to use a module.


click the insert button and create a class module, I named it c_Dimension; dimension being the domain specific name and c_ denotating that it is a class module. you can modify the name of the module in the properties window of the module itself, if you are wondering why mine isn't called module1

next lets write some code

Dim cName As String
Dim cSubDimensions As Variant

'name properties
Public Property Get name() As String
    name = cName
End Property

Public Property Let name(name As String)
    cName = name
End Property

'subdimension properites
Public Property Get subDimensions() As Variant
    subDimensions = cSubDimensions
End Property

Public Property Let subDimensions(subDimension As Variant)
    cSubDimensions = subDimension
End Property

'initialization
Public Sub Init(name As String, subDimensions As Variant)
    cName = name
    cSubDimensions = subDimensions
    Debug.Print "Created '" & name & "' dimension with " & UBound(subDimensions) & " subdimensions"
End Sub

  • ok so first thing we do is create two private variables to store the name and the subdimensions of the object
  • next we create public get and let property accessors, this lets us read and write to our private properties.
  • then finally we create an init function, this is because modules do not have what we would consider a constructor where you can pass parameters, we could have simply used the let statements, but this is just a preference of mine.

now that we have our "Class Module" built lets go back to our "Module" and create a function that populates our dimensions array with instances of our c_dimension class module. 

we create our populateDimensions sub procedure

Private Sub PopulateDimensions()
    'Health
    Dim Health As c_Dimension
    Set Health = New c_Dimension
    Health.Init "Health", Array("Access", "Knowledge", "Enviroment", "Practice", "Disease Control", "Maternal & Child Health")
    
    'Risk
    Dim Risk As c_Dimension
    Set Risk = New c_Dimension
    Risk.Init "Risk", Array("Early Action", "Climate Change", "Household DDR practices", "Community Preparedness", "Community Risk Reduction", "Maternal & Child Health")
    
    'Wash
    Dim Wash As c_Dimension
    Set Wash = New c_Dimension
    Wash.Init "Wash", Array("Water Access", "Sanitation", "Hygiene", "Safe water", "Water Storage", "Maternal & Child Health")
    
    'Shelter
    Dim Shelter As c_Dimension
    Set Shelter = New c_Dimension
    Shelter.Init "Shelter", Array("Access", "Knowledge", "Practice", "Building Regulations")
    
    'Food
    Dim Food As c_Dimension
    Set Food = New c_Dimension
    Food.Init "Food", Array("Food Availability", "Dietary Diversity", "Coping capacity", "Nutrition", "Current Coping Strategies")
    
    dimensions = Array(Health, Risk, Wash, Shelter, Food)
End Sub

above we manually configure each dimension and then at the end we assign them all to the dimensions array.

now we are done with the populate sub procedure we can call them from our macro 

Option Explicit

Dim recordCount As Integer
Dim dimensions As Variant
Dim communities As Variant
Dim radars As Variant

Sub GenerateData()
    Call GetRecordCount
    Call GenderateHeader

    Call PopulateDimensions
    Call PopulateCommunities
    Call PopulateRadars
End Sub

Now we have all of the data we need to start configuring our records; to do that you guessed it we are going to need another for loop, so lets set that up now

Option Explicit

Dim recordCount As Integer
Dim dimensions As Variant
Dim communities As Variant
Dim radars As Variant

Sub GenerateData()
    Call GetRecordCount
    Call GenderateHeader

    Call PopulateDimensions
    Call PopulateCommunities
    Call PopulateRadars

    Dim i As Integer
    Dim dimensionIndex As Integer
    For i = 1 To recordCount
        'generate ID value
        Cells(i + 1, 1).Value = i
        
        'generate data
        
        'generate value
        Cells(i + 1, 10).Value = Round(Rnd, 2)
    Next i
End Sub

we added a for loop to our main macro sub procedure in this for loop we are going to create sub procedures to generate all of our data. we included the population of the ID column, since it is simple enough and the value couple because again simple enough; we used the same cell function with the (row, column) coordinate system, the Rnd procedure returns a random float between 0 and 1 which we try to round to 2 decimal places (though that doesn't seem to work too great, but i dont care enough to figure out why)

now there is about 8 columns worth of data to create, I am only going to go into detail about two of them. one easy and one difficult

an easy one first, since most of the sub procedures will be at about the same level of difficulty 

Private Sub WriteCommunity(row As Integer, col As Integer)
    Dim index As Integer
    index = Int((UBound(communities) + 1) * Rnd)
    Cells(row, col).Value = communities(index)
End Sub

the above just fills in a random community from our communities array, you simply call it from our main macro sub procedure like so

Option Explicit

Dim recordCount As Integer
Dim dimensions As Variant
Dim communities As Variant
Dim radars As Variant

Sub GenerateData()
    Call GetRecordCount
    Call GenderateHeader

    Call PopulateDimensions
    Call PopulateCommunities
    Call PopulateRadars

    Dim i As Integer
    Dim dimensionIndex As Integer
    For i = 1 To recordCount
        'generate ID value
        Cells(i + 1, 1).Value = i
        
        'write community data to record
        WriteCommunity i + 1, 2
        
        'generate value
        Cells(i + 1, 10).Value = Round(Rnd, 2)
    Next i
End Sub

notice that we call our write community sub procedure with a space, the row and then the column, we offset the row by 1 because of the header.

next lets look at creating our dimensions 

Function WriteDimension(row As Integer, col As Integer) As Integer
    Dim index As Integer
    index = Int((UBound(dimensions) + 1) * Rnd)
    Cells(row, col).Value = dimensions(index).name
    WriteDimension = index
End Function

now this for once is a function that returns a value, however notice that there is no return keyword, instead we use this bizarre "WriteDimension = index" syntax to return a value to our main sub procedure.

Option Explicit

Dim recordCount As Integer
Dim dimensions As Variant
Dim communities As Variant
Dim radars As Variant

Sub GenerateData()
    Call GetRecordCount
    Call GenderateHeader

    Call PopulateDimensions
    Call PopulateCommunities
    Call PopulateRadars

    Dim i As Integer
    Dim dimensionIndex As Integer
    For i = 1 To recordCount
        'generate ID value
        Cells(i + 1, 1).Value = i
        
        'write community data to record
        WriteCommunity i + 1, 2

        'write dimension data to record and return dimension index 
        dimensionIndex = WriteDimension(i + 1, 8)

        'generate value
        Cells(i + 1, 10).Value = Round(Rnd, 2)
    Next i
End Sub

the reason we return the dimension index is so that we can pass it to the subdimension write function and we can get the corresponding index to populate the subdimension column with the correct values.

Function WriteSubdimension(row As Integer, col As Integer, dimIndex As Integer)
    Dim index As Integer
    Dim dimension As c_Dimension
    Set dimension = dimensions(dimIndex)
    
    index = Int((UBound(dimensions(dimIndex).subDimensions) + 1) * Rnd)
    Debug.Print "Dimension: " & dimension.name
    Debug.Print "Subdimension: " & dimension.subDimensions(index)
    
    Cells(row, col).Value = dimension.subDimensions(index)
End Function

the important takeaway from this function is that you do not try something like dimensions(1).subDimensions, because it will not work, you really have to create a reference to the dimension then access the subdimensions through that reference.

and of course we have to call the writeSubdimension sub procedure from our main macro 

Option Explicit

Dim recordCount As Integer
Dim dimensions As Variant
Dim communities As Variant
Dim radars As Variant

Sub GenerateData()
    Call GetRecordCount
    Call GenderateHeader

    Call PopulateDimensions
    Call PopulateCommunities
    Call PopulateRadars

    Dim i As Integer
    Dim dimensionIndex As Integer
    For i = 1 To recordCount
        'generate ID value
        Cells(i + 1, 1).Value = i
        
        'write community data to record
        WriteCommunity i + 1, 2

        'write dimension data to record and return dimension index 
        dimensionIndex = WriteDimension(i + 1, 8)

        'write subdimension data to record
        WriteSubdimension i + 1, 9, dimensionIndex

        'generate value
        Cells(i + 1, 10).Value = Round(Rnd, 2)
    Next i
End Sub

Finaly here is the full module followed by the class moduel

start module
Option Explicit

Dim recordCount As Integer
Dim dimensions As Variant
Dim communities As Variant
Dim radars As Variant

Sub GenerateData()
    Call GetRecordCount
    Call GenderateHeader
    Call PopulateDimensions
    Call PopulateCommunities
    Call PopulateRadars
    
    Dim i As Integer
    Dim dimensionIndex As Integer
    For i = 1 To recordCount
        'generate ID value
        Cells(i + 1, 1).Value = i
        WriteCommunity i + 1, 2
        WriteRadar i + 1, 3
        WriteGender i + 1, 4
        WriteAge i + 1, 5
        WriteHouseholdSize i + 1, 6
        WriteEducation i + 1, 7
        dimensionIndex = WriteDimension(i + 1, 8)
        WriteSubdimension i + 1, 9, dimensionIndex
        
        'generate value
        Cells(i + 1, 10).Value = Round(Rnd, 2)
    Next i
    
    Call AddConditionalFormating
End Sub

Private Sub WriteCommunity(row As Integer, col As Integer)
    Dim index As Integer
    index = Int((UBound(communities) + 1) * Rnd)
    Cells(row, col).Value = communities(index)
End Sub

Private Sub WriteRadar(row As Integer, col As Integer)
    Dim index As Integer
    index = Int((UBound(radars) + 1) * Rnd)
    Cells(row, col).Value = radars(index)
End Sub

Private Sub WriteGender(row As Integer, col As Integer)
    If (Int(3 * Rnd) Mod 2 = 0) Then
        Cells(row, col).Value = "M"
    Else
        Cells(row, col).Value = "F"
    End If
End Sub

Private Sub WriteAge(row As Integer, col As Integer)
    Dim age As Integer
    age = Int(100 * Rnd)
    Cells(row, col).Value = Switch(age < 18, "Youth", age < 66, "Adult", age < 100, "Senior")
End Sub

Private Sub WriteHouseholdSize(row As Integer, col As Integer)
    Dim size As Integer
    size = Int(6 * Rnd)
    Cells(row, col).Value = Switch(size = 1, "Single", size < 6, "Medium", size > 5, "High")
End Sub

Private Sub WriteEducation(row As Integer, col As Integer)
    Dim years As Integer
    years = Int(50 * Rnd)
    Cells(row, col).Value = Switch(years < 6, "Basic", years < 11, "Normal", years > 10, "High")
End Sub

Function WriteDimension(row As Integer, col As Integer) As Integer
    Dim index As Integer
    index = Int((UBound(dimensions) + 1) * Rnd)
    Cells(row, col).Value = dimensions(index).name
    WriteDimension = index
End Function

Function WriteSubdimension(row As Integer, col As Integer, dimIndex As Integer)
    Dim index As Integer
    Dim dimension As c_Dimension
    Set dimension = dimensions(dimIndex)
    
    index = Int((UBound(dimensions(dimIndex).subDimensions) + 1) * Rnd)
    Debug.Print "Dimension: " & dimension.name
    Debug.Print "Subdimension: " & dimension.subDimensions(index)
    
    Cells(row, col).Value = dimension.subDimensions(index)
End Function

Private Sub PopulateCommunities()
    communities = Array("Bell River", "Lasalle", "Leamington", "Tecumseh", "Windsor")
End Sub

Private Sub PopulateRadars()
    radars = Array("Baseline", "Midline", "Endline")
End Sub

Private Sub PopulateDimensions()
    'Health
    Dim Health As c_Dimension
    Set Health = New c_Dimension
    Health.Init "Health", Array("Access", "Knowledge", "Enviroment", "Practice", "Disease Control", "Maternal & Child Health")
    
    'Risk
    Dim Risk As c_Dimension
    Set Risk = New c_Dimension
    Risk.Init "Risk", Array("Early Action", "Climate Change", "Household DDR practices", "Community Preparedness", "Community Risk Reduction", "Maternal & Child Health")
    
    'Wash
    Dim Wash As c_Dimension
    Set Wash = New c_Dimension
    Wash.Init "Wash", Array("Water Access", "Sanitation", "Hygiene", "Safe water", "Water Storage", "Maternal & Child Health")
    
    'Shelter
    Dim Shelter As c_Dimension
    Set Shelter = New c_Dimension
    Shelter.Init "Shelter", Array("Access", "Knowledge", "Practice", "Building Regulations")
    
    'Food
    Dim Food As c_Dimension
    Set Food = New c_Dimension
    Food.Init "Food", Array("Food Availability", "Dietary Diversity", "Coping capacity", "Nutrition", "Current Coping Strategies")
    
    dimensions = Array(Health, Risk, Wash, Shelter, Food)
End Sub

Private Sub GetRecordCount()
    recordCount = Application.InputBox("How many records would you like to generate?")
    Debug.Print "Records to create: " & recordCount
End Sub

Sub GenderateHeader()
    Dim i As Integer
    
    Dim Headers As Variant
    Headers = Array("Id", "Community", "Radar", "Gender", "Age", "Household Size", "Education", "Dimension", "Subdimension", "Value")
    
    For i = 1 To UBound(Headers) + 1
        Cells(1, i).Value = Headers(i - 1)
        Cells(1, i).Font.Bold = True
        Debug.Print "Created '" & Headers(i - 1) & "' Header"
    Next i
End Sub

Private Sub AddConditionalFormating()
    Cells.FormatConditions.Delete
    Dim range As String
    Cells.FormatConditions.Add Type:=xlExpression, Formula1:= _
        "=AND(OR($B1=""Bell River"";$B1=""Tecumseh"";$B1=""Windsor"");OR($C1=""Baseline"";$C1=""Midline""); $F1=""Medium""; $G1=""High"")"
    Cells.FormatConditions(Selection.FormatConditions.Count).SetFirstPriority
    With Selection.FormatConditions(1).Interior
        .PatternColorIndex = xlAutomatic
        .Color = 49407
        .TintAndShade = 0
    End With
    Cells.FormatConditions(1).StopIfTrue = False
End Sub

end module

start class module

Dim cName As String
Dim cSubDimensions As Variant

'name properties
Public Property Get name() As String
    name = cName
End Property

Public Property Let name(name As String)
    cName = name
End Property

'subdimension properites
Public Property Get subDimensions() As Variant
    subDimensions = cSubDimensions
End Property

Public Property Let subDimensions(subDimension As Variant)
    cSubDimensions = subDimension
End Property

'initilisation
Public Sub Init(name As String, subDimensions As Variant)
    cName = name
    cSubDimensions = subDimensions
    Debug.Print "Created '" & name & "' dimension with " & UBound(subDimensions) & " subdimensions"
End Sub

end class module

Tuesday 5 January 2021

Create a "Simple" webpack project

If you ever find a developer hiding in a closet head in hands and tears running down his or her face, you can be sure that webpack did it to them. Web pack is this omnipresent monster that most front-end developers don't know is lurking in the shadows, so close that it can touch you, so near that it can feel your heartbeat. well in this post we are going to pull out that flashlight and shine a light under the bed to expose webpack for the pussy cat it is rather than the hound of hell it's been made out to be.

let's start by creating a directory for our project, i went with pav.wpbase because i go by pav and wpbase stands for "winnie the pooh bear abolishes sour elephants"... no wait it stands for webpack base

mkdir pav.wpbase


any way next enter your directory and initialize a npm project

cd pav.wpbase

npm init

once you initialize your npm project you'll be asked a bunch of config questions, your answers wont really matter but they will be used to initialize your package.json file, the only thing of consequence that i changes was the entry point to the application, however you can always do that after in the actual package.json file.


now with that done if you do a dir of the directory you should have a package.json file

if you open that file it should look very familiar to you


{
  "name""pav.wpbase",
  "version""1.0.0",
  "description""base webpack project",
  "main""src/index.js",
  "scripts": {
    "test""echo \"Error: no test specified\" && exit 1"
  },
  "author""pav",
  "license""ISC"
}

exactly what you entered above during your npm interrogation.

anyway next we obviously need to bring in webpack and a whole bunch of other packages so run the following command

npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin copy-webpack-plugin babel-loader style-loader sass-loader sass  @babel/core @babel/preset-env webpack

now to break that down 

npm i -D install dev dependencies 
   webpack: bundles all of your assets into 
   webpack-cli: lets you use the webpack command line interface ie npx commands
   webpack-dev-server: lets you run a localhost server
   html-webpack-plugin: lets you copy you html file to your dist folder
   copy-webpack-plugin: lets you copy files from your working directory to your dist folder
   @babel/core
   @babel/preset-env webpack 
   babel-loader: lets us write backwards/cross browser compatible code
   css-loader: translates css to common js
   style-loader: Creates style nodes from JS strings
   sass-loader: Complies your scss to css
   sass: lets you write sass instead of basic css

now that those are referenced make sure to run the npm install command this will actually pull down the referenced packages to be used in your project.

now let's take a look at our package.json file one more time


{
  "name""pav.wpbase",
  "version""1.0.0",
  "description""base webpack project",
  "main""src/index.js",
  "scripts": {
    "test""echo \"Error: no test specified\" && exit 1"
  },
  "author""pav",
  "license""ISC",
  "devDependencies": {
    "@babel/core""^7.15.5",
    "@babel/preset-env""^7.15.6",
    "babel-loader""^8.2.2",
    "copy-webpack-plugin""^9.0.1",
    "css-loader""^6.3.0",
    "html-webpack-plugin""^5.3.2",
    "sass""^1.42.1",
    "sass-loader""^12.1.0",
    "style-loader""^3.3.0",
    "webpack""^5.56.1",
    "webpack-cli""^4.8.0",
    "webpack-dev-server""^4.3.1"
  }
}

and as you can see we reference all of the packages that we are going to need for our "simple" webpack project.

next lets create our src folder structure, this is where we are going to code up our website, now this is mostly a matter of preference; there are some conventions that are followed but not religiously, so figure out what works for you are just copy somebody else's and remember you can always refactor.



Now that's the simple structure that I go with.

Next let's create a webpack.config.jsfile at the root of the project (the same level as your src folder), this is where the terror I mean magic happens. so let's start with a very simple implementation, just enough to build a dist folder and create a bundle.js with a index.html page 


const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/scripts/index.js',
  output: {
    path: __dirname + '/dist',
    filename: "bundle.js"
  },
  plugins:[
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    })
  ]
}


Now let's go back into our command line and run the command 
npx webpack build
this command will use our webpack config file to create a dist folder; after running the above command take a look at our project


pretty cool, we now have an index.html based on what we did in our src folder, and a bundle.js that is based on our index.js file and anything that it references. 

now let's start with something simple open up your html in your src folder and add some content.


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>wpbase</title>
</head>
<body>
  <header>
    <h1>Hello world</h1>
  </header>
  <section>section 1</section>
  <section>section 2</section>
  <section>section 3</section>
  <section>section 4</section>
  <section>section 5</section>
  <section>section 6</section>
  <footer>Goodbye world</footer>
</body>
</html>


now let's run our build command again 

npx webpack build and if we open the index.html file in our dist folder we'll see the following




the exact changes we made and if you notice the URL it is in fact being served from our dist folder, now before we continue let's set up a script in our package.json file so that we don't have to use npx


{
  "name""pav.wpbase",
  "version""1.0.0",
  "description""base webpack project",
  "main""src/scripts/index.js",
  "scripts": {
    "serve""webpack serve",
    "test""echo \"Error: no test specified\" && exit 1",
    "build""webpack build"
  },
  "author""pav",
  "license""ISC",
  "devDependencies": {
    "@babel/core""^7.15.5",
    "@babel/preset-env""^7.15.6",
    "babel-loader""^8.2.2",
    "copy-webpack-plugin""^9.0.1",
    "css-loader""^6.3.0",
    "html-webpack-plugin""^5.3.2",
    "sass""^1.42.1",
    "sass-loader""^12.1.0",
    "style-loader""^3.3.0",
    "webpack""^5.56.1",
    "webpack-cli""^4.8.0",
    "webpack-dev-server""^4.3.1"
  }
}


notice the scripts section, we added two more script run and build, these are our scripts that we execute on an npm run <<script name>> so in our cases

npm run build
npm run serve

both commands will work however the latter will not have live update, that is once the server starts it will not update with any changes we make, but let's fix that; back to the webpack.config.js file


const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/scripts/index.js',
  output: {
    path: __dirname + '/dist',
    filename: "bundle.js"
  },
  plugins:[
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    })
  ],
  devtool: 'source-map',
  devServer: {
    static: {                               
      directory: path.join(__dirname'./'),  
      watch: true
    }
  }
}


notice the last two properties in our config, devTool: this will allow for friendlier debugging, so when you you console logs the debugger will let you know the originating js file rather than just the bundled one. as for the second property devServer, this loads your src file into memory and lets your local host serve it via the browser, also the watch property tells our server to update any time our source code changes.

this is huge, this will drastically speed up our workflow.

next let's configure our scss, because who writes basic css anymore. so let's again open up our webpack.config.js file and add a rule


const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/scripts/index.js',
  output: {
    path: __dirname + '/dist',
    filename: "bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // 3 Creates `style` nodes from JS strings
          "style-loader",
          // 2 Translates CSS into CommonJS
          "css-loader",
          // 1 Compiles Sass to CSS
          "sass-loader",
        ]
      }
    ]
  },
  plugins:[
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    })
  ],
  devtool: 'source-map',
  devServer: {
    static: {                               
      directory: path.join(__dirname'./'),  
      watch: true
    }
  }
}


notice the new module section with an array of rules. first we test for files that end in a sass extension if a file ends in a sass extension we then run it through our pipeline, where we start by converting sass to scss then css into common js then finally we move that js into our js bundle

to test this lets create two scss files base.scss and header.scss

in our base.scss 

$primaryColor#F0f;


next in our header.scss

@import 'base';

header{
  h1 {
    color:$primaryColor
  }
}


and finally in our index.js
import "../styles/header.scss";

now for our scss files to be bundled they have to be referenced in our js file. 

make sure to hit ctrl+c to stop your web server and npm run serve to restart it so that your new webpack.config file takes effect.

and voilia we have the following 

we're successfully compiling our styles into our bundle.js file, if you build your project using npm run build you'll find something like the following in bundle.js


// Module
___CSS_LOADER_EXPORT___.push([module.id"h1 {\n  color: #F0f;\n}"
"",{"version":3,"sources":["webpack://./src/styles/header.scss",
"webpack://./src/styles/base.scss"],"names":[],"mappings":
"AAEA;EACE,WCHa;ADEf","sourcesContent":["@import 'base';\r\n\r\nh1 
{\r\n  color:$primaryColor\r\n}\r\n","$primaryColor: #F0f;\r\n"],
"sourceRoot":""}]);

 
which is a js compiled version of our scss

now for the final step let's include our babel to make our code backwards compatible, again you guessed it back to the webpack.config.js file


const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/scripts/index.js',
  output: {
    path: __dirname + '/dist',
    filename: "bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          // 3 Creates `style` nodes from JS strings
          "style-loader",
          // 2 Translates CSS into CommonJS
          "css-loader",
          // 1 Compiles Sass to CSS
          "sass-loader",
        ]
      }
    ]
  },
  plugins:[
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    })
  ],
  devtool: 'source-map',
  devServer: {
    static: {                               
      directory: path.join(__dirname'./'),  
      watch: true
    }
  }
}


and again we added a rule this time to use the babel-loader. next we have to create a .babelrc file at our project root.

{
  "presets": ["@babel/preset-env"]
}

simple enough, 99 times out of 100 this will suffice

and that's it, now our JavaScript is cross browser and backwards compatible to the best of babel's ability.