diff --git a/.jsdoc.js b/.jsdoc.js index bf72c506..d56c069b 100644 --- a/.jsdoc.js +++ b/.jsdoc.js @@ -22,18 +22,13 @@ module.exports = { template: './node_modules/jsdoc-fresh', recurse: true, verbose: true, - destination: './docs/' + destination: './docs/', }, - plugins: [ - 'plugins/markdown', - 'jsdoc-region-tag' - ], + plugins: ['plugins/markdown', 'jsdoc-region-tag'], source: { excludePattern: '(^|\\/|\\\\)[._]', - include: [ - 'build/src', - ], - includePattern: '\\.js$' + include: ['build/esm/src/'], + includePattern: '\\.js$', }, templates: { copyright: 'Copyright 2019 Google, LLC.', @@ -42,10 +37,10 @@ module.exports = { systemName: 'gaxios', theme: 'lumen', default: { - outputSourceFiles: false - } + outputSourceFiles: false, + }, }, markdown: { - idInHeadings: true - } + idInHeadings: true, + }, }; diff --git a/README.md b/README.md index 85622aa7..9513e596 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ $ npm install gaxios ## Example ```js -const {request} = require('gaxios'); -const res = await request('https://www.googleapis.com/discovery/v1/apis/'); +import {request} from 'gaxios'; +const res = await request({url: 'https://google.com/'}); ``` ## Setting Defaults @@ -24,14 +24,16 @@ const res = await request('https://www.googleapis.com/discovery/v1/apis/'); Gaxios supports setting default properties both on the default instance, and on additional instances. This is often useful when making many requests to the same domain with the same base settings. For example: ```js -const gaxios = require('gaxios'); -gaxios.instance.defaults = { +import {request, instance} from 'gaxios'; + +instance.defaults = { baseURL: 'https://example.com' headers: new Headers({ Authorization: 'SOME_TOKEN' }) } -gaxios.request({url: '/data'}).then(...); + +await request({url: '/data'}); ``` Note that setting default values will take precedence diff --git a/package.json b/package.json index c5a00926..953e8443 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,29 @@ "name": "gaxios", "version": "6.7.1", "description": "A simple common HTTP client specifically for Google APIs and services.", - "main": "build/src/index.js", - "types": "build/src/index.d.ts", + "main": "build/cjs/src/index.js", + "types": "build/cjs/src/index.d.ts", "files": [ - "build/src" + "build/" ], + "exports": { + ".": { + "import": { + "types": "./build/esm/src/index.d.ts", + "default": "./build/esm/src/index.js" + }, + "require": { + "types": "./build/cjs/src/index.d.ts", + "default": "./build/cjs/src/index.js" + } + } + }, "scripts": { "lint": "gts check", - "test": "c8 mocha build/test", + "test": "c8 mocha build/esm/test", "presystem-test": "npm run compile", - "system-test": "mocha build/system-test --timeout 80000", - "compile": "tsc -p .", + "system-test": "mocha build/esm/system-test --timeout 80000", + "compile": "tsc -b ./tsconfig.json ./tsconfig.cjs.json && node utils/enable-esm.mjs", "fix": "gts fix", "prepare": "npm run compile", "pretest": "npm run compile", @@ -24,8 +36,7 @@ "predocs-test": "npm run docs", "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", "prelint": "cd samples; npm link ../; npm install", - "clean": "gts clean", - "precompile": "gts clean" + "clean": "gts clean" }, "repository": "googleapis/gaxios", "keywords": [ @@ -75,6 +86,7 @@ "ncp": "^2.0.0", "nock": "^14.0.0-beta.13", "null-loader": "^4.0.0", + "pack-n-play": "^2.0.3", "puppeteer": "^23.0.0", "sinon": "^17.0.0", "stream-browserify": "^3.0.0", diff --git a/src/common.ts b/src/common.ts index 17572649..a0590a46 100644 --- a/src/common.ts +++ b/src/common.ts @@ -13,10 +13,13 @@ import {Agent} from 'http'; import {URL} from 'url'; +import {Readable} from 'stream'; -import {pkg} from './util'; import extend from 'extend'; -import {Readable} from 'stream'; + +import util from './util.cjs'; + +const pkg = util.pkg; /** * TypeScript does not have this type available globally - however `@types/node` includes `undici-types`, which has it: diff --git a/src/gaxios.ts b/src/gaxios.ts index dba42b9d..3e8982e9 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -25,10 +25,10 @@ import { GaxiosPromise, GaxiosResponse, defaultErrorRedactor, -} from './common'; -import {getRetryConfig} from './retry'; +} from './common.js'; +import {getRetryConfig} from './retry.js'; import {Readable} from 'stream'; -import {GaxiosInterceptorManager} from './interceptor'; +import {GaxiosInterceptorManager} from './interceptor.js'; /* eslint-disable @typescript-eslint/no-explicit-any */ diff --git a/src/index.ts b/src/index.ts index c563eac6..1b98e087 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,8 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {GaxiosOptions} from './common'; -import {Gaxios} from './gaxios'; +import {GaxiosOptions} from './common.js'; +import {Gaxios} from './gaxios.js'; export { GaxiosError, @@ -20,9 +20,9 @@ export { GaxiosResponse, GaxiosOptionsPrepared, RetryConfig, -} from './common'; +} from './common.js'; export {Gaxios, GaxiosOptions}; -export * from './interceptor'; +export * from './interceptor.js'; /** * The default instance used when the `request` method is directly diff --git a/src/interceptor.ts b/src/interceptor.ts index 9ccfad86..2e8e0689 100644 --- a/src/interceptor.ts +++ b/src/interceptor.ts @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {GaxiosError, GaxiosOptionsPrepared, GaxiosResponse} from './common'; +import {GaxiosError, GaxiosOptionsPrepared, GaxiosResponse} from './common.js'; /** * Interceptors that can be run for requests or responses. These interceptors run asynchronously. diff --git a/src/retry.ts b/src/retry.ts index 8c732d3f..68c33167 100644 --- a/src/retry.ts +++ b/src/retry.ts @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {GaxiosError, RetryConfig} from './common'; +import {GaxiosError, RetryConfig} from './common.js'; export async function getRetryConfig(err: GaxiosError) { let config = getConfig(err); diff --git a/src/util.ts b/src/util.cts similarity index 90% rename from src/util.ts rename to src/util.cts index 1428c3bc..9c201694 100644 --- a/src/util.ts +++ b/src/util.cts @@ -11,7 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -export const pkg: { +const pkg: { name: string; version: string; -} = require('../../package.json'); +} = require('../../../package.json'); + +export = {pkg}; diff --git a/system-test/fixtures/sample/package.json b/system-test/fixtures/sample/package.json index 34935ed6..abafbbb9 100644 --- a/system-test/fixtures/sample/package.json +++ b/system-test/fixtures/sample/package.json @@ -1,6 +1,7 @@ { "name": "gaxios-sample-fixture", - "description": "An app we're using to test the library. ", + "description": "An app we're using to test the library.", + "type": "commonjs", "scripts": { "check": "gts check", "clean": "gts clean", diff --git a/system-test/test.install.ts b/system-test/test.install.ts index 9f64c01a..62875dda 100644 --- a/system-test/test.install.ts +++ b/system-test/test.install.ts @@ -16,60 +16,131 @@ import assert from 'assert'; import execa from 'execa'; import fs from 'fs'; import mv from 'mv'; -import {ncp} from 'ncp'; +import ncp from 'ncp'; import path from 'path'; import tmp from 'tmp'; import {promisify} from 'util'; import {describe, it, before, after} from 'mocha'; +import {packNTest} from 'pack-n-play'; + +import {createServer, Server} from 'node:http'; + +import util from '../src/util.cjs'; + +/** + * Optionally keep the staging directory between tests. + */ +const KEEP_STAGING_DIRECTORY = false; -const keep = false; const mvp = promisify(mv) as {} as (...args: string[]) => Promise; const ncpp = promisify(ncp); -const stagingDir = tmp.dirSync({keep, unsafeCleanup: true}); -const stagingPath = stagingDir.name; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const pkg = require('../../package.json'); + +const pkg = util.pkg; describe('📦 pack and install', () => { - /** - * Create a staging directory with temp fixtures used to test on a fresh - * application. - */ - before('pack and install', async () => { - await execa('npm', ['pack']); - const tarball = `${pkg.name}-${pkg.version}.tgz`; - await mvp(tarball, `${stagingPath}/gaxios.tgz`); - await ncpp('system-test/fixtures/sample', `${stagingPath}/`); - await execa('npm', ['install'], { - cwd: `${stagingPath}/`, - stdio: 'inherit', - }); - }); + let stagingDir: tmp.DirResult; + let stagingPath: string; - it('should run the sample', async () => { - await execa('node', ['--throw-deprecation', 'build/src/index.js'], { - cwd: `${stagingPath}/`, - stdio: 'inherit', + before(() => { + stagingDir = tmp.dirSync({ + keep: KEEP_STAGING_DIRECTORY, + unsafeCleanup: true, }); + stagingPath = stagingDir.name; }); - it('should be able to webpack the library', async () => { - // we expect npm install is executed in the before hook - await execa('npx', ['webpack'], { - cwd: `${stagingPath}/`, - stdio: 'inherit', - }); - const bundle = path.join(stagingPath, 'dist', 'bundle.min.js'); - const stat = fs.statSync(bundle); - assert(stat.size < 256 * 1024); - }).timeout(20000); - - /** - * CLEAN UP - remove the staging directory when done. - */ after('cleanup staging', () => { - if (!keep) { + if (!KEEP_STAGING_DIRECTORY) { stagingDir.removeCallback(); } }); + + describe('pack-n-play', () => { + let server: Server; + let url: string; + + before(async () => { + server = createServer((req, res) => { + res.writeHead(200, {'content-type': 'text/plain'}); + res.end(`Hello, ${req.headers['user-agent'] || 'World'}`); + }); + + await new Promise((resolve, reject) => { + server.on('error', reject); + server.listen(0, resolve); + }); + + const address = server.address()!; + + if (typeof address === 'string') { + url = address; + } else { + const base = new URL('http://localhost'); + base.host = address.address; + base.port = address.port.toString(); + + url = base.toString(); + } + }); + + after(() => { + server.close(); + }); + + it('supports ESM', async () => { + await packNTest({ + sample: { + description: 'import as ESM', + esm: ` + import {Gaxios} from 'gaxios'; + + const gaxios = new Gaxios(); + await gaxios.request({url: '${url}'}); + `, + }, + }); + }); + + it('supports CJS', async () => { + await packNTest({ + sample: { + description: 'require as CJS', + cjs: ` + const {Gaxios} = require('gaxios'); + + const gaxios = new Gaxios(); + gaxios.request({url: '${url}'}).then(console.log); + `, + }, + }); + }); + }); + + describe('webpack', () => { + /** + * Create a staging directory with temp fixtures used to test on a fresh + * application. + */ + before('pack and install', async () => { + await execa('npm', ['pack']); + const tarball = `${pkg.name}-${pkg.version}.tgz`; + await mvp(tarball, `${stagingPath}/gaxios.tgz`); + await ncpp('system-test/fixtures/sample', `${stagingPath}/`); + await execa('npm', ['install'], { + cwd: `${stagingPath}/`, + stdio: 'inherit', + }); + }); + + it('should be able to webpack the library', async () => { + // we expect npm install is executed in the before hook + await execa('npx', ['webpack'], { + cwd: `${stagingPath}/`, + stdio: 'inherit', + }); + const bundle = path.join(stagingPath, 'dist', 'bundle.min.js'); + const stat = fs.statSync(bundle); + assert(stat.size < 256 * 1024); + }).timeout(20000); + }); }); diff --git a/test/test.getch.ts b/test/test.getch.ts index 8b987f9a..77387fdc 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -24,11 +24,14 @@ import { GaxiosOptions, GaxiosResponse, GaxiosPromise, -} from '../src'; -import {GAXIOS_ERROR_SYMBOL, GaxiosOptionsPrepared} from '../src/common'; -import {pkg} from '../src/util'; +} from '../src/index.js'; +import {GAXIOS_ERROR_SYMBOL, GaxiosOptionsPrepared} from '../src/common.js'; +import util from '../src/util.cjs'; + import fs from 'fs'; +const pkg = util.pkg; + nock.disableNetConnect(); const sandbox = sinon.createSandbox(); @@ -738,11 +741,12 @@ describe('🥁 configuration options', () => { describe('🎏 data handling', () => { it('should accpet a ReadableStream as request data', async () => { - const body = fs.createReadStream('package.json'); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const contents = require('../../package.json'); - const scope = nock(url).post('/', contents).reply(200, {}); - const res = await request({url, method: 'POST', data: body}); + const scope = nock(url).post('/', 'test').reply(200, {}); + const res = await request({ + url, + method: 'POST', + data: Readable.from('test'), + }); scope.done(); assert.deepStrictEqual(res.data, {}); }); diff --git a/test/test.index.ts b/test/test.index.ts index de6eab4b..7f7f03e8 100644 --- a/test/test.index.ts +++ b/test/test.index.ts @@ -13,7 +13,7 @@ import assert from 'assert'; import {describe, it} from 'mocha'; -import * as main from '../src/index'; +import * as main from '../src/index.js'; describe('📝 main exports', () => { it('should export all the types', () => { diff --git a/test/test.retry.ts b/test/test.retry.ts index 47111b65..2f5c26cf 100644 --- a/test/test.retry.ts +++ b/test/test.retry.ts @@ -14,7 +14,7 @@ import assert from 'assert'; import nock from 'nock'; import {describe, it, afterEach} from 'mocha'; -import {Gaxios, GaxiosError, GaxiosOptions, request} from '../src'; +import {Gaxios, GaxiosError, GaxiosOptions, request} from '../src/index.js'; nock.disableNetConnect(); diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 00000000..31a5d2ff --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,9 @@ +{ + "extends": "gts/tsconfig-google.json", + "compilerOptions": { + "lib": ["es2020", "dom"], + "esModuleInterop": true, + "rootDir": "." + }, + "include": ["src/*.*ts", "test/*.ts", "browser-test/*.ts", "system-test/*.ts"] +} diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 00000000..0829cb24 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "build/cjs" + } +} diff --git a/tsconfig.json b/tsconfig.json index 3538ab11..da56c0c4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,7 @@ { - "extends": "./node_modules/gts/tsconfig-google.json", + "extends": "./tsconfig.base.json", "compilerOptions": { - "lib": ["es2023", "dom"], - "rootDir": ".", - "outDir": "build", - "esModuleInterop": true, - "module": "Node16", - "moduleResolution": "Node16" - }, - "include": ["src/*.ts", "test/*.ts", "browser-test/*.ts", "system-test/*.ts"] + "module": "Preserve", + "outDir": "build/esm" + } } diff --git a/utils/enable-esm.mjs b/utils/enable-esm.mjs new file mode 100644 index 00000000..4d0f6ae6 --- /dev/null +++ b/utils/enable-esm.mjs @@ -0,0 +1,4 @@ +import fs from 'node:fs/promises'; + +// Enables all `.js` files in `build/esm` to be treated as ESM +await fs.writeFile('./build/esm/package.json', '{"type": "module"}');