Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved typings compatibility #371

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
41213e0
use typescript 3.6.x for backwards compatibility w.r.t. to "ambient a…
russaa Dec 6, 2019
c6bce4b
improve compatibility for typings w.r.t. AsyncIterator
russaa Dec 6, 2019
87dbf05
revert downgrading typescript version
russaa Dec 17, 2019
639740a
Merge branch 'master' into feature_improved-typings-compatibility
russaa Dec 17, 2019
cc0f475
use github:web-streams-polyfill/build/downlevel-dts for creating back…
russaa Dec 17, 2019
3c7c638
Merge branch 'master' into feature_improved-typings-compatibility
russaa Jan 22, 2020
d761f5f
reverted hardcoded downgrading for AsyncIterator and improved downlev…
russaa Jan 23, 2020
54a424e
added tests for tsc version typings compatibility
russaa Jan 23, 2020
1eaa161
renamed typings test & dir
russaa Jan 23, 2020
28bf175
do create compatibility typings when running tests
russaa Jan 23, 2020
ef9c9e2
use strict compiler setting for typings-compatibility tests
russaa Jan 28, 2020
4a7c3ef
make used compiler settings explicit
russaa Jan 28, 2020
ba01930
changed minimal target/lib requirements for typings compatibility tes…
russaa Jan 28, 2020
b820f84
fix formatting
russaa Jan 28, 2020
8b6965c
skip typings-compatibility-test in ALPINE environment
russaa Jan 30, 2020
4874024
TEST print env setting for EXCLUDE_TYPINGS_COMPAT_TESTS
russaa Jan 30, 2020
2021ad7
re-enable typings-compatibility-test in ALPINE environment
russaa Jan 30, 2020
d6c0637
detect package manager npm or yarn and use for typings compatibility …
russaa Jan 30, 2020
54f65c0
FIX formatting
russaa Jan 30, 2020
7a44b4e
fix/improve informational fields and description
russaa Jan 30, 2020
ddb4c14
Merge branch 'master' into feature_improved-typings-compatibility
russaa Jan 30, 2020
32479c9
added example and requirements for use with TypeScript to README
russaa Feb 4, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Semantically similar to the [native](https://github.com/zeromq/libzmq) ØMQ library, while sticking to JavaScript idioms.
* Use modern JavaScript and Node.js features such as `async`/`await` and async iterators.
* High performance.
* Fully usable with TypeScript (3.6+).
* Fully usable with TypeScript (3+).

### Useful links

Expand All @@ -26,6 +26,7 @@
* [Push/Pull](#pushpull)
* [Pub/Sub](#pubsub)
* [Req/Rep](#reqrep)
* [TypeScript](#typescript)
* [More examples](#more-examples)
* [Compatibility layer for version 4/5](#compatibility-layer-for-version-45)
* [Contribution](#contribution)
Expand Down Expand Up @@ -228,6 +229,29 @@ async function run() {
run()
```

## TypeScript

The library provides typings for TypeScript version 3.0.x and later.


* _Requirements_
* TypeScript version >= 3
* [compilerOptions](https://www.typescriptlang.org/docs/handbook/compiler-options.html)
for TypeScript version < 3.6:
* either set `compilerOptions.target` to `esnext` or later (e.g. `es2018`)
* or add the following, or similar, libraries to `compilerOptions.lib`
(and include their corresponding polyfills if needed):
`es2015`, `ESNext.AsyncIterable`
* _Example Usage_
```typescript
import { Request } from "zeromq"
// or as namespace
import * as zmq from "zeromq"

const reqSock = new Request()
//...
const repSock = new zmq.Reply()
```

## More examples

Expand Down
14 changes: 12 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
"description": "Next-generation ZeroMQ bindings for Node.js",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"typesVersions": {
">=3.6": {
"lib/*": [
"lib/ts3.6/*"
]
}
},
"gypfile": true,
"repository": {
"type": "git",
Expand All @@ -15,6 +22,7 @@
"devDependencies": {
"@gnd/typedoc": "^0.15.0-0",
"@types/chai": ">= 4.1",
"@types/fs-extra": "^8.0.1",
"@types/mocha": ">= 5.2",
"@types/node": ">= 8.0",
"@types/semver": ">= 0",
Expand All @@ -27,6 +35,7 @@
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-prettier": "^3.1.1",
"fs-extra": "^8.1.0",
"gunzip-maybe": "^1.4.1",
"mocha": ">= 4.0",
"node-addon-api": "nodejs/node-addon-api",
Expand All @@ -35,6 +44,7 @@
"prettier": "^1.19.1",
"semver": ">= 0",
"tar-fs": "^2.0.0",
"ts-morph": "^6.0.2",
"ts-node": ">= 7",
"typescript": ">= 3.6",
"weak-napi": ">= 1.0"
Expand Down Expand Up @@ -63,11 +73,11 @@
],
"scripts": {
"install": "node-gyp-build",
"ci:compile": "tsc --project tsconfig-build.json",
"ci:compile": "tsc --project tsconfig-build.json && node script/ci/downlevel-dts.js",
"ci:doc": "typedoc --out docs --name zeromq.js --excludeProtected --excludePrivate --excludeNotExported --excludeExternals --externalPattern 'src/+(draft|native|compat).ts' --tsconfig tsconfig-build.json --mode file",
"ci:prebuild": "prebuildify --napi --strip",
"dev:build": "rm -f vendor/* && touch vendor/.gitkeep && cp node_modules/node-addon-api/{*.h,LICENSE.md} vendor && prebuildify --napi --build-from-source --debug",
"dev:test": "tsc --project tsconfig-build.json && mocha && script/format.sh && rm -f tmp/*",
"dev:test": "tsc --project tsconfig-build.json && node script/ci/downlevel-dts.js && mocha && script/format.sh && rm -f tmp/*",
"dev:bench": "node --expose-gc test/bench"
},
"keywords": [
Expand Down
183 changes: 183 additions & 0 deletions script/ci/downlevel-dts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Based on web-streams-polyfill/build/downlevel-dts (MIT licensed) by Mattias Buelens, Diwank Singh Tomer
// https://github.com/MattiasBuelens/web-streams-polyfill/blob/57d83d0372428532fab83f8d252a5aad950892da/build/downlevel-dts.js
// Based on downlevel-dts (MIT licensed) by Nathan Shively-Sanders
// https://github.com/sandersn/downlevel-dts/blob/e7d1cb5aced5686826fe8aac4d4af2f745a9ef60/index.js

/* eslint-env node */

const {Project, ts} = require("ts-morph")
const path = require("path")
const fs = require("fs-extra")

const project = new Project()
const inputDir = project.addDirectoryAtPath(path.join(__dirname, "../../lib/"))

// Create output directory
fs.emptyDirSync(path.join(inputDir.getPath().toString(), "ts3.6"))
const ts36Dir = inputDir.createDirectory("ts3.6")
project.saveSync()

// Down-level all *.d.ts files in input directory
const files = inputDir.addSourceFilesAtPaths("*.d.ts")
for (const f of files) {
// Create copy for TypeScript 3.6+
copyTypingsFile(f, ts36Dir)
downlevelTS36(f)
downlevelTS34(f)
// Original file will be overwritten by down-leveled file when saved
}
project.saveSync()

/**
* Copy typings source file *.d.ts to target dir
*/
function copyTypingsFile(f, targetDir) {
const cf = f.copyToDirectory(targetDir, {overwrite: true})
const srcPath = f.getDirectoryPath()
const targetPath = targetDir.getPath()
revertModulePathChange(cf.getImportDeclarations(), srcPath, targetPath)
revertModulePathChange(cf.getExportDeclarations(), srcPath, targetPath)
}

/**
* HELPER for reverting changed relative paths for import/export declarations in
* copied files, so they to not point to the source-directory but the other
* copied files in the target directory.
*
* For example:
* [original in srcDir] from "./<module-file>"
* [after copied to targetDir] from "../<module-file>"
* [reverted in targetDir] from "./<module-file>"
*/
function revertModulePathChange(importExportDecl, srcDir, targetDir) {
const absSrcDir = path.resolve(srcDir)
for (const decl of importExportDecl) {
if (!decl.isModuleSpecifierRelative()) {
continue
}
const declPath = decl.getModuleSpecifierValue()
if (!declPath) {
continue
}
const absDeclPath = path.resolve(path.join(targetDir, declPath))
if (absDeclPath.indexOf(absSrcDir) === 0) {
decl.setModuleSpecifier(relativeModulePath(absDeclPath, targetDir))
}
}
}

/**
* Down-level TypeScript 3.6 types in the given source file
*/
function downlevelTS36(f) {
// Replace get/set accessors with (read-only) properties
const gs = f.getDescendantsOfKind(ts.SyntaxKind.GetAccessor)
for (const g of gs) {
const comment = getLeadingComments(g)
const s = g.getSetAccessor()
const returnTypeNode = g.getReturnTypeNode()
const returnType = returnTypeNode ? returnTypeNode.getText() : "any"
g.replaceWithText(
`${comment}${getModifiersText(g)}${
s ? "" : "readonly "
}${g.getName()}: ${returnType};`,
)
if (s) {
s.remove()
}
}
const ss = f.getDescendantsOfKind(ts.SyntaxKind.SetAccessor)
for (const s of ss) {
const g = s.getGetAccessor()
if (!g) {
const comment = getLeadingComments(s)
const firstParam = s.getParameters()[0]
const firstParamTypeNode = firstParam && firstParam.getTypeNode()
const firstParamType = firstParamTypeNode
? firstParamTypeNode.getText()
: "any"
s.replaceWithText(
`${comment}${getModifiersText(s)}${s.getName()}: ${firstParamType};`,
)
}
}
}

/**
* Down-level TypeScript 3.4 types in the given source file
*/
function downlevelTS34(f) {
// Replace "es2018.asynciterable" with "esnext.asynciterable" in lib references
const refs = f.getLibReferenceDirectives()
for (const r of refs) {
if (r.getFileName() === "es2018.asynciterable") {
f.replaceText([r.getPos(), r.getEnd()], "esnext.asynciterable")
}
}
downlevelEs2018(f)
}

/**
* Down-level es2018 to esnext library in the given source file
*/
function downlevelEs2018(f) {
// Replace AsyncIterator<T1,T2> with AsyncIterator<T1>
const typeParams = f.getDescendantsOfKind(ts.SyntaxKind.TypeReference)
for (const t of typeParams) {
if (t.wasForgotten()) {
continue
}
const typeName = t.getTypeName().getText()
if (typeName === "AsyncIterator") {
const params = t.getTypeArguments()
if (params.length > 1) {
t.replaceWithText(`${typeName}<${params[0].getText()}>`)
}
}
}
}

function getModifiersText(node) {
const modifiersText = node
.getModifiers()
.map(m => m.getText())
.join(" ")
return modifiersText.length > 0 ? modifiersText + " " : ""
}

function getLeadingComments(node) {
const t = node.getText()
const tlen = t.length
const ct = node.getText(true)
const ctlen = ct.length
// if no comment, or comment not leading, return empty string:
if (tlen === ctlen || ct.indexOf(t) !== ctlen - tlen) {
return ""
}
// remove indentation (execept 1 space for "stars-aligning") of comment lines,
// since they will be re-indented on insertion
// (and remove all leading whitespaces from non-comment lines)
return ct
.replace(t, "")
.replace(/(\r?\n)\s+ /gm, "$1 ")
.replace(/(\r?\n)\s+$/gm, "$1")
}

function relativeModulePath(fromAbsModulePath, toAbsTargetDir) {
const fromModDir = path.dirname(fromAbsModulePath)
const revertedPath = path.resolve(
fromModDir,
path.relative(fromModDir, toAbsTargetDir),
)
let relMod = path.relative(
revertedPath,
path.join(revertedPath, path.basename(fromAbsModulePath)),
)
if (!/^\./.test(relMod)) {
relMod = "./" + relMod
}
if (path.sep === "/") {
return relMod
}
return relMod.replace(/\\/g, "/")
}
27 changes: 27 additions & 0 deletions test/typings-compatibility/template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# template files for typescript compile test

the tsc versions test is for ensuring the library can be used in projects
with supported typescript versions

the test cases are specified in `test/unit/tsc-versions-test.ts`

during the test, temporary directories are created for the corresponding
typescript version x.x.x:
```
test/tsc-versions/ts-x.x.x/
package.json
tsconfig.json
<modified copy of typings-test.ts>
```

the file `typings-test.ts` is copied from `test/unit/typings-test.ts`
and only its `import` statement for `zmq` is changed from
```typescript
// from:
import * as zmq from "../../src"
// modified to:
import * as zmq from "../../../"
```

relevant fields in `package.json` and `tsconfig.json` are set according to the
test case definition when they are copied to the temporary directory
13 changes: 13 additions & 0 deletions test/typings-compatibility/template/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "test-typings-ts-x.x.x",
"version": "0.0.0",
"description": "template for testing typings in ts projects with specific ts version",
"main": "typings-test.ts",
"scripts": {
"test": "echo TypeScript && tsc --version && tsc --project tsconfig.json"
},
"license": "MIT",
"devDependencies": {
"typescript": "x.x.x"
}
}
10 changes: 10 additions & 0 deletions test/typings-compatibility/template/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"exclude": ["*.d.ts"],
"compilerOptions": {
"target": "esXXX",
"noEmit": true,
"strict": true,
"module": "commonjs",
"types": ["node", "mocha"]
}
}
Loading