Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(esm): use CJS wrapper for ESM default interop #10119

Merged
merged 5 commits into from
Mar 7, 2024
Merged

Conversation

jtoar
Copy link
Contributor

@jtoar jtoar commented Mar 6, 2024

This PR builds on the work started in #10083. It does two things:

  • builds the @redwoodjs/vite package with esbuild instead of babel (this is optional but just did it to debug, but I'd imagine we'd want to start doing this anyway)
    • CI wasn't happy with this change so removed for now
  • introduces a CJS wrapper to handle ESM export default interoperation in Node

To frame this change at a high level, one of the ways I'm approaching ESM is backwards from Redwood apps. If Redwood apps can be ESM (via "type": "module" in package.jsons, updates to tsconfigs, adding file extensions to relative imports, etc), then we may have an easier time rolling out packages that can't be shipped as dual ESM/CJS exports. (Vite is probably one of them. But since Vite evaluates the config file itself, it may be ok.)

One of the framework-level bugs that shows up when you make an app ESM is this error:

failed to load config from ~/redwood/redwood-app-esm/web/vite.config.mts
file:///~/redwood/redwood-app-esm/web/vite.config.mts.timestamp-1709251612918- 
6f6a7f8efad05.mjs:7
  plugins: [redwood()]
            ^

TypeError: redwood is not a function
    at file:///~/redwood/redwood-app-esm/web/vite.config.mts.timestamp-1709251612918- 
6f6a7f8efad05.mjs:7:13

The culprit is this import in web/vite.config.ts:

import redwood from '@redwoodjs/vite'

The problem is confusing, but basically... when you 1) transpile an ES module into CJS (which is what we're doing for most of our packages) and then 2) import that CJS module from a bona fide ES module, the default export doesn't behave how you'd expect. Evan Wallace has a great write up of it here. (For Node's official docs, see https://nodejs.org/docs/latest/api/esm.html#commonjs-namespaces.)

As a potential fix, @Tobbe pointed me to this Vite plugin: https://github.com/cyco130/vite-plugin-cjs-interop. I tried adding it but the issue is that this is the Vite config file. 😅

But I thought I may be able to add the plugin here when we call createServer:

const devServer = await createServer({
configFile,
envFile: false, // env file is handled by plugins in the redwood-vite plugin
optimizeDeps: {
// This is the only value that isn't a server option
force: forceOptimize,
},
server: forwardedServerArgs,
logLevel: debug ? 'info' : undefined,
})

But it still wasn't working. I stepped through the source a bit and it doesn't seem like there's room for configuring how Vite loads the config file. The two main functions were these, loadConfigFromBundledFile and loadConfigFromFile:

bundleConfigFile has plugins configured, but they're hardcoded.

But luckily I don't think it's necessary... based on this esbuild thread, it seems like we can get away with a small wrapper. See evanw/esbuild#532. When we actually make this an ES module (which will happen pretty soon I think), this will go away. But for the time being, this is a CJS module.

@jtoar jtoar added the release:fix This PR is a fix label Mar 6, 2024
@jtoar jtoar added this to the next-release-patch milestone Mar 6, 2024
@jtoar jtoar force-pushed the ds-vite/use-cjs-wrapper branch from 26bdf8b to 1512890 Compare March 6, 2024 09:57
@jtoar jtoar force-pushed the ds-vite/use-cjs-wrapper branch from bd025fb to 7a3524c Compare March 6, 2024 12:10
@jtoar jtoar marked this pull request as ready for review March 6, 2024 12:25
@jtoar jtoar enabled auto-merge (squash) March 7, 2024 18:01
@jtoar jtoar merged commit a85e0de into main Mar 7, 2024
41 checks passed
@jtoar jtoar deleted the ds-vite/use-cjs-wrapper branch March 7, 2024 18:10
jtoar added a commit that referenced this pull request Mar 8, 2024
Tried building with esbuild in
#10119 but ran into an error
with CI. Keen on trying to fix that here. Looks like it was just a
matter of updating the default entry points so far.
ahaywood pushed a commit that referenced this pull request Mar 8, 2024
This PR builds on the work started in
#10083. It does two things:

- ~builds the `@redwoodjs/vite` package with esbuild instead of babel
(this is optional but just did it to debug, but I'd imagine we'd want to
start doing this anyway)~
  - CI wasn't happy with this change so removed for now
- introduces a CJS wrapper to handle ESM export default interoperation
in Node

To frame this change at a high level, one of the ways I'm approaching
ESM is backwards from Redwood apps. If Redwood apps can be ESM (via
"type": "module" in package.jsons, updates to tsconfigs, adding file
extensions to relative imports, etc), then we may have an easier time
rolling out packages that can't be shipped as dual ESM/CJS exports.
(Vite is probably one of them. But since Vite evaluates the config file
itself, it may be ok.)

One of the framework-level bugs that shows up when you make an app ESM
is this error:

```
failed to load config from ~/redwood/redwood-app-esm/web/vite.config.mts
file:///~/redwood/redwood-app-esm/web/vite.config.mts.timestamp-1709251612918-
6f6a7f8efad05.mjs:7
  plugins: [redwood()]
            ^

TypeError: redwood is not a function
    at file:///~/redwood/redwood-app-esm/web/vite.config.mts.timestamp-1709251612918-
6f6a7f8efad05.mjs:7:13
```

The culprit is this import in `web/vite.config.ts`:

```ts
import redwood from '@redwoodjs/vite'
```

The problem is confusing, but basically... when you 1) transpile an ES
module into CJS (which is what we're doing for most of our packages) and
then 2) import that CJS module from a bona fide ES module, the default
export doesn't behave how you'd expect. Evan Wallace has a great write
up of it
[here](https://esbuild.github.io/content-types/#default-interop). (For
Node's official docs, see
https://nodejs.org/docs/latest/api/esm.html#commonjs-namespaces.)

As a potential fix, @Tobbe pointed me to this Vite plugin:
https://github.com/cyco130/vite-plugin-cjs-interop. I tried adding it
but the issue is that this **is** the Vite config file. 😅

But I thought I may be able to add the plugin here when we call
`createServer`:

https://github.com/redwoodjs/redwood/blob/e9ecbb07da216210c59a1e499816f31c025fe81d/packages/vite/bins/rw-vite-dev.mjs#L28-L37

But it still wasn't working. I stepped through the source a bit and it
doesn't seem like there's room for configuring how Vite loads the config
file. The two main functions were these, `loadConfigFromBundledFile` and
`loadConfigFromFile`:
-
https://github.com/vitejs/vite/blob/e92abe58164682c2e468318c05023bfb4ecdfa02/packages/vite/src/node/config.ts#L1186
-
https://github.com/vitejs/vite/blob/e92abe58164682c2e468318c05023bfb4ecdfa02/packages/vite/src/node/config.ts#L962

`bundleConfigFile` has plugins configured, but they're hardcoded.

But luckily I don't think it's necessary... based on this esbuild
thread, it seems like we can get away with a small wrapper. See
evanw/esbuild#532. When we actually make this
an ES module (which will happen pretty soon I think), this will go away.
But for the time being, this is a CJS module.
jtoar added a commit that referenced this pull request Mar 8, 2024
Tried building with esbuild in
#10119 but ran into an error
with CI. Keen on trying to fix that here. Looks like it was just a
matter of updating the default entry points so far.
@jtoar jtoar modified the milestones: next-release-patch, v7.1.1 Mar 8, 2024
jtoar added a commit that referenced this pull request Mar 8, 2024
This PR builds on the work started in
#10083. It does two things:

- ~builds the `@redwoodjs/vite` package with esbuild instead of babel
(this is optional but just did it to debug, but I'd imagine we'd want to
start doing this anyway)~
  - CI wasn't happy with this change so removed for now
- introduces a CJS wrapper to handle ESM export default interoperation
in Node

To frame this change at a high level, one of the ways I'm approaching
ESM is backwards from Redwood apps. If Redwood apps can be ESM (via
"type": "module" in package.jsons, updates to tsconfigs, adding file
extensions to relative imports, etc), then we may have an easier time
rolling out packages that can't be shipped as dual ESM/CJS exports.
(Vite is probably one of them. But since Vite evaluates the config file
itself, it may be ok.)

One of the framework-level bugs that shows up when you make an app ESM
is this error:

```
failed to load config from ~/redwood/redwood-app-esm/web/vite.config.mts
file:///~/redwood/redwood-app-esm/web/vite.config.mts.timestamp-1709251612918-
6f6a7f8efad05.mjs:7
  plugins: [redwood()]
            ^

TypeError: redwood is not a function
    at file:///~/redwood/redwood-app-esm/web/vite.config.mts.timestamp-1709251612918-
6f6a7f8efad05.mjs:7:13
```

The culprit is this import in `web/vite.config.ts`:

```ts
import redwood from '@redwoodjs/vite'
```

The problem is confusing, but basically... when you 1) transpile an ES
module into CJS (which is what we're doing for most of our packages) and
then 2) import that CJS module from a bona fide ES module, the default
export doesn't behave how you'd expect. Evan Wallace has a great write
up of it
[here](https://esbuild.github.io/content-types/#default-interop). (For
Node's official docs, see
https://nodejs.org/docs/latest/api/esm.html#commonjs-namespaces.)

As a potential fix, @Tobbe pointed me to this Vite plugin:
https://github.com/cyco130/vite-plugin-cjs-interop. I tried adding it
but the issue is that this **is** the Vite config file. 😅

But I thought I may be able to add the plugin here when we call
`createServer`:

https://github.com/redwoodjs/redwood/blob/e9ecbb07da216210c59a1e499816f31c025fe81d/packages/vite/bins/rw-vite-dev.mjs#L28-L37

But it still wasn't working. I stepped through the source a bit and it
doesn't seem like there's room for configuring how Vite loads the config
file. The two main functions were these, `loadConfigFromBundledFile` and
`loadConfigFromFile`:
-
https://github.com/vitejs/vite/blob/e92abe58164682c2e468318c05023bfb4ecdfa02/packages/vite/src/node/config.ts#L1186
-
https://github.com/vitejs/vite/blob/e92abe58164682c2e468318c05023bfb4ecdfa02/packages/vite/src/node/config.ts#L962

`bundleConfigFile` has plugins configured, but they're hardcoded.

But luckily I don't think it's necessary... based on this esbuild
thread, it seems like we can get away with a small wrapper. See
evanw/esbuild#532. When we actually make this
an ES module (which will happen pretty soon I think), this will go away.
But for the time being, this is a CJS module.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release:fix This PR is a fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants