In 2015, if you had TypeScript code and browser tests, you needed some glue in between. This post is about the glue I wrote: karma-typescript-preprocessor2. You will see what it solved, how to configure it, and how it worked inside.
A note on timing: I’m filing this post on the day version 1.0.0 went to npm, November 11th, 2015, but I’m writing it looking back, so I will also tell you where the project ended up.
First, a few words
- Test runner: a program that finds your tests, runs them and reports what passed and what failed. Karma is a test runner that runs your JavaScript tests inside real browsers.
- Transpile: translate code from one language to another at the same level. TypeScript is transpiled to JavaScript, because browsers only understand JavaScript.
- Preprocessor: in Karma, a plugin that transforms each file before it is sent to the browser.
Here, it takes a
.tsfile and hands Karma the.jsversion.
The problem
Karma can’t run a .ts file, so something has to compile it first.
A preprocessor for that already existed on npm: karma-typescript-preprocessor, published by
Sergey Todyshev in 2013, described as “a karma-runner plugin to compile typescript files on the
fly”. Mine is not a fork of it; the git history starts from scratch. But the name was taken, so I
added a 2 at the end. The README even says it: “note number 2 at the end”. Both plugins
register the same typescript preprocessor name and read the same typescriptPreprocessor
section of karma.conf.js.
What mine does differently is written in the first line of its README:
Transpile files in memory using gulp-typescript. No temp files are generated.
How to use it
Install it as a dev dependency:
$ npm install karma-typescript-preprocessor2 --save-dev
Then point Karma at it. This is the full example from the README:
// karma.conf.js
module.exports = function(config) {
config.set({
files: [
'**/*.ts' // Preprocessor will convert Typescript to Javascript
],
preprocessors: {
'**/*.ts': ['typescript', 'sourcemap'] // Use karma-sourcemap-loader
},
typescriptPreprocessor: {
// options passed to typescript compiler
tsconfigPath: './tsconfig.json', // *obligatory
compilerOptions: { // *optional
removeComments: false
},
// Options passed to gulp-sourcemaps to create sourcemaps
sourcemapOptions: {includeContent: true, sourceRoot: '/src'},
// ignore all files that ends with .d.ts (this files will not be served)
ignorePath: function(path){
return /\.d\.ts$/.test(path);
},
// transforming the filenames
// you can pass more than one, they will be execute in order
transformPath: [function(path) { // *optional
return path.replace(/\.ts$/, '.js');
}, function(path) {
return path.replace(/[\/\\]test[\/\\]/i, '/'); // remove directory test and change to /
}]
}
});
};
The short version of each option:
tsconfigPathis required. The plugin refuses to start without it.compilerOptionslets you override (or add) anything fromtsconfig.json.ignorePathskips files you don’t want served, like.d.tsdefinition files.transformPathrenames the virtual path Karma sees; the example strips atestfolder so specs can import modules fromsrc.sourcemapOptionsis passed to gulp-sourcemaps.
The one opinion it had
The plugin only accepts configuration through tsconfig.json. In the README’s words, “a lot of
problems with typos and references are completely solved, as compiler will use same basedir to
resolve files”.
How it worked inside
The whole plugin is a single index.js. Three ideas carry it:
- Compile the project, not the file. When Karma starts, the plugin calls
ts.createProject(tsconfigPath, compilerOptions)from gulp-typescript and compiles everything once. The output goes into a writable stream that keeps each compiled file in an in-memory buffer. Nothing touches the disk. The README credits gulp-typescript’s incremental compilation for fast rebuilds. - A tiny state machine. The plugin is
idle,compilingorcompilationCompleted. If Karma asks for a file while a compilation is running, the request waits in a queue. When compilation ends, the queue is drained and each file is served from the buffer. - Recompile on change. Karma marks a changed file with a
sha. When the plugin sees it, it queues the request and triggers a new compilation.
My first pull request
This was my first open-source project, and I was really happy the day another contributor sent me a pull
request. It was #2, opened by
@rich-j on February 7th, 2016, and merged the next day. It added
sourcemap support through gulp-sourcemaps, because gulp-typescript ignores the sourcemap settings in
tsconfig.json, and it made tsconfigPath relative to Karma’s basePath.
A few months later, @lelandmiller sent #3, merged on May 21st, 2016. It compared files by full path instead of file name, so two files with the same name in different folders stopped colliding.
Where it ended up
The last version on npm is 1.2.1,
from June 19th, 2016. The repository got a few more commits until 2017 (a refactor and a fix for
default compiler options), but they were never published. If you read master today, be aware it
does not match what npm serves: index.js requires lodash.assignin while package.json lists
lodash.assign, and the transformPath check throws when you pass an array.
The ecosystem moved on too: Karma’s own README now says “Karma is deprecated and is not accepting new features or general bug fixes”.
What I take from it
Two decisions still feel right to me: keeping compiled output in memory, so there is nothing to
clean up, and letting tsconfig.json be the single source of truth for the compiler.
The code is about 200 lines and MIT licensed. If you are curious how a Karma plugin is wired
together (the $inject array, the factory, the done callback), it is a quick read.
Take a look on GitHub, and if you
still have an old project running it, I would love to hear about it in the comments.
Comments
Comments are hosted by Disqus, which sets cookies and shares data with Disqus. Load comments? Read the privacy page for details.