diff --git a/apps/demo/emails/local-assets.tsx b/apps/demo/emails/local-assets.tsx
new file mode 100644
index 00000000..bb7e5623
--- /dev/null
+++ b/apps/demo/emails/local-assets.tsx
@@ -0,0 +1,68 @@
+import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from 'jsx-email';
+
+import * as React from 'react';
+
+export const TemplateName = 'local-assets';
+
+const main = {
+ backgroundColor: '#f6f9fc',
+ fontFamily:
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif'
+};
+
+const container = {
+ backgroundColor: '#ffffff',
+ margin: '0 auto',
+ marginBottom: '64px',
+ padding: '20px 0 48px'
+};
+
+const box = {
+ padding: '0 48px'
+};
+
+const hr = {
+ borderColor: '#e6ebf1',
+ margin: '20px 0'
+};
+
+const paragraph = {
+ color: '#777',
+ fontSize: '16px',
+ lineHeight: '24px',
+ textAlign: 'left' as const
+};
+
+const anchor: React.CSSProperties = {
+ color: '#3869d4',
+ textDecoration: 'underline'
+};
+
+const baseUrl = import.meta.env.DEV
+ ? import.meta.resolve('./static/')
+ : 'https://assets.example.com/';
+
+export const Template = () => {
+ const catUrl = `${baseUrl}cat.jpeg`;
+ return (
+
+
+
+
+
+
+
+ URL:
+
+
+ {catUrl}
+
+
+
+
+
+
+ );
+};
diff --git a/apps/demo/emails/static/cat.jpeg b/apps/demo/emails/static/cat.jpeg
new file mode 100644
index 00000000..6e680aeb
Binary files /dev/null and b/apps/demo/emails/static/cat.jpeg differ
diff --git a/apps/demo/package.json b/apps/demo/package.json
index fb54449f..5f90eee1 100644
--- a/apps/demo/package.json
+++ b/apps/demo/package.json
@@ -9,5 +9,8 @@
},
"dependencies": {
"jsx-email": "workspace:*"
+ },
+ "devDependencies": {
+ "@types/node": "20.10.5"
}
}
diff --git a/apps/demo/tsconfig.json b/apps/demo/tsconfig.json
new file mode 100644
index 00000000..4ebca792
--- /dev/null
+++ b/apps/demo/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../shared/tsconfig.base.json",
+ "compilerOptions": {
+ "module": "ESNext",
+ "types": ["./node_modules/jsx-email/node_modules/vite/client", "./node_modules/@types/node"]
+ }
+}
diff --git a/docs/components/image.md b/docs/components/image.md
index b29b3aab..e918b611 100644
--- a/docs/components/image.md
+++ b/docs/components/image.md
@@ -28,6 +28,23 @@ referenced, so avoid using these. See [Can I
Email](https://www.caniemail.com/features/image-svg/) for more information.
:::
+::: tip
+To use local images during development, you can resolve the path with node.
+Just remember, for production, you'll need to host the images somewhere and
+reference them with a URL.
+
+```jsx
+import { Img } from 'jsx-email';
+
+const baseUrl = import.meta.DEV ? import.meta.resolve('../assets/') : 'https://assets.example.com/';
+
+const Email = () => {
+ return
;
+};
+```
+
+:::
+
## Component Props
```ts
diff --git a/packages/jsx-email/src/cli/commands/preview.ts b/packages/jsx-email/src/cli/commands/preview.ts
index b2859e59..64f75d6e 100644
--- a/packages/jsx-email/src/cli/commands/preview.ts
+++ b/packages/jsx-email/src/cli/commands/preview.ts
@@ -65,8 +65,8 @@ export const start = async (targetPath: string, argv: PreviewOptions) => {
...viteConfig.resolve?.alias
}
},
- server: { host, port: parseInt(port as any, 10) }
- } as InlineConfig;
+ server: { fs: { strict: false }, host, port: parseInt(port.toString(), 10) }
+ } satisfies InlineConfig;
const server = await createServer(mergedConfig);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 080c9373..834c2792 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -84,6 +84,10 @@ importers:
react:
specifier: 18.2.0
version: 18.2.0
+ devDependencies:
+ '@types/node':
+ specifier: 20.10.5
+ version: 20.10.5
apps/web:
devDependencies:
@@ -2264,7 +2268,7 @@ packages:
resolution: {integrity: sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==}
dependencies:
'@types/jsonfile': 6.1.2
- '@types/node': 20.6.2
+ '@types/node': 20.10.5
dev: true
/@types/hast@3.0.1:
@@ -2296,7 +2300,7 @@ packages:
/@types/jsonfile@6.1.2:
resolution: {integrity: sha512-8t92P+oeW4d/CRQfJaSqEwXujrhH4OEeHRjGU3v1Q8mUS8GPF3yiX26sw4svv6faL2HfBtGTe2xWIoVgN3dy9w==}
dependencies:
- '@types/node': 20.6.2
+ '@types/node': 20.10.5
dev: true
/@types/linkify-it@3.0.3:
@@ -2332,6 +2336,12 @@ packages:
resolution: {integrity: sha512-MG+oI3oelPKLN2gpkel08v6Tp6zU2zZQRq+eSpRsFtLNTd2kxZolOHQTmQQs0wqXTLOqs+ri3tRUaagH5u0quw==}
dev: false
+ /@types/node@20.10.5:
+ resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==}
+ dependencies:
+ undici-types: 5.26.5
+ dev: true
+
/@types/node@20.6.2:
resolution: {integrity: sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==}
@@ -2356,7 +2366,7 @@ packages:
/@types/prompts@2.4.8:
resolution: {integrity: sha512-fPOEzviubkEVCiLduO45h+zFHB0RZX8tFt3C783sO5cT7fUXf3EEECpD26djtYdh4Isa9Z9tasMQuZnYPtvYzw==}
dependencies:
- '@types/node': 20.6.2
+ '@types/node': 20.10.5
kleur: 3.0.3
dev: true
@@ -7640,6 +7650,10 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
+ /undici-types@5.26.5:
+ resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+ dev: true
+
/unified@11.0.4:
resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==}
dependencies:
@@ -7789,7 +7803,7 @@ packages:
vfile-message: 4.0.2
dev: false
- /vite-node@0.34.5(@types/node@20.6.2):
+ /vite-node@0.34.5(@types/node@20.10.5):
resolution: {integrity: sha512-RNZ+DwbCvDoI5CbCSQSyRyzDTfFvFauvMs6Yq4ObJROKlIKuat1KgSX/Ako5rlDMfVCyMcpMRMTkJBxd6z8YRA==}
engines: {node: '>=v14.18.0'}
hasBin: true
@@ -7799,7 +7813,7 @@ packages:
mlly: 1.4.2
pathe: 1.1.1
picocolors: 1.0.0
- vite: 4.4.9(@types/node@20.6.2)
+ vite: 4.4.9(@types/node@20.10.5)
transitivePeerDependencies:
- '@types/node'
- less
@@ -7825,6 +7839,42 @@ packages:
- rollup
dev: false
+ /vite@4.4.9(@types/node@20.10.5):
+ resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': '>= 14'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ '@types/node': 20.10.5
+ esbuild: 0.18.20
+ postcss: 8.4.30
+ rollup: 3.28.1
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
/vite@4.4.9(@types/node@20.6.2):
resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -7944,7 +7994,7 @@ packages:
dependencies:
'@types/chai': 4.3.6
'@types/chai-subset': 1.3.3
- '@types/node': 20.6.2
+ '@types/node': 20.10.5
'@vitest/expect': 0.34.5
'@vitest/runner': 0.34.5
'@vitest/snapshot': 0.34.5
@@ -7964,8 +8014,8 @@ packages:
strip-literal: 1.3.0
tinybench: 2.5.1
tinypool: 0.7.0
- vite: 4.4.9(@types/node@20.6.2)
- vite-node: 0.34.5(@types/node@20.6.2)
+ vite: 4.4.9(@types/node@20.10.5)
+ vite-node: 0.34.5(@types/node@20.10.5)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less