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): fix initial esm blockers for redwood apps #10083

Merged
merged 12 commits into from
Mar 1, 2024

Conversation

jtoar
Copy link
Contributor

@jtoar jtoar commented Mar 1, 2024

This PR makes some initial fixes that were required for making a Redwood app ESM. @Josh-Walker-GM and I tested by making a new project, specifying "type": "module" in all the package.jsons, and trying to get things to work (namely, yarn rw build and yarn rw serve). We'll ramp up our testing to one of the test project fixtures after we get these initial things fixed. Everything here is backwards-compatible.

Some of the changes we had to make were in the project itself. Namely, specifying file extensions for relative imports (import { db } from 'src/lib/db.js' instead of import { db } from 'src/lib/db'). But others needed to be made at the framework level:

  • esbuild has to emit ESM

  • packages/api-server/src/plugins/lambdaLoader.ts can't use require to import an ES module

  • Babel plugins have to emit relative file imports with extensions

  • The @redwoodjs/graphql-server package needs to be built differently to avoid having to do this...

    // ./api/src/functions/graphql.ts
    import pkg from '@redwoodjs/graphql-server'
    const { createGraphQLHandler } = pkg

    Not 100% sure why, but Babel makes some exports getters instead of just exporting them statically. Better to have less magic going on in dist.

Some manual changes to a project are inevitable (the file extensions), but we'd like to minimize them as much as possible. Here's a few that were necessary that we haven't figured out yet...

  • dist imports, like import ... from '@redwoodjs/api/logger', don't work

    They need to be changed to import ... from '@redwoodjs/api/logger/index.js'. It feels like the only way to really fix this one is to use exports in the package.json, but if I remember correctly, that requires tsconfig settings that requires ESM

  • redwood is not a function in ./web/vite.config.ts:

    We haven't gotten to the bottom of this one yet:

    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 fix is accessing redwood.default:

    image

@jtoar jtoar added the release:fix This PR is a fix label Mar 1, 2024
@jtoar jtoar added this to the next-release-patch milestone Mar 1, 2024
@jtoar jtoar marked this pull request as draft March 1, 2024 00:52
@jtoar jtoar marked this pull request as ready for review March 1, 2024 02:47
@jtoar jtoar marked this pull request as draft March 1, 2024 03:32
@jtoar jtoar marked this pull request as ready for review March 1, 2024 05:54
@jtoar jtoar merged commit 7dc7f55 into main Mar 1, 2024
41 checks passed
@jtoar jtoar deleted the ds-jgmw-esm/initial-esm-work-for-rw-apps branch March 1, 2024 06:23
ahaywood pushed a commit that referenced this pull request Mar 1, 2024
This PR makes some initial fixes that were required for making a Redwood
app ESM. @Josh-Walker-GM and I tested by making a new project,
specifying `"type": "module"` in all the package.jsons, and trying to
get things to work (namely, `yarn rw build` and `yarn rw serve`). We'll
ramp up our testing to one of the test project fixtures after we get
these initial things fixed. Everything here is backwards-compatible.

Some of the changes we had to make were in the project itself. Namely,
specifying file extensions for relative imports (`import { db } from
'src/lib/db.js'` instead of `import { db } from 'src/lib/db'`). But
others needed to be made at the framework level:

- esbuild has to emit ESM

- `packages/api-server/src/plugins/lambdaLoader.ts` can't use `require`
to import an ES module

- Babel plugins have to emit relative file imports with extensions

- The `@redwoodjs/graphql-server` package needs to be built differently
to avoid having to do this...

  ```ts
  // ./api/src/functions/graphql.ts
  import pkg from '@redwoodjs/graphql-server'
  const { createGraphQLHandler } = pkg
  ```

Not 100% sure why, but Babel makes some exports getters instead of just
exporting them statically. Better to have less magic going on in dist.

Some manual changes to a project are inevitable (the file extensions),
but we'd like to minimize them as much as possible. Here's a few that
were necessary that we haven't figured out yet...

- dist imports, like `import ... from '@redwoodjs/api/logger'`, don't
work

They need to be changed to `import ... from
'@redwoodjs/api/logger/index.js'`. It feels like the only way to really
fix this one is to use `exports` in the package.json, but if I remember
correctly, that requires tsconfig settings that requires ESM

- `redwood` is not a function in `./web/vite.config.ts`:

  We haven't gotten to the bottom of this one yet:

  ```
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 fix is accessing `redwood.default`:

<img width="685" alt="image"
src="https://github.com/redwoodjs/redwood/assets/32992335/4f09fea4-42d3-49a4-9339-74b42c90621e">
@ahaywood ahaywood modified the milestones: next-release-patch, v7.0.6 Mar 1, 2024
ahaywood pushed a commit that referenced this pull request Mar 1, 2024
This PR makes some initial fixes that were required for making a Redwood
app ESM. @Josh-Walker-GM and I tested by making a new project,
specifying `"type": "module"` in all the package.jsons, and trying to
get things to work (namely, `yarn rw build` and `yarn rw serve`). We'll
ramp up our testing to one of the test project fixtures after we get
these initial things fixed. Everything here is backwards-compatible.

Some of the changes we had to make were in the project itself. Namely,
specifying file extensions for relative imports (`import { db } from
'src/lib/db.js'` instead of `import { db } from 'src/lib/db'`). But
others needed to be made at the framework level:

- esbuild has to emit ESM

- `packages/api-server/src/plugins/lambdaLoader.ts` can't use `require`
to import an ES module

- Babel plugins have to emit relative file imports with extensions

- The `@redwoodjs/graphql-server` package needs to be built differently
to avoid having to do this...

  ```ts
  // ./api/src/functions/graphql.ts
  import pkg from '@redwoodjs/graphql-server'
  const { createGraphQLHandler } = pkg
  ```

Not 100% sure why, but Babel makes some exports getters instead of just
exporting them statically. Better to have less magic going on in dist.

Some manual changes to a project are inevitable (the file extensions),
but we'd like to minimize them as much as possible. Here's a few that
were necessary that we haven't figured out yet...

- dist imports, like `import ... from '@redwoodjs/api/logger'`, don't
work

They need to be changed to `import ... from
'@redwoodjs/api/logger/index.js'`. It feels like the only way to really
fix this one is to use `exports` in the package.json, but if I remember
correctly, that requires tsconfig settings that requires ESM

- `redwood` is not a function in `./web/vite.config.ts`:

  We haven't gotten to the bottom of this one yet:

  ```
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 fix is accessing `redwood.default`:

<img width="685" alt="image"
src="https://github.com/redwoodjs/redwood/assets/32992335/4f09fea4-42d3-49a4-9339-74b42c90621e">
jtoar added a commit that referenced this pull request Mar 7, 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.
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
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