How to merge d.ts typings with dts-bundle and Webpack

Vladimir Tolstikov
3 min readDec 2, 2016

--

Hi there! If you’re using TypeScript in your JS-workflow you should already know about d.ts files where 3rd-party libraries store public definitions for your IDE and TypeScript compiler could do their job. Today we will talk about how to generate typings for your custom npm library. I write this because not found solution on the internet, hope that will help to someone else too :)

Ok, straight to the business. Hope you already setted up Webpack to work with TypeScript, that article not about it, but you can check config in the end if needed.

Firstly, enable d.ts file emission by ts-compiler in tsconfig.json

{
"compilerOptions": {
"declaration": true,
// ...

It will create one file per .ts source, but we need one file. Here’s dts-bundle join the game. Install it first from npm

npm install dts-bundle --save-dev

Now we need to link it with Webpack. To do that I wrote short Webpack-plugin right in webpack.config.js. Check DtsBundlePlugin and how it added to plugins list right in my config below.

var webpack = require('webpack'),
path = require('path'),
yargs = require('yargs');

var libraryName = 'CBAnalytics',
plugins = [],
isProductionBuild = yargs.argv.mode === 'build';

if (isProductionBuild) {
plugins.push(new DtsBundlePlugin());
plugins.push(new webpack.optimize.UglifyJsPlugin({ minimize: true }));
}

var entry = {};
entry[libraryName] = __dirname + '/src/index.ts';
entry[libraryName + '.min'] = __dirname + '/src/index.ts';

var config = {
entry: entry,
devtool: isProductionBuild ? null : 'source-map',
output: {
path: path.join(__dirname),
filename: "[name].js",
library: libraryName,
libraryTarget: 'umd',
umdNamedDefine: true
},
module: {
preLoaders: [
{ test: /\.tsx?$/, loader: 'tslint', exclude: /node_modules/ }
],
loaders: [
{ test: /\.tsx?$/, loader: 'ts', exclude: /node_modules/ }
]
},
resolve: {
root: path.resolve('./src'),
extensions: [ '', '.js', '.ts', '.jsx', '.tsx' ]
},
plugins: plugins,

// Individual Plugin Options
tslint: {
emitErrors: true,
failOnHint: true
}
};

module.exports = config;


function DtsBundlePlugin(){}
DtsBundlePlugin.prototype.apply = function (compiler) {
compiler.plugin('done', function(){
var dts = require('dts-bundle');

dts.bundle({
name: libraryName,
main: 'src/index.d.ts',
out: '../index.d.ts',
removeSource: true,
outputAsModuleFolder: true // to use npm in-package typings
});
});
};

Now one single .d.ts file will be generated in root of the project. One thing remains — need to add that d.ts to package.json (official doc on TypeScript npm publishing)

{
"name": "cb-analytics",
"version": "1.0.0",
"description": "",
"main": "CBAnalytics.min.js",
"types": "index.d.ts",
"scripts": {
"start": "webpack --progress --colors --watch",
"build": "webpack --mode=build",
"test": "karma start"
},
...

That’s it, I hope nothing missed :) If you will not use npm typings definition, you need to set outputAsModuleFolder to false. removeSource flag should remove generated d.ts files, but if they’re still there you can ignore them in .gitignore with

*.d.ts
# Exclude root d.ts
!/*.d.ts

or leave or delete them in another step — your choice.

Happy coding :)

UPD:
I see most people need to see how final files/configs look, here they are:

package.json

{
"name": "cb-analytics",
"version": "1.0.0",
"description": "",
"main": "CBAnalytics.min.js",
"types": "index.d.ts",
"scripts": {
"start": "webpack --progress --colors --watch",
"build": "webpack --mode=build",
"test": "karma start"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"extend": "^3.0.0",
"js-cookie": "^2.1.4",
"nanoajax": "^0.4.3"
},
"devDependencies": {
"@types/extend": "^2.0.30",
"@types/jasmine": "^2.5.38",
"@types/js-cookie": "^2.0.28",
"@types/nanoajax": "^0.2.29",
"dts-bundle": "^0.6.1",
"jasmine-core": "^2.5.2",
"karma": "^1.3.0",
"karma-chrome-launcher": "^2.0.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-webpack": "^1.8.0",
"ts-loader": "^1.2.2",
"tslint": "^4.0.2",
"tslint-loader": "^3.2.0",
"typescript": "^2.0.10",
"webpack": "^1.13.3",
"yargs": "^6.4.0"
}
}

tsconfig.json

{
"compileOnSave": false,
"compilerOptions": {
"declaration": true,
"jsx": "react",
"module": "commonjs",
"noImplicitAny": true,
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true,
"target": "es5"
},
"exclude": [
"node_modules"
]
}

webpack.config.js

var webpack = require('webpack'),
path = require('path'),
yargs = require('yargs');
var libraryName = 'CBAnalytics',
plugins = [],
isProductionBuild = yargs.argv.mode === 'build';
var entry = {};
entry[libraryName] = __dirname + '/src/index.ts';
if (isProductionBuild) {
entry[libraryName + '.min'] = __dirname + '/src/index.ts';

plugins.push(new DtsBundlePlugin());
plugins.push(new webpack.optimize.UglifyJsPlugin({
include: /\.min\.js$/,
minimize: true
}));
}
var config = {
entry: entry,
devtool: isProductionBuild ? null : 'source-map',
output: {
path: path.join(__dirname),
filename: "[name].js",
library: libraryName,
libraryTarget: 'umd',
umdNamedDefine: true
},
module: {
preLoaders: [
{ test: /\.tsx?$/, loader: 'tslint', exclude: /node_modules/ }
],
loaders: [
{ test: /\.tsx?$/, loader: 'ts', exclude: /node_modules/ }
]
},
resolve: {
root: path.resolve('./src'),
extensions: [ '', '.js', '.ts', '.jsx', '.tsx' ]
},
plugins: plugins,
// Individual Plugin Options
tslint: {
emitErrors: true,
failOnHint: true
}
};
module.exports = config;
function DtsBundlePlugin(){}
DtsBundlePlugin.prototype.apply = function (compiler) {
compiler.plugin('done', function(){
var dts = require('dts-bundle');
dts.bundle({
name: libraryName,
main: 'src/index.d.ts',
out: '../index.d.ts',
removeSource: true,
outputAsModuleFolder: true // to use npm in-package typings
});

// Delete unneeded files

});
};

--

--

Vladimir Tolstikov

Full Stack Web Developer passionate about interesting projects and challenges. https://bobrosoft.com