resend / react-email

💌 Build and send emails using React
https://react.email
MIT License
12.67k stars 583 forks source link

Preview server and export does not support aliases #895

Closed Gregory-Gerard closed 4 months ago

Gregory-Gerard commented 9 months ago

Describe the Bug

I use aliases with my TypeScript configuration in order to have simpler paths (@components/Test.tsx as an example), with the way the dev server currently works, I can't configure the tsconfig.json of the dev server, so it does not use proper aliases and do not found my components.

error - ../src/emails/TestEmail.tsx:19:0
Module not found: Can't resolve '@components/Test'

If I update the tsconfig.json used by the dev server, it works. In export mode, no problem.

Current workaround is to not use paths option, or to edit tsconfig.json inside .react-email folder, but it is auto-generated.

I am available to help with this fix if necessary and if you wish.

Which package is affected (leave empty if unsure)

react-email

Link to the code that reproduces this issue

None

To Reproduce

Add paths option in compilerOptions in tsconfig.json :

"paths": {
  "@components/*": ["components/*"],
  "@utils/*": ["utils/*"]
}

Expected Behavior

Dev server should work with aliases, like export mode.

What's your node version? (if relevant)

v20.5.1

Gregory-Gerard commented 8 months ago

I've implemented a workaround to temporarily address this issue by updating paths in React Email TypeScript configuration file (.react-email/tsconfig.json) and have also added a script entry for automating this process. Here's the code:

// src/utils/updateTsConfigDevServer.mjs
import * as fs from 'fs';

const devServerTsConfig = JSON.parse(fs.readFileSync('.react-email/tsconfig.json').toString());
const currentTsConfig = JSON.parse(fs.readFileSync('tsconfig.json').toString());

const normalizedPaths = Object.entries(currentTsConfig.compilerOptions.paths).reduce((prev, [key, paths]) => {
    return {...prev, [key]: paths.map(path => `../src/${path}`)}
}, {})

devServerTsConfig.compilerOptions.paths = normalizedPaths;

fs.writeFileSync('.react-email/tsconfig.json', JSON.stringify(devServerTsConfig, null, 2));

In addition, I've included a script in the package.json file to automate the workaround:

"scripts": {
    "dev": "node src/utils/updateTsConfigDevServer.mjs && email dev --dir src/emails"
}

Please note that this code may need modifications to suit another project folder structure.

I'm available and willing to assist with resolving this issue further. I've noticed that React Email seem to be using a similar approach to my workaround, such as merging the package.json for the dev server, could this be applied to tsconfig.json?. Please feel free to reach out if I can contribute to resolving the issue.

taylor-lindores-reeves commented 6 months ago

Any updates on this? It's a real hassle to have to update all path aliases to relative imports in order to preview and test emails. This also includes path aliases inside nested imports. The issue is happening on both the email dev command, as well as the VSCode extension. Any workarounds or suggestions would be greatly appreciated.

shellscape commented 6 months ago

I'd love to see a screenshot of how you're organizing the filesystem with aliases for email templates. Given the simplistic and encapsulated nature of email templates, it's unusual that a complex file tree would be needed.

Gregory-Gerard commented 5 months ago

I'd love to see a screenshot of how you're organizing the filesystem with aliases for email templates. Given the simplistic and encapsulated nature of email templates, it's unusual that a complex file tree would be needed.

Hi @shellscape,

Personally, I agree that I don't have a particularly complex structure for my emails. However, I'm in a monorepo and each workspace uses more or less the same rules, including aliasing.

If I didn't have to use aliases, it wouldn't be very complicated without them either, but I wouldn't expect a dev server to change this behavior (and therefore my configurations), at least without it being documented.

Here's an example of the structure I use for my emails:

.
└── @acme/email/
    ├── atoms/
    │   └── Link.tsx
    ├── molecules/
    │   └── Article.tsx
    ├── organisms/
    │   └── ArticlesGrid.tsx
    └── emails/
        └── Newsletter.tsx
Gregory-Gerard commented 4 months ago

We've just upgraded React Email and aliases no longer work in export mode, just like the dev server. This seems to have been introduced by this PR https://github.com/resend/react-email/pull/1027 which forces the use of a tsconfig.

I'm still of the opinion that React Email should be more flexible on the tools used for build and development and give this responsibility to the user but could facilitate integration with existing stacks. Or at least make this part configurable?

For the moment, I've simply removed the aliases from my project and I'm no longer using my previous workaround, which no longer works.

The PR https://github.com/resend/react-email/pull/1100 seems to work in this direction, and I am available to provide help if needed.

gabrielmfern commented 4 months ago

@Gregory-Gerard

I'm still of the opinion that React Email should be more flexible on the tools used for build and development and give this responsibility to the user but could facilitate integration with existing stacks. Or at least make this part configurable?

That is our intention with the project, what other pain points do you find make us hard to use on some stacks?

We've just upgraded React Email and aliases no longer work in export mode, just like the dev server. This seems to have been introduced by this PR https://github.com/resend/react-email/pull/1027 which forces the use of a tsconfig.

Sorry to hear that, I'll try getting a fix on this for you soon. For context, we had to use a specific tsconfig due to the esbuild JSX option getting overridden by tsconfig's which caused errors with users that had "jsx": "preserve". Just bumping esbuild should fix this issue and not need the specific tsconfig. If you want I can get you a quick patch for now since I'm not sure I'll be able to get this fixed and released very soon.

Gregory-Gerard commented 4 months ago

@gabrielmfern

Thanks for your reply.

Mainly the pain points are with build / dev opinionated tools, which is great for getting started quickly but obviously less flexible. Maybe we could set up something to be able to configure at least the config files (tsconfig and so on) used by export and build?

There's no real critical issue, I just removed the aliases from my project for now, but thanks for offering me a quick patch.

I just saw that @bukinoshita closed this issue, may I open another issue about export mode?

gabrielmfern commented 4 months ago

@Gregory-Gerard Yeah, please do that. After today I'll tackle that issue, if you want I can also get you a quick patch you can use for now.

gabrielmfern commented 4 months ago

@Gregory-Gerard Hey, I forgot that I had implemented this, but I think the 2.0.0 also includes a fix for this. Can you check it out?

Gregory-Gerard commented 4 months ago

@gabrielmfern

Hey, thanks for your patience and assistance. The preview server seems to work perfectly with aliases and launches with serious performance improvements. Congrats!

Unfortunately, it seems that export is broken for now. I keep getting this error:

~/projects/stackblitz-starters-bziz8k 3s
❯ email export
✔ Preparing files...

✖ Failed when rendering Test.js
TypeError: component.default is not a function
    at eval (file:///home/projects/stackblitz-starters-bziz8k/node_modules/react-email/cli/index.js:777:75)
    at step (file:///home/projects/stackblitz-starters-bziz8k/node_modules/react-email/cli/index.js:185:23)
    at Object.eval [as next] (file:///home/projects/stackblitz-starters-bziz8k/node_modules/react-email/cli/index.js:126:20)
    at asyncGeneratorStep (file:///home/projects/stackblitz-starters-bziz8k/node_modules/react-email/cli/index.js:11:28)
    at _next (file:///home/projects/stackblitz-starters-bziz8k/node_modules/react-email/cli/index.js:29:17)

It does not seem to be my configuration because here is a minimal repro with just React Email installed: Minimal Repro (just launch email export in terminal to see this error)

It appears to be related to this line: export.ts#65

By replacing component.default({}) with component.default.default({}), it works without any issues as before. I don't believe the problem is specifically with this line, but rather with the build method that may have changed.

gabrielmfern commented 4 months ago

Interesting, I think this might be an ESM issue since we build with CJS format. Maybe using require might fix this, will open a PR for it.

tim3w4rp commented 4 months ago

Hi. I can see it's been just 4 days. However, any progress on this?

I just initiated a new project inside turborepo using this guide and also took inspiration from this example and I am running into the TypeError: component.default is not a function issue as well.

The output is a bunch of javascript. I also found this already closed issue which basically has the same result.

We do have various different tsconfigs in our monorepo but don't use aliases within the packages folder that this project resides in.

Our base tsconfig is this:

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Default",
  "compilerOptions": {
    "plugins": [
      { "name": "next" }
    ],
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "strictNullChecks": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true
  },
  "exclude": ["node_modules"]
}

However, I have doubts about this having an influence. I don't extend this base.

So far, I've tried this, but to no avail:

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "jsx": "react",
    "baseUrl": "."
  },
  "exclude": ["node_modules", "out"]
}
gabrielmfern commented 4 months ago

@tim3w4rp PR is open for this #1214

RakaDoank commented 2 weeks ago

Hope, it would be resolved sooner. Temporarily, i've just added my components or the whole my "src" folder as a dependency in package.json using local paths

{
  "dependencies": {
    "src": "file:./src"
  }
}

so, i able to import my src files with absolute path by importing the files from node_modules,

import { MyComponentLego } from 'src/components'
import { SomeHelper } from 'src/helpers'