react-email 4.1.0-canary.6 → 4.1.0-canary.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/{cli/index.mjs → index.js} +437 -429
  3. package/package.json +10 -45
  4. package/src/commands/build.ts +306 -0
  5. package/src/commands/dev.ts +27 -0
  6. package/src/commands/export.ts +204 -0
  7. package/src/commands/start.ts +38 -0
  8. package/src/index.ts +55 -0
  9. package/src/utils/__snapshots__/tree.spec.ts.snap +27 -0
  10. package/src/utils/esbuild/renderring-utilities-exporter.ts +1 -1
  11. package/src/utils/get-emails-directory-metadata.spec.ts +1 -1
  12. package/src/utils/get-preview-server-location.ts +32 -0
  13. package/src/utils/index.ts +2 -6
  14. package/src/utils/packageJson.ts +4 -0
  15. package/src/utils/preview/get-env-variables-for-preview-app.ts +14 -0
  16. package/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts +281 -0
  17. package/src/utils/preview/hot-reloading/create-dependency-graph.ts +321 -0
  18. package/src/utils/preview/hot-reloading/get-imported-modules.spec.ts +151 -0
  19. package/src/utils/preview/hot-reloading/get-imported-modules.ts +49 -0
  20. package/src/utils/preview/hot-reloading/resolve-path-aliases.spec.ts +11 -0
  21. package/src/utils/preview/hot-reloading/resolve-path-aliases.ts +32 -0
  22. package/src/utils/preview/hot-reloading/setup-hot-reloading.ts +121 -0
  23. package/src/utils/preview/hot-reloading/test/tsconfig.json +8 -0
  24. package/src/utils/preview/index.ts +2 -0
  25. package/src/utils/preview/serve-static-file.ts +51 -0
  26. package/src/utils/preview/start-dev-server.ts +234 -0
  27. package/src/utils/tree.spec.ts +5 -0
  28. package/src/utils/tree.ts +76 -0
  29. package/src/utils/types/hot-reload-change.ts +1 -1
  30. package/src/utils/types/hot-reload-event.ts +1 -1
  31. package/tsconfig.json +4 -10
  32. package/dist/preview/.next/BUILD_ID +0 -1
  33. package/dist/preview/.next/app-build-manifest.json +0 -44
  34. package/dist/preview/.next/app-path-routes-manifest.json +0 -6
  35. package/dist/preview/.next/build-manifest.json +0 -33
  36. package/dist/preview/.next/diagnostics/build-diagnostics.json +0 -6
  37. package/dist/preview/.next/diagnostics/framework.json +0 -1
  38. package/dist/preview/.next/export-marker.json +0 -6
  39. package/dist/preview/.next/images-manifest.json +0 -57
  40. package/dist/preview/.next/next-minimal-server.js.nft.json +0 -1
  41. package/dist/preview/.next/next-server.js.nft.json +0 -1
  42. package/dist/preview/.next/package.json +0 -1
  43. package/dist/preview/.next/prerender-manifest.json +0 -41
  44. package/dist/preview/.next/react-loadable-manifest.json +0 -1
  45. package/dist/preview/.next/required-server-files.json +0 -311
  46. package/dist/preview/.next/routes-manifest.json +0 -64
  47. package/dist/preview/.next/server/app/_not-found/page.js +0 -1
  48. package/dist/preview/.next/server/app/_not-found/page.js.nft.json +0 -1
  49. package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  50. package/dist/preview/.next/server/app/favicon.ico/route.js +0 -1
  51. package/dist/preview/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  52. package/dist/preview/.next/server/app/favicon.ico.body +0 -0
  53. package/dist/preview/.next/server/app/favicon.ico.meta +0 -1
  54. package/dist/preview/.next/server/app/page.js +0 -1
  55. package/dist/preview/.next/server/app/page.js.nft.json +0 -1
  56. package/dist/preview/.next/server/app/page_client-reference-manifest.js +0 -1
  57. package/dist/preview/.next/server/app/preview/[...slug]/page.js +0 -321
  58. package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +0 -1
  59. package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +0 -1
  60. package/dist/preview/.next/server/app-paths-manifest.json +0 -6
  61. package/dist/preview/.next/server/chunks/134.js +0 -6
  62. package/dist/preview/.next/server/chunks/235.js +0 -15
  63. package/dist/preview/.next/server/chunks/315.js +0 -1
  64. package/dist/preview/.next/server/chunks/343.js +0 -20
  65. package/dist/preview/.next/server/chunks/428.js +0 -14
  66. package/dist/preview/.next/server/chunks/963.js +0 -1
  67. package/dist/preview/.next/server/functions-config-manifest.json +0 -4
  68. package/dist/preview/.next/server/interception-route-rewrite-manifest.js +0 -1
  69. package/dist/preview/.next/server/middleware-build-manifest.js +0 -1
  70. package/dist/preview/.next/server/middleware-manifest.json +0 -6
  71. package/dist/preview/.next/server/middleware-react-loadable-manifest.js +0 -1
  72. package/dist/preview/.next/server/next-font-manifest.js +0 -1
  73. package/dist/preview/.next/server/next-font-manifest.json +0 -1
  74. package/dist/preview/.next/server/pages/500.html +0 -1
  75. package/dist/preview/.next/server/pages/_app.js +0 -1
  76. package/dist/preview/.next/server/pages/_app.js.nft.json +0 -1
  77. package/dist/preview/.next/server/pages/_document.js +0 -1
  78. package/dist/preview/.next/server/pages/_document.js.nft.json +0 -1
  79. package/dist/preview/.next/server/pages/_error.js +0 -1
  80. package/dist/preview/.next/server/pages/_error.js.nft.json +0 -1
  81. package/dist/preview/.next/server/pages-manifest.json +0 -5
  82. package/dist/preview/.next/server/server-reference-manifest.js +0 -1
  83. package/dist/preview/.next/server/server-reference-manifest.json +0 -1
  84. package/dist/preview/.next/server/webpack-runtime.js +0 -1
  85. package/dist/preview/.next/static/EYH0WN4--LLC3GZrZIVN8/_buildManifest.js +0 -1
  86. package/dist/preview/.next/static/EYH0WN4--LLC3GZrZIVN8/_ssgManifest.js +0 -1
  87. package/dist/preview/.next/static/chunks/107-3043079e7cb8bcae.js +0 -1
  88. package/dist/preview/.next/static/chunks/293-297b1eb2241f9a70.js +0 -1
  89. package/dist/preview/.next/static/chunks/3bd82e28-cda2c00a924937c5.js +0 -1
  90. package/dist/preview/.next/static/chunks/45-1021fac82f766268.js +0 -1
  91. package/dist/preview/.next/static/chunks/484-a7b30a88a7939680.js +0 -1
  92. package/dist/preview/.next/static/chunks/589-817d8691661d370e.js +0 -1
  93. package/dist/preview/.next/static/chunks/902-c34acb56733e0ce1.js +0 -1
  94. package/dist/preview/.next/static/chunks/app/_not-found/page-4cbc7dce3ad33336.js +0 -1
  95. package/dist/preview/.next/static/chunks/app/layout-46a09d953364e102.js +0 -1
  96. package/dist/preview/.next/static/chunks/app/page-65fd67d48528e2ba.js +0 -1
  97. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-5e69ffe7506383a0.js +0 -1
  98. package/dist/preview/.next/static/chunks/f33a14d2-ec7c5f0b91818561.js +0 -6
  99. package/dist/preview/.next/static/chunks/framework-b887e9fc751a9906.js +0 -1
  100. package/dist/preview/.next/static/chunks/main-9a03e7ba8acb1900.js +0 -1
  101. package/dist/preview/.next/static/chunks/main-app-dbd8e1ec12eabb66.js +0 -1
  102. package/dist/preview/.next/static/chunks/pages/_app-542a93a5a214e1c0.js +0 -1
  103. package/dist/preview/.next/static/chunks/pages/_error-d5fe1b1612642f76.js +0 -1
  104. package/dist/preview/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  105. package/dist/preview/.next/static/chunks/webpack-31c45daa2bd82a7b.js +0 -1
  106. package/dist/preview/.next/static/css/6f42d128f111d7fa.css +0 -3
  107. package/dist/preview/.next/static/media/05613964ce6c782e-s.p.otf +0 -0
  108. package/dist/preview/.next/static/media/11c6126b9369e85e-s.p.otf +0 -0
  109. package/dist/preview/.next/static/media/26a46d62cd723877-s.woff2 +0 -0
  110. package/dist/preview/.next/static/media/26cb97734d8cb717-s.p.otf +0 -0
  111. package/dist/preview/.next/static/media/55c55f0601d81cf3-s.woff2 +0 -0
  112. package/dist/preview/.next/static/media/581909926a08bbc8-s.woff2 +0 -0
  113. package/dist/preview/.next/static/media/6d93bde91c0c2823-s.woff2 +0 -0
  114. package/dist/preview/.next/static/media/97e0cb1ae144a2a9-s.woff2 +0 -0
  115. package/dist/preview/.next/static/media/a34f9d1faa5f3315-s.p.woff2 +0 -0
  116. package/dist/preview/.next/static/media/bb6462617151f6b7-s.p.otf +0 -0
  117. package/dist/preview/.next/static/media/cf6daef822ab0142-s.p.otf +0 -0
  118. package/dist/preview/.next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  119. package/dist/preview/.next/static/media/e4051546b3043204-s.p.otf +0 -0
  120. package/dist/preview/.next/static/media/logo.2ce2a759.png +0 -0
  121. package/dist/preview/.next/trace +0 -28
  122. package/dist/preview/.next/types/app/layout.ts +0 -84
  123. package/dist/preview/.next/types/app/page.ts +0 -84
  124. package/dist/preview/.next/types/app/preview/[...slug]/page.ts +0 -84
  125. package/dist/preview/.next/types/cache-life.d.ts +0 -141
  126. package/dist/preview/.next/types/package.json +0 -1
  127. package/module-punycode.d.ts +0 -3
  128. package/next-env.d.ts +0 -5
  129. package/next.config.js +0 -22
  130. package/postcss.config.js +0 -8
  131. package/scripts/build-preview-server.mjs +0 -33
  132. package/scripts/fill-caniemail-data.mjs +0 -36
  133. package/src/actions/email-validation/caniemail-data.ts +0 -85993
  134. package/src/actions/email-validation/check-compatibility.ts +0 -333
  135. package/src/actions/email-validation/check-images.spec.tsx +0 -100
  136. package/src/actions/email-validation/check-images.ts +0 -160
  137. package/src/actions/email-validation/check-links.spec.tsx +0 -113
  138. package/src/actions/email-validation/check-links.ts +0 -113
  139. package/src/actions/email-validation/get-code-location-from-ast-element.ts +0 -18
  140. package/src/actions/email-validation/quick-fetch.ts +0 -14
  141. package/src/actions/get-email-path-from-slug.ts +0 -32
  142. package/src/actions/get-emails-directory-metadata-action.ts +0 -19
  143. package/src/actions/render-email-by-path.tsx +0 -121
  144. package/src/animated-icons-data/help.json +0 -1082
  145. package/src/animated-icons-data/link.json +0 -1309
  146. package/src/animated-icons-data/load.json +0 -443
  147. package/src/animated-icons-data/mail.json +0 -1320
  148. package/src/app/env.ts +0 -15
  149. package/src/app/favicon.ico +0 -0
  150. package/src/app/fonts/SFMono/SFMonoBold.otf +0 -0
  151. package/src/app/fonts/SFMono/SFMonoBoldItalic.otf +0 -0
  152. package/src/app/fonts/SFMono/SFMonoHeavy.otf +0 -0
  153. package/src/app/fonts/SFMono/SFMonoHeavyItalic.otf +0 -0
  154. package/src/app/fonts/SFMono/SFMonoLight.otf +0 -0
  155. package/src/app/fonts/SFMono/SFMonoLightItalic.otf +0 -0
  156. package/src/app/fonts/SFMono/SFMonoMedium.otf +0 -0
  157. package/src/app/fonts/SFMono/SFMonoMediumItalic.otf +0 -0
  158. package/src/app/fonts/SFMono/SFMonoRegular.otf +0 -0
  159. package/src/app/fonts/SFMono/SFMonoRegularItalic.otf +0 -0
  160. package/src/app/fonts/SFMono/SFMonoSemibold.otf +0 -0
  161. package/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf +0 -0
  162. package/src/app/fonts.ts +0 -39
  163. package/src/app/globals.css +0 -15
  164. package/src/app/layout.tsx +0 -45
  165. package/src/app/logo.png +0 -0
  166. package/src/app/page.tsx +0 -46
  167. package/src/app/preview/[...slug]/page.tsx +0 -157
  168. package/src/app/preview/[...slug]/preview.tsx +0 -234
  169. package/src/app/preview/[...slug]/rendering-error.tsx +0 -40
  170. package/src/components/button.tsx +0 -101
  171. package/src/components/code-container.tsx +0 -164
  172. package/src/components/code-snippet.tsx +0 -9
  173. package/src/components/code.tsx +0 -184
  174. package/src/components/heading.tsx +0 -113
  175. package/src/components/icons/icon-arrow-down.tsx +0 -16
  176. package/src/components/icons/icon-base.tsx +0 -26
  177. package/src/components/icons/icon-bug.tsx +0 -19
  178. package/src/components/icons/icon-button.tsx +0 -23
  179. package/src/components/icons/icon-check.tsx +0 -19
  180. package/src/components/icons/icon-clipboard.tsx +0 -40
  181. package/src/components/icons/icon-download.tsx +0 -19
  182. package/src/components/icons/icon-email.tsx +0 -18
  183. package/src/components/icons/icon-file.tsx +0 -19
  184. package/src/components/icons/icon-folder-open.tsx +0 -19
  185. package/src/components/icons/icon-folder.tsx +0 -18
  186. package/src/components/icons/icon-hide-sidebar.tsx +0 -23
  187. package/src/components/icons/icon-image.tsx +0 -19
  188. package/src/components/icons/icon-info.tsx +0 -18
  189. package/src/components/icons/icon-link.tsx +0 -14
  190. package/src/components/icons/icon-monitor.tsx +0 -19
  191. package/src/components/icons/icon-moon.tsx +0 -16
  192. package/src/components/icons/icon-phone.tsx +0 -26
  193. package/src/components/icons/icon-reload.tsx +0 -18
  194. package/src/components/icons/icon-source.tsx +0 -19
  195. package/src/components/icons/icon-stamp.tsx +0 -14
  196. package/src/components/icons/icon-sun.tsx +0 -16
  197. package/src/components/icons/icon-warning.tsx +0 -31
  198. package/src/components/index.ts +0 -7
  199. package/src/components/logo.tsx +0 -63
  200. package/src/components/resizable-wrapper.tsx +0 -173
  201. package/src/components/send.tsx +0 -134
  202. package/src/components/shell.tsx +0 -92
  203. package/src/components/sidebar/file-tree-directory-children.tsx +0 -139
  204. package/src/components/sidebar/file-tree-directory.tsx +0 -92
  205. package/src/components/sidebar/file-tree.tsx +0 -31
  206. package/src/components/sidebar/index.ts +0 -1
  207. package/src/components/sidebar/sidebar.tsx +0 -43
  208. package/src/components/text.tsx +0 -99
  209. package/src/components/toolbar/checking-results.tsx +0 -150
  210. package/src/components/toolbar/code-preview-line-link.tsx +0 -40
  211. package/src/components/toolbar/compatibility.tsx +0 -113
  212. package/src/components/toolbar/linter.tsx +0 -278
  213. package/src/components/toolbar/results.tsx +0 -51
  214. package/src/components/toolbar/spam-assassin.tsx +0 -155
  215. package/src/components/toolbar/toolbar-button.tsx +0 -52
  216. package/src/components/toolbar/use-cached-state.ts +0 -33
  217. package/src/components/toolbar.tsx +0 -349
  218. package/src/components/tooltip-content.tsx +0 -31
  219. package/src/components/tooltip.tsx +0 -19
  220. package/src/components/topbar/active-view-toggle-group.tsx +0 -86
  221. package/src/components/topbar/theme-toggle-group.tsx +0 -87
  222. package/src/components/topbar/view-size-controls.tsx +0 -247
  223. package/src/components/topbar.tsx +0 -59
  224. package/src/contexts/emails.tsx +0 -59
  225. package/src/contexts/fragment-identifier.tsx +0 -48
  226. package/src/contexts/preview.tsx +0 -79
  227. package/src/hooks/use-clamped-state.ts +0 -24
  228. package/src/hooks/use-email-rendering-result.ts +0 -58
  229. package/src/hooks/use-fragment-identifier.ts +0 -14
  230. package/src/hooks/use-hot-reload.ts +0 -31
  231. package/src/hooks/use-icon-animation.ts +0 -41
  232. package/src/hooks/use-iframe-color-scheme.ts +0 -35
  233. package/src/hooks/use-rendering-metadata.ts +0 -36
  234. package/src/utils/__snapshots__/get-email-component.spec.ts.snap +0 -3
  235. package/src/utils/caniemail/all-css-properties.ts +0 -358
  236. package/src/utils/caniemail/ast/__snapshots__/get-object-variables.spec.ts.snap +0 -74
  237. package/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap +0 -24
  238. package/src/utils/caniemail/ast/get-object-variables.spec.ts +0 -19
  239. package/src/utils/caniemail/ast/get-object-variables.ts +0 -61
  240. package/src/utils/caniemail/ast/get-used-style-properties.spec.ts +0 -23
  241. package/src/utils/caniemail/ast/get-used-style-properties.ts +0 -91
  242. package/src/utils/caniemail/get-compatibility-stats-for-entry.ts +0 -118
  243. package/src/utils/caniemail/get-css-functions.ts +0 -25
  244. package/src/utils/caniemail/get-css-property-names.ts +0 -32
  245. package/src/utils/caniemail/get-css-property-with-value.ts +0 -14
  246. package/src/utils/caniemail/get-css-unit.ts +0 -3
  247. package/src/utils/caniemail/get-element-attributes.ts +0 -7
  248. package/src/utils/caniemail/get-element-names.ts +0 -20
  249. package/src/utils/caniemail/tailwind/generate-tailwind-rules.ts +0 -30
  250. package/src/utils/caniemail/tailwind/get-tailwind-config.ts +0 -187
  251. package/src/utils/caniemail/tailwind/get-tailwind-metadata.spec.ts +0 -25
  252. package/src/utils/caniemail/tailwind/get-tailwind-metadata.ts +0 -45
  253. package/src/utils/caniemail/tailwind/setup-tailwind-context.ts +0 -15
  254. package/src/utils/cn.ts +0 -6
  255. package/src/utils/constants.ts +0 -6
  256. package/src/utils/contains-email-template.spec.ts +0 -107
  257. package/src/utils/contains-email-template.ts +0 -33
  258. package/src/utils/copy-text-to-clipboard.ts +0 -7
  259. package/src/utils/get-email-component.spec.ts +0 -41
  260. package/src/utils/get-email-component.ts +0 -134
  261. package/src/utils/get-line-and-column-from-offset.spec.ts +0 -11
  262. package/src/utils/get-line-and-column-from-offset.ts +0 -11
  263. package/src/utils/improve-error-with-sourcemap.ts +0 -86
  264. package/src/utils/js-email-detection.spec.ts +0 -24
  265. package/src/utils/language-map.ts +0 -7
  266. package/src/utils/linting.ts +0 -60
  267. package/src/utils/load-stream.ts +0 -15
  268. package/src/utils/result.ts +0 -49
  269. package/src/utils/run-bundled-code.ts +0 -64
  270. package/src/utils/sanitize.ts +0 -6
  271. package/src/utils/static-node-modules-for-vm.ts +0 -93
  272. package/src/utils/testing/js-email-export-default.js +0 -17
  273. package/src/utils/testing/js-email-test.js +0 -18
  274. package/src/utils/testing/mdx-email-test.js +0 -128
  275. package/src/utils/testing/request-response-email.tsx +0 -9
  276. package/src/utils/types/as.ts +0 -26
  277. package/src/utils/types/email-template.ts +0 -8
  278. package/src/utils/types/error-object.ts +0 -11
  279. package/src/utils/unreachable.ts +0 -8
  280. package/tailwind-internals.d.ts +0 -133
  281. package/tailwind.config.ts +0 -99
  282. /package/src/{components/toolbar/results-table.tsx → utils/preview/hot-reloading/test/some-file.ts} +0 -0
@@ -1,24 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`getUsedStyleProperties() 1`] = `
4
- [
5
- {
6
- "location": SourceLocation {
7
- "end": Position {
8
- "column": 21,
9
- "index": 91,
10
- "line": 5,
11
- },
12
- "filename": undefined,
13
- "identifierName": undefined,
14
- "start": Position {
15
- "column": 2,
16
- "index": 72,
17
- "line": 5,
18
- },
19
- },
20
- "name": "borderRadius",
21
- "value": "5px",
22
- },
23
- ]
24
- `;
@@ -1,19 +0,0 @@
1
- import { parse } from '@babel/parser';
2
- import { getObjectVariables } from './get-object-variables';
3
-
4
- test('getObjectVariables()', () => {
5
- const reactCode = `
6
- <Button style={buttonStyle}>Click me</Button>
7
-
8
- const buttonStyle = {
9
- borderRadius: '5px',
10
- };
11
- `;
12
- const ast = parse(reactCode, {
13
- strictMode: false,
14
- errorRecovery: true,
15
- sourceType: 'unambiguous',
16
- plugins: ['jsx', 'typescript', 'decorators'],
17
- });
18
- expect(getObjectVariables(ast)).toMatchSnapshot();
19
- });
@@ -1,61 +0,0 @@
1
- import traverse from '@babel/traverse';
2
- import type { Node } from '@babel/traverse';
3
- import type { AST } from '../../../actions/email-validation/check-compatibility';
4
-
5
- export interface Position {
6
- line: number;
7
- column: number;
8
- index: number;
9
- }
10
-
11
- export const convertLocationIntoObject = (
12
- location: SourceLocation,
13
- ): SourceLocation => {
14
- return {
15
- start: {
16
- line: location.start.line,
17
- column: location.start.column,
18
- index: location.start.index,
19
- },
20
- end: {
21
- line: location.end.line,
22
- column: location.end.column,
23
- index: location.end.index,
24
- },
25
- filename: location.filename,
26
- identifierName: location.identifierName,
27
- };
28
- };
29
-
30
- export interface SourceLocation {
31
- start: Position;
32
- end: Position;
33
- filename: string;
34
- identifierName: string | undefined | null;
35
- }
36
-
37
- type ObjectProperty = Node & { type: 'ObjectProperty' };
38
-
39
- export type ObjectVariables = Record<string, ObjectProperty[]>;
40
-
41
- export const getObjectVariables = (ast: AST) => {
42
- const objectVariables: ObjectVariables = {};
43
- traverse(ast, {
44
- ObjectExpression(nodePath) {
45
- if (nodePath.parent.type === 'VariableDeclarator') {
46
- if (nodePath.parent.id.type === 'Identifier') {
47
- const variableName = nodePath.parent.id.name;
48
- const properties: ObjectProperty[] = [];
49
- for (const property of nodePath.node.properties) {
50
- if (property.type === 'ObjectProperty') {
51
- properties.push(property);
52
- }
53
- }
54
- objectVariables[variableName] = properties;
55
- }
56
- }
57
- },
58
- });
59
-
60
- return objectVariables;
61
- };
@@ -1,23 +0,0 @@
1
- import { parse } from '@babel/parser';
2
- import { getObjectVariables } from './get-object-variables';
3
- import { getUsedStyleProperties } from './get-used-style-properties';
4
-
5
- test('getUsedStyleProperties()', async () => {
6
- const reactCode = `
7
- <Button style={buttonStyle}>Click me</Button>
8
-
9
- const buttonStyle = {
10
- borderRadius: '5px',
11
- };
12
- `;
13
- const ast = parse(reactCode, {
14
- strictMode: false,
15
- errorRecovery: true,
16
- sourceType: 'unambiguous',
17
- plugins: ['jsx', 'typescript', 'decorators'],
18
- });
19
- const objectVariables = getObjectVariables(ast);
20
- expect(
21
- await getUsedStyleProperties(ast, reactCode, '', objectVariables),
22
- ).toMatchSnapshot();
23
- });
@@ -1,91 +0,0 @@
1
- import traverse from '@babel/traverse';
2
- import type { AST } from '../../../actions/email-validation/check-compatibility';
3
- import { generateTailwindCssRules } from '../tailwind/generate-tailwind-rules';
4
- import { getTailwindMetadata } from '../tailwind/get-tailwind-metadata';
5
- import type { ObjectVariables, SourceLocation } from './get-object-variables';
6
-
7
- export interface StylePropertyUsage {
8
- location: SourceLocation | undefined | null;
9
- name: string;
10
- value: string;
11
- }
12
-
13
- export const doesPropertyHaveLocation = (
14
- prop: StylePropertyUsage,
15
- ): prop is StylePropertyUsage & { location: SourceLocation } => {
16
- return prop.location !== undefined && prop.location !== null;
17
- };
18
-
19
- export const getUsedStyleProperties = async (
20
- ast: AST,
21
- sourceCode: string,
22
- sourcePath: string,
23
- objectVariables: ObjectVariables,
24
- ) => {
25
- const styleProperties: StylePropertyUsage[] = [];
26
- const tailwindMetadata = await getTailwindMetadata(
27
- ast,
28
- sourceCode,
29
- sourcePath,
30
- );
31
-
32
- if (tailwindMetadata.hasTailwind) {
33
- traverse(ast, {
34
- JSXAttribute(path) {
35
- if (path.node.name.name === 'className') {
36
- path.traverse({
37
- StringLiteral(stringPath) {
38
- const className = stringPath.node.value;
39
- const { rules } = generateTailwindCssRules(
40
- className.split(' '),
41
- tailwindMetadata.context,
42
- );
43
- for (const rule of rules) {
44
- rule.walkDecls((decl) => {
45
- styleProperties.push({
46
- location: stringPath.node.loc,
47
- name: decl.prop,
48
- value: decl.value,
49
- });
50
- });
51
- }
52
- },
53
- });
54
- }
55
- },
56
- });
57
- }
58
-
59
- traverse(ast, {
60
- JSXAttribute(path) {
61
- if (
62
- path.node.value?.type === 'JSXExpressionContainer' &&
63
- path.node.value.expression.type === 'Identifier' &&
64
- path.node.name.name === 'style'
65
- ) {
66
- const styleVariable = objectVariables[path.node.value.expression.name];
67
- if (styleVariable) {
68
- for (const property of styleVariable) {
69
- if (
70
- (property.key.type === 'StringLiteral' ||
71
- property.key.type === 'Identifier') &&
72
- property.value.type === 'StringLiteral'
73
- ) {
74
- const propertyName =
75
- property.key.type === 'StringLiteral'
76
- ? property.key.value
77
- : property.key.name;
78
- styleProperties.push({
79
- name: propertyName,
80
- value: property.value.value,
81
- location: property.loc,
82
- });
83
- }
84
- }
85
- }
86
- }
87
- },
88
- });
89
-
90
- return styleProperties;
91
- };
@@ -1,118 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
- import type {
3
- EmailClient,
4
- Platform,
5
- SupportEntry,
6
- } from '../../actions/email-validation/check-compatibility';
7
-
8
- export type SupportStatus = DetailedSupportStatus['status'];
9
-
10
- export type DetailedSupportStatus =
11
- | {
12
- status: 'success';
13
- }
14
- | {
15
- status: 'error';
16
- }
17
- | {
18
- status: 'warning';
19
- notes: string;
20
- };
21
-
22
- type EmailClientStats = {
23
- status: SupportStatus;
24
- perPlatform: Partial<Record<Platform, DetailedSupportStatus>>;
25
- };
26
-
27
- export type CompatibilityStats = {
28
- status: SupportStatus;
29
- perEmailClient: Partial<Record<EmailClient, EmailClientStats>>;
30
- };
31
-
32
- const noteNumbersRegex = /#(?<noteNumber>\d+)/g;
33
-
34
- export const getCompatibilityStatsForEntry = (
35
- entry: SupportEntry,
36
- emailClients: EmailClient[],
37
- ) => {
38
- const stats: CompatibilityStats = {
39
- status: 'success',
40
- perEmailClient: {},
41
- };
42
- for (const emailClient of emailClients) {
43
- const rawStats = entry.stats[emailClient];
44
- if (rawStats) {
45
- const emailClientStats: EmailClientStats = {
46
- status: 'success',
47
- perPlatform: {},
48
- };
49
-
50
- for (const [platform, statusPerVersion] of Object.entries(rawStats)) {
51
- const latestStatus = statusPerVersion[statusPerVersion.length - 1];
52
- if (latestStatus === undefined)
53
- throw new Error(
54
- 'Cannot load in status because there are none recorded for this platform/email client',
55
- {
56
- cause: {
57
- latestStatus,
58
- statusPerVersion,
59
- platform,
60
- emailClient,
61
- supportEntry: entry,
62
- },
63
- },
64
- );
65
- const statusString = latestStatus[Object.keys(latestStatus)[0]!]!;
66
- if (statusString.startsWith('u')) continue;
67
- if (statusString.startsWith('a')) {
68
- const notes: string[] = [];
69
- noteNumbersRegex.lastIndex = 0;
70
- for (const match of statusString.matchAll(noteNumbersRegex)) {
71
- if (match.groups?.noteNumber) {
72
- const { noteNumber } = match.groups;
73
- const note = entry.notes_by_num?.[Number.parseInt(noteNumber)];
74
- if (note) {
75
- notes.push(note);
76
- }
77
- // else if (isInternalDev) {
78
- // console.warn(
79
- // 'Could not get note by the number for a support entry',
80
- // {
81
- // platform,
82
- // statusString,
83
- // note,
84
- // },
85
- // );
86
- // }
87
- }
88
- }
89
- if (emailClientStats.status === 'success')
90
- emailClientStats.status = 'warning';
91
- if (stats.status === 'success') stats.status = 'warning';
92
- emailClientStats.perPlatform[platform as Platform] = {
93
- status: 'warning',
94
- notes:
95
- notes.length === 1
96
- ? notes[0]!
97
- : notes.map((note) => `- ${note}`).join('\n'),
98
- };
99
- } else if (statusString.startsWith('y')) {
100
- emailClientStats.perPlatform[platform as Platform] = {
101
- status: 'success',
102
- };
103
- } else if (statusString.startsWith('n')) {
104
- if (emailClientStats.status !== 'error')
105
- emailClientStats.status = 'error';
106
- if (stats.status !== 'error') stats.status = 'error';
107
- emailClientStats.perPlatform[platform as Platform] = {
108
- status: 'error',
109
- };
110
- }
111
- }
112
-
113
- stats.perEmailClient[emailClient] = emailClientStats;
114
- }
115
- }
116
-
117
- return stats;
118
- };
@@ -1,25 +0,0 @@
1
- export function getCssFunctions(title: string) {
2
- if (/^[a-zA-Z]\(\)$/.test(title.trim())) {
3
- return [title.replace('()', '')];
4
- }
5
-
6
- // ex: lch(), oklch(), lab(), oklab()
7
- // this regex avoids matching entries that are for CSS properties listed
8
- // separated by commas as well
9
- if (/^(?:[^(),]+?\(\),?)*$/.test(title.trim())) {
10
- return title
11
- .split(/\s*,\s*/)
12
- .map((functionCallWithoutParameters) =>
13
- functionCallWithoutParameters.replace('()', ''),
14
- );
15
- }
16
-
17
- // ex: CSS calc() function
18
- if (/^CSS [a-z]+\(\) function$/.test(title.trim())) {
19
- return [
20
- title.replace('CSS ', '').replace(' function', '').replace('()', ''),
21
- ];
22
- }
23
-
24
- return [];
25
- }
@@ -1,32 +0,0 @@
1
- import { allCssProperties } from './all-css-properties';
2
-
3
- export const getCssPropertyNames = (title: string, keywords: string | null) => {
4
- if (allCssProperties.includes(title.replace(' property', '')))
5
- return [title.replace(' property', '')];
6
-
7
- if (title.split('&').length > 1) {
8
- return title
9
- .split(/\s*&\s*/)
10
- .map((piece) => piece.trim())
11
- .filter((possiblePropertyName) =>
12
- allCssProperties.includes(possiblePropertyName),
13
- );
14
- }
15
-
16
- if (title.split(',').length > 1) {
17
- return title
18
- .split(/\s*,\s*/)
19
- .map((piece) => piece.trim())
20
- .filter((possiblePropertyName) =>
21
- allCssProperties.includes(possiblePropertyName),
22
- );
23
- }
24
-
25
- if (keywords) {
26
- return keywords
27
- .split(/\s*,\s*/)
28
- .filter((keyword) => allCssProperties.includes(keyword));
29
- }
30
-
31
- return [];
32
- };
@@ -1,14 +0,0 @@
1
- const propertyRegex =
2
- /(?<propertyName>[a-z-]+)\s*:\s*(?<propertyValue>[a-zA-Z\-0-9()+*/_ ]+)/;
3
-
4
- export const getCssPropertyWithValue = (title: string) => {
5
- const match = propertyRegex.exec(title.trim());
6
- if (match) {
7
- const [_full, propertyName, propertyValue] = match;
8
- return {
9
- name: propertyName!,
10
- value: propertyValue!,
11
- };
12
- }
13
- return undefined;
14
- };
@@ -1,3 +0,0 @@
1
- export const getCssUnit = (title: string) => {
2
- return title.endsWith(' unit') ? title.replace(' unit', '') : undefined;
3
- };
@@ -1,7 +0,0 @@
1
- export function getElementAttributes(title: string) {
2
- if (title.endsWith(' attribute')) {
3
- return [title.replace(' attribute', '')];
4
- }
5
-
6
- return [];
7
- }
@@ -1,20 +0,0 @@
1
- export const getElementNames = (title: string, keywords: string | null) => {
2
- const match = /<(?<elementName>[^>]*)> element/.exec(title);
3
- if (match) {
4
- const [_full, elementName] = match;
5
-
6
- if (elementName) {
7
- return [elementName];
8
- }
9
- }
10
-
11
- if (keywords !== null && keywords.length > 0) {
12
- return keywords.split(/\s*,\s*/).map((piece) => piece.trim());
13
- }
14
-
15
- if (title.split(',').length > 1) {
16
- return title.split(/\s*,\s*/).map((piece) => piece.trim());
17
- }
18
-
19
- return [];
20
- };
@@ -1,30 +0,0 @@
1
- import postcss from 'postcss';
2
- import type { Root, Rule } from 'postcss';
3
- import evaluateTailwindFunctions from 'tailwindcss/lib/lib/evaluateTailwindFunctions';
4
- import { generateRules as rawGenerateRules } from 'tailwindcss/lib/lib/generateRules';
5
- import type { JitContext } from 'tailwindcss/lib/lib/setupContextUtils';
6
-
7
- export const generateTailwindCssRules = (
8
- classNames: string[],
9
- tailwindContext: JitContext,
10
- ): { root: Root; rules: Rule[] } => {
11
- const bigIntRuleTuples: [bigint, Rule][] = rawGenerateRules(
12
- new Set(classNames),
13
- tailwindContext,
14
- );
15
-
16
- const root = postcss.root({
17
- nodes: bigIntRuleTuples.map(([, rule]) => rule),
18
- });
19
- evaluateTailwindFunctions(tailwindContext)(root);
20
-
21
- const actualRules: Rule[] = [];
22
- root.walkRules((rule) => {
23
- actualRules.push(rule);
24
- });
25
-
26
- return {
27
- root,
28
- rules: actualRules,
29
- };
30
- };
@@ -1,187 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import traverse from '@babel/traverse';
4
- import type { Node } from '@babel/traverse';
5
- import * as esbuild from 'esbuild';
6
- import type { Config as TailwindOriginalConfig } from 'tailwindcss';
7
- import type { AST } from '../../../actions/email-validation/check-compatibility';
8
- import { isErr } from '../../result';
9
- import { runBundledCode } from '../../run-bundled-code';
10
-
11
- export type TailwindConfig = Pick<
12
- TailwindOriginalConfig,
13
- | 'important'
14
- | 'prefix'
15
- | 'separator'
16
- | 'safelist'
17
- | 'blocklist'
18
- | 'presets'
19
- | 'future'
20
- | 'experimental'
21
- | 'darkMode'
22
- | 'theme'
23
- | 'corePlugins'
24
- | 'plugins'
25
- >;
26
-
27
- const getFirstExistingFilepath = (filePaths: string[]) => {
28
- for (const filePath of filePaths) {
29
- if (fs.existsSync(filePath)) {
30
- return filePath;
31
- }
32
- }
33
- };
34
-
35
- type ImportDeclaration = Node & { type: 'ImportDeclaration' };
36
-
37
- export const getTailwindConfig = async (
38
- sourceCode: string,
39
- ast: AST,
40
- sourcePath: string,
41
- ): Promise<TailwindConfig> => {
42
- const configAttribute = getTailwindConfigNode(ast);
43
-
44
- if (configAttribute) {
45
- const configIdentifierName =
46
- configAttribute.value?.type === 'JSXExpressionContainer' &&
47
- configAttribute.value.expression.type === 'Identifier'
48
- ? configAttribute.value.expression.name
49
- : undefined;
50
- if (configIdentifierName) {
51
- const tailwindConfigImport = getImportWithGivenDefaultSpecifier(
52
- ast,
53
- configIdentifierName,
54
- );
55
- if (tailwindConfigImport) {
56
- return getConfigFromImport(tailwindConfigImport, sourcePath);
57
- }
58
- }
59
-
60
- const configObjectExpression =
61
- configAttribute.value?.type === 'JSXExpressionContainer' &&
62
- configAttribute.value.expression.type === 'ObjectExpression'
63
- ? configAttribute.value.expression
64
- : undefined;
65
- if (configObjectExpression?.start && configObjectExpression.end) {
66
- const configObjectSourceCode = sourceCode.slice(
67
- configObjectExpression.start,
68
- configObjectExpression.end,
69
- );
70
-
71
- try {
72
- const getConfig = new Function(`return ${configObjectSourceCode}`);
73
- return getConfig() as TailwindConfig;
74
- } catch (exception) {
75
- console.warn(exception);
76
- console.warn(
77
- `Tried reading the config defined directly in the Tailwind component but was unable to, probably because it can't run by itself.`,
78
- );
79
- }
80
- }
81
- }
82
-
83
- return {};
84
- };
85
-
86
- const getConfigFromImport = async (
87
- tailwindConfigImport: ImportDeclaration,
88
- sourcePath: string,
89
- ): Promise<TailwindConfig> => {
90
- const configRelativePath = tailwindConfigImport.source.value;
91
- const sourceDirpath = path.dirname(sourcePath);
92
- const configFilepath = path.join(sourceDirpath, configRelativePath);
93
-
94
- const configBuildResult = await esbuild.build({
95
- bundle: true,
96
- stdin: {
97
- contents: `import tailwindConfig from "${configRelativePath}";
98
- export { tailwindConfig };`,
99
- loader: 'tsx',
100
- resolveDir: path.dirname(sourcePath),
101
- },
102
- platform: 'node',
103
- write: false,
104
- format: 'cjs',
105
- logLevel: 'silent',
106
- });
107
- const configFile = configBuildResult.outputFiles[0];
108
- if (configFile === undefined) {
109
- throw new Error(
110
- 'Could not build config file as it was found as undefined, this is most likely a bug, please open an issue.',
111
- );
112
- }
113
- const configModule = runBundledCode(configFile.text, configFilepath);
114
- if (isErr(configModule)) {
115
- throw new Error(
116
- `Error when trying to run the config file: ${configModule.error}`,
117
- );
118
- }
119
-
120
- if (
121
- typeof configModule.value === 'object' &&
122
- configModule.value !== null &&
123
- 'tailwindConfig' in configModule.value
124
- ) {
125
- return configModule.value.tailwindConfig as TailwindConfig;
126
- }
127
-
128
- throw new Error(
129
- `Could not read Tailwind config at ${configFilepath} because it doesn't have a default export in it.`,
130
- {
131
- cause: {
132
- configModule,
133
- configFilepath,
134
- },
135
- },
136
- );
137
- };
138
-
139
- const getImportWithGivenDefaultSpecifier = (
140
- ast: AST,
141
- specifierName: string,
142
- ) => {
143
- let importNode: ImportDeclaration | undefined;
144
- traverse(ast, {
145
- ImportDeclaration(nodePath) {
146
- if (
147
- nodePath.node.specifiers.some(
148
- (specifier) =>
149
- specifier.type === 'ImportDefaultSpecifier' &&
150
- specifier.local.name === specifierName,
151
- )
152
- ) {
153
- importNode = nodePath.node;
154
- }
155
- },
156
- });
157
- return importNode;
158
- };
159
-
160
- type JSXAttribute = Node & { type: 'JSXAttribute' };
161
-
162
- const getTailwindConfigNode = (ast: AST) => {
163
- let tailwindConfigNode: JSXAttribute | undefined;
164
- traverse(ast, {
165
- JSXOpeningElement(nodePath) {
166
- if (
167
- nodePath.node.name.type === 'JSXIdentifier' &&
168
- nodePath.node.name.name === 'Tailwind'
169
- ) {
170
- const configAttribute = nodePath.node.attributes.find(
171
- (
172
- attribute,
173
- ): attribute is Node & {
174
- type: 'JSXAttribute';
175
- } =>
176
- attribute.type === 'JSXAttribute' &&
177
- attribute.name.type === 'JSXIdentifier' &&
178
- attribute.name.name === 'config',
179
- );
180
- if (configAttribute) {
181
- tailwindConfigNode = configAttribute;
182
- }
183
- }
184
- },
185
- });
186
- return tailwindConfigNode;
187
- };
@@ -1,25 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { parse } from '@babel/parser';
4
- import { getTailwindMetadata } from './get-tailwind-metadata';
5
-
6
- describe('getTailwindMetadata()', () => {
7
- test('with the netlify-welcome demo email', async () => {
8
- const emailPath = path.resolve(
9
- __dirname,
10
- '../../../../../../apps/demo/emails/welcome/netlify-welcome.tsx',
11
- );
12
- const reactCode = await fs.readFile(emailPath, 'utf8');
13
- const ast = parse(reactCode, {
14
- strictMode: false,
15
- errorRecovery: true,
16
- sourceType: 'unambiguous',
17
- plugins: ['jsx', 'typescript', 'decorators'],
18
- });
19
-
20
- const tailwindMetadata = getTailwindMetadata(ast, reactCode, emailPath);
21
-
22
- expect(tailwindMetadata).toBeDefined();
23
- // console.log(tailwindMetadata);
24
- });
25
- });