Bootstrap a VS Code Front-End Web Development Environment
Setting up our environment for interactive debugging and continuous testing
Introduction
Would you like to do modern front-end web development but you’re not sure how to get started with a local development environment? Do you know some JavaScript and want to start learning TypeScript? Are you curious about React but want to build the toolchain yourself instead of using create-react-
app?
In this tutorial, we are going to bootstrap a front-end web development environment with support for modern languages and assets, intelligent code completion, continuous linting, continuous styling, continuous testing, and interactive remote debugging. We will set up the environment in Visual Studio Code (VS Code) on a Mac.
Spoiler alert! The github.com/bacongravy/bootstrap-vscode-web project contains the result of following this tutorial. If you have trouble following this tutorial, you can try cloning that project and starting from there.
Goals
Before we begin the tutorial, let us review our six primary goals.
1. Modern languages and assets
We will have support for TypeScript and modern JavaScript (ES2019), with the latest features like arrow functions, import/export, async/await, and object rest/spread properties. We will also have support for JSX syntax, SCSS, and more.
Parcel is a web application bundler that we will use to package our app into a format that can be served to web browsers. Parcel will automatically use Babel to compile our code into a backwards compatible version of JavaScript that works in both current and older browsers. Besides being able to compile Typescript and JavaScript code, with or without JSX, Parcel also handles many other kinds of assets, including HTML, CSS, SCSS, and Vue.
2. Intelligent code completion
We will have intelligent code completion.
VS Code is a free, open-source code editor. It includes an IntelliSense feature based on the TypeScript language service, which provides smart completions and parameter info for both JavaScript and TypeScript. This feature is enabled by default and alone is a great reason to use VS Code for JavaScript development.
3. Continuous linting
We will have continuous linting and automatic fix-on-save behavior.
ESLint is a highly configurable static analyzer, able to report problems in your code before it runs. For instance, it can detect if you use variables that haven’t been defined. It can even fix your code for you (sometimes).
We will configure ESLint to use rules from the popular Standard JS project so that our code follows common standards. We will use a VS Code extension to integrate continuous static analysis into the source editor view, and we will configure VS Code to use ESLint to automatically fix everything it can, every time we save our code.
VS Code uses the TypeScript language service to lint TypeScript files by default, and provides opt-in support for linting JavaScript files. We will enable the feature so that the TypeScript language service infers the types in our JavaScript files and reports any errors it detects.
4. Continuous styling
We will have automatic style-on-save behavior.
Prettier is an opinionated code formatter that understands JavaScript code formatting conventions. It takes maximum line length into account, causing it to reflow single-line statements into multi-line statements if they get too long, making code more readable. It eliminates all custom styling by parsing the code to an abstract syntax tree (AST) and then re-printing it according to its own consistent rules.
Continuous styling isn’t just about making your code pretty. It can save you time, too. For instance, if you’re working on a team it can help avoid arguments between teammates over style during code reviews.
We will configure ESLint to apply Prettier styles as fixable linting rules and automatically style the code every time we save the code. We will also install a VS Code extension and configure it to automatically format-on-save other file formats (JSON, HTML, Markdown, YAML, etc.), too.
5. Continuous testing
We will have continuous testing that runs whenever we modify and save files in our project.
Jest is a testing framework with a focus on simplicity and speed. We will install a VS Code extension that runs Jest in watch mode while our workspace is open. We will immediately see if any of our tests are passing or failing whenever we save our code. The extension reports status inline with the source code and makes it easy to debug failing tests with one click.
6. Interactive remote debugging
We will have interactive remote debugging that lets us run our app in a web browser while stepping through the code in VS Code.
VS Code includes an interactive debugger that lets us set breakpoints, step through code, and examine variables. We will install two VS Code extensions that will allow us to debug our app from VS Code while the app is running remotely in either Edge or Chrome. We will configure VS Code to start the Parcel development server and launch the web browser whenever we begin a debug session. We will also configure VS Code to allow us to debug our Jest tests interactively.
Tutorial
We will start with a Mac fresh out of the box. You can skip the first few steps if you already have all of the software installed.
Pro tip! Do not open the workspace in VS Code until the tutorial tells you to, or else you will need to manually reload the window or app yourself to get the extensions to work properly.
First, we are going to use brew
from Homebrew to install Node, jq, and VS Code, and then use code
from VS Code to install the extensions. Next we will use npm
from Node to install the modules. Finally, we will configure the components to work together. When we are done, we will take our development environment for a spin by writing a test and debugging it.
Install Homebrew
Homebrew is a package manager. It includes a command, brew
, which we will use to install some packages. Follow the instructions at brew.sh to install it now.
Install Node
Node is the engine powering most of the tools we are going to use. We use Homebrew to install the Node package, which includes node
, npm
, and npx
. Install the engine:
brew install node
Install jq
jq is a lightweight and flexible command-line JSON processor. We use jq to edit our config files in this tutorial. Install the processor:
brew install jq
Install VS Code
VS Code is an extensible source editor that will host our development environment. Install the app:
brew cask install visual-studio-code
In addition to Visual Studio Code.app
, the Homebrew package also installs the command line tool code
, which we will use to install our VS Code extensions next.
Install VS Code extensions
To fully integrate ESLint, Prettier, and Jest into VS Code, and to be able to debug our app running in a web browser, we need some VS Code extensions. Install the extensions:
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension Orta.vscode-jest
To be able to debug our app running in a web browser we also need another VS extension. You can either pick one to install, if you have a preference between Edge and Chrome, or you can choose to install both:
code --install-extension msjsdiag.debugger-for-edge
code --install-extension msjsdiag.debugger-for-chrome
Install web browsers
You probably already have the web browser you want to test with installed, but if not, here is how you can install Microsoft Edge and Google Chrome with Homebrew:
brew cask install microsoft-edge
brew cask install google-chrome
We only need one browser for this tutorial, but you can install both if you like.
Create a project
Before we can install Node modules, we need to be in a project directory. If you don’t already have one, then begin by creating an empty directory for the project and initializing it with npm
. Run the following commands:
PROJECT_NAME="$(npx project-name-generator -o dashed)"
mkdir "${PROJECT_NAME}"
cd "${PROJECT_NAME}"
npm init -y
We use npx
to run project-name-generator
, which generates a random project name for us. Feel free to pick your own name instead, if you wish. We run npm init
to generate a package.json
file. The defaults are fine for now.
Install Parcel
Parcel is a web application bundler. It also includes a development server with support for hot module replacement (HMR), making for a quick, iterative development experience. Install the bundler:
npm install -D parcel
Install ESLint
ESLint is a static analyzer, but by itself, it doesn’t do much. We will be installing configs and plugins later in this tutorial to tell ESLint how to do its magic. Install the static analyzer:
npm install -D eslint
Install TypeScript
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. We will use Parcel to compile our TypeScript code, but we will use the TypeScript compiler tsc
to lint our code from the command-line, in order to match the built-in support for TypeScript linting provided by VS Code. ESLint doesn’t understand TypeScript out-of-the-box so we also need to install a parser and plugin to integrate them together. Install the compiler and integrations:
npm install -D typescript \
@typescript-eslint/parser \
@typescript-eslint/eslint-plugin
Install the Standard JS style guide rules
Standard JS is a popular style guide for JavaScript that has been extended to support TypeScript. Use npx
to run the install-peerdeps
command to install the TypeScript-compatible Standard JS config for ESLint, along with its peer dependencies:
npx install-peerdeps -D eslint-config-standard-with-typescript
Install Prettier
Prettier is an opinionated code formatter. It integrates with ESLint by first disabling all of the ESLint rules that would conflict with Prettier and then adding rules to enforce the Prettier style. Install the formatter and integrations:
npm install -D prettier \
eslint-config-prettier \
eslint-plugin-prettier
Install Jest
Jest is a fast and easy-to-use test runner. There is a Jest plugin for ESLint that keeps our test suites looking nice. There is a Jest types package so that the VS Code type checker doesn’t complain about the Jest globals. Install the test runner and integrations:
npm install -D jest \
eslint-plugin-jest-formatting \
@types/jest
Install Babel presets
Babel is a JavaScript compiler. Parcel takes care of configuring Babel for itself, but not for Jest. To allow Jest to test our code we will need to configure Babel with preset packages. Install the presets:
npm install -D @babel/preset-env \
@babel/preset-react \
@babel/preset-typescript
Install React
React is a JavaScript library for building user interfaces. To make React easier to use we will also install ESLint plugins for React, and TypeScript types. Install the library and integrations:
npm install react react-dom
npm install -D eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-jsx-a11y \
@types/react \
@types/react-dom
Install Enzyme
Enzyme is a JavaScript library for testing React components. Enzyme has an integration that enhances Jest and requires an adaptor to connect it to the specific version of React we are using. (If you’re reading this in the future you might need to change which adaptor you use.) Install the library, integration, adaptor, and corresponding TypeScript types:
npm install -D enzyme \
jest-enzyme \
enzyme-adapter-react-16 \
@types/enzyme \
@types/enzyme-adapter-react-16
Review
We began by installing Homebrew, Node, jq, VS Code, and some VS Code extensions. We then created a project. Finally we installed Parcel, ESLint, TypeScript, the Standard JS style guide rules, Prettier, Jest, Babel presets, React, Enzyme, and all of the extra bits to glue them together.
Configure Everything
The components we just installed need to be configured to work with each other. We are going to create some files in our project directory to configure them.
Create tsconfig.json
This file configures TypeScript. Run the following command in the project directory to create the file:
cat >tsconfig.json <<EOF
{
"include": ["src/**/*", ".enzyme.ts"],
"exclude": ["dist"],
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"allowJs": true,
"checkJs": true,
"jsx": "preserve",
"noEmit": true,
"strict": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true
}
}
EOF
We configure the TypeScript compiler to act only as a linter for our project because we will be using Parcel and Babel to actually compile TypeScript code. The settings in this file also affect the VS Code TypeScript linter.
- The
include
setting tells the TypeScript compiler to lint all files insrc
, and to read type imports from.enzyme.ts
. - The
exclude
setting tells the VS Code TypeScript language service to ignore the files generated by Parcel indist
. - The
target
andmodule
settings tell the TypeScript compiler to output modern JavaScript code with no extra polyfills or glue code. - The
allowJS
andcheckJS
settings tell TypeScript to also lint JavaScript files. - The
jsx
setting tells TypeScript to allow JSX in JavaScript and TypeScript files. - The
noEmit
setting tells the TypeScript compiler to only lint our code and not produce any output files. - The
strict
setting tells TypeScript to enable all strict type checking options. - The
allowSyntheticDefaultImports
setting tells TypeScript to make traditional ES modules look like TypeScript modules with a default export when importing them into TypeScript code. - The
forceConsistentCasingInFileNames
setting tells TypeScript to error if an import uses the wrong casing in a filename.
Create .eslintrc
This file configures ESLint. Run the following command to create the file:
cat >.eslintrc <<EOF
{
"extends": [
"plugin:jest-formatting/recommended",
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"standard-with-typescript",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"env": {
"es6": true,
"browser": true,
"jest": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": 2020,
"sourceType": "module"
},
"settings": {
"react": {
"version": "detect"
}
},
"rules": {}
}
EOF
This configures ESLint to understand TypeScript and to use the Standard JS style guide, the React plugins, and the Jest and Prettier integrations.
- The
extends
setting tells ESLint to use recommended rules from each of the configurations that we’ve installed. - The
env
setting tells ESLint which environments we are targeting, which mostly affects which global variables are allowed. - The
parser
settings tells ESLint to use the TypeScript parser. - The
parserOptions
setting tells the TypeScript parser where ourtsconfig.json
file is and what syntax to expect. - The
settings
setting tells the React plugin to detect the version of React being used. - The
rules
setting is where you can add your own linting rules and overrides later.
Create .eslintignore
This file configures ESLint to ignore the files generated by Parcel in the dist
directory. Run the following command to create the file:
cat >.eslintignore <<EOF
dist/
EOF
Create .prettierrc
This file configures Prettier to match the Standard JS style guide we’ve chosen to use. Run the following command to create the file:
cat >.prettierrc <<EOF
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"trailingComma": "all"
}
EOF
Create .babelrc
This file configures Babel. We wouldn’t normally need this file when using Parcel, because Parcel configures Babel for us, but Jest also needs to know how to compile our code. Run the following command to create the file:
cat >.babelrc <<EOF
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript",
"@babel/preset-react"
]
}
EOF
Create .enzyme.ts
This file makes the TypeScript linter aware of the jest-enzyme
types and configures Enzyme to use an adapter. Run the following command to create the file:
cat >.enzyme.ts <<EOF
import 'jest-enzyme'
import { configure } from 'enzyme'
import EnzymeAdapter from 'enzyme-adapter-react-16'
configure({ adapter: new EnzymeAdapter() })
EOF
Create jest.config.json
This file configures Jest to ignore the dist
directory and to use the Enzyme configuration we just created along with the setup files from jest-enzyme
. Run the following command to create the file:
cat >jest.config.json <<EOF
{
"testPathIgnorePatterns": [
"<rootDir>/dist/",
"<rootDir>/node_modules/"
],
"setupFilesAfterEnv": [
"<rootDir>/.enzyme.ts",
"<rootDir>/node_modules/jest-enzyme/lib/index.js"
]
}
EOF
Create .vscode
VS Code looks in this directory in our project for workspace settings and launch configurations. Run the following command to create the directory:
mkdir .vscode
Create .vscode/settings.json
This file configures the VS Code editor. Run the following command to create the file:
cat >.vscode/settings.json <<EOF
{
"editor.codeActionsOnSave": { "source.fixAll": true },
"editor.formatOnSave": true,
"[javascript]": { "editor.formatOnSave": false },
"[javascriptreact]": { "editor.formatOnSave": false },
"[typescript]": { "editor.formatOnSave": false },
"[typescriptreact]": { "editor.formatOnSave": false },
"prettier.disableLanguages": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }
}
EOF
In this file we begin by configuring VS Code to automatically fix and format all files on save. Then we disable the Prettier format-on-save behavior for JavaScript and TypeScript files and enable ESLint validation for them so that the TypeScript language service, ESLint, and the Prettier ESLint plugin can do their jobs. Finally, we set Prettier as the default formatter for JSON files.
Create .vscode/tasks.json
This file describes some custom tasks that we will use to help us debug our code. Run the following command to create the file:
cat >.vscode/tasks.json <<'EOF'
{
"version": "2.0.0",
"tasks": [
{
"label": "debug-start",
"command": "npx",
"args": ["parcel", "src/index.html"],
"isBackground": true,
"problemMatcher": {
"fileLocation": "relative",
"pattern": {
"regexp": "^.*$",
"file": 1,
"location": 2,
"severity": 3,
"code": 4,
"message": 5
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*$",
"endsPattern": "^Server running at .*$"
}
}
},
{
"label": "debug-stop",
"command": "echo ${input:terminate}",
"type": "shell"
}
],
"inputs": [
{
"id": "terminate",
"type": "command",
"command": "workbench.action.tasks.terminate",
"args": "debug-start"
}
]
}
EOF
These tasks are used to start the Parcel development server whenever a debug session begins and stop the server when the session ends. The debug-start
task runs the parcel
command in the background, and the debug-stop
task terminates the debug-start
task.
Create .vscode/launch.json
This file describes to VS Code how to debug our code. Run the following command to create the file:
cat >.vscode/launch.json <<'EOF'
{
"version": "0.2.0",
"configurations": [
{
"name": "edge",
"type": "edge",
"request": "launch",
"preLaunchTask": "debug-start",
"postDebugTask": "debug-stop",
"skipFiles": [
"<node_internals>/**",
"${workspaceFolder}/node_modules/**"
],
"url": "http://localhost:1234",
"webRoot": "${workspaceFolder}/dist"
},
{
"name": "chrome",
"type": "chrome",
"request": "launch",
"preLaunchTask": "debug-start",
"postDebugTask": "debug-stop",
"skipFiles": [
"<node_internals>/**",
"${workspaceFolder}/node_modules/**"
],
"url": "http://localhost:1234",
"webRoot": "${workspaceRoot}/dist"
},
{
"name": "vscode-jest-tests",
"type": "node",
"request": "launch",
"skipFiles": [
"<node_internals>/**",
"${workspaceFolder}/node_modules/**"
],
"args": ["--runInBand"],
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
}
]
}
EOF
In this file we include three ways to debug our code. The first two configurations allow us to debug our app in either the Edge or Chrome browsers. The third configuration is used by the Jest extension to run individual tests and also allows us to debug the entire test suite.
Update package.json
This file contains our package configuration. We use jq
to merge some scripts into the existing file. Run the following command to update the file:
jq -s '.[0] * .[1]' package.json <(cat <<EOF
{
"scripts": {
"start": "parcel src/index.html",
"build": "parcel build src/index.html",
"clean": "rm -rf dist",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}' --fix --color && tsc",
"lint:check": "eslint --print-config .eslintrc | eslint-config-prettier-check",
"test": "jest",
"test:watch": "jest --watchAll"
}
}
EOF) > package-new.json && mv package-new.json package.json
- The
start
script uses Parcel to bundle our app and then serve it using the built-in Parcel development server. - The
build
script uses Parcel to bundle our app for production. - The
clean
script deletes thedist
directory containing the bundled app. - The
lint
script checks the code for lint issues, fixing any issues which can be fixed. - The
lint:check
script verifies that the ESLint rules don’t conflict with the Prettier rules. - The
test
script runs Jest. - The
test:watch
script runs Jest in watch mode.
Most of the time you won’t need to use any of these scripts from VS Code because we have configured VS Code to do all of this for us: the IDE continuously lints and tests while we are editing, and automatically starts the Parcel development server when we begin debugging. The scripts are most useful if you need to access the project from the command-line, outside of VS Code.
Try It Out
To get a feel for our new development environment, we are going to practice test-driven design (TDD) by writing a test that fails and then implementing the code to make the test pass.
We can use the code
command to open a new workspace window in VS Code from our current directory. We will create a directory, touch some files to create them, and then open the workspace:
mkdir src
touch src/index.html src/index.css src/index.jsx \
src/App.tsx src/App.test.tsx
code .
We should be greeted with a view like this:

We already have one error in our workspace (look in the bottom left of the window, in the blue status bar) because Jest doesn’t like it when test files are empty, and we haven’t put anything in App.test.tsx
yet:

Now we will fill in the files with content. We will have an HTML file that links to a JavaScript JSX file. The JavaScript JSX file will import a CSS file, and a component from a TypeScript JSX file. Finally, we will have a test for our component, also written in TypeScript JSX.
Click on “> src” in the sidebar file explorer to reveal the files we previously created:

Put the following content into index.html
and save: