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/README.md +8 -0
- package/dist/esbuild.d.ts.map +1 -1
- package/dist/esbuild.js +7 -0
- package/dist/esbuild.js.map +1 -1
- package/dist/exporter.d.ts +1 -1
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +120 -45
- package/dist/exporter.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +7 -1
- package/dist/utils.js.map +1 -1
- package/esm/esbuild.d.ts.map +1 -1
- package/esm/esbuild.js +7 -0
- package/esm/esbuild.js.map +1 -1
- package/esm/exporter.d.ts +1 -1
- package/esm/exporter.d.ts.map +1 -1
- package/esm/exporter.js +121 -46
- package/esm/exporter.js.map +1 -1
- package/esm/utils.d.ts +1 -0
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +6 -0
- package/esm/utils.js.map +1 -1
- package/package.json +3 -1
- package/src/esbuild.ts +10 -1
- package/src/exporter.ts +153 -56
- package/src/utils.ts +8 -0
package/src/exporter.ts
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
403
|
+
To style components you can pass a \`style\` or \`className\` prop (but remember to use !important to increase the specificity).
|
|
350
404
|
|
|
351
|
-
|
|
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
|
-
|
|
635
|
-
|
|
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) {
|