Skip to content

Commit 9a6f1b1

Browse files
Johannes KadakKarlMae
Johannes Kadak
andauthored
feat: compile as ES Module (#431)
* feat: compile as ES Module * update CI workflows to newer node * release workflow to newer node * update yarn version to 3.3.1 * explicitly set yarnPath * fix: frozen-lockfile to immutable * replace isomorphic-fetch with cross-fetch * fix: done() called only once * update jest + fix a few tests * Add .js to local imports as per https://github.com/microsoft/TypeScript/issues/16577\#issuecomment-754941937 * prettier write * example for adding it in the browser * github workflows to one version (hashes may change when changing node version, due to yarn patches) * update package.json * Update yarn lock * fix: graphql subscription tests don't leak handles * Node tests for graphql subscriptions are now working * Don't try to make WebSocket tests work in two environments After struggling for a long while with trying to "shim" isomorphic-ws to work normally in a jest environment, it is easier to just run this test only in a Node environment. Here are the issues why this test fails in JSDOM: 1. Imports fail The statement `import WebSocket from 'isomorphic-ws';` is passed to the Jest module resolver, which utilises `ts-jest-resolver` (in our case) to find the right file to load. Sadly, it will always load the "main" module of a node package. 2. isomorphic-ws "browser" option is ESM for some reason If we use jest.config.cjs "resolver" field [1] to define a custom script, we will be able to modify the "main" field by patching package.json in memory. This works fine, however isomorphic-ws's browser version is an ES Module for some reason, even if the package.json > "type" is "commonjs". 3. Something inside jest doesn't want me to hack it to support ESM If I override the package.json even harder to also change the "type" to "module" just for isomorphic-ws, Jest doesn't respect it 4. ts-jest doesn't want to change the ESM imports to CJS imports Since... we told it to not change ESM imports to CJS imports, it will not. [1]: https://jestjs.io/docs/configuration#resolver-string * prettier write * follow flags in tsconfig --------- Co-authored-by: karl <[email protected]>
1 parent 1484a11 commit 9a6f1b1

29 files changed

+9901
-6384
lines changed

.eslintrc.json

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,34 @@
22
"env": {
33
"browser": true
44
},
5-
"extends": ["eslint:recommended", "@qminder", "prettier"],
5+
"plugins": ["import", "@typescript-eslint"],
6+
"extends": [
7+
"eslint:recommended",
8+
"prettier",
9+
"plugin:import/recommended",
10+
"plugin:import/typescript"
11+
],
12+
"parser": "@typescript-eslint/parser",
613
"parserOptions": {
14+
"ecmaVersion": 2020,
715
"sourceType": "module"
816
},
17+
"settings": {
18+
"import/resolver": {
19+
"typescript": {}
20+
}
21+
},
22+
"rules": {
23+
"import/namespace": "off",
24+
"no-unused-vars": "off",
25+
"import/no-named-as-default": "off"
26+
},
927
"overrides": [
1028
{
11-
"files": ["*.test.ts"],
29+
"files": ["*.test.ts", "test/unit/jestSetupFile.ts"],
1230
"env": {
13-
"jest": true
31+
"jest": true,
32+
"node": true
1433
},
1534
"rules": {
1635
"func-names": "off"

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.yarn/releases/*.cjs binary
2+
.yarn/plugins/@yarnpkg/*.cjs binary

.github/workflows/code-coverage.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ jobs:
1010
- uses: actions/checkout@master
1111
- uses: actions/setup-node@v3
1212
with:
13-
node-version: 14.x
14-
- run: yarn install --frozen-lockfile
13+
node-version: 16.x
14+
cache: 'yarn'
15+
- run: yarn install --immutable
1516
- run: yarn jest --coverage
1617
- uses: codecov/codecov-action@v3
1718
with:

.github/workflows/push.yml

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,71 @@ on: push
22
name: Test on push
33
jobs:
44
test:
5-
strategy:
6-
matrix:
7-
node-version: [12.x, 14.x, 15.x, 16.x]
8-
name: Test on NodeJS ${{ matrix.node-version }}
5+
name: Test (JSDOM)
96
runs-on: ubuntu-latest
107
steps:
118
- uses: actions/checkout@master
129
- uses: actions/setup-node@v3
1310
with:
14-
node-version: ${{ matrix.node-version }}
11+
node-version: 16.x
12+
cache: 'yarn'
1513
- name: Install Deps
16-
run: yarn install --frozen-lockfile
14+
run: yarn install --immutable
1715
- name: Run tests (browser)
1816
run: yarn test
17+
test_node:
18+
name: Test (Node.js)
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@master
22+
- uses: actions/setup-node@v3
23+
with:
24+
node-version: 16.x
25+
cache: 'yarn'
26+
- name: Install Deps
27+
run: yarn install --immutable
1928
- name: Run tests (node)
2029
run: yarn test-node
30+
test_pack:
31+
name: Build
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@master
35+
- uses: actions/setup-node@v3
36+
with:
37+
node-version: 16.x
38+
cache: 'yarn'
39+
- name: Install Deps
40+
run: yarn install --immutable
2141
- name: Build the package
2242
run: yarn pack
2343
prettier:
2444
name: Check prettier
25-
runs-on: ubuntu-18.04
45+
runs-on: ubuntu-latest
2646
steps:
2747
- uses: actions/checkout@master
48+
- uses: actions/setup-node@v3
49+
with:
50+
node-version: 16.x
51+
cache: 'yarn'
2852
- name: Install Deps
2953
run: |
30-
yarn install --frozen-lockfile
54+
yarn install --immutable
3155
- name: Prettier check
3256
run: |
3357
yarn run lint-prettier
3458
eslint:
3559
name: Check eslint
36-
runs-on: ubuntu-18.04
60+
runs-on: ubuntu-latest
3761
steps:
3862
- uses: actions/checkout@master
63+
- uses: actions/setup-node@v3
64+
with:
65+
node-version: 16.x
66+
cache: 'yarn'
3967
- name: Install Deps
4068
run: |
41-
yarn install --frozen-lockfile
69+
yarn install --immutable
4270
- name: Eslint check
4371
run: |
4472
yarn run lint-eslint

.github/workflows/release.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ jobs:
1212
- uses: actions/checkout@master
1313
- uses: actions/setup-node@v3
1414
with:
15-
node-version: 12.x
15+
node-version: 16.x
16+
cache: 'yarn'
1617
always-auth: true
1718
registry-url: https://registry.npmjs.org
1819
- name: Install Deps
19-
run: yarn install --frozen-lockfile --ignore-scripts
20+
run: yarn install --immutable --ignore-scripts
2021
env:
2122
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
2223
- name: Build Library

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,10 @@ graphql.config.json
3434
.idea
3535

3636
build
37+
38+
.yarn/*
39+
!.yarn/releases
40+
!.yarn/plugins
41+
!.yarn/sdks
42+
!.yarn/versions
43+
.pnp.*

.yarn/releases/yarn-3.3.1.cjs

Lines changed: 823 additions & 0 deletions
Large diffs are not rendered by default.

.yarnrc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
nodeLinker: node-modules
2+
yarnPath: .yarn/releases/yarn-3.3.1.cjs

examples/browser/index.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Qminder API in browser</title>
8+
</head>
9+
<body>
10+
<label for="apikey">API key:</label><input type="password" id="apikey" />
11+
<button id="load-button">Load locations</button>
12+
<div id="output"></div>
13+
<script type="module" src="dist/main.out.js"></script>
14+
</body>
15+
</html>

examples/browser/main.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as Qminder from '../../build/qminder-api.js';
2+
3+
const buttonEl = document.getElementById('load-button');
4+
const outputEl = document.getElementById('output');
5+
const apiKeyEl = document.getElementById('apikey');
6+
7+
buttonEl.addEventListener('click', async () => {
8+
Qminder.setKey(apiKeyEl.value);
9+
const locations = await Qminder.locations.list();
10+
for (const location of locations) {
11+
const div = document.createElement('div');
12+
div.textContent = location.name;
13+
outputEl.appendChild(div);
14+
}
15+
});

examples/browser/webpack.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default {
2+
mode: 'production',
3+
entry: './main.js',
4+
output: {
5+
filename: '[name].out.js',
6+
},
7+
};

jest.config.js renamed to jest.config.cjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
2-
preset: 'ts-jest',
2+
preset: 'ts-jest/presets/default-esm',
3+
resolver: 'ts-jest-resolver',
34
testEnvironment: 'jsdom',
45
setupFilesAfterEnv: ['jest-sinon', '<rootDir>/test/unit/jestSetupFile.ts'],
56
moduleFileExtensions: ['js', 'ts'],

package.json

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,43 +26,53 @@
2626
"bugs": "https://github.com/Qminder/javascript-api/issues",
2727
"author": "Qminder <[email protected]> (https://www.qminder.com)",
2828
"license": "Apache-2.0",
29+
"type": "module",
2930
"engines": {
30-
"node": ">=10.0"
31+
"node": ">=14.0"
3132
},
3233
"browserslist": [
3334
"last 2 major versions"
3435
],
36+
"exports": {
37+
".": {
38+
"import": "./build/qminder-api.js",
39+
"types": "./build/qminder-api.d.ts"
40+
}
41+
},
3542
"main": "build/qminder-api.js",
36-
"types": "build/qminder-api.d.ts",
3743
"repository": "Qminder/javascript-api",
3844
"devDependencies": {
3945
"@babel/core": "7.20.12",
4046
"@babel/preset-env": "7.20.2",
41-
"@qminder/eslint-config": "0.0.3",
4247
"@types/jest": "26.0.24",
4348
"@types/node": "18.11.18",
4449
"@types/sinon": "10.0.13",
45-
"@typescript-eslint/eslint-plugin": "4.33.0",
46-
"@typescript-eslint/parser": "4.33.0",
47-
"eslint": "7.32.0",
50+
"@typescript-eslint/eslint-plugin": "^5.49.0",
51+
"@typescript-eslint/parser": "^5.49.0",
52+
"eslint": "^8.32.0",
4853
"eslint-config-prettier": "8.6.0",
49-
"eslint-plugin-import": "2.27.5",
50-
"jest": "26.6.3",
54+
"eslint-import-resolver-typescript": "^3.5.3",
55+
"eslint-plugin-import": "^2.27.5",
56+
"graphql": "15.8.0",
57+
"graphql-tag": "2.12.6",
58+
"jest": "^29.4.0",
59+
"jest-environment-jsdom": "^29.4.0",
5160
"jest-sinon": "1.0.4",
5261
"prettier": "2.8.3",
5362
"rxjs": "7.8.0",
5463
"sinon": "15.0.1",
55-
"ts-jest": "26.5.6",
64+
"ts-jest": "^29.0.5",
65+
"ts-jest-resolver": "^2.0.0",
66+
"ts-loader": "^9.4.2",
5667
"typedoc": "0.22.17",
5768
"typescript": "4.9.5",
58-
"graphql": "15.8.0",
59-
"graphql-tag": "2.12.6"
69+
"webpack": "^5.75.0",
70+
"webpack-cli": "^5.0.1"
6071
},
6172
"dependencies": {
62-
"@types/isomorphic-fetch": "^0.0.36",
6373
"@types/ws": "^8.0.0",
74+
"cross-fetch": "^3.1.5",
6475
"es6-promise": "^4.1.1",
65-
"isomorphic-fetch": "^3.0.0",
6676
"isomorphic-ws": "^5.0.0",
6777
"ws": "^8.0.0"
6878
},
@@ -74,5 +84,6 @@
7484
"publishConfig": {
7585
"access": "public",
7686
"registry": "https://registry.npmjs.org"
77-
}
87+
},
88+
"packageManager": "[email protected]"
7889
}

scripts/build.sh

100644100755
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ set -e
66
#
77

88
rm -rf build
9-
tsc --declaration --outDir build --module commonjs --target es2017 --moduleResolution node --esModuleInterop true ./src/qminder-api.ts
9+
yarn run tsc -p .

src/api-base.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import fetch from 'isomorphic-fetch';
2-
import { GraphQLApiError } from './util/errors';
3-
import { ClientError } from './model/ClientError';
1+
import fetch from 'cross-fetch';
2+
import { GraphQLApiError } from './util/errors.js';
3+
import { ClientError } from './model/ClientError.js';
44

55
type HTTPMethod =
66
| 'GET'
@@ -139,9 +139,6 @@ class ApiBase {
139139
*/
140140
constructor() {
141141
this.fetch = fetch;
142-
if (typeof (fetch as any).default === 'function') {
143-
this.fetch = (fetch as any).default as Function;
144-
}
145142
this.setServer('api.qminder.com');
146143
}
147144

src/model/User.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Location from './Location';
1+
import Location from './Location.js';
22
/**
33
* Represents a single user picture.
44
* The 'medium' sized user picture is available if the user has an image.

src/model/Webhook.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ID } from './ID';
1+
import { ID } from './ID.js';
22
/**
33
* A Webhook is a URL that Qminder sends automatic POST requests into, in order to notify
44
* downstream listeners about various events such as ticket creation or location changes.

src/qminder-api.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
// Import data structures
2-
import Desk from './model/Desk';
3-
import Device from './model/Device';
4-
import Line from './model/Line';
5-
import Location from './model/Location';
6-
import Ticket from './model/Ticket';
7-
import User from './model/User';
8-
import Webhook from './model/Webhook';
9-
import { ClientError } from './model/ClientError';
2+
import Desk from './model/Desk.js';
3+
import Device from './model/Device.js';
4+
import Line from './model/Line.js';
5+
import Location from './model/Location.js';
6+
import Ticket from './model/Ticket.js';
7+
import User from './model/User.js';
8+
import Webhook from './model/Webhook.js';
9+
import { ClientError } from './model/ClientError.js';
1010

1111
// Import services
12-
import ApiBase from './api-base';
13-
import DeviceService from './services/DeviceService';
14-
import LineService from './services/LineService';
15-
import LocationService from './services/LocationService';
16-
import TicketService from './services/TicketService';
17-
import UserService from './services/UserService';
18-
import WebhooksService from './services/WebhooksService';
19-
import GraphQLService, { ConnectionStatus } from './services/GraphQLService';
12+
import ApiBase from './api-base.js';
13+
import DeviceService from './services/DeviceService.js';
14+
import LineService from './services/LineService.js';
15+
import LocationService from './services/LocationService.js';
16+
import TicketService from './services/TicketService.js';
17+
import UserService from './services/UserService.js';
18+
import WebhooksService from './services/WebhooksService.js';
19+
import GraphQLService, { ConnectionStatus } from './services/GraphQLService.js';
2020

2121
// Export all data structures
2222
export {

src/services/DeviceService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import ApiBase from '../api-base';
2-
import Device from '../model/Device';
3-
import { extractId, IdOrObject } from '../util/id-or-object';
1+
import ApiBase from '../api-base.js';
2+
import Device from '../model/Device.js';
3+
import { extractId, IdOrObject } from '../util/id-or-object.js';
44

55
/**
66
* DeviceService allows the developer to manage devices such as iPads that have the Qminder

0 commit comments

Comments
 (0)