unframer 2.27.2 → 3.0.0

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 (118) hide show
  1. package/dist/babel-jsx.js +2 -2
  2. package/dist/babel-jsx.js.map +1 -1
  3. package/dist/babel-typedoc.d.ts +39 -0
  4. package/dist/babel-typedoc.d.ts.map +1 -0
  5. package/dist/babel-typedoc.js +77 -0
  6. package/dist/babel-typedoc.js.map +1 -0
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +7 -2
  9. package/dist/cli.js.map +1 -1
  10. package/dist/esbuild.d.ts +2 -1
  11. package/dist/esbuild.d.ts.map +1 -1
  12. package/dist/esbuild.js +16 -9
  13. package/dist/esbuild.js.map +1 -1
  14. package/dist/exporter.d.ts +25 -8
  15. package/dist/exporter.d.ts.map +1 -1
  16. package/dist/exporter.js +381 -195
  17. package/dist/exporter.js.map +1 -1
  18. package/dist/exporter.test.js +0 -4
  19. package/dist/exporter.test.js.map +1 -1
  20. package/dist/framer.js +229 -102
  21. package/dist/generated/api-client.d.ts +3 -3
  22. package/dist/generated/api-client.d.ts.map +1 -1
  23. package/dist/package-manager.d.ts +10 -0
  24. package/dist/package-manager.d.ts.map +1 -0
  25. package/dist/package-manager.js +145 -0
  26. package/dist/package-manager.js.map +1 -0
  27. package/dist/react.d.ts +32 -0
  28. package/dist/react.d.ts.map +1 -1
  29. package/dist/react.js +1 -3
  30. package/dist/react.js.map +1 -1
  31. package/dist/undici-dispatcher.js +1 -2
  32. package/dist/undici-dispatcher.js.map +1 -1
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.d.ts.map +1 -1
  35. package/dist/version.js +1 -1
  36. package/dist/version.js.map +1 -1
  37. package/esm/babel-jsx.js +2 -2
  38. package/esm/babel-jsx.js.map +1 -1
  39. package/esm/babel-typedoc.d.ts +39 -0
  40. package/esm/babel-typedoc.d.ts.map +1 -0
  41. package/esm/babel-typedoc.js +74 -0
  42. package/esm/babel-typedoc.js.map +1 -0
  43. package/esm/cli.d.ts.map +1 -1
  44. package/esm/cli.js +7 -2
  45. package/esm/cli.js.map +1 -1
  46. package/esm/esbuild.d.ts +2 -1
  47. package/esm/esbuild.d.ts.map +1 -1
  48. package/esm/esbuild.js +16 -9
  49. package/esm/esbuild.js.map +1 -1
  50. package/esm/exporter.d.ts +25 -8
  51. package/esm/exporter.d.ts.map +1 -1
  52. package/esm/exporter.js +378 -194
  53. package/esm/exporter.js.map +1 -1
  54. package/esm/exporter.test.js +0 -4
  55. package/esm/exporter.test.js.map +1 -1
  56. package/esm/framer.js +229 -102
  57. package/esm/package-manager.d.ts +10 -0
  58. package/esm/package-manager.d.ts.map +1 -0
  59. package/esm/package-manager.js +141 -0
  60. package/esm/package-manager.js.map +1 -0
  61. package/esm/react.d.ts +32 -0
  62. package/esm/react.d.ts.map +1 -1
  63. package/esm/react.js +1 -3
  64. package/esm/react.js.map +1 -1
  65. package/esm/undici-dispatcher.js +1 -2
  66. package/esm/undici-dispatcher.js.map +1 -1
  67. package/esm/version.d.ts +1 -1
  68. package/esm/version.d.ts.map +1 -1
  69. package/esm/version.js +1 -1
  70. package/esm/version.js.map +1 -1
  71. package/package.json +5 -4
  72. package/src/babel-jsx.ts +2 -2
  73. package/src/babel-typedoc.ts +132 -0
  74. package/src/cli.ts +7 -2
  75. package/src/esbuild.ts +17 -12
  76. package/src/exporter.test.ts +0 -5
  77. package/src/exporter.ts +448 -237
  78. package/src/framer.js +237 -103
  79. package/src/package-manager.ts +164 -0
  80. package/src/react.tsx +33 -0
  81. package/src/undici-dispatcher.ts +1 -1
  82. package/src/version.ts +1 -1
  83. package/dist/framer.d.ts.map +0 -1
  84. package/dist/framer.js.map +0 -1
  85. package/esm/framer-chunks/chunk-22NYTOTD.d.ts +0 -14
  86. package/esm/framer-chunks/chunk-22NYTOTD.d.ts.map +0 -1
  87. package/esm/framer-chunks/chunk-22NYTOTD.js +0 -99
  88. package/esm/framer-chunks/chunk-22NYTOTD.js.map +0 -1
  89. package/esm/framer-chunks/fontshare-GSJIWLGZ-7BHTUG6K.d.ts +0 -115
  90. package/esm/framer-chunks/fontshare-GSJIWLGZ-7BHTUG6K.d.ts.map +0 -1
  91. package/esm/framer-chunks/fontshare-GSJIWLGZ-7BHTUG6K.js +0 -5
  92. package/esm/framer-chunks/fontshare-GSJIWLGZ-7BHTUG6K.js.map +0 -1
  93. package/esm/framer-chunks/fontshare-SSHBFVID-ZX5Y6FJ4.d.ts +0 -781
  94. package/esm/framer-chunks/fontshare-SSHBFVID-ZX5Y6FJ4.d.ts.map +0 -1
  95. package/esm/framer-chunks/fontshare-SSHBFVID-ZX5Y6FJ4.js +0 -5
  96. package/esm/framer-chunks/fontshare-SSHBFVID-ZX5Y6FJ4.js.map +0 -1
  97. package/esm/framer-chunks/fontshare-X6MCIXW5-FUMOBUA2.d.ts +0 -634
  98. package/esm/framer-chunks/fontshare-X6MCIXW5-FUMOBUA2.d.ts.map +0 -1
  99. package/esm/framer-chunks/fontshare-X6MCIXW5-FUMOBUA2.js +0 -5
  100. package/esm/framer-chunks/fontshare-X6MCIXW5-FUMOBUA2.js.map +0 -1
  101. package/esm/framer-chunks/framer-font-TNC5DMGA-XVG7BST3.d.ts +0 -18
  102. package/esm/framer-chunks/framer-font-TNC5DMGA-XVG7BST3.d.ts.map +0 -1
  103. package/esm/framer-chunks/framer-font-TNC5DMGA-XVG7BST3.js +0 -5
  104. package/esm/framer-chunks/framer-font-TNC5DMGA-XVG7BST3.js.map +0 -1
  105. package/esm/framer-chunks/google-3GQMHAEU-KEOTHDV6.d.ts +0 -9827
  106. package/esm/framer-chunks/google-3GQMHAEU-KEOTHDV6.d.ts.map +0 -1
  107. package/esm/framer-chunks/google-3GQMHAEU-KEOTHDV6.js +0 -5
  108. package/esm/framer-chunks/google-3GQMHAEU-KEOTHDV6.js.map +0 -1
  109. package/esm/framer-chunks/google-42BCYVR5-PDCHFNPY.d.ts +0 -3231
  110. package/esm/framer-chunks/google-42BCYVR5-PDCHFNPY.d.ts.map +0 -1
  111. package/esm/framer-chunks/google-42BCYVR5-PDCHFNPY.js +0 -5
  112. package/esm/framer-chunks/google-42BCYVR5-PDCHFNPY.js.map +0 -1
  113. package/esm/framer-chunks/google-LHIHIYDX-FZZ6UXE7.d.ts +0 -1499
  114. package/esm/framer-chunks/google-LHIHIYDX-FZZ6UXE7.d.ts.map +0 -1
  115. package/esm/framer-chunks/google-LHIHIYDX-FZZ6UXE7.js +0 -5
  116. package/esm/framer-chunks/google-LHIHIYDX-FZZ6UXE7.js.map +0 -1
  117. package/esm/framer.d.ts.map +0 -1
  118. package/esm/framer.js.map +0 -1
package/dist/exporter.js CHANGED
@@ -4,19 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.bundle = bundle;
7
+ exports.resolvePackageVersion = resolvePackageVersion;
7
8
  exports.resolvePackage = resolvePackage;
8
- exports.checkUnframerVersion = checkUnframerVersion;
9
9
  exports.getDarkModeSelector = getDarkModeSelector;
10
10
  exports.getStyleTokensCss = getStyleTokensCss;
11
11
  exports.findRelativeLinks = findRelativeLinks;
12
12
  exports.extractPropControlsUnsafe = extractPropControlsUnsafe;
13
- exports.propControlsToType = propControlsToType;
13
+ exports.propControlsToTypedocComments = propControlsToTypedocComments;
14
14
  exports.parsePropertyControls = parsePropertyControls;
15
15
  exports.extractTokenInfo = extractTokenInfo;
16
16
  exports.componentCamelCase = componentCamelCase;
17
+ exports.indentWithTabs = indentWithTabs;
17
18
  exports.createExampleComponentCode = createExampleComponentCode;
19
+ exports.isVersionGreater = isVersionGreater;
18
20
  const esbuild_1 = require("esbuild");
19
- const package_json_1 = __importDefault(require("../package.json"));
20
21
  const url_1 = __importDefault(require("url"));
21
22
  const async_sema_1 = require("async-sema");
22
23
  const esbuild_plugins_node_modules_polyfill_1 = require("esbuild-plugins-node-modules-polyfill");
@@ -27,12 +28,15 @@ const fs_1 = __importDefault(require("fs"));
27
28
  const path_1 = __importDefault(require("path"));
28
29
  const utils_js_1 = require("./utils.js");
29
30
  const babel_jsx_js_1 = require("./babel-jsx.js");
31
+ const babel_typedoc_js_1 = require("./babel-typedoc.js");
30
32
  const compat_js_1 = require("./compat.js");
31
33
  const css_js_1 = require("./css.js");
32
34
  const esbuild_2 = require("./esbuild");
33
35
  const framer_js_1 = require("./framer.js");
34
36
  const sentry_1 = require("./sentry");
35
37
  const utils_js_2 = require("./utils.js");
38
+ const package_manager_js_1 = require("./package-manager.js");
39
+ const version_js_1 = require("./version.js");
36
40
  const js_api_1 = require("@biomejs/js-api");
37
41
  let biome;
38
42
  async function bundle({ config, cwd: out = '', watch = false, signal = undefined, }) {
@@ -43,6 +47,24 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
43
47
  await fs_1.default.promises.mkdir(out, { recursive: true });
44
48
  }
45
49
  catch (e) { }
50
+ // Prefix for temporary .js files to avoid HMR issues
51
+ const tempJsPrefix = 'temp_';
52
+ // Helper function to handle file path transformations with temp prefix
53
+ function getFilePaths(filePath, outDir) {
54
+ const baseName = path_1.default.basename(filePath);
55
+ const dirName = path_1.default.dirname(filePath);
56
+ const tempFileName = tempJsPrefix + baseName;
57
+ const tempFilePath = path_1.default.join(dirName, tempFileName);
58
+ return {
59
+ originalPath: filePath,
60
+ tempJsPath: path_1.default.resolve(outDir, tempFilePath),
61
+ finalJsPath: path_1.default.resolve(outDir, filePath),
62
+ jsxPath: path_1.default.resolve(outDir, filePath.replace(/\.js$/, '.jsx')),
63
+ tempFilePath,
64
+ baseName,
65
+ dirName,
66
+ };
67
+ }
46
68
  utils_js_2.spinner.start('exporting components...');
47
69
  const otherRoutes = Object.fromEntries((config.framerWebPages || []).map((page) => [
48
70
  page.webPageId,
@@ -59,7 +81,7 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
59
81
  };
60
82
  }
61
83
  const fn = watch ? esbuild_1.context : fakeContext;
62
- let foundError = false;
84
+ const missingPackages = new Set();
63
85
  const buildContext = await fn({
64
86
  absWorkingDir: out,
65
87
  entryPoints: Object.keys(components)
@@ -88,9 +110,12 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
88
110
  signal,
89
111
  externalPackages: config.externalPackages,
90
112
  externalizeNpm: config.allExternal,
91
- outDir: config.outDir,
113
+ outDir: out,
92
114
  onMissingPackage: (e) => {
93
- foundError = true;
115
+ // No longer needed - packages are auto-installed
116
+ },
117
+ onCollectMissingPackage: (pkg) => {
118
+ missingPackages.add(pkg);
94
119
  },
95
120
  }),
96
121
  (0, esbuild_plugins_node_modules_polyfill_1.nodeModulesPolyfillPlugin)({}),
@@ -105,6 +130,13 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
105
130
  });
106
131
  build.onLoad({ filter: /.*/, namespace: 'virtual' }, async (args) => {
107
132
  const name = args.path;
133
+ // Handle virtual routes module
134
+ if (name === '__routes') {
135
+ return {
136
+ contents: `export const routes = ${JSON.stringify(otherRoutes, null, 2)};`,
137
+ loader: 'js',
138
+ };
139
+ }
108
140
  const url = components[name];
109
141
  const componentBreakpoints = config.componentBreakpoints?.filter((x) => x.componentName === name) || [];
110
142
  const brk = breakpointSizes
@@ -126,6 +158,8 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
126
158
  ...brk.slice(1),
127
159
  ])
128
160
  : {};
161
+ // Use virtual routes module
162
+ const routesImportPath = 'virtual:__routes';
129
163
  return {
130
164
  contents: /** js **/ `
131
165
  'use client'
@@ -136,30 +170,31 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
136
170
  signal,
137
171
  })}'
138
172
  import { WithFramerBreakpoints } from 'unframer'
173
+ import { routes } from '${routesImportPath}'
139
174
  const locales = ${JSON.stringify(config.locales) || '[]'}
140
175
  const defaultResponsiveVariants = ${JSON.stringify(responsiveVariants, null, 2)}
141
176
 
142
- Component.Responsive = ({ locale, ...rest }) => {
177
+
178
+ function ComponentWithRoot({ locale, ...rest }) {
143
179
  return (
144
180
  <ContextProviders
145
- routes={${JSON.stringify(otherRoutes)}}
146
- children={<WithFramerBreakpoints
147
- Component={Component}
148
- variants={defaultResponsiveVariants}
149
- {...rest}
150
- />}
181
+ routes={routes}
182
+ children={<Component {...rest} />}
151
183
  framerSiteId={${JSON.stringify(config.fullFramerProjectId)}}
152
184
  locale={locale}
153
185
  locales={locales}
154
186
  />
155
187
  )
156
188
  }
157
-
158
- export default function ComponentWithRoot({ locale, ...rest }) {
189
+ ComponentWithRoot.Responsive = ({ locale, ...rest }) => {
159
190
  return (
160
191
  <ContextProviders
161
- routes={${JSON.stringify(otherRoutes, null, 2)}}
162
- children={<Component {...rest} />}
192
+ routes={routes}
193
+ children={<WithFramerBreakpoints
194
+ Component={Component}
195
+ variants={defaultResponsiveVariants}
196
+ {...rest}
197
+ />}
163
198
  framerSiteId={${JSON.stringify(config.fullFramerProjectId)}}
164
199
  locale={locale}
165
200
  locales={locales}
@@ -167,6 +202,7 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
167
202
  )
168
203
  }
169
204
  Object.assign(ComponentWithRoot, Component)
205
+ export default ComponentWithRoot
170
206
  `,
171
207
  loader: 'jsx',
172
208
  };
@@ -179,10 +215,27 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
179
215
  });
180
216
  const doNotEditComment = `/* This file was generated by Unframer for Framer project ${config.projectId || ''} "${config.projectName}", do not edit manually */\n`;
181
217
  async function rebuild() {
218
+ // Clear missing packages for each rebuild (important for watch mode)
219
+ missingPackages.clear();
220
+ try {
221
+ const installedVersion = await resolvePackageVersion({
222
+ cwd: out,
223
+ pkg: 'unframer',
224
+ });
225
+ if (isVersionGreater(installedVersion || '0.0.0', version_js_1.version || '0.0.0')) {
226
+ // Version mismatch, add with specific version
227
+ missingPackages.add(`unframer@${version_js_1.version}`);
228
+ utils_js_2.spinner.info(`Different unframer version detected (${installedVersion}), will install unframer@${version_js_1.version}`);
229
+ }
230
+ }
231
+ catch (e) {
232
+ // Unframer not installed, add with specific version
233
+ missingPackages.add(`unframer@${version_js_1.version}`);
234
+ utils_js_2.spinner.info(`Missing package detected: unframer@${version_js_1.version}`);
235
+ }
182
236
  const prevFiles = await recursiveReaddir(out);
183
237
  const buildResult = await buildContext.rebuild().catch((e) => {
184
238
  if (e.message.includes('No matching export ')) {
185
- foundError = true;
186
239
  utils_js_2.spinner.error(`esbuild failed to import from an external package, this usually means that the npm package version in Framer is older than the latest.`);
187
240
  utils_js_2.spinner.error(`Use --external to make all npm packagess external, then install the right version`);
188
241
  process.exit(1);
@@ -190,105 +243,31 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
190
243
  throw e;
191
244
  });
192
245
  utils_js_2.spinner.update('Finished build');
193
- for (let file of buildResult.outputFiles) {
194
- const resultPathAbsJs = path_1.default.resolve(out, file.path);
195
- const resultPathAbsJsx = resultPathAbsJs.replace(/\.js$/, '.jsx');
196
- const existing = await fs_1.default.promises
197
- .readFile(resultPathAbsJsx, 'utf-8')
198
- .catch(() => null);
199
- const tooBigSize = 0.7 * 1024 * 1024;
200
- let formatted = file.text;
201
- let tooBig = file.text.length >= tooBigSize;
202
- let didFormat = false;
203
- if (config.jsx &&
204
- !tooBig &&
205
- !resultPathAbsJs.includes('/chunks/') &&
206
- !resultPathAbsJs.includes('\\chunks\\')) {
207
- try {
208
- let res = (0, core_1.transform)(file.text || '', {
209
- babelrc: false,
210
- sourceType: 'module',
211
- plugins: [
212
- // babelPluginDeduplicateImports,
213
- babel_jsx_js_1.babelPluginJsxTransform,
214
- babel_jsx_js_1.removeJsxExpressionContainer,
215
- ],
216
- // ast: true,
217
- // code: false,
218
- filename: 'x.jsx',
219
- compact: false,
220
- sourceMaps: false,
221
- });
222
- if (res?.code) {
223
- if (!biome) {
224
- biome = await js_api_1.Biome.create({
225
- distribution: js_api_1.Distribution.NODE,
226
- });
227
- }
228
- let result = biome.formatContent(res.code, {
229
- filePath: 'example.jsx',
230
- });
231
- didFormat = true;
232
- formatted = result.content;
233
- }
234
- }
235
- catch (e) {
236
- (0, sentry_1.notifyError)(e, 'babel transform and format');
237
- }
246
+ // Install missing packages if any were collected
247
+ if (missingPackages.size > 0) {
248
+ const packagesToInstall = Array.from(missingPackages);
249
+ utils_js_2.logger.log(`Installing missing packages: ${packagesToInstall.join(', ')}`);
250
+ const installResult = await (0, package_manager_js_1.installPackagesBatch)({
251
+ packageNames: packagesToInstall,
252
+ cwd: out,
253
+ isDev: false,
254
+ });
255
+ if (!installResult.success) {
256
+ utils_js_2.spinner.error(`Failed to install packages: ${installResult.error}`);
257
+ // Don't fail the build, just warn
238
258
  }
239
- // let inputCode = res!.code!
240
- // let shouldFormat = !tooBig && !file.path.includes('chunks')
241
- // if (shouldFormat) {
242
- // spinner.update(`Formatting ${path.relative(out, file.path)}`)
243
- // formatted = dprint.format('file.jsx', file.text, {
244
- // lineWidth: 140,
245
- // quoteStyle: 'alwaysSingle',
246
- // trailingCommas: 'always',
247
- // semiColons: 'always',
248
- // })
249
- // }
250
- // if (tooBig) {
251
- // spinner.info(
252
- // `skipping formatting ${path.relative(
253
- // out,
254
- // file.path,
255
- // )}, too big`,
256
- // )
257
- // }
258
- // if (framerWebPages?.length) {
259
- // codeNew = replaceWebPageIds({
260
- // code: codeNew,
261
- // elements: framerWebPages,
262
- // })
263
- // }
264
- // const lines = findRelativeLinks(codeNew)
265
- // if (lines.length) {
266
- // spinner.error(
267
- // `found broken links for ${path.relative(out, file.path)}`,
268
- // )
269
- // lines.forEach((line) => {
270
- // logger.log(`${path.resolve(out, file.path)}:${line + 1}`)
271
- // })
272
- // }
259
+ }
260
+ // First, write raw JS files for type extraction with temp prefix
261
+ for (let file of buildResult.outputFiles) {
262
+ const paths = getFilePaths(file.path, out);
273
263
  const prefix = `// @ts-nocheck\n` + `/* eslint-disable */\n` + doNotEditComment;
274
- const codeJsx = prefix + formatted;
275
264
  const codeJs = prefix + file.text;
276
- // if (existing === codeJsx) {
277
- // continue
278
- // }
279
- utils_js_2.logger.log(`writing`, path_1.default.relative(out, file.path));
280
- await fs_1.default.promises.mkdir(path_1.default.dirname(resultPathAbsJsx), {
265
+ utils_js_2.logger.log(`writing temp JS`, path_1.default.relative(out, paths.tempFilePath));
266
+ await fs_1.default.promises.mkdir(path_1.default.dirname(paths.tempJsPath), {
281
267
  recursive: true,
282
268
  });
283
- if (codeJs !== codeJsx || !didFormat) {
284
- await fs_1.default.promises.writeFile(resultPathAbsJs, codeJs, 'utf-8');
285
- }
286
- if (didFormat) {
287
- await fs_1.default.promises.writeFile(resultPathAbsJsx, codeJsx, 'utf-8');
288
- }
269
+ await fs_1.default.promises.writeFile(paths.tempJsPath, codeJs, 'utf-8');
289
270
  }
290
- utils_js_2.spinner.stop();
291
- await fs_1.default.promises.writeFile(path_1.default.resolve(out, '.cursorignore'), `**/*.js\nchunks\n`, 'utf-8');
292
271
  if (!buildResult?.outputFiles) {
293
272
  throw new Error('Failed to generate result');
294
273
  }
@@ -304,7 +283,8 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
304
283
  const name = path_1.default
305
284
  .relative(out, file.path)
306
285
  .replace(/\.jsx?$/, '');
307
- const resultPathAbs = path_1.default.resolve(out, file.path);
286
+ const paths = getFilePaths(file.path, out);
287
+ const resultPathAbs = paths.tempJsPath;
308
288
  if (!components[name]) {
309
289
  return;
310
290
  }
@@ -313,6 +293,7 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
313
293
  return;
314
294
  }
315
295
  utils_js_2.logger.log(`extracting types for ${name}`);
296
+ utils_js_2.spinner.info(`Extracting types for component: ${name}`);
316
297
  utils_js_2.spinner.update(`Extracting types for ${name}`);
317
298
  const { propertyControls, fonts } = await extractPropControlsUnsafe(resultPathAbs, name);
318
299
  if (!propertyControls) {
@@ -322,17 +303,20 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
322
303
  ...x,
323
304
  fileName: path_1.default.basename(file.path),
324
305
  })));
325
- const types = propControlsToType({
306
+ const typedocComments = propControlsToTypedocComments({
326
307
  controls: propertyControls,
327
308
  fileName: name,
328
309
  config,
329
310
  });
311
+ utils_js_2.logger.log(`Generated TypeDoc comments for ${name}: ${!!typedocComments.headerComment}`);
330
312
  await fs_1.default.promises.mkdir(out, { recursive: true });
331
- await fs_1.default.promises.writeFile(path_1.default.resolve(out, `${name}.d.ts`), types);
313
+ // .d.ts generation removed – types are now injected as typedoc
314
+ // comments directly inside the generated JSX file.
332
315
  return {
333
316
  propertyControls,
334
317
  fonts,
335
318
  name,
319
+ typedocComments,
336
320
  };
337
321
  }
338
322
  finally {
@@ -346,7 +330,6 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
346
330
  // Ignore error if file doesn't exist or can't be deleted
347
331
  }
348
332
  });
349
- // spinner.stop()
350
333
  const cssString = doNotEditComment +
351
334
  '/* This css file has all the necessary styles to run all your components */\n' +
352
335
  '\n' +
@@ -364,24 +347,27 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
364
347
  .forEach((x) => utils_js_2.logger.log(x));
365
348
  const jsxFiles = buildResult.outputFiles
366
349
  .filter((x) => x.path.endsWith('.js') &&
367
- fs_1.default.existsSync(x.path.replace(/\.js$/, '.jsx')))
368
- .map((x) => x.path.replace(/\.js$/, '.jsx'));
350
+ fs_1.default.existsSync(getFilePaths(x.path, out).jsxPath))
351
+ .map((x) => getFilePaths(x.path, out).jsxPath);
369
352
  const outFiles = buildResult.outputFiles
370
- .map((x) => path_1.default.resolve(out, x.path))
353
+ .map((x) => {
354
+ const paths = getFilePaths(x.path, out);
355
+ if (x.path.endsWith('.js') && fs_1.default.existsSync(paths.jsxPath)) {
356
+ return null; // Will be handled by jsx files
357
+ }
358
+ return paths.finalJsPath;
359
+ })
360
+ .filter(Boolean)
371
361
  .concat([
372
362
  path_1.default.resolve(out, 'meta.json'),
373
363
  path_1.default.resolve(out, 'tokens.css'),
374
364
  path_1.default.resolve(out, '.cursorignore'),
375
365
  path_1.default.resolve(out, 'styles.css'),
376
366
  ])
377
- .concat(jsxFiles)
378
- .concat(buildResult.outputFiles.map((x) => path_1.default.resolve(out, x.path.replace(/\.jsx?$/, '.d.ts'))));
367
+ .concat(jsxFiles);
379
368
  const filesToDelete = prevFiles
380
369
  .filter((x) => !outFiles.includes(x))
381
- .concat(buildResult.outputFiles
382
- .map((x) => x.path)
383
- .filter((js) => js.endsWith('.js') &&
384
- jsxFiles.some((x) => x.startsWith(js))));
370
+ .filter((x) => !x.includes(tempJsPrefix)); // Don't delete temp files here, they're handled separately
385
371
  for (let file of filesToDelete) {
386
372
  utils_js_2.logger.log('deleting', path_1.default.relative(out, file));
387
373
  try {
@@ -398,12 +384,6 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
398
384
  if (watch) {
399
385
  utils_js_2.logger.log('waiting for components or config changes');
400
386
  }
401
- if (!tokens?.length) {
402
- const tokensCss = "/* This css file contains your color variables, sometimes these get desynced when updated in Framer so it's good that you copy and paste this snippet into your app css */\n" +
403
- '/* Bug: https://www.framer.community/c/bugs/color-style-unlinks-when-copying-component-between-projects-resulting-in-potential-value-discrepancy */\n' +
404
- getTokensCss({ out, result: buildResult });
405
- await fs_1.default.promises.writeFile(path_1.default.resolve(out, 'tokens.css'), tokensCss, 'utf-8');
406
- }
407
387
  const res = {
408
388
  components: Object.entries(components).map(([name, v]) => {
409
389
  const propControls = propControlsData.find((x) => x?.name === name);
@@ -416,6 +396,131 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
416
396
  };
417
397
  }),
418
398
  };
399
+ // Process and write JSX files with TypeDoc comments
400
+ utils_js_2.spinner.update('Processing JSX files with TypeDoc comments');
401
+ for (let file of buildResult.outputFiles) {
402
+ const paths = getFilePaths(file.path, out);
403
+ const componentName = path_1.default
404
+ .relative(out, file.path)
405
+ .replace(/\.js$/, '');
406
+ const propData = propControlsData.find((p) => p?.name === componentName);
407
+ const typedocComments = propData?.typedocComments;
408
+ utils_js_2.logger.log(`Processing component: ${componentName}`);
409
+ utils_js_2.spinner.update(`Processing JSX for ${componentName}`);
410
+ if (!propData) {
411
+ utils_js_2.logger.log(` No propData found for ${componentName}`);
412
+ }
413
+ else {
414
+ utils_js_2.logger.log(` PropData found for ${componentName}, has propertyControls: ${!!propData.propertyControls}`);
415
+ if (!typedocComments) {
416
+ utils_js_2.logger.log(` No typedocComments for ${componentName}`);
417
+ }
418
+ else {
419
+ utils_js_2.logger.log(` TypeDoc comments available for ${componentName}`);
420
+ }
421
+ }
422
+ const existing = await fs_1.default.promises
423
+ .readFile(paths.jsxPath, 'utf-8')
424
+ .catch(() => null);
425
+ const tooBigSize = 0.7 * 1024 * 1024;
426
+ let formatted = file.text;
427
+ let tooBig = file.text.length >= tooBigSize;
428
+ let didFormat = false;
429
+ if (config.jsx &&
430
+ !tooBig &&
431
+ !paths.tempJsPath.includes('/chunks/') &&
432
+ !paths.tempJsPath.includes('\\chunks\\')) {
433
+ try {
434
+ const plugins = [
435
+ // babelPluginDeduplicateImports,
436
+ babel_jsx_js_1.babelPluginJsxTransform,
437
+ babel_jsx_js_1.removeJsxExpressionContainer,
438
+ ];
439
+ // Add TypeDoc plugin if we have comments for this component
440
+ if (typedocComments) {
441
+ utils_js_2.logger.log(` Adding TypeDoc plugin for ${componentName}`);
442
+ plugins.push((0, babel_typedoc_js_1.babelPluginTypedoc)(typedocComments));
443
+ }
444
+ else {
445
+ utils_js_2.logger.log(` No TypeDoc comments to add for ${componentName}`);
446
+ }
447
+ let res = (0, core_1.transform)(file.text || '', {
448
+ babelrc: false,
449
+ sourceType: 'module',
450
+ parserOpts: {
451
+ plugins: ['jsx'],
452
+ },
453
+ plugins,
454
+ // ast: true,
455
+ // code: false,
456
+ filename: 'x.jsx',
457
+ compact: false,
458
+ sourceMaps: false,
459
+ });
460
+ if (res?.code) {
461
+ if (!biome) {
462
+ biome = await js_api_1.Biome.create({
463
+ distribution: js_api_1.Distribution.NODE,
464
+ });
465
+ }
466
+ let result = biome.formatContent(res.code, {
467
+ filePath: 'example.jsx',
468
+ });
469
+ didFormat = true;
470
+ formatted = result.content;
471
+ }
472
+ }
473
+ catch (e) {
474
+ (0, sentry_1.notifyError)(e, 'babel transform and format');
475
+ }
476
+ }
477
+ const prefix = `// @ts-nocheck\n` + `/* eslint-disable */\n` + doNotEditComment;
478
+ const codeJsx = prefix + formatted;
479
+ const codeJs = prefix + file.text;
480
+ utils_js_2.logger.log(`writing`, path_1.default.relative(out, file.path));
481
+ await fs_1.default.promises.mkdir(path_1.default.dirname(paths.jsxPath), {
482
+ recursive: true,
483
+ });
484
+ // Always write the temp .js file for type extraction
485
+ await fs_1.default.promises.writeFile(paths.tempJsPath, codeJs, 'utf-8');
486
+ // Only write .jsx file if it's different from existing or if formatting was done
487
+ if (didFormat && codeJsx !== existing) {
488
+ await fs_1.default.promises.writeFile(paths.jsxPath, codeJsx, 'utf-8');
489
+ }
490
+ }
491
+ utils_js_2.spinner.stop();
492
+ // await fs.promises.writeFile(
493
+ // path.resolve(out, '.cursorignore'),
494
+ // `**/*.js\nchunks\n`,
495
+ // 'utf-8',
496
+ // )
497
+ // Clean up temp .js files and handle prefixes
498
+ for (let file of buildResult.outputFiles) {
499
+ if (file.path.endsWith('.js')) {
500
+ const paths = getFilePaths(file.path, out);
501
+ if (fs_1.default.existsSync(paths.jsxPath)) {
502
+ // Remove temp .js file if .jsx equivalent exists
503
+ utils_js_2.logger.log('removing temp JS file with JSX equivalent:', path_1.default.relative(out, paths.tempJsPath));
504
+ try {
505
+ await fs_1.default.promises.rm(paths.tempJsPath);
506
+ await fs_1.default.promises.rm(paths.finalJsPath);
507
+ }
508
+ catch (error) {
509
+ // Ignore error if file doesn't exist
510
+ }
511
+ }
512
+ else {
513
+ // Rename temp .js file to final name if no .jsx equivalent
514
+ utils_js_2.logger.log('renaming temp JS file to final name:', path_1.default.relative(out, paths.tempJsPath), '->', path_1.default.relative(out, paths.finalJsPath));
515
+ try {
516
+ await fs_1.default.promises.rename(paths.tempJsPath, paths.finalJsPath);
517
+ }
518
+ catch (error) {
519
+ // Ignore error if file doesn't exist
520
+ }
521
+ }
522
+ }
523
+ }
419
524
  utils_js_2.spinner.info(`Build completed`);
420
525
  return res;
421
526
  }
@@ -441,8 +546,11 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
441
546
  const result = await rebuild();
442
547
  console.log();
443
548
  console.log();
444
- const outDirForExample = path_1.default.posix.relative(process.cwd(), out).replace(/^src\//, '') ||
445
- 'framer'; // remove src so file works inside src
549
+ const outDirForExample = path_1.default
550
+ .relative(process.cwd(), out)
551
+ .split(path_1.default.sep)
552
+ .join('/')
553
+ .replace(/^src\//, '') || 'framer'; // remove src so file works inside src
446
554
  const { exampleCode } = await createExampleComponentCode({
447
555
  outDir: out,
448
556
  // buildResult: result,
@@ -455,38 +563,31 @@ async function bundle({ config, cwd: out = '', watch = false, signal = undefined
455
563
  });
456
564
  await fs_1.default.promises.writeFile(utils_js_2.stackblitzDemoExample, exampleCode);
457
565
  }
458
- if (!foundError) {
459
- console.log((0, utils_js_2.terminalMarkdown)((0, utils_js_1.dedent) `
460
- # How to use the Framer components
566
+ console.log((0, utils_js_2.terminalMarkdown)((0, utils_js_1.dedent) `
567
+ # How to use the Framer components
461
568
 
462
- Your components are exported to \`${outDirForExample}\` folder. Now please install the \`unframer\` runtime dependency:
569
+ Your components are exported to \`${outDirForExample}\` folder.
463
570
 
464
- \`\`\`sh
465
- npm install unframer
466
- \`\`\`
571
+ Each component has a \`.Responsive\` variant that allows you to specify different variants for different breakpoints.
467
572
 
468
- Each component has a \`.Responsive\` variant that allows you to specify different variants for different breakpoints.
573
+ You can use the components like this (try copy pasting the code below into your React app):
469
574
 
470
- You can use the components like this (try copy pasting the code below into your React app):
575
+ \`\`\`jsx
576
+ ${exampleCode}
577
+ \`\`\`
471
578
 
472
- \`\`\`jsx
473
- ${exampleCode}
474
- \`\`\`
579
+ It's very important to import the \`styles.css\` file to include the necessary styles for the components.
475
580
 
476
- It's very important to import the \`styles.css\` file to include the necessary styles for the components.
581
+ To style components you can pass a \`style\` or \`className\` prop (but remember to use !important to increase the specificity).
477
582
 
478
- To style components you can pass a \`style\` or \`className\` prop (but remember to use !important to increase the specificity).
583
+ Read more on GitHub: https://github.com/remorses/unframer
479
584
 
480
- Read more on GitHub: https://github.com/remorses/unframer
481
-
482
- `));
483
- }
484
- await checkUnframerVersion({ cwd: out });
585
+ `));
485
586
  console.log();
486
587
  return { result, rebuild, buildContext };
487
588
  }
488
589
  const packageVersionCache = new Map();
489
- function resolvePackage({ cwd, pkg }) {
590
+ function resolvePackageVersion({ cwd, pkg }) {
490
591
  if (packageVersionCache.has(pkg)) {
491
592
  return Promise.resolve(packageVersionCache.get(pkg));
492
593
  }
@@ -501,7 +602,7 @@ function resolvePackage({ cwd, pkg }) {
501
602
  cwd,
502
603
  }, (error, stdout, stderr) => {
503
604
  if (error) {
504
- utils_js_2.logger.log(stderr);
605
+ // Package not installed - this is expected and handled by auto-install
505
606
  reject(new Error(`${pkg} is not installed in your project`));
506
607
  return;
507
608
  }
@@ -511,17 +612,25 @@ function resolvePackage({ cwd, pkg }) {
511
612
  });
512
613
  });
513
614
  }
514
- async function checkUnframerVersion({ cwd }) {
515
- const currentVersion = package_json_1.default.version;
516
- try {
517
- const installedVersion = await resolvePackage({ cwd, pkg: 'unframer' });
518
- if (installedVersion !== currentVersion) {
519
- utils_js_2.spinner.error(`IMPORTANT: Unframer version mismatch. Please run: npm update unframer@latest`);
520
- }
521
- }
522
- catch (e) {
523
- utils_js_2.spinner.error('IMPORTANT: Unframer is not installed in your project. Please run: npm install unframer');
524
- }
615
+ function resolvePackage({ cwd, pkg }) {
616
+ return new Promise((resolve) => {
617
+ const code = `import('${pkg}/package.json', { with: { type: 'json' } }).then(()=>console.log('true')).catch(()=>import('${pkg}').then(()=>console.log('true')).catch(()=>console.log('false')));`;
618
+ const command = [
619
+ JSON.stringify(nodePath),
620
+ '-e',
621
+ JSON.stringify(code),
622
+ ].join(' ');
623
+ (0, child_process_1.exec)(command, {
624
+ cwd,
625
+ }, (error, stdout) => {
626
+ if (error) {
627
+ resolve(false);
628
+ return;
629
+ }
630
+ const exists = stdout.trim().split('\n').pop() === 'true';
631
+ resolve(exists);
632
+ });
633
+ });
525
634
  }
526
635
  function getDarkModeSelector(opts) {
527
636
  const { darkModeType = 'class', content } = opts;
@@ -615,7 +724,7 @@ async function extractPropControlsSafe(text, name) {
615
724
  utils_js_2.logger.error(`Cannot get property controls for ${name}`, e.stack);
616
725
  }
617
726
  }
618
- function getTokensCss({ out, result, }) {
727
+ async function getTokensCss({ out, result, }) {
619
728
  const allTokens = [];
620
729
  for (let file of result.outputFiles) {
621
730
  const code = fs_1.default.readFileSync(path_1.default.resolve(out, file.path), 'utf-8');
@@ -745,7 +854,11 @@ function safeJsonParse(text) {
745
854
  return null;
746
855
  }
747
856
  }
748
- function propControlsToType({ config, fileName, controls, }) {
857
+ /**
858
+ * Generates TypeDoc comments that will be injected into JSX files
859
+ * instead of generating separate .d.ts files
860
+ */
861
+ function propControlsToTypedocComments({ config, fileName, controls, }) {
749
862
  try {
750
863
  const types = Object.entries(controls || {})
751
864
  .map(([key, value]) => {
@@ -803,40 +916,76 @@ function propControlsToType({ config, fileName, controls, }) {
803
916
  if (!name) {
804
917
  return '';
805
918
  }
806
- return ` ${JSON.stringify(name)}?: ${typescriptType(value)}`;
919
+ return ` * ${name}?: ${typescriptType(value)} // ${value.title || name}`;
807
920
  })
808
921
  .filter(Boolean)
809
922
  .join('\n');
810
923
  const componentName = componentCamelCase(fileName);
811
- const defaultPropsTypes = [
812
- 'children?: React.ReactNode',
813
- 'locale?: Locale',
814
- 'style?: React.CSSProperties',
815
- 'className?: string',
816
- 'id?: string',
817
- 'width?: any',
818
- 'height?: any',
819
- 'layoutId?: string',
820
- ]
821
- .map((line) => ` ${line}`)
822
- .join('\n') + '\n';
823
- let t = '';
824
- t += '/* This file was generated by Unframer, do not edit manually */\n';
825
- t += 'import * as React from "react"\n\n';
826
- t += 'import { UnframerBreakpoint } from "unframer"\n\n';
827
- t += `type Locale = ${config.locales?.length
828
- ? config.locales.map((l) => `'${l.code}'`).join(' | ')
829
- : 'string'}\n`;
830
- t += `export interface Props {\n${defaultPropsTypes}${types}\n}\n\n`;
831
- t += `const ${componentName} = (props: Props) => any\n\n`;
832
- t += `type VariantsMap = Partial<Record<UnframerBreakpoint, Props['variant']>> & { base: Props['variant'] }\n\n`;
833
- t += `${componentName}.Responsive = (props: Omit<Props, 'variant'> & {variants?: VariantsMap}) => any\n\n`;
834
- t += `export default ${componentName}\n\n`;
835
- return t;
924
+ const defaultPropsJsDoc = [
925
+ ' * children?: React.ReactNode',
926
+ ' * locale?: Locale',
927
+ ' * style?: React.CSSProperties',
928
+ ' * className?: string',
929
+ ' * id?: string',
930
+ ' * ref?: any',
931
+ ' * width?: any',
932
+ ' * height?: any',
933
+ ' * layoutId?: string',
934
+ ].join('\n');
935
+ // Generate header comment with type definitions
936
+ let headerComment = '/**\n';
937
+ headerComment += ' * @typedef Locale\n';
938
+ // Generate union type from config.locales if available
939
+ const localeType = (() => {
940
+ if (config?.locales &&
941
+ Array.isArray(config.locales) &&
942
+ config.locales.length > 0) {
943
+ return config.locales
944
+ .map((locale) => `'${locale.slug}'`)
945
+ .join(' | ');
946
+ }
947
+ return 'string';
948
+ })();
949
+ headerComment += ` * ${localeType}\n`;
950
+ headerComment += ' */\n\n';
951
+ headerComment += '/**\n';
952
+ headerComment +=
953
+ ' * @typedef {{\n';
954
+ headerComment += defaultPropsJsDoc;
955
+ if (types) {
956
+ headerComment += '\n' + types;
957
+ }
958
+ headerComment += `\n}} Props\n`;
959
+ headerComment += '\n */\n\n';
960
+ headerComment += '/**\n';
961
+ headerComment += ' * @type {import("unframer").UnframerBreakpoint}\n';
962
+ headerComment += ' * Represents a responsive breakpoint for unframer.\n';
963
+ headerComment += ' */\n\n';
964
+ headerComment += '/**\n';
965
+ headerComment += ' * @typedef VariantsMap\n';
966
+ headerComment +=
967
+ " * Partial record of UnframerBreakpoint to Props.variant, with a mandatory 'base' key.\n";
968
+ headerComment +=
969
+ " * { [key in UnframerBreakpoint]?: Props['variant'] } & { base: Props['variant'] }\n";
970
+ headerComment += ' */';
971
+ // Generate responsive comment
972
+ const responsiveComment = `/**\n * Renders ${componentName} for all breakpoints with a variants map. Variant prop is inferred per breakpoint.\n * @function\n * @param {Omit<Props, 'variant'> & {variants?: VariantsMap}} props\n * @returns {any}\n */`;
973
+ // Generate default export comment - use inline function type instead of referencing undefined type
974
+ const defaultExportComment = `/** @type {function(Props): any} */`;
975
+ return {
976
+ headerComment,
977
+ responsiveComment,
978
+ defaultExportComment,
979
+ };
836
980
  }
837
981
  catch (e) {
838
- utils_js_2.logger.error('cannot generate types', e.stack);
839
- return '';
982
+ utils_js_2.logger.error(e.message);
983
+ utils_js_2.logger.error('cannot generate typedoc comments', e.stack);
984
+ return {
985
+ headerComment: '',
986
+ responsiveComment: '',
987
+ defaultExportComment: '',
988
+ };
840
989
  }
841
990
  }
842
991
  function parsePropertyControls(code) {
@@ -1021,8 +1170,10 @@ function indentWithTabs(str, tabs) {
1021
1170
  .join('\n');
1022
1171
  }
1023
1172
  async function createExampleComponentCode({ outDir, config, }) {
1024
- const outDirForExample = path_1.default.posix
1173
+ const outDirForExample = path_1.default
1025
1174
  .relative(process.cwd(), outDir)
1175
+ .split(path_1.default.sep)
1176
+ .join('/')
1026
1177
  .replace(/^src\//, ''); // remove src so file works inside src
1027
1178
  const instances = config?.componentInstancesInIndexPage?.sort((a, b) => {
1028
1179
  // Order first by nodeDepth (lower is better)
@@ -1068,7 +1219,42 @@ async function createExampleComponentCode({ outDir, config, }) {
1068
1219
  };
1069
1220
  `;
1070
1221
  return {
1222
+ outDirForExample,
1071
1223
  exampleCode,
1072
1224
  };
1073
1225
  }
1226
+ /**
1227
+ * Compares two semantic version strings.
1228
+ * Returns true if versionB is greater than versionA.
1229
+ * Handles x.y.z, x.y, x, and optional pre-release (-alpha, etc).
1230
+ */
1231
+ function isVersionGreater(versionA, versionB) {
1232
+ try {
1233
+ function parseVersion(version) {
1234
+ // Remove pre-release (e.g. -alpha.1)
1235
+ let [core] = version.trim().split('-');
1236
+ return core.split('.').map((x) => parseInt(x, 10));
1237
+ }
1238
+ const [a1 = 0, a2 = 0, a3 = 0] = parseVersion(versionA);
1239
+ const [b1 = 0, b2 = 0, b3 = 0] = parseVersion(versionB);
1240
+ if (b1 > a1)
1241
+ return true;
1242
+ if (b1 < a1)
1243
+ return false;
1244
+ if (b2 > a2)
1245
+ return true;
1246
+ if (b2 < a2)
1247
+ return false;
1248
+ if (b3 > a3)
1249
+ return true;
1250
+ if (b3 < a3)
1251
+ return false;
1252
+ // If all equal, not greater
1253
+ return false;
1254
+ }
1255
+ catch (error) {
1256
+ utils_js_2.spinner.error(`Error comparing versions "${versionA}" and "${versionB}": ${error?.stack || error?.message || error}`);
1257
+ return true;
1258
+ }
1259
+ }
1074
1260
  //# sourceMappingURL=exporter.js.map