Microsoft MVP Logo

I'm working on another post that explains the details of how VS Code handles debugging and how sourcemaps work for Node.js projects. In that post I explain how I setup my project so that I can debug a Node.js project written in TypeScript using VS Code, either by running the project, attaching to an existing process on my laptop or attaching to the Node.js process running in a local Docker container.

But before share that post, I wanted to have something that I could reference which explains how I setup my Node.js projects. I wasn't planning to write this, but in the last few months I've had a few people ask how I do this so I figured this would be the easier approach: something to reference.

Keep in mind that this is how I do it... not how I say you should do it. Like so many other similar like this, use this to get some ideas or how to figure out your project structure & workflow.

The Scenario

I like to do a lot of my server-side dev in Node.js for multiple reasons that are beyond the scope of this post. While Node.js is all JavaScript, I prefer to write everything in TypeScript. When I say everything, I mean everything, including gulp which I use to handle a bunch of my build tasks. So I have a project filled with TypeSript, no JavaScript, and two types of files to build: project/build files & app source files.

This presents a bit of a challenge as I need two different transpilation configurations: one for transpiling the build files, or those files that are used in the management, testing and building of the application, and another for transpiling the project files.

Building Project Files

Almost all of my build assets are in a folder /build. This includes gulp tasks project configuration settings and TypeScript type definitions. Here's a sample of what that looks like (I've removed a lot of stuff that's not relevant to the project like the source, or other stuff that doesn't relate to this discussion)

Let me explain a few things while referencing the above structure:

  • config: this folder contains constants that are used by various build tasks, such as lists of where all TypeScript & JavaScript is in the project, where tests are, what folders are dynamically built that can be cleaned out (ie: /logs, /dist & /reports)
  • gulp: this contains a folder, tasks, that contains a separate class representing each gulp task used within the project. I wrote about how I do this in my post Dynamically Loading Gulp Tasks For Simplified Reuse and Maintenance. This folder also contains two barrels:
  • buildBarrel.ts: Exports the config and utility assets for easy reuse elsewhere in the codebase
  • gulpPlugins.ts: Barrel of all gulp plugins I'm using, easily exported for use elsewhere. This makes using plugins nice and clean, with IntelliSense, in the different tasks:

    import { gulpIf, gulpPlumber, gulpPrint, gulpSourcemaps, gulpTypeScript } from '../gulpPlugins';

    For instance, here's what one barrel looks like for a project I'm working on:

  • *.d.ts: Various TypeScript type definitions that I use internally throughout the project. This can include type definitions that don't exist that I wanted to create making my developer experience better (ie: wallaby.d.ts).

  • tsconfig.*.json: These are my TypeScript project files... I'll explain why I need two of these in a moment.
  • wallaby.conf.ts: If you haven't checked out WallabyJS, you really should. This is my project configuration.

One of the first things I have to do is build my project files, or files that help me work with the rest of the project. This is because as you can see above, all my gulp tasks and configuration files for things like WallabyJS are written in TypeScript... which won't run. I need to transpile these to JavaScript. But it's not that simple. See, I like to build my applications to a different folder from /src, such as /dist. This presents a challenge because now I have one part of my codebase that needs to build to /dist and another part where it does matter (the build stuff) since it won't get distributed.

I created a seperate TypeScript project configuration file, tsconfig.build.json, that excludes the folders I don't want TypeScript to compile with all the compiler settings I want on it:

To transpile the build stuff, I have a NPM script called build:project that I use to download all type definitions, compile the project related TypeScript and run gulp to show the available tasks. The script from package.json looks like this:

"build:project": "typings install && tsc -p tsconfig.build.json && gulp"

So typically, right after running npm install, another npm script (install) runs this using npm run build:project.

At this point, my project build artifacts are now in JavaScript so I can run gulp like normal and get ready to build my project.

Building Application Files

The whole reason you've created a project is to build an app, right? So far I've explained how my project setup is done... the next step is to see how to handle the source files for the actual application. My entire application resides in the directory tree within a /src folder.

Building the application is a bit different from building the project files. As I mention above, I build all the project related TypeScript files (gulp tasks, utilities, etc) side-by-side to their generated JavaScript files. But for the application, I want the output to go to the /dist folder. To do this, I programatically control the exclusions for the TypeScript project. Within my gulp task build, I first load the tsconfig.json file and then modify all the exclusions like so:

Notice here that I'm excluding two TypeScript files from the root of the project that are not part of the application, rather they are project management files. I then exclude a bunch of directories like where all my build utilities are, the node modules folder, where files are being build (output) and reports generated like test results and code coverage reports.

One extra setup I do run into is for things like websites that have static artifacts like CSS, images or views. For these, whenever I do a build, I also copy over these assets from the /src to the /dist folder. Typically this is just another gulp task that is set as a dependency on my build task so it's always run without having to remember to run it when building the application.

Conclusion

In this post I shared how I like to setup my projects. You may look at this and think "dang that seems complicated" or "seems like a lot of project related stuff that's duplicated in each project". You're right... there is a bunch of duplicated stuff. Adding it to each project isn't hard as I setup my own custom Yeoman generator that I run to start a new project or to add things to an existing project (like additional gulp tasks).

I also like how I can modify the build process for each project if one is a bit different than others.

But I do admit that I'm looking for a way to make things a bit more reusable. I'd like to have an NPM package that import as a dev dependency without having to dupe everything. It won't handle everything, but it should handle a bunch of stuff. Maybe the way my gulpfile.ts loads all the tasks dynamically, maybe some common tasks and configuration stuff is in that shared package. Not sure... but for now, this works.

Comments powered by Disqus