diff --git a/.gitignore b/.gitignore index 203f2fb1356..093c2629632 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ javascript_client/subscriptions javascript_client/sync javascript_client/cli* javascript_client/index* +javascript_client/esm/**/*.d.ts +javascript_client/esm/**/*.js +!javascript_client/esm/package.json # Don't commit compiled extension files: *.bundle *.so diff --git a/javascript_client/esm/package.json b/javascript_client/esm/package.json new file mode 100644 index 00000000000..3dbc1ca591c --- /dev/null +++ b/javascript_client/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/javascript_client/package.json b/javascript_client/package.json index c5da8bf73d4..0b901a32dfa 100644 --- a/javascript_client/package.json +++ b/javascript_client/package.json @@ -3,7 +3,66 @@ "version": "1.14.9", "description": "JavaScript client for graphql-ruby", "main": "index.js", + "module": "esm/index.js", "types": "index.d.ts", + "exports": { + ".": { + "types": "./index.d.ts", + "import": "./esm/index.js", + "require": "./index.js", + "default": "./index.js" + }, + "./index.js": { + "types": "./index.d.ts", + "import": "./esm/index.js", + "require": "./index.js", + "default": "./index.js" + }, + "./cli.js": "./cli.js", + "./sync": { + "types": "./sync/index.d.ts", + "import": "./esm/sync/index.js", + "require": "./sync/index.js", + "default": "./sync/index.js" + }, + "./sync/*": { + "types": "./sync/*", + "import": "./esm/sync/*.js", + "require": "./sync/*.js", + "default": "./sync/*.js" + }, + "./sync/*.js": { + "types": "./sync/*.d.ts", + "import": "./esm/sync/*.js", + "require": "./sync/*.js", + "default": "./sync/*.js" + }, + "./subscriptions/*": { + "types": "./subscriptions/*", + "import": "./esm/subscriptions/*.js", + "require": "./subscriptions/*.js", + "default": "./subscriptions/*.js" + }, + "./subscriptions/*.js": { + "types": "./subscriptions/*.d.ts", + "import": "./esm/subscriptions/*.js", + "require": "./subscriptions/*.js", + "default": "./subscriptions/*.js" + } + }, + "typesVersions": { + "*": { + "sync": [ + "sync/index.d.ts" + ], + "sync/*": [ + "sync/*" + ], + "subscriptions/*": [ + "subscriptions/*" + ] + } + }, "repository": "https://github.com/rmosolgo/graphql-ruby", "author": "Robert Mosolgo", "license": "LGPL-3.0", @@ -34,8 +93,10 @@ "urql": "^2.2.2" }, "scripts": { + "build": "tsc && node scripts/clean-esm.js && tsc -p tsconfig.esm.json && node scripts/prepare-esm.js", + "prepack": "npm run build", "test": "tsc && jest", - "prepublishOnly": "tsc" + "prepublishOnly": "npm test" }, "dependencies": { "glob": "^13.0.6", diff --git a/javascript_client/scripts/clean-esm.js b/javascript_client/scripts/clean-esm.js new file mode 100644 index 00000000000..09345e4e395 --- /dev/null +++ b/javascript_client/scripts/clean-esm.js @@ -0,0 +1,7 @@ +const fs = require("fs") +const path = require("path") + +const esmRoot = path.join(__dirname, "..", "esm") + +fs.rmSync(esmRoot, { recursive: true, force: true }) +fs.mkdirSync(esmRoot, { recursive: true }) diff --git a/javascript_client/scripts/prepare-esm.js b/javascript_client/scripts/prepare-esm.js new file mode 100644 index 00000000000..7ab80ba7a59 --- /dev/null +++ b/javascript_client/scripts/prepare-esm.js @@ -0,0 +1,57 @@ +const fs = require("fs") +const path = require("path") + +const esmRoot = path.join(__dirname, "..", "esm") + +fs.writeFileSync( + path.join(esmRoot, "package.json"), + JSON.stringify({ type: "module" }, null, 2) + "\n" +) + +for (const filePath of findJavaScriptFiles(esmRoot)) { + const source = fs.readFileSync(filePath, "utf8") + const nextSource = source + .replaceAll("@apollo/client/core\"", "@apollo/client/core/index.js\"") + .replaceAll("@apollo/client/core'", "@apollo/client/core/index.js'") + .replaceAll("graphql/language/printer\"", "graphql/language/printer.js\"") + .replaceAll("graphql/language/printer'", "graphql/language/printer.js'") + .replace(/(from\s+["'])(\.[^"']+)(["'])/g, function(match, prefix, specifier, suffix) { + return prefix + resolveRelativeSpecifier(filePath, specifier) + suffix + }) + + if (nextSource !== source) { + fs.writeFileSync(filePath, nextSource) + } +} + +function findJavaScriptFiles(directory) { + return fs.readdirSync(directory, { withFileTypes: true }).flatMap(function(entry) { + const entryPath = path.join(directory, entry.name) + + if (entry.isDirectory()) { + return findJavaScriptFiles(entryPath) + } + + return entry.isFile() && entry.name.endsWith(".js") ? [entryPath] : [] + }) +} + +function resolveRelativeSpecifier(fromPath, specifier) { + if (path.extname(specifier)) { + return specifier + } + + const fromDirectory = path.dirname(fromPath) + const filePath = path.resolve(fromDirectory, specifier + ".js") + const indexPath = path.resolve(fromDirectory, specifier, "index.js") + + if (fs.existsSync(filePath)) { + return specifier + ".js" + } + + if (fs.existsSync(indexPath)) { + return specifier + "/index.js" + } + + return specifier +} diff --git a/javascript_client/src/__tests__/esmTest.ts b/javascript_client/src/__tests__/esmTest.ts new file mode 100644 index 00000000000..a6b0254cb9a --- /dev/null +++ b/javascript_client/src/__tests__/esmTest.ts @@ -0,0 +1,26 @@ +var childProcess = require("child_process") + +function runCommand(commandStr: string) { + var buffer = childProcess.execSync(commandStr, {stdio: "pipe"}) + return buffer.toString().replace(/\033\[[0-9;]*m/g, "") +} +describe("ESM build", () => { + beforeAll(() => { + runCommand("npm pack --dry-run") + }) + + it("Can import ESM modules", () => { + const importResult = runCommand("node --input-type=module -e 'import { sync, ActionCableLink } from \"graphql-ruby-client\"; console.log(typeof sync, typeof ActionCableLink)'") + expect(importResult).toEqual("function function\n") + + const importResult2 = runCommand("node --input-type=module -e 'import ActionCableLink from \"graphql-ruby-client/subscriptions/ActionCableLink.js\"; console.log(typeof ActionCableLink)'") + expect(importResult2).toEqual("function\n") + }) + + it("Can require", () => { + const requireResult = runCommand("node -e 'const ActionCableLink = require(\"graphql-ruby-client/subscriptions/ActionCableLink.js\"); console.log(typeof ActionCableLink.default || typeof ActionCableLink)'") + expect(requireResult).toEqual("function\n") + const requireResult2 = runCommand("node -e 'require.resolve(\"graphql-ruby-client/cli.js\"); console.log(\"ok\")'") + expect(requireResult2).toEqual("ok\n") + }) +}) diff --git a/javascript_client/tsconfig.esm.json b/javascript_client/tsconfig.esm.json new file mode 100644 index 00000000000..6187a1e50bc --- /dev/null +++ b/javascript_client/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["**/*.js", "src/**/__tests__/**"], + "compilerOptions": { + "module": "ES2020", + "moduleResolution": "node", + "outDir": "./esm" + } +}