ts-demo-npm-cjs
.gitignore
.npmignore
package.json
dependencies
vs. devDependencies
package.json
tsconfig.json
index.ts
index_test.ts
This chapter describes how to use TypeScript to create packages for the package manager npm that are based on the CommonJS module format.
GitHub repository: ts-demo-npm-cjs
In this chapter, we are exploring the repository ts-demo-npm-cjs
which can be downloaded on GitHub. (I deliberately have not published it as a package to npm.)
You should be roughly familiar with:
CommonJS modules – a module format that originated in, and was designed for, server-side JavaScript. It was popularized by the server-side JavaScript platform Node.js. CommonJS modules preceded JavaScript’s built-in ECMAScript modules and are still much used and very well supported by tooling (IDEs, built tools, etc.).
TypeScript’s modules – whose syntax is based on ECMAScript modules. However, they are often compiled to CommonJS modules.
npm packages – directories with files that are installed via the npm package manager. They can contain CommonJS modules, ECMAScript modules, and various other files.
In this chapter, we are using what TypeScript currently supports best:
.js
.Especially on Node.js, TypeScript currently doesn’t really support ECMAScript modules and filename extensions other than .js
.
ts-demo-npm-cjs
This is how the repository ts-demo-npm-cjs
is structured:
ts-demo-npm-cjs/
.gitignore
.npmignore
dist/ (created on demand)
package.json
ts/
src/
index.ts
test/
index_test.ts
tsconfig.json
Apart from the package.json
for the package, the repository contains:
ts/src/index.ts
: the actual code of the packagets/test/index_test.ts
: a test for index.ts
tsconfig.json
: configuration data for the TypeScript compilerpackage.json
contains scripts for compiling:
ts/
(TypeScript code)dist/
(CommonJS modules; the directory doesn’t yet exist in the repository)This is where the compilation results for the two TypeScript files are put:
ts/src/index.ts --> dist/src/index.js
ts/test/index_test.ts --> dist/test/index_test.js
.gitignore
This file lists the directories that we don’t want to check into git:
node_modules/
dist/
Explanations:
node_modules/
is set up via npm install
.dist/
are created by the TypeScript compiler (more on that later)..npmignore
When it comes to which files should and should not be uploaded to the npm registry, we have different needs than we did for git. Therefore, in addition to .gitignore
, we also need the file .npmignore
:
ts/
The two differences are:
dist/
).ts/
).Note that npm ignores the directory node_modules/
by default.
package.json
package.json
looks like this:
{
···
"type": "commonjs",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"scripts": {
"clean": "shx rm -rf dist/*",
"build": "tsc",
"watch": "tsc --watch",
"test": "mocha --ui qunit",
"testall": "mocha --ui qunit dist/test",
"prepack": "npm run clean && npm run build"
},
"// devDependencies": {
"@types/node": "Needed for unit test assertions (assert.equal() etc.)",
"shx": "Needed for development-time package.json scripts"
},
"devDependencies": {
"@types/lodash": "···",
"@types/mocha": "···",
"@types/node": "···",
"mocha": "···",
"shx": "···"
},
"dependencies": {
"lodash": "···"
}
}
Let’s take a look at the properties:
type
: The value "commonjs"
means that .js
files are interpreted as CommonJS modules.main
: If there is a so-called bare import that only mentions the name of the current package, then this is the module that will be imported.types
points to a declaration file with all the type definitions for the current package.The next two subsections cover the remaining properties.
Property scripts
defines various commands that can be invoked via npm run
. For example, the script clean
is invoked via npm run clean
. The previous package.json
contains the following scripts:
clean
uses the cross-platform package shx
to delete the compilation results via its implementation of the Unix shell command rm
. shx
supports a variety of shell commands with the benefit of not needing a separate package for each command we may want to use.
build
and watch
use the TypeScript compiler tsc
to compile the TypeScript files according to tsconfig.json
. tsc
must be installed globally or locally (inside the current package), usually via the npm package typescript
.
test
and testall
use the unit test framework Mocha to run one test or all tests.
prepack
: This script is run run before a tarball is packed (due to npm pack
, npm publish
, or an installation from git).
Note that when we are using an IDE, we don’t need the scripts build
and watch
because we can let the IDE build the artifacts. But they are needed for the script prepack
.
dependencies
vs. devDependencies
dependencies
should only contain the packages that are needed when importing a package. That excludes packages that are used for running tests etc.
Packages whose names start with @types/
provide TypeScript type definitions for packages that don’t have any. Without the former, we can’t use the latter. Are these normal dependencies or dev dependencies? It depends:
If the type definitions of our package refer to type definitions in another package, that package is a normal dependency.
Otherwise, the package is only needed during development time and a dev dependency.
package.json
package.json
explain various properties of that file.scripts
explain the package.json
property scripts
.tsconfig.json
{
"compilerOptions": {
"rootDir": "ts",
"outDir": "dist",
"target": "es2019",
"lib": [
"es2019"
],
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"declaration": true,
"sourceMap": true
}
}
rootDir
: Where are our TypeScript files located?
outDir
: Where should the compilation results be put?
target
: What is the targeted ECMAScript version? If the TypeScript code uses a feature that is not supported by the targeted version, then it is compiled to equivalent code that only uses supported features.
lib
: What platform features should TypeScript be aware of? Possibilities include the ECMAScript standard library and the DOM of browsers. The Node.js API is supported differently, via the package @types/node
.
module
: Specifies the format of the compilation output.
The remaining options are explained by the official documentation for tsconfig.json
.
index.ts
This file provides the actual functionality of the package:
import endsWith from 'lodash/endsWith';
function removeSuffix(str: string, suffix: string) {
export if (!endsWith(str, suffix)) {
new Error(JSON.stringify(suffix)} + ' is not a suffix of ' +
throw JSON.stringify(str));
}.slice(0, -suffix.length);
return str }
It uses function endsWith()
of the library Lodash. That’s why Lodash is a normal dependency – it is needed at runtime.
index_test.ts
This file contains a unit test for index.ts
:
import { strict as assert } from 'assert';
import { removeSuffix } from '../src/index';
test('removeSuffix()', () => {
.equal(
assertremoveSuffix('myfile.txt', '.txt'),
'myfile');
.throws(() => removeSuffix('myfile.txt', 'abc'));
assert; })
We can run the test like this:
npm t dist/test/index_test.js
t
is an abbreviation for the npm command test
.test
is an abbreviation for run test
(which runs the script test
from package.json
).As you can see, we are running the compiled version of the test (in directory dist/
), not the TypeScript code.
For more information on the unit test framework Mocha, see its homepage.