0. DevContainer Setup
These steps were completed in class in CL01 and you should not need to repeat. They are here only so that we have a complete record of steps needed.
- Be sure Docker Desktop is installed and successfully running
- Be sure VSCode is installed and successfully running (VSCode > Open Extensions > DevContainers by Microsoft)
- On your laptop, create a folder named
ts-container
. Using VSCode, use the Open Folder feature, and select this folder you just created. (You should be able to complete both steps via the Open Folder feature). - With VSCode opened to your
ts-containe
directory, create a new directory (use VSCode’s File Explorer) named.devcontainer
- Within the
.devcontainer
directory, create a new file nameddevcontainer.json
and set its contents to:
{
"name": "ts-devcontainer-demo",
"image": "mcr.microsoft.com/vscode/devcontainers/typescript-node"
}
- Open Command Palette and run “Dev Containers: Reopen in Container” (This will take a few minutes.)
- Once fully opened in a new VSCode editor, start a terminal and try running:
node --version
and you should see a reasonably modern version of node installed. At the time of this writing, it isv22.6.0
.
What’s going on in these steps?
Steps 1 through 5. The DevContainers standard specifies .devcontainer
a special, “hidden” folder where a devcontainer.json
file is placed. This file specifies the settings of a DevContainer for a project. There are some other ways of organizing this, as well, but as our setups grow more complex this folder organization offers benefits in keeping DevContainer-related files bundled together.
Step 6 starts the DevContainer. This involves downloading an image, made up of multiple layers, found at the "image"
URL in your devcontainer.json
file. You will notice this is hosted by Microsoft and is one of their first-party DevContainer images for working with TypeScript / Node.js JavaScript projects. The image contains all of the files and some metadata for running a Docker Container that powers your VSCode’s “backend”. Your project directory, ts-container
is mounted into the DevContainer so that these files are shared between the container environment and your host machine.
The DevContainer environment you just setup ensures consistency across your IDE and everyone else in this course, or on a software project, utilizing it.
1. Initializing a git
Repository
Since this is a new project and we’d like to use source code control to checkpoint our progress along the way, let’s initialize a new git
repository for this project. First let’s set our system’s default branch name:
git config --global init.defaultBranch main
Then, let’s initialize our git
repository:
git init
You will notice some successful output and that (main)
now shows us in our CLI $PROMPT
the current branch our repository is on. To convince you this is a brand new git
repository, try running the command git log
to be greeted with a message saying as much.
Let’s make our first commit, adding our DevContainer settings to the repository:
git add .devcontainer
Confirm our staged commit has what we would expect:
git status
Form our first commit:
git commit -m 'Add DevContainer settings'
Then check our log:
git log
Congratulations, you’ve begun your first git
repository for the project!
2. Establishing a Project in Node.js
Modern language platforms, such as Node.js/JavaScript, have standards or conventions around project structure. Additionally, Package Managers are responsible for specifying versions of 3rd party dependencies (libraries) called packages, installing, and updating them. In the Node.js platform, npm
Node Package Manager is the standard tool. Let’s setup a basic project using npm
:
- Be sure you are working in your DevContainer! If you are reopening a project, look for “Dev Container” in the bottom left corner of your VSCode. If it is not there, reopen your project folder in a DevContainer like the instructions above, or look for it in the File
- Open a new Terminal Session in VSCode
- Run the command
npm init
. Thenpm
program uses a “subcommand” pattern where the first shell argument is the “subcomand” you wantnpm
to perform. Theinit
subcommand initializes a new project. - Press Enter to accept the default settings for name
- Press Enter to accept the default settings for version
- Press Enter to accept the default settings for description
- For “entry point”, use:
dist/index.js
- This will be our “main” program file found in thedist
directory where our project will build. - Press enter to accept default setting for the rest of the settings and accepting the file
- Open the file “package.json”, these are your project’s settings for
npm
’s purposes
Ultimately, this script simply produces the package.json
file you see here. This file is expected in the root directory of a Node/JavaScript project.
There is one customization we will make to this file to make use of modern module syntax you’ll see shortly. Add this line after the main
line in package.json
:
"type": "module",
Make another commit:
git add .
This adds everything in the current working directory to git
’s commit staging area. Finally, commit it:
git commit -m 'Add npm project package.json'
3. Adding TypeScript as a Project Dependency
The TypeScript programming language adds static type annotations, static type checking, and some additional modern language features to JavaScript. Static typing is valuable in software engineering projects because it offers additional levels of confidence in the correctness of your program, enables many language IDE features (such as smart autocomplete) not otherwise possible, and better specifies your functions, classes, and so on, in a declarative fashion.
TypeScript is implemented as an npm
package we can take a development dependency on. TypeScript is needed to transpile (compile from TypeScript to JavaScript) a our project at development/design time, but it is not needed at runtime, thus it is a development dependency and not a proper runtime dependency. We will see this nuance more closely soon.
The subcommand for installing a package is install
(it can be shorthanded as i
). The --save-dev
long flag will save the dependency in package.json
as a development dependency.
npm install --save-dev typescript
This command has three significant effects:
- It installs the TypeScript package in
node_modules
, a directory wherenpm
organizes your code dependencies.- The
typescript
package’s library files are stored innode_modules/typescript
- The
typescript
package’s executable scripts are stored innode_modules/.bin
- The
- It adds the TypeScript dependency to
"devDependencies"
inpackage.json
(this is from specifying--save-dev
). - It created a
package-lock.json
file that has more information about the very specific dependencies installed for the project.
When the time comes to establish a project source code repository, e.g. a git
repository, generally node_modules
and 3rd party libaries are not included in project repositories. These files are easily reinstalled based on package.json
and package-lock.json
via npm
. The files package.json
and package-lock.json
are included in project repositories.
Try running tsc
via npx
:
npx tsc --version
or ./node_modules/.bin/tsc
Notice that npx
is shorthand for running an executable script in ./node_modules/.bin
, in this case the TypeScript Compiler tsc
.
Before commiting our work: let’s establish a .gitignore
file!
Notice above that node_modules
should not be added to source code control. Why? Because for non-trivial projects this directory will contain orders of more magnitude more code than your project. Additionally, the purpose of npm
is that it makes it easy to install and update the necessary packages in node_modules
based only on what you specify in package.json
and what npm
manages based on this in package-lock.json
.
To ignore a directory, or specific files, in a git
repository, we add a hidden file named .gitignore
. Add this new file to your project and then add the following line to it:
node_modules/
Try running git add .
and then git status
and notice that the files inside of node_modules
are ignored. Go ahead and make a commit with message: 'Add TypeScript as a development dependency'
.
4. Initializing a TypeScript Project
The TypeScript compiler also has configuration options. You can initialize a project’s settings by running the command:
npx tsc --init
You will notice this created a file named tsconfig.json
in your root directory. Try uncommenting and updating the following two lines to specify where our source code files and build files will be located:
"rootDir": "./src", /* Specify the root folder within your source files. */
Set rootDir
to ./src
. **Create a directory named src
in VSCode File Explorer, or mkdir src
from the Terminal.
There are two lines right around rootDir
which control the kind of module code that is used and generated. We want to use "Node16"
for both, so update these two lines, as well:
"module": "Node16", /* Specify what module code is generated. */
"moduleResolution": "Node16", /* Specify how TypeScript looks up a file from a given module specifier. */
Finally, update outDir
:
"outDir": "./dist", /* Specify an output folder for all emitted files. */
Set outDir
to ./dist
. **Create a directory named dist
in VSCode File Explorer, or mkdir dist
from the Terminal.
Now try adding a file to src
named index.ts
with the following structure:
= () => {
let main : string = "Hello, world.";
let messageconsole.log(message);
;
}
main();
You can compile this TypeScript file by running the tsc
program which is guided by the tsconfig.json
file you just configured:
npx tsc
Upon doing so, look in your dist
directory and you should see index.js
. Compare it with src/index.ts
, can you spot any differences?
Finally, try running the compiled program with node
:
node ./dist/index.js
You should see Hello, world.
printed out! Woo! You’ve compiled and ran your first TypeScript program as a JavaScript program.
Finally, let’s make a commit. Before doing so, let’s also have git
ignore our dist
directory. Typically it is considered best practice for your Source Code Control (SCC) system, git
in this case, to track primarily your source code and not built artifacts such as the generated files in dist
.
Add the following line to .gitignore
:
dist/
Then add your files git add .
and form another git
commit with message 'Add initial entry point to program and TypeScript config'
5. Automating the Compilation and Running of Our Program
It’s tedius and undocumented to have to remember to run npx tsc
for compilation and node ./dist/index.js
for running the program. Let’s automate these steps with simpler commands thanks to npm
’s scripts
feature in package.json
.
Open package.json
and add the following lines to the "scripts": {}
block:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "npx tsc",
"start": "node ./dist/index.js",
"build+start": "npx tsc && node ./dist/index.js"
},
You can now run each of these scripts using the npm
subcommand called run
, for example:
npm run build
npm run start
npm run build+start
Notice each of the string values in the "scripts"
dictionary is a command-line expression. The test
and start
scripts are special, they can be run as subcommands directly from npm
, e.g. npm test
and npm start
. We’ll see how to configure a testing framework later. Generally, though, you can invent your own commands to be run as a script and invoke them using the run
subcommand of npm
.
Congratulations, you have now followed through the steps to setup a clean and simple TypeScript project. Let’s try adding a 3rd party library to our project and making use of it in our simple program.
Make an additional commit in git
:
git add .
git commit -m 'Automate building and starting the app'
6. Adding a 3rd Party Dependency
Let’s try adding some color to our command-line output! The most popular package on npm
for doing this is called chalk
. Let’s install it and save it as a first-class dependency:
npm install --save chalk
You will notice that this updates your package.json
and package-lock.json
files to have the chalk
dependency. Additionally, it installed the source code for chalk
into node_modules
.
Now let’s use it in our program. Update your src/index.ts
program to contain the following:
import chalk from "chalk";
= () => {
let main : string = chalk.bgGreen("Hello, world.");
let messageconsole.log(message);
;
}
main();
Try building and running your program using npm
! Your output should have a green background!
Finally, make one last git commit with the files in your project. Congrats on making your way through setting up a first TypeScript project!