sanity-io / assist

Sanity AI Assist: A plugin for Sanity Studio
MIT License
13 stars 5 forks source link

feat!: add strict ESM support #40

Closed stipsan closed 5 months ago

stipsan commented 5 months ago

@snorrees working on this I realise it's time for @sanity/plugin-kit to get some love. I'll make a PR on that repo with the new best practices we've developed for semantic-release, @sanity/pkg-utils, shipping ESM, using tsconfig's new module: "Preserve" config and more 🙌

In this PR the goal is to enable strict ESM mode support. What is that? The ability to create a file like test.mjs that does this:

import { assist } from "@sanity/assist";

console.log(await import.meta.resolve("@sanity/assist"), { assist });

And have it output file:///node_modules/@sanity/assist/dist/index.mjs { assist: [Function (anonymous)] }

Today, due to our esm wrapper pattern, it outputs:

file:///node_modules/@sanity/assist/dist/index.cjs.mjs { assist: [Function (anonymous)] }

The index.cjs.mjs file is re-exporting the cjs file. In other words, it takes Node out of ESM mode and resolves @sanity/assist, and all its deps, in CJS mode. It's not enough to simply remove the re-export pattern:

-      "node": {
-        "module": "./dist/index.esm.js",
-        "import": "./dist/index.cjs.mjs"
-      },

This will create this output

file:///test.mjs:1
import { assist } from "@sanity/assist";
         ^^^^^^
SyntaxError: Named export 'assist' not found. The requested module '@sanity/assist' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from '@sanity/assist';
const { assist } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:132:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:214:5)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async loadESM (node:internal/process/esm_loader:28:7)
    at async handleMainPromise (node:internal/modules/run_main:113:12)

Node.js v20.11.1

As you also need to use .mjs file endings:

-      "import": "./dist/index.esm.js",
+      "import": "./dist/index.mjs",

And used dependencies (like in this case date-fns) has to be updated to support native ESM, or inlined by moving them out of peerDependencies and dependencies and into devDependencies (@sanity/pkg-utils marks packages in those two lists as external).

Why do we want native ESM mode? New environments like Remix + Vite are much stricter with how it deals with ESM, and doesn't support workarounds like exports["."].node.import that re-exports CJS. To support Studios embedding in those environments we have to be strictly ESM ready.