You want to create something awesome in TypeScript, so you set up a nice little directory structure:
You want to support older versions of node, so you set up your typescript compiler accordingly:
{
"compilerOptions": {
"target": "es5",
"sourceMap": true,
"outDir": "dist",
"moduleResolution": "node"
},
"exclude": [
"node_modules"
],
"files": [
"src/index.ts"
]
}
But wait!
What do you mean I can't use promises? I don't want to import a polyfill, it'd pollute my nice index.ts
! If I change to ES6, I get ESM import statements. I can't use those in node!
Enter Gulp and Babel
There's a better way. We can use Gulp. It's a task runner. It runs tasks.
yarn add --dev gulp gulp-babel gulp-jsdoc3 gulp-sourcemaps gulp-typescript babel-preset-env
Note: you can replace yarn add --dev
with npm install --save-dev
Now that we've got Gulp, we can take the ES6 output from TypeScript and polyfill that to whatever version we want to support using babel-preset-env
.
Here's the part you were probably looking for:
For this, we need to set up two files: gulpfile.js
and .babelrc
. We'll also amend our tsconfig.json
.
// gulpfile.js
const gulp = require('gulp');
const babel = require('gulp-babel');
const sourcemaps = require('gulp-sourcemaps');
const ts = require('gulp-typescript');
const tsProj = ts.createProject('tsconfig.json');
gulp.task('build', () => {
gulp.src('src/**/*.ts')
.pipe(sourcemaps.init())
.pipe(tsProj())
.pipe(babel())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('dist'));
});
gulp.task('default', ['build']);
// .babelrc
{
"presets": [
["babel-preset-env", {
"targets": {
"node": "6.10"
}
}]
]
}
// tsconfig.json
{
"compilerOptions": {
"target": "es6",
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"outDir": "dist",
"moduleResolution": "node"
},
"exclude": [
"node_modules"
],
"files": [
"lib/index.ts"
]
}
And our final directory structure:
To build, we simply run: npx gulp
, which runs Gulp.
An Explanation
If you were scouring Google for a solution on how to do this, and you've got other stuff to fix, this part isn't for you. If you want to understand what we've just done, stick with me.
Gulp
We use Gulp as the heart of our build. It's a task runner, which means that we can get it to do all sorts of things. Compile SASS, create JSDoc, and even compile TypeScript.
Our Gulp 'build' command does the following:
- Get all of our TypeScript files:
gulp.src('src/**/*.ts')
- Begin a sourcemap (ideal for debugging in VS Code):
.pipe(sourcemaps.init())
- Compile the TypeScript (Using our tsconfig defined earlier):
.pipe(tsProj())
- Pass the compiled code through Babel:
.pipe(babel())
- Finish our sourcemap:
.pipe(sourcemaps.write('.'))
- Stick out output in 'dist/':
.pipe(gulp.dest('dist'));
Babel
We use .pipe(babel())
to run our code through Babel. Babel polyfills. If no arguments are passed, it looks for .babelrc
.
Our .babelrc
uses babel-preset-env
, a fairly new preset for Babel. It's fantastic - all you need to do is provide a version to polyfill* for. More on preset-env here.
*A polyfill, or polyfiller, is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser (read: interpreter) to provide natively - source
npx
npx is a powerful tool that essentially lets you run programs from your node_modules/
. Try it with eslint! yarn add eslint && npx eslint --init
. It'a sometimes easier if you don't want that binary installed permanently on your system.
I hope this was somewhat informative! It was a total adventure getting this set up for the first time today!
Top comments (6)
Actually, it's even easier - the Typescript compiler has an option for converting
import
statements torequire
statements (andexport
statements tomodule.exports
) built in. You can simply put"module": "CommonJS"
into yourtsconfig.json
file, and you can still stick to using just the Typescript compiler to generate ES6 code that will run in Node.js.That's awesome, I really didn't know that!
I like being able to polyfill though, which was more the point of this. Writing clean async/await code and then compiling to an older version of node is great. I have to support node 6 in my day job.
Setting module to CommonJs will compile async await to ES6, the same way Babel does.
Buy this guide is pretty useful considering we can add more functionality with Babel and gulp later on.
For Babel 7 (which is still beta) you can use transform-typescript plugin. This way you won't need to use Gulp, just let Babel handle all the transpilation!
You could also just add
es2015
totsconfig -> compilerOptions -> lib
and setmodule
as mentioned elsewhere in the comments.lib
tells tsc "Hey, I know I'm compiling to ES5, but you can count on an ES2015 polyfill." You could restrict the scope further with justes2015.promise
, which would tell it that a Promise constructor will be available at runtime.If you're explicit, you end up with
lib
being the exact list of polyfills that will be required.It's also worth mentioning, in this setup, you could add your
gulp
command to thepackage.json
scripts, and then no one needs to know about npx as npm will add./node_modules/.bin
to the path for anything executed withnpm run
.e.g.
You'd then be able to run gulp with
npm run build
oryarn build
.I like using package.json scripts, because it's self-documenting in terms of all of the available build commands.