unframer 2.7.0 → 2.7.2

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.
package/src/exporter.ts CHANGED
@@ -1,23 +1,15 @@
1
- import { Plugin, build, transform, context, BuildResult } from 'esbuild'
1
+ import { BuildResult, context } from 'esbuild'
2
2
  import url from 'url'
3
3
 
4
4
  import { Sema } from 'async-sema'
5
5
  import dprint from 'dprint-node'
6
- import tmp from 'tmp'
7
6
 
8
7
  import { polyfillNode } from 'esbuild-plugin-polyfill-node'
9
8
 
10
- import {
11
- ComponentFont,
12
- ControlDescription,
13
- ControlType,
14
- PropertyControls,
15
- combinedCSSRules,
16
- } from './framer'
17
- import { fetch as _fetch } from 'native-fetch'
9
+ import { exec } from 'child_process'
10
+ import dedent from 'dedent'
18
11
  import fs from 'fs'
19
12
  import path from 'path'
20
- import { exec, execSync } from 'child_process'
21
13
  import {
22
14
  BreakpointSizes,
23
15
  ComponentFontBundle,
@@ -26,13 +18,18 @@ import {
26
18
  groupBy,
27
19
  logFontsUsage,
28
20
  } from './css.js'
29
- import dedent from 'dedent'
30
- import { logger, terminalMarkdown } from './utils.js'
31
21
  import {
32
22
  esbuildPluginBundleDependencies,
33
- resolveRedirect,
34
23
  externalPackages,
24
+ resolveRedirect,
35
25
  } from './esbuild'
26
+ import {
27
+ ControlDescription,
28
+ ControlType,
29
+ PropertyControls,
30
+ combinedCSSRules
31
+ } from './framer'
32
+ import { logger, spinner, terminalMarkdown } from './utils.js'
36
33
 
37
34
  function validateUrl(url: string) {
38
35
  try {
@@ -63,6 +60,7 @@ export async function bundle({
63
60
  fs.mkdirSync(out, { recursive: true })
64
61
  } catch (e) {}
65
62
 
63
+ spinner.start()
66
64
  const buildContext = await context({
67
65
  // entryPoints: {
68
66
  // index: url,
@@ -160,14 +158,14 @@ export async function bundle({
160
158
  })
161
159
  const lines = findRelativeLinks(codeNew)
162
160
  if (lines.length) {
163
- logger.error(
161
+ spinner.error(
164
162
  `found broken links for ${path.relative(
165
163
  out,
166
164
  file.path,
167
165
  )}, don't use relative links in Framer components`,
168
166
  )
169
167
  lines.forEach((line) => {
170
- logger.error(`${path.resolve(out, file.path)}:${line + 1}`)
168
+ logger.log(`${path.resolve(out, file.path)}:${line + 1}`)
171
169
  })
172
170
  }
173
171
 
@@ -177,8 +175,10 @@ export async function bundle({
177
175
  logger.log(`writing`, path.relative(out, file.path))
178
176
  fs.writeFileSync(resultPathAbs, codeNew, 'utf-8')
179
177
  }
178
+ spinner.stop()
179
+
180
180
  let allFonts = [] as ComponentFontBundle[]
181
- const sema = new Sema(10)
181
+
182
182
  const packageJson = path.resolve(out, 'package.json')
183
183
  fs.writeFileSync(
184
184
  packageJson,
@@ -188,7 +188,8 @@ export async function bundle({
188
188
  if (!result?.outputFiles) {
189
189
  throw new Error('Failed to generate result')
190
190
  }
191
-
191
+ const sema = new Sema(6)
192
+ spinner.start('Extracting types')
192
193
  const propControlsData = await Promise.all(
193
194
  result?.outputFiles.map(async (file) => {
194
195
  try {
@@ -199,6 +200,7 @@ export async function bundle({
199
200
  return
200
201
  }
201
202
  logger.log(`extracting types for ${name}`)
203
+ spinner.update(`Extracting types for ${name}`)
202
204
  const { propertyControls, fonts } =
203
205
  await extractPropControlsUnsafe(resultPathAbs, name)
204
206
  if (!propertyControls) {
@@ -227,6 +229,7 @@ export async function bundle({
227
229
  }
228
230
  }),
229
231
  ).finally(() => fs.rmSync(packageJson))
232
+ spinner.stop()
230
233
 
231
234
  const cssString =
232
235
  '/* This file was generated by Unframer, do not edit manually */\n' +
@@ -303,41 +306,92 @@ export async function bundle({
303
306
  }
304
307
  }),
305
308
  }
309
+
306
310
  return res
307
311
  }
308
312
 
309
313
  if (!watch) {
310
314
  const result = await rebuild()
311
315
  await buildContext.dispose()
316
+ console.log()
317
+ console.log()
318
+
319
+ let exampleComponent = result?.components?.find((x) => {
320
+ if (!x.propertyControls) return false
321
+ const variants = getVariantsFromPropControls(x.propertyControls)
322
+ return variants?.breakpoints.length >= 2
323
+ })
324
+ if (!exampleComponent) {
325
+ logger.log(
326
+ `No example component found with breakpoints, using random example`,
327
+ )
328
+ // Create an example component if none found with breakpoints
329
+ exampleComponent = {
330
+ path: 'hero',
331
+ componentName: 'HeroFramerComponent',
332
+ propertyControls: {
333
+ variant: {
334
+ type: ControlType.Enum,
335
+ options: ['Desktop', 'Tablet', 'Mobile'],
336
+ optionTitles: ['Desktop', 'Tablet', 'Mobile'],
337
+ },
338
+ } as any,
339
+ name: 'Hero',
340
+ url: '',
341
+ }
342
+ if (!exampleComponent) {
343
+ return
344
+ }
345
+ }
346
+ const variants = getVariantsFromPropControls(
347
+ exampleComponent?.propertyControls,
348
+ )
349
+ const breakpoints = variants?.breakpoints
350
+ if (!breakpoints) {
351
+ return
352
+ }
353
+ logger.log(
354
+ 'exampleComponent?.propertyControls',
355
+ exampleComponent?.propertyControls,
356
+ )
357
+ const variantsExample = {
358
+ lg: breakpoints[1],
359
+ base: breakpoints[0],
360
+ }
361
+ let prop =
362
+ findExampleProperty(exampleComponent?.propertyControls) ||
363
+ 'exampleFramerVariable'
364
+ const outDir = path.posix.relative(process.cwd(), out)
312
365
  console.log(
313
366
  terminalMarkdown(dedent`
314
367
  # How to use the Framer components
315
368
 
316
- The components are exported to the \`framer\` directory (or the directory you specified in the config).
369
+ Your components are exported to \`${outDir}\` folder. Now please install the \`unframer\` runtime dependency:
370
+
371
+ \`\`\`sh
372
+ npm install unframer
373
+ \`\`\`
374
+
317
375
  Each component has a \`.Responsive\` variant that allows you to specify different variants for different breakpoints.
318
- The breakpoints are:
319
- - base: 0-319px
320
- - sm: 320-767px
321
- - md: 768-959px
322
- - lg: 960-1199px
323
- - xl: 1200-1535px
324
- - 2xl: 1536px+
325
-
326
- You can import the components like this:
327
-
328
- \`\`\`tsx
329
- import './framer/styles.css'
330
- import Logos from './framer/logos'
376
+
377
+ You can use the components like this (try copy pasting the code below into your React app):
378
+
379
+ \`\`\`jsx
380
+ import './${outDir}/styles.css'
381
+ import ${exampleComponent?.componentName} from './${outDir}/${
382
+ exampleComponent?.path
383
+ }'
331
384
 
332
385
  export default function App() {
333
386
  return (
334
387
  <div>
335
- <Logos.Responsive
336
- variants={{
337
- lg: 'Desktop',
338
- md: 'Tablet',
339
- base: 'Mobile',
340
- }}
388
+ <${exampleComponent?.componentName}
389
+ ${prop}='example'
390
+ style={{ width: '100%' }}
391
+ />
392
+ <${exampleComponent?.componentName}.Responsive
393
+ ${prop}='example'
394
+ variants={${JSON.stringify(variantsExample || {})}}
341
395
  />
342
396
  </div>
343
397
  );
@@ -346,20 +400,9 @@ export async function bundle({
346
400
 
347
401
  It's very important to import the \`styles.css\` file to include the necessary styles for the components.
348
402
 
349
- You can also use the components without the responsive wrapper:
403
+ To style components you can pass a \`style\` or \`className\` prop (but remember to use !important to increase the specificity).
350
404
 
351
- \`\`\`tsx
352
- import './framer/styles.css'
353
- import Logos from './framer/logos'
354
-
355
- export default function App() {
356
- return (
357
- <div>
358
- <Logos variant="Desktop" />
359
- </div>
360
- );
361
- };
362
- \`\`\`
405
+ Read more on GitHub: https://github.com/remorses/unframer
363
406
  `),
364
407
  )
365
408
  return result
@@ -614,7 +657,6 @@ function getTokensCss({
614
657
  const tokensCss = `:root {\n${cssStrings}\n}`
615
658
  return tokensCss
616
659
  }
617
-
618
660
  export async function extractPropControlsUnsafe(
619
661
  filename,
620
662
  name,
@@ -631,19 +673,35 @@ export async function extractPropControlsUnsafe(
631
673
  )}).then(x => { console.log(${JSON.stringify(
632
674
  delimiter,
633
675
  )}); console.log(${propCode}) })`
634
- let stdout = await new Promise<string>((res, rej) =>
635
- exec(
676
+
677
+ const TIMEOUT = 2 * 1000
678
+ let stdout = await new Promise<string>((res, rej) => {
679
+ let childProcess = exec(
636
680
  `${JSON.stringify(
637
681
  nodePath,
638
682
  )} --input-type=module -e ${JSON.stringify(code)}`,
639
683
  (err, stdout) => {
684
+ clearTimeout(timer)
640
685
  if (err) {
641
686
  return rej(err)
642
687
  }
643
688
  res(stdout)
644
689
  },
645
- ),
646
- )
690
+ )
691
+
692
+ const timer = setTimeout(() => {
693
+ childProcess.kill()
694
+ rej(
695
+ new Error(
696
+ `Timed out after ${TIMEOUT}ms while extracting types for ${name}`,
697
+ ),
698
+ )
699
+ }, TIMEOUT)
700
+ }).catch((e) => {
701
+ logger.error(`error extracting types for ${name}`)
702
+ logger.log(e.stack)
703
+ throw e
704
+ })
647
705
 
648
706
  stdout = stdout.split(delimiter)[1]
649
707
  // console.log(stdout)
@@ -879,3 +937,42 @@ export function componentCamelCase(str: string) {
879
937
  str = str + 'FramerComponent'
880
938
  return str
881
939
  }
940
+
941
+ const breakpointVariants = ['mobile', 'tablet', 'desktop']
942
+
943
+ function getVariantsFromPropControls(propControls?: PropertyControls) {
944
+ if (!propControls?.variant) {
945
+ return null
946
+ }
947
+
948
+ let variants =
949
+ propControls.variant?.['optionTitles'] ||
950
+ propControls.variant?.['options'] ||
951
+ []
952
+ // Sort breakpoint-related variants first
953
+ return {
954
+ variants: variants,
955
+ breakpoints: variants.filter((v) =>
956
+ breakpointVariants.some((device) =>
957
+ v.toLowerCase().includes(device),
958
+ ),
959
+ ),
960
+ }
961
+ }
962
+
963
+ function findExampleProperty(propertyControls?: PropertyControls) {
964
+ if (!propertyControls) {
965
+ return null
966
+ }
967
+
968
+ const stringProp = Object.entries(propertyControls).find(([_, control]) => {
969
+ // console.log('control', _, control)
970
+ return control?.type === ControlType.String
971
+ })
972
+
973
+ if (!stringProp) {
974
+ return null
975
+ }
976
+
977
+ return stringProp[0]
978
+ }
package/src/utils.ts CHANGED
@@ -2,6 +2,9 @@ import pico from 'picocolors'
2
2
 
3
3
  import { marked } from 'marked'
4
4
  import { markedTerminal } from 'marked-terminal'
5
+ import { createSpinner } from 'nanospinner'
6
+
7
+ export const spinner = createSpinner('Downloading Framer Components') as any
5
8
 
6
9
  marked.use(markedTerminal())
7
10
 
@@ -9,9 +12,14 @@ export function terminalMarkdown(markdown: string) {
9
12
  return marked(markdown)
10
13
  }
11
14
 
15
+ const shouldDebugUnframer = !!process.env.DEBUG_UNFRAMER
16
+
12
17
  const prefix = '[unframer]'
13
18
  export const logger = {
14
19
  log(...args) {
20
+ if (!shouldDebugUnframer) {
21
+ return
22
+ }
15
23
  console.log(prefix, ...args)
16
24
  },
17
25
  green(...args) {