- So, to get started with this project,
I've just moved all the before code into this after folder,
so we have a place we can work with it.
And as I mentioned, since we're using vite,
we could either look at the documentation on everything
we need to know about vite and TypeScript
or sometimes the easier path is just create
a TypeScript project in vite and then figure out
what the differences are between your project
and that project.
That's what I'm going to do.
I'm just gonna create a brand new tab here,
and I'm gonna create vite project in just a random folder.
We'll say NPM create vite at latest.
And it doesn't matter what this folder is called,
we'll just call it test.
And that's going to create that vite project.
And we want this to be a TypeScript project.
Now, what we can do if we just minimize this down,
we don't even need that terminal tab anymore.
so we can look in this folder and you can see, okay,
we have this TS config that we need to worry about.
We have this package JSON,
which is generally where most of your changes are gonna be.
So, we have this build step, which is different,
and TypeScript as a dev dependency.
And we also have here inside this source folder
some different code inside of here.
So, that's what I wanna make sure I add.
I add this as a dev dependency,
I change my build and I make sure that I move all my code
to the source folder and make sure I deal with this JSON.
Now, we don't have to move the code to the source folder,
but I generally find it easier to work with a source folder,
so that's what we're gonna do.
So, first off, we're gonna create a source folder.
I'm gonna take my script, move it in there,
take my styles, move it in there,
and this minesweeper file and move it in there.
And now, obviously, we have a bunch of errors on our page.
That's perfectly okay for now.
Then, what we wanna do is we wanna copy over our TS config
into this after folder, so we have the exact same config.
We could make changes here if we need it,
but for our purposes we're just gonna leave it
completely as is.
Then what we want to do is we want to install TypeScript,
so we can say NPMI save dev TypeScript,
there we go.
That will we have TypeScript inside of our project.
If we look at our package JSON,
we can now see that we have TypeScript as a dev dependency.
And finally, we just wanna make sure our build command
looks exactly the same.
So, we'll just copy over this just like that,
and that should be all we need to do to actually get
TypeScript working inside of our project.
And the next step is to get our project working again,
since we moved all these files to this source folder
inside of here, we need to make sure
that we reference them from this source folder.
There you go, you can now see we have our code
and it's still working, which is great.
If you convert over to TypeScript,
all your codes should still work
because obviously, converting to TypeScript
doesn't actually change the functionality
or implementation of your code.
The first step I recommend doing is once you have
everything converted over, so your project is using
and compiling TypeScript, make sure that everything
still works, and in our case,
as you can see, everything is still working,
which is a really good sign.
And the next step is to rename things
to actually use TypeScript.
And you can either use the top down approach
or the bottom up approach.
Generally, I like to use the bottom up approach
because it's a lot easier to type some of these simpler,
smaller functions, but it's entirely up to you.
In our case, I'm going to rename this Minesweeper file
because this is the one that has like,
most of our small functions and all of our Minesweeper stuff
is dependent on by this file right here.
So, that's what I'm gonna do,
and I'm gonna rename this Minesweeper file
to add TS at the end of it.
And that's how you go about adding typescripts.
You just rename your JavaScript files one by one,
to be TypeScript, 'cause by default,
we're not type checking any of these JavaScript files.
So, everything is working just fine.
And now, we converted this over to TypeScript.
We're getting tons of errors inside of here
and also we need to make sure up here this import
says .ts instead of .js.
So, our code should still work.
As you can see, everything is still working
just as it did before.
That's a good sign just by changing things to be .ts,
nothing in your code should change.
Now, we have tons of errors because we don't have types
on pretty much anything, that's going to be what we work on.
And first, I'm gonna work on our imports
'cause as you can see we're importing load dash,
but we need to make sure we have the types for that.
So, I'm just going to import the types
for low dash really quick.
So we can just do that.
That shouldn't take too long.
And then, we'll rerun our dev server.
As you can see that at least gave us our types
and got rid of that one error.
Now, the next step is going to be going ahead
and doing this from the bottom up approach.
So, what I like to do is I like to look at
the functions that have no dependencies at all.
This function has dependencies.
You can see it's calling this position match function.
So let's just go ahead and look at this function
and see what it does.
As you can see, this is a relatively simple function.
We have an A variable with an X property,
a B variable with an X property,
A variable with Y and a B variable with Y.
So, both of these properties have an X and a Y coordinate.
And probably what we want to do is create
a separate type for this because if you kind of look at
the rest of this code, you can see here's X
and Y being used.
You can see there's lots of other places
where we're using X and Y.
So, it kind of makes sense that this should be a type
and I like to put my types at the top.
So, we're gonna come up here,
we're gonna create a type,
we're gonna call it position.
We're gonna say it's an X and it's going to be a Y.
And right now I don't know what these X and Y's are
just by looking at my code.
So, I'm just gonna type them as any.
This is kind of like the first step.
A lot of times you don't know what things are,
so you just type them as any to get started
and then you'll make it more specific as you go.
So, we'll come down here and we'll add that in as our type.
So, A is gonna be this position
and B is gonna be this position
because right now I don't know what this equals
is actually comparing for these different things.
Now, if I want to try to figure out what that does,
a good idea would be to look at the rest of my code
and kind of see how this is used.
So, if we go back to where this position match
was being used, you can see it's being used here
with some X and Y information.
And you'll notice really importantly that I actually get
some information about what X and Y are.
As you can see they're the second property to calling.map.
If I call this on an array,
the second property is always the index,
which is a number.
So now I know that X and Y are probably numbers just because
I assume that this row and this board
probably refer to an array.
So what I can do is just assume they're numbers.
Also, same thing here,
I'm using them inside of like this array syntax.
So it's a really good indicator to me that these are numbers
and if you wrote this code yourself,
you'd probably know that these yes should be numbers.
So let's go ahead and make these numbers.
And now what we can do is again,
we can keep going from the bottom up
and look at some of these other smaller functions.
For example,
let's look at this check lose function.
This takes in a board property and what it does
is it has a board that it's going to do some looping over.
So this is an array and it gives us another array
which has this tile information.
So it kind of has a lot of stuff going on in it.
So let's figure out where this function's used to figure out
what this board property actually refers to.
So we'll go to check loose,
you can just do that control click
and then double click in here to go directly
to where it's at.
In our case,
we wanna figure out where it's actually being used at.
And as you can see, it's being used inside this script file.
So maybe this isn't a good choice because right now we're
focusing more on our mine sweeper file.
So we're gonna skip that one for now and move on to a
different function maybe.
Let's try and go ahead and we know that position match
is fine so we can find places where this is being used.
As you can see it's being used up here and this create board
function as well as being used inside of this tile function.
So let's just go to the very top
at the create board function.
So we have here this create board.
You can see it takes in a board size
and it takes in mine positions
so we can kind of figure out exactly what these
functions return for us.
If we look at times here,
you can see that times expects us to pass in a function
as the first property and the second property
expects us to pass in a number.
So we know that this board size is probably a number.
And the really nice thing about using VS code
and TypeScript is if you hit control period
or on a Mac it be command period,
you can see you get some quick fixes
and you can actually infer the types from the usage
And TypeScript will look at your code and say,
okay, based on how you're using this,
this is what I think the type is going to be.
So if we click on that,
you can see it's inferring these different parameters for us
and it's saying, "Hey, you know what?
This board is probably a number
based on how you're using it."
And it's saying this mine position is essentially
going to be an array with this position thing going on.
It doesn't quite know what this mine position is.
Well we can figure out a little bit better what mine
positions is because right now where mine positions
is being used,
we can see it's an array because it's using that dot sum
property which comes from arrays and it's calling position
match and it's using the bind syntax,
which is a little bit confusing.
So I'm gonna actually rewrite this so it doesn't use bind
just so we can see exactly what this does.
So we have our position that we're getting.
So this is like our mine position.
There we go.
And then we're calling position match,
make sure we return this position match and the first
property we're passing into this is going to be our mine
position and then we're passing this X Y property into it
and make sure close everything off.
There we go.
So this is what this looks like for us.
We're passing this mine position
into this position match function.
We know position X accepts two positions.
So our mine position must just be an array of positions.
So we can say that this is a position array
and as you can see, that is working fine.
And again, if you weren't quite sure
what this mine position was,
you could go to this create board,
which is used inside this script here.
We could kind of look at where it's called and we could see,
okay, it's calling get mind positions.
And if we look into that you can see it's just returning to
us a bunch of different X and Y positions.
You can see that that is what this thing is doing
if you need to go a little bit further.
But just by looking at our code,
we were able to determine essentially
what this thing was doing.
And when we rewrote this to not use bind,
it was a little easier to understand.
Now this create board function is entirely typed force.
We can minimize that to kind of know
that we're done with that.
Same thing down here where we have that position matched,
it's entirely typed.
So let's minimize that down so we know it's done.
Now if we go back to create board,
you'll notice we have references to board everywhere
and I assume that our board is the same type
as the thing that we return from create board.
Just by looking at my code,
knowing how this code is written,
I know that that is a fact.
I know that this should probably have a specific type to it.
What we do, if we look at this code real quick,
you can see that our board is represented by
an array of arrays.
That's what this double times is doing.
It's returning to us an array of arrays.
And as you can see,
the actual inferred return type is an array of arrays
and each element in the array
is an X and Y coordinate as well
as a Boolean for if it's a mine and a status a string.
So we should probably type out this information.
So we need to type out what our board is.
We also need to create a type for the thing
that's in our board,
which in our case is called a tile.
That's kind of the naming we used in this code.
So a tile here is this right here.
It has an X and Y property,
a mine Boolean and a status.
So we know that it's going to be an X and Y property
just like our position.
So it's actually going to extend our position by
intersecting these two types.
So it's going to be a position and something else.
So we have mine, which is a Boolean,
and we have a status and that status
is going to be some type of string.
Now if we look at this tile,
you could see we have our position
and then it's combining these together.
And our board is just going to be taking this
and we want an array of arrays of these,
essentially a matrix of these different elements.
Now I can specify that this is going to return to us a board
object and this is going to help us in our code,
because right now, it's inferring the return type.
As you can see,
it's inferring this specific return type.
I'm going to be explicit in what this is returning
just so that the rest of our code knows
that this is returning a board
and it'll help with some of our type safety
going down in the future.
Now another thing to look at is our statuses
are not just a string.
Instead they are one of these four different types
and this looks a lot like an e-num to you
or maybe something like an array of as const values.
The reason that this is written as an object
is because I wanted to have auto complete
when I wrote this code in JavaScript
and to make sure that I had everything set up correctly.
The only way to do that is to create an object with keys
that link to different strings.
But with TypeScript,
I don't need to take this extra step,
I can just use a type of tile statuses
and it's going to do all this for me.
So I can actually come up here,
I'm gonna create an array.
So we'll say const,
tile statuses and instead of doing an object,
I'm gonna convert this to an array
of these different values.
So we're gonna have hidden,
we're gonna have mine,
we're gonna have number,
and we're gonna have marked.
Then what I'm gonna do is I'm gonna export this
'cause it looks like this is exported as well.
And then I'm gonna set this as a const variable
'cause I can use that to actually create a type
which is going to be my status.
So we can say tile status is equal to
and it's going to take getting the type of my tile status.
I want to get the number properties from that.
As you can see,
that gives me a union of all those types
and now I can set my status to that union right here.
So this is a little bit cleaner for me to work with
because now I get this auto complete automatically.
For example down here,
instead of saying tile status.hidden,
I can just type in hidden and you can see
I actually should be getting auto complete
for that if I come in here hidden.
There we go.
That's working just fine.
Now if I move on to the next functions,
you'll really see this in work.
So you can see here we have our markeds tiles count.
It's taking in a board so we can say that
that is a board type.
And now here, we can say that this is a markeds tile
and we get perfect auto complete for that.
Same thing here, we have a board being passed in
and this right here is a position
relatively straightforward based on the types we've created.
This is going to be a hidden one and this one right here
is going to be marked.
There we go.
And one thing that we could do to maybe clean this up a
little bit is I can highlight all instances
of tile status.marked,
just hit Ctrl D a bunch.
That'll select all of them and I can change that
to the text marked.
Same thing here with hidden.
Let's just change all those to hidden.
That'll clean that up a little bit for us,
which is really nice.
So now if we look this mark tile
is actually completely type safe,
which is good.
Now the one thing is this replaced tile isn't type safe yet.
So this may throw some errors when we start to add types
to this function.
Let's look at this.
We have our board here,
we have our position.
The nice thing is since these had good variable names,
it's pretty easy for me to know what these are.
And if I really needed to,
I could look in the code to make sure they are the types
I'm actually typing them as.
And this seems to be a tile based on the name.
So we're gonna go with that.
And it looks like everything in here
is working perfectly fine.
So that's good.
Which means we know that this is type safe as well.
Now we have our reveal tile which looks to be taken
in a board and a position.
So that's pretty straightforward for us.
And now here, you can see we have this tile status as mine.
I can just replace this with the string mine again,
which is great.
Here I can replace this with the string of number,
which is great.
But now I'm running into a new error.
I have this adjacent mines count
that I'm trying to add onto my tile object.
I'm trying to add a property onto my tile object
and this means that most likely my tile
should have this property but it should be optional
because when I originally create them
in this create board function,
you can see they don't have that property
but it looks like sometimes they can have that property.
I'm gonna add that to my tile type here.
I'm gonna make sure it's optional.
And this is a number based on the code
that we were looking at.
If we scroll down you can see that that is working.
Now there's no more errors inside of here,
but I do have an error on this.
The reason for this is because reveal title
is actually a recursive function that calls itself
and returns itself.
So TypeScript can't infer the type of this function
'cause it can't infer a function that calls itself
and returns itself.
As you can see here,
we are returning the results of this reveal tile
as part of our function.
If we look here,
you can see that in all of the instances
where we're returning,
this is returning a new board.
Here, this is returning replace tile
which returns a tile array of arrays,
which is essentially a board here we're returning our board
so we know overall this is just returning to us a board.
So we can make that right there.
That looks like that is working.
Here we're getting a board.
So we can just manually type that and change this to number.
So this string is actually working.
Here same exact thing.
This is a board and this is going to be that mine type.
And then finally we have a board followed by some X and Ys
which are gonna be a position
and it looks like no error showed up.
The really great thing is about TypeScript,
especially if you do the bottom up approach
is that the more of your code that becomes typed,
the easier it is to write the rest of your code.
Because this was a little bit tricky,
there was quite a few different things
we had to change inside of here.
But now you'll see that everything in here
is perfectly typed.
We no longer have any more errors inside of this file.
And when I go over to type my script.js file,
you'll notice that that one's actually relatively easy
to change because of all the changes we made in here.
As you get further up the chain in the bottom up approach,
it becomes easier and easier to type things.
So let's go ahead and change this to a .ts file
and make sure that we import here the .ts file
so that we have our code still working.
As you can see, everything's still working.
Generally I recommend when you're typing things
after you change like one file or one set of code,
make sure your entire project still works.
If you have test, run those tests.
If not, just manually check your code to make sure.
As you can see everything is still working as I expect.
Now let's go into this script file.
You notice we have errors absolutely everywhere
so we need to go ahead and make sure
we fix these different errors.
So let's scroll on down here away and we can see we have
this board element is either null or an element.
And that's 'cause we're using query selectors.
I'm gonna make sure that all these query selectors
just have an exclamation point at the end
to say that they're always going to be there.
That's because I know that in my HTML file,
you can see that these are always going to be there.
Now what I can do is I can determine
what the type of these are.
As you can see this div right here,
we have a span right here.
This is a div.
So our board here is going to be an HTML div element
not dialogue element.
There we go.
Our mines left text.
If we look at that,
that is going to be a span.
So we can come in here and say that that is a span element.
And then finally our subtext.
If we look at the HTML file for that,
you can see that that is a div.
So we can mark this as a div as well.
There we go.
Now we have everything marked at least
and that got rid of a lot of our TypeScript error.
For example, this render function now no longer
has any type errors in it,
this one doesn't.
But here we have tile two element.
This is great.
We already have a tile type.
Let's just add that on there.
We need to make sure we import that though.
So let's export the tile type from here
and we'll import it right into here.
There we go.
Now we are getting a bunch of different warnings
but we can look at what these errors are
and see what they need.
So this one says, "Type number is not assignable
to type string."
That makes sense 'cause this dataset always expects
a string or undefined.
And X right here is a number.
So we need to convert this to a string.
There we go.
So TypeScript is nice 'cause it lets us pick up on
maybe potential subtle bugs that would happen
inside of our code.
So this is going to be great.
So now there's no more errors there.
This text content here is again getting an error
because it's expecting a string and we're passing it
a number or a string.
So you may think,
well we'll just add .tostring to the end of this one
and that'll work.
But we actually are not going to have this work
if we do that.
And first of all,
this is obviously undefined possibly.
So we'll make sure that we check to make sure it exists
before we convert it to a string.
So you may think this works,
if this value is not undefined,
it'll be converted to a string,
otherwise it'll be an empty string.
As you can see,
our code looks like it's working,
but when I click you notice now it's populating
a bunch of numbers zeros instead of an empty string.
And the reason that's happening here
is because our adjacent mine count is set to zero
and this is just saying an or equals sign.
So what we had before like this,
if adjacent mine count was zero,
it would use this empty string here,
which we still want to make sure we enforce that behavior.
Now I wanna do essentially a check to see if it's undefined
or zero then use this empty string.
Otherwise don't.
So if the adjacent mine count is actually there,
we'll do a simple turnery.
We can say that we want to take
the tile.adjacent mine count
and convert it to a string.
Otherwise we're gonna fall back
and we're gonna use that empty string.
Now you can see when I click all these are empty strings
by default 'cause we're just first doing a check
where if it's zero or undefined then we're gonna make sure
we do an empty string instead.
So this is an important thing to make sure
you're watching it for.
Sometimes fixing a type error may accidentally introduce
a bug if your code wasn't written super well
in the first place or maybe you didn't understand
what the code was doing.
Now if we go down a little further,
we get to some of the more complicated parts of converting
this over to TypeScript just because of how TypeScript works
with the dom.
You'll see here we're getting some errors.
If I hover over this,
it says property matches does not exist
on type event target
and here the event is also possibly null.
So I have a few different problems here.
First of all, my target is set to event target,
which is a super generic type.
I know for a fact that when I click my event target here
is going to be equal to some type of HTML element,
but I should probably check to make sure that's the case.
So what I can do is use that type and narrowing
way we've talked about.
So I can say E.target instance of HTML element.
I can have that as one of my checks and I can essentially
say if this is not an instance of an HTML element
or if it does not match this data status,
then I can do a return right here.
So this first line is just a type narrowing
that's essentially saying,
"Hey is this thing an HTML element?"
If it's not return and don't do anything,
but if it is,
then go a step further and check to see
if it matches this particular thing.
And now you can see when I hover target it has the correct
type of HTML element 'cause I did this type card
before I went any further but I'm still getting errors.
As you can see here,
this string here for our X could be a string or undefined.
We need to make sure we check for that as well.
What I probably wanna do is add
some additional checks into here
to see if our target.dataset.X is equal to null
or if the exact same thing is true for our Y property.
That'll just make sure if either of those are null.
We don't actually do the code inside of here
'cause it wouldn't make sense to.
And by doing this I've actually fixed some potential bugs
because if for some reason my dataset wasn't set properly,
this would throw a bunch of errors for us
and now we're actually making sure we check for that.
And what we could do is we could throw an error
or do whatever we want,
but we're actually purposely checking for it,
which is really great.
Down here you'll notice we have almost the exact same code.
I'm just gonna copy the same exact if check.
I'm gonna paste it in there because it's essentially doing
the exact same thing that fixed up that as well.
Now if we move a little bit further on down here,
you're gonna see we get an error
because this should be a string instead of a number.
So just convert it to a string.
Super simple, straightforward stuff.
And here this should also be a string instead of a number.
So we'll convert this entire thing to a string as well.
There we go.
And if we scroll through a little further,
you notice here we have our tile status stuff
and the nice thing is I don't have to worry about
importing and exporting this variable.
I just get my awesome auto complete.
So we can see this should say marked,
so we'll convert it to marked.
And now that's working just fine.
Here we have an event object that we're getting passed in.
If I look at where this stop prop is being used,
you can see it's being used inside of our event listeners.
So this I know is an event object.
So we can just say it's an event object
and now we have perfect TypeScript
because the event object has this function on it.
Now this takes in a board size,
which we know for a fact is a number just based on our code.
But if you look at our code,
you can see it's passing in board size,
which is a number,
a number of mines, which is a number.
So we can also type this as a number as well.
Our positions here is most likely a position array.
And if we look here you can see that we're creating a
position with an X and a y property.
So it is in fact a position array.
We just need to make sure we import that type.
So we can come in here,
export the position array and we can import it
right here as you can see that fixed up,
all those types.
And here we'll just make sure our size is a number
because if we look at where this is being used
and we actually go to the implementation of it,
you can see it's just passing in a number.
So now that's all we need to have done right here
for this and hopefully our code should be working
if we save this.
So if we go over here, we start clicking on things.
You'll notice if I actually can find a block
where there's a bunch of zeros,
everything seems to be working just like it did before.
So to do a quick recap,
we essentially focused on the bottom up approach
of doing TypeScript,
which is really great when you're working on
really simple functions where you kind of know
the inputs and outputs.
We also notice we ran into quite a few problems
where we had a function, for example,
inside of here we had this like reveal tile function,
whereas was passing in some properties and we weren't really
sure what those properties were.
So we had to move upwards through the stack to figure out
what those were.
This is one downside to this approach
and one benefit to the top down approach.
'Cause in the top down approach you always know
what values are being passed into the functions
that you're calling.
But at the very least, it's very easy for you
to see what's being passed into all of your functions.
You just don't have the return values for you.
So for example, if I were to go to create board here,
I don't know what the return value of position matches
if I'm doing a top down approach,
but I do know what gets passed into that function.
So it can be really great if you're trying to make sure
that you try to know what's being passed
into all your functions
and you can't just look at the code
and know by just reading it.
Sometimes your code's not very well written
and it's hard to know.
Most of this code was pretty well written
and had good naming,
so it was kind of easy to guess the type
even without knowing for a hundred percent sure
what the type was.
Also, I was just looking at the code
and I realized there's one thing I forgot to move over
and that is inside of that test folder,
if we look inside this source,
there is this vite-ent.d.ts.
We should make sure we include that in our source folder
as well if we wanna make sure we have all of our typings
for vite as well.
Also, if we look in our script,
we do have one warning in here
and that's because we're importing this tile statuses
but never using it,
which means we can remove that import
and inside of here we can actually probably remove
the export on that tile statuses as well.
Let's get rid of that there.