Skip to content

Commit

Permalink
Remove need for module resolution esnext (#142)
Browse files Browse the repository at this point in the history
![the same thing we do every day, pinky: muck with ESM](https://github.com/fixie-ai/ai-jsx/assets/829827/89596f17-e02c-4c6c-992a-c2cdd08af9b2)

Previously, consumers need to set `"moduleResolution": "nodenext"` in their `tsconfig.json`. Otherwise, importing from `ai-jsx` fails.

I confirmed the original problem, and that this PR fixes it, in https://github.com/NickHeiner/esm-cjs-test.

The reason for the problem: in the legacy module resolution scheme, an import to `ai-jsx/stream` would be resolved by looking for these files:

```
node_modules/ai-jsx/stream.js
node_modules/ai-jsx/stream/index.js
```

We don't have files at that location, because we use the `package.json` `exports` field. But that field isn't respected in the legacy resolution scheme, so we got TS build errors.

To fix it, I added a script that writes proxy files in the locations the legacy resolution algo expects to find them.
  • Loading branch information
NickHeiner committed Jun 23, 2023
1 parent d5f5c87 commit 4697540
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 28 deletions.
2 changes: 1 addition & 1 deletion packages/ai-jsx/.eslintrc.cjs
Expand Up @@ -4,7 +4,7 @@ module.exports = {
extends: ['eslint:recommended', 'plugin:@typescript-eslint/strict', 'nth'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: [path.join(__dirname, 'tsconfig.json')],
project: [path.join(__dirname, 'tsconfig.json'), path.join(__dirname, 'scripts', 'tsconfig.json')],
},
plugins: ['@typescript-eslint'],
root: true,
Expand Down
20 changes: 6 additions & 14 deletions packages/ai-jsx/package.json
Expand Up @@ -4,7 +4,7 @@
"repository": "fixie-ai/ai-jsx",
"bugs": "https://github.com/fixie-ai/ai-jsx/issues",
"homepage": "https://ai-jsx.com",
"version": "0.5.3",
"version": "0.5.4",
"volta": {
"extends": "../../package.json"
},
Expand Down Expand Up @@ -69,16 +69,6 @@
"default": "./dist/cjs/batteries/use-tools.cjs"
}
},
"./batteries/langchain-wrapper": {
"import": {
"types": "./dist/esm/batteries/langchain-wrapper.d.ts",
"default": "./dist/esm/batteries/langchain-wrapper.js"
},
"require": {
"types": "./dist/cjs/batteries/langchain-wrapper.d.cts",
"default": "./dist/cjs/batteries/langchain-wrapper.cjs"
}
},
"./batteries/logging-integrations": {
"import": {
"types": "./dist/esm/batteries/logging-integrations.d.ts",
Expand Down Expand Up @@ -287,27 +277,29 @@
"@types/node": "^20.2.1",
"@types/react": "^18.2.7",
"@types/uuid": "^9",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"cheerio": "^1.0.0-rc.12",
"csv-stringify": "^6.4.0",
"eslint": "^8.40.0",
"eslint-config-nth": "^2.0.1",
"globby": "^13.1.4",
"jest": "^29.5.0",
"load-json-file": "^7.0.1",
"node-fetch": "^3.3.1",
"pino-pretty": "^10.0.0",
"react": "^18.2.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"tsx": "^3.12.7",
"type-fest": "^3.10.0",
"typescript": "^5.1.3",
"write-json-file": "^5.0.0"
},
"type": "module",
"scripts": {
"create-entry-points": "git clean -fd && rm -rf scripts/dist && tsc -p scripts && node scripts/dist/create-entry-points.js",
"build": "rm -rf dist && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && node scripts/move-cjs-to-dist.js",
"prepack": "yarn build",
"lint": "eslint . --max-warnings 0",
"lint:fix": "eslint . --fix",
"test": "jest",
Expand Down
97 changes: 97 additions & 0 deletions packages/ai-jsx/scripts/create-entry-points.ts
@@ -0,0 +1,97 @@
import { loadJsonFile } from 'load-json-file';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import fs from 'fs/promises';
import ts from 'typescript';

const currentPath = path.dirname(fileURLToPath(import.meta.url));
const packageRoot = path.resolve(currentPath, '..', '..');
const pathToThisFile = path.relative(packageRoot, fileURLToPath(import.meta.url));

interface PackageJson {
exports: Record<
string,
{
import: {
types: string;
default: string;
};
require: {
types: string;
default: string;
};
}
>;
}

const packageJson = await loadJsonFile<PackageJson>(path.resolve(packageRoot, 'package.json'));

async function writeFile(filePath: string, contents: string) {
const dirname = path.dirname(filePath);
await fs.mkdir(dirname, { recursive: true });
return fs.writeFile(
path.join(packageRoot, filePath),
`// Auto-generated by ${pathToThisFile}. Do not edit.
// prettier-ignore
/* eslint-disable */
${contents}\n`,
'utf8'
);
}

for (const [exportKey, exportValue] of Object.entries(packageJson.exports)) {
const importObj = exportValue.import;
const requireObj = exportValue.require;

const filePath = exportKey === '.' ? 'index' : exportKey.slice('./'.length);

const getPathToDest = (dest: string) => `./${path.relative(path.dirname(filePath), dest)}`;

const program = ts.createProgram({
rootNames: [importObj.types],
options: {
module: ts.ModuleKind.ESNext,
},
});
const checker = program.getTypeChecker();
const sourceFile = program.getSourceFile(importObj.types);
if (!sourceFile) {
throw new Error(`Failed to find source file: ${importObj.types}`);
}

const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
if (!moduleSymbol) {
throw new Error(`Failed to find module symbol for file: ${importObj.types}`);
}

const exports = checker.getExportsOfModule(moduleSymbol);

const namedExports: string[] = [];
let hasDefaultExport = false;

for (const exp of exports) {
if ((exp.escapedName as string) === 'default') {
hasDefaultExport = true;
} else {
namedExports.push(exp.escapedName as string);
}
}

const foundAnyExport = hasDefaultExport || namedExports.length;

if (!foundAnyExport) {
throw new Error(`Could not find any exports for file ${importObj.types}`);
}

if (hasDefaultExport) {
throw new Error('create-entry-points.ts does not support default exports yet.');
}

const typeFileDestContents = `export { ${namedExports.join(', ')} } from '${getPathToDest(importObj.types)}';
`;

await writeFile(`${filePath}.js`, `export * from '${getPathToDest(importObj.default)}'`);
await writeFile(`${filePath}.d.ts`, typeFileDestContents);
await writeFile(`${filePath}.cjs`, `module.exports = require('${getPathToDest(requireObj.default)}')`);
}
10 changes: 10 additions & 0 deletions packages/ai-jsx/scripts/tsconfig.json
@@ -0,0 +1,10 @@
{
"extends": "@tsconfig/node18",
"include": ["**/*.ts"],
"compilerOptions": {
"resolveJsonModule": true,
"outDir": "dist",
"rootDir": ".",
"module": "ESNext"
}
}
10 changes: 10 additions & 0 deletions packages/docs/docs/contributing/index.md
Expand Up @@ -54,3 +54,13 @@ Follow these steps to make a new demo.
yarn workspace examples run demo:your-demo
```

## Publishing

To publish:

1. Make sure the `version` field in `packages/ai-jsx/package.json` has been incremented in accordance with [semver](https://semver.org/).
1. `cd packages/ai-jsx`
1. `npm publish`

This will create a bunch of temp files in your current working directory. To remove them, run `git clean -fd`.
4 changes: 2 additions & 2 deletions packages/examples/package.json
Expand Up @@ -10,8 +10,8 @@
"@types/node": "^20.3.1",
"@types/prompt-sync": "^4.2.0",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"eslint": "^8.40.0",
"eslint-config-nth": "^2.0.1",
"typescript": "^5.1.3"
Expand Down
4 changes: 2 additions & 2 deletions packages/tutorial/package.json
Expand Up @@ -11,8 +11,8 @@
"@types/prompt-sync": "^4.2.0",
"@types/turndown": "^5.0.1",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"eslint": "^8.40.0",
"eslint-config-nth": "^2.0.1",
"typescript": "^5.1.3"
Expand Down
18 changes: 9 additions & 9 deletions yarn.lock
Expand Up @@ -6887,7 +6887,7 @@ __metadata:
languageName: node
linkType: hard

"@typescript-eslint/eslint-plugin@npm:^5.5.0, @typescript-eslint/eslint-plugin@npm:^5.59.11, @typescript-eslint/eslint-plugin@npm:^5.59.6, @typescript-eslint/eslint-plugin@npm:^5.59.9":
"@typescript-eslint/eslint-plugin@npm:^5.5.0, @typescript-eslint/eslint-plugin@npm:^5.59.11, @typescript-eslint/eslint-plugin@npm:^5.59.9, @typescript-eslint/eslint-plugin@npm:^5.60.0":
version: 5.60.0
resolution: "@typescript-eslint/eslint-plugin@npm:5.60.0"
dependencies:
Expand Down Expand Up @@ -6922,7 +6922,7 @@ __metadata:
languageName: node
linkType: hard

"@typescript-eslint/parser@npm:^5.42.0, @typescript-eslint/parser@npm:^5.5.0, @typescript-eslint/parser@npm:^5.59.11, @typescript-eslint/parser@npm:^5.59.6, @typescript-eslint/parser@npm:^5.59.9":
"@typescript-eslint/parser@npm:^5.42.0, @typescript-eslint/parser@npm:^5.5.0, @typescript-eslint/parser@npm:^5.59.11, @typescript-eslint/parser@npm:^5.59.9, @typescript-eslint/parser@npm:^5.60.0":
version: 5.60.0
resolution: "@typescript-eslint/parser@npm:5.60.0"
dependencies:
Expand Down Expand Up @@ -7392,8 +7392,8 @@ __metadata:
"@types/node": ^20.2.1
"@types/react": ^18.2.7
"@types/uuid": ^9
"@typescript-eslint/eslint-plugin": ^5.59.6
"@typescript-eslint/parser": ^5.59.6
"@typescript-eslint/eslint-plugin": ^5.60.0
"@typescript-eslint/parser": ^5.60.0
axios: ^1.4.0
cheerio: ^1.0.0-rc.12
cli-highlight: ^2.1.11
Expand All @@ -7407,6 +7407,7 @@ __metadata:
jest: ^29.5.0
js-yaml: ^4.1.0
langchain: ^0.0.81
load-json-file: ^7.0.1
lodash: ^4.17.21
ml-distance: ^4.0.1
node-fetch: ^3.3.1
Expand All @@ -7417,7 +7418,6 @@ __metadata:
server-only: ^0.0.1
ts-jest: ^29.1.0
ts-node: ^10.9.1
tsx: ^3.12.7
type-fest: ^3.10.0
typescript: ^5.1.3
uuid: ^9.0.0
Expand Down Expand Up @@ -11888,8 +11888,8 @@ __metadata:
"@types/node": ^20.3.1
"@types/prompt-sync": ^4.2.0
"@types/uuid": ^9.0.2
"@typescript-eslint/eslint-plugin": ^5.59.6
"@typescript-eslint/parser": ^5.59.6
"@typescript-eslint/eslint-plugin": ^5.60.0
"@typescript-eslint/parser": ^5.60.0
"@wandb/sdk": ^0.5.1
ai-jsx: ^0.5.1
axios: ^1.4.0
Expand Down Expand Up @@ -22332,8 +22332,8 @@ __metadata:
"@types/prompt-sync": ^4.2.0
"@types/turndown": ^5.0.1
"@types/uuid": ^9.0.2
"@typescript-eslint/eslint-plugin": ^5.59.6
"@typescript-eslint/parser": ^5.59.6
"@typescript-eslint/eslint-plugin": ^5.60.0
"@typescript-eslint/parser": ^5.60.0
ai-jsx: ^0.5.1
axios: ^1.4.0
eslint: ^8.40.0
Expand Down

3 comments on commit 4697540

@vercel
Copy link

@vercel vercel bot commented on 4697540 Jun 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ai-jsx-docs – ./packages/docs

ai-jsx-docs-git-main-fixie-ai.vercel.app
ai-jsx-docs.vercel.app
ai-jsx-docs-fixie-ai.vercel.app
docs.ai-jsx.com

@vercel
Copy link

@vercel vercel bot commented on 4697540 Jun 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ai-jsx-tutorial-nextjs – ./packages/tutorial-nextjs

ai-jsx-tutorial-nextjs.vercel.app
ai-jsx-tutorial-nextjs-fixie-ai.vercel.app
ai-jsx-tutorial-nextjs-git-main-fixie-ai.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 4697540 Jun 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ai-jsx-nextjs-demo – ./packages/nextjs-demo

ai-jsx-nextjs-demo.vercel.app
ai-jsx-nextjs-demo-fixie-ai.vercel.app
ai-jsx-nextjs-demo-git-main-fixie-ai.vercel.app

Please sign in to comment.