unframer 2.7.6 → 2.7.7

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 (46) hide show
  1. package/README.md +0 -5
  2. package/dist/cli.d.ts +14 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +7 -31
  5. package/dist/cli.js.map +1 -1
  6. package/dist/esbuild.d.ts +7 -0
  7. package/dist/esbuild.d.ts.map +1 -1
  8. package/dist/esbuild.js +15 -1
  9. package/dist/esbuild.js.map +1 -1
  10. package/dist/exporter.d.ts +6 -14
  11. package/dist/exporter.d.ts.map +1 -1
  12. package/dist/exporter.js +24 -9
  13. package/dist/exporter.js.map +1 -1
  14. package/dist/exporter.test.js +48 -0
  15. package/dist/exporter.test.js.map +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/unframer-loader.d.ts.map +1 -1
  19. package/dist/unframer-loader.js +4 -3
  20. package/dist/unframer-loader.js.map +1 -1
  21. package/esm/cli.d.ts +14 -0
  22. package/esm/cli.d.ts.map +1 -1
  23. package/esm/cli.js +7 -31
  24. package/esm/cli.js.map +1 -1
  25. package/esm/esbuild.d.ts +7 -0
  26. package/esm/esbuild.d.ts.map +1 -1
  27. package/esm/esbuild.js +13 -0
  28. package/esm/esbuild.js.map +1 -1
  29. package/esm/exporter.d.ts +6 -14
  30. package/esm/exporter.d.ts.map +1 -1
  31. package/esm/exporter.js +25 -10
  32. package/esm/exporter.js.map +1 -1
  33. package/esm/exporter.test.js +48 -0
  34. package/esm/exporter.test.js.map +1 -1
  35. package/esm/index.d.ts.map +1 -1
  36. package/esm/index.js.map +1 -1
  37. package/esm/unframer-loader.d.ts.map +1 -1
  38. package/esm/unframer-loader.js +4 -3
  39. package/esm/unframer-loader.js.map +1 -1
  40. package/package.json +2 -1
  41. package/src/cli.tsx +9 -40
  42. package/src/esbuild.ts +24 -2
  43. package/src/exporter.test.ts +66 -0
  44. package/src/exporter.ts +33 -14
  45. package/src/index.ts +2 -0
  46. package/src/unframer-loader.ts +7 -3
package/src/cli.tsx CHANGED
@@ -40,7 +40,9 @@ cli.command('[projectId]', 'Run unframer with optional project ID')
40
40
  return
41
41
  }
42
42
  const data = await response.json()
43
- return processConfig({
43
+ logger.log('unframer data', data)
44
+ let cwd = path.resolve(process.cwd(), outDir || 'framer')
45
+ return await bundle({
44
46
  config: {
45
47
  outDir,
46
48
  components: Object.fromEntries(
@@ -50,10 +52,11 @@ cli.command('[projectId]', 'Run unframer with optional project ID')
50
52
  ]),
51
53
  ),
52
54
  tokens: data.colorStyles,
55
+ framerWebPages: data.framerWebPages || [],
53
56
  },
54
57
  watch: false,
55
58
 
56
- configBasename: 'remote config',
59
+ cwd,
57
60
  signal: new AbortController().signal,
58
61
  })
59
62
  }
@@ -79,11 +82,11 @@ cli.command('[projectId]', 'Run unframer with optional project ID')
79
82
 
80
83
  let controller = new AbortController()
81
84
  setMaxListeners(0, controller.signal)
82
- processConfig({
85
+ await bundle({
83
86
  config,
84
87
  watch: false,
85
88
  signal: controller.signal,
86
- configBasename,
89
+ cwd: path.resolve(process.cwd(), outDir || 'framer'),
87
90
  })
88
91
  })
89
92
 
@@ -154,46 +157,12 @@ function getNewNames(oldConfig: Config, newConfig: Config) {
154
157
  return newNames
155
158
  }
156
159
 
157
- type Config = {
160
+ export type Config = {
158
161
  components: {
159
162
  [name: string]: string
160
163
  }
164
+ framerWebPages?: { webPageId: string; path: string }[]
161
165
  breakpoints?: BreakpointSizes
162
166
  tokens?: StyleToken[]
163
167
  outDir?: string
164
168
  }
165
- async function processConfig({
166
- config,
167
- watch,
168
- signal,
169
- configBasename,
170
- }: {
171
- config: Config
172
- watch: boolean
173
- configBasename: string
174
- signal?: AbortSignal
175
- }) {
176
- try {
177
- const { components, breakpoints, outDir } = config || {}
178
- const installDir = path.resolve(process.cwd(), outDir || 'framer')
179
- if (!components) {
180
- logger.log(`No components found in ${configBasename}`)
181
- return
182
- }
183
-
184
- await bundle({
185
- components,
186
- breakpoints,
187
- cwd: installDir,
188
- watch,
189
- tokens: config.tokens,
190
- signal,
191
- })
192
- } catch (e: any) {
193
- if (signal) {
194
- logger.log('Error processing config', e.stack)
195
- return
196
- }
197
- throw e
198
- }
199
- }
package/src/esbuild.ts CHANGED
@@ -12,6 +12,28 @@ export const externalPackages = [
12
12
  ]
13
13
 
14
14
  let redirectCache = new Map<string, Promise<string>>()
15
+
16
+ export const replaceWebPageIds = ({
17
+ elements,
18
+ code,
19
+ }: {
20
+ elements: { webPageId: string; path: string }[]
21
+ code: string
22
+ }) => {
23
+ // Match webPageId pattern with optional trailing comma
24
+ const pattern = /{[\s\n]*webPageId[\s\n]*:[\s\n]*(['"])(.*?)\1[\s\n]*,?[\s\n]*}/g
25
+
26
+ return code.replace(pattern, (match, quote, id) => {
27
+ const path = elements.find((e) => e.webPageId === id)?.path
28
+ if (!path) {
29
+ return match
30
+ }
31
+
32
+ logger.log(`Replacing relative link to ${id} with fixed path: ${path}`)
33
+ return `'${path}'`
34
+ })
35
+ }
36
+
15
37
  export function esbuildPluginBundleDependencies({
16
38
  signal = undefined as AbortSignal | undefined,
17
39
  externalizeNpm = false,
@@ -96,7 +118,7 @@ export function esbuildPluginBundleDependencies({
96
118
  build.onEnd(() => {
97
119
  spinner.stop()
98
120
  })
99
-
121
+
100
122
  build.onLoad({ filter: /.*/, namespace }, async (args) => {
101
123
  if (signal?.aborted) {
102
124
  throw new Error('aborted')
@@ -119,7 +141,7 @@ export function esbuildPluginBundleDependencies({
119
141
  const promise = Promise.resolve().then(async () => {
120
142
  logger.log('fetching', url.replace(/https?:\/\//, ''))
121
143
  spinner.update(`Fetching ${url.replace(/https?:\/\//, '')}`)
122
-
144
+
123
145
  const res = await fetchWithRetry(resolved, { signal })
124
146
  if (!res.ok) {
125
147
  throw new Error(
@@ -1,5 +1,71 @@
1
1
  import { describe, test, expect } from 'vitest'
2
2
  import { propCamelCase } from './exporter'
3
+ import { replaceWebPageIds } from './esbuild'
4
+
5
+ describe('replaceWebPageIds', () => {
6
+ test('replaces webPageIds with paths', () => {
7
+ const elements = [
8
+ { webPageId: 'abc123', path: '/page1' },
9
+ { webPageId: 'def456', path: '/page2' },
10
+ ]
11
+ const code = `{ webPageId: 'abc123' }`
12
+ expect(replaceWebPageIds({ elements, code })).toEqual(`'/page1'`)
13
+
14
+ const code2 = `{ webPageId: "def456" }`
15
+ expect(replaceWebPageIds({ elements, code: code2 })).toEqual(`'/page2'`)
16
+ })
17
+
18
+ test('handles whitespace variations', () => {
19
+ const elements = [{ webPageId: 'abc123', path: '/page1' }]
20
+
21
+ const code = `{webPageId:'abc123'}`
22
+ expect(replaceWebPageIds({ elements, code })).toEqual(`'/page1'`)
23
+
24
+ const code2 = `{ webPageId : 'abc123' }`
25
+ expect(replaceWebPageIds({ elements, code: code2 })).toEqual(`'/page1'`)
26
+ const code3 = `{ href: { webPageId: 'zRPFqFbvc' } }`
27
+ expect(
28
+ replaceWebPageIds({
29
+ elements: [{ webPageId: 'zRPFqFbvc', path: '/page1' }],
30
+ code: code3,
31
+ }),
32
+ ).toEqual(`{ href: '/page1' }`)
33
+ const code4 = `/* @__PURE__ */ _jsx(Link, {
34
+ href: { webPageId: 'zRPFqFbvc', },
35
+ nodeId: 'aU2SMIi6t',`
36
+ expect(
37
+ replaceWebPageIds({
38
+ elements: [{ webPageId: 'zRPFqFbvc', path: '/page1' }],
39
+ code: code4,
40
+ }),
41
+ ).toMatchInlineSnapshot(
42
+ `
43
+ "/* @__PURE__ */ _jsx(Link, {
44
+ href: '/page1',
45
+ nodeId: 'aU2SMIi6t',"
46
+ `,
47
+ )
48
+ })
49
+
50
+ test('preserves non-matching webPageIds', () => {
51
+ const elements = [{ webPageId: 'abc123', path: '/page1' }]
52
+ const code = `{ webPageId: 'xyz789' }`
53
+
54
+ expect(replaceWebPageIds({ elements, code })).toEqual(
55
+ `{ webPageId: 'xyz789' }`,
56
+ )
57
+ })
58
+
59
+ test('handles newlines in input', () => {
60
+ const elements = [{ webPageId: 'abc123', path: '/page1' }]
61
+
62
+ const code = `{\n webPageId: 'abc123'\n}`
63
+ expect(replaceWebPageIds({ elements, code })).toEqual(`'/page1'`)
64
+
65
+ const code2 = `{\n\n webPageId:\n 'abc123'\n\n}`
66
+ expect(replaceWebPageIds({ elements, code: code2 })).toEqual(`'/page1'`)
67
+ })
68
+ })
3
69
 
4
70
  describe('propCamelCase', () => {
5
71
  test('converts dashes to camelCase', () => {
package/src/exporter.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { BuildResult, context } from 'esbuild'
2
+ import { Config } from './cli'
2
3
  import url from 'url'
3
4
 
4
5
  import { Sema } from 'async-sema'
@@ -21,6 +22,7 @@ import {
21
22
  import {
22
23
  esbuildPluginBundleDependencies,
23
24
  externalPackages,
25
+ replaceWebPageIds,
24
26
  resolveRedirect,
25
27
  } from './esbuild'
26
28
  import {
@@ -47,13 +49,17 @@ export type StyleToken = {
47
49
  }
48
50
 
49
51
  export async function bundle({
52
+ config,
50
53
  cwd: out = '',
51
54
  watch = false,
52
- components = {} as Record<string, string>,
53
- tokens = [] as StyleToken[],
54
- breakpoints = {} as BreakpointSizes,
55
55
  signal = undefined as AbortSignal | undefined,
56
+ }: {
57
+ config: Config
58
+ cwd: string
59
+ watch?: boolean
60
+ signal?: AbortSignal
56
61
  }) {
62
+ const { components, breakpoints, tokens, outDir, framerWebPages } = config
57
63
  out ||= path.resolve(process.cwd(), 'example')
58
64
  out = path.resolve(out)
59
65
  try {
@@ -156,6 +162,12 @@ export async function bundle({
156
162
  trailingCommas: 'always',
157
163
  semiColons: 'always',
158
164
  })
165
+ if (framerWebPages?.length) {
166
+ codeNew = replaceWebPageIds({
167
+ code: codeNew,
168
+ elements: framerWebPages,
169
+ })
170
+ }
159
171
  const lines = findRelativeLinks(codeNew)
160
172
  if (lines.length) {
161
173
  spinner.error(
@@ -236,7 +248,7 @@ export async function bundle({
236
248
  '/* This file was generated by Unframer, do not edit manually */\n' +
237
249
  '/* This css file has all the necessary styles to run all your components */\n' +
238
250
  '\n' +
239
- getStyleTokensCss(tokens) +
251
+ getStyleTokensCss(tokens || []) +
240
252
  breakpointsStyles(breakpoints) +
241
253
  '\n\n' +
242
254
  combinedCSSRules
@@ -663,10 +675,6 @@ function getTokensCss({
663
675
 
664
676
  const nodePath = process.argv[0] || 'node'
665
677
 
666
- let UNFRAMER_RUNTIME_PATH = url.pathToFileURL(
667
- require.resolve('../esm/index.js'),
668
- ).href
669
-
670
678
  export async function extractPropControlsUnsafe(
671
679
  filename,
672
680
  name,
@@ -685,6 +693,11 @@ export async function extractPropControlsUnsafe(
685
693
  )}); console.log(${propCode}) })`
686
694
 
687
695
  const TIMEOUT = 5 * 1000
696
+ const UNFRAMER_MAP_PACKAGES = JSON.stringify({
697
+ unframer: url.pathToFileURL(require.resolve('../esm/index.js')).href,
698
+ react: url.pathToFileURL(require.resolve('react')).href,
699
+ 'react-dom': url.pathToFileURL(require.resolve('react-dom')).href,
700
+ })
688
701
  let stdout = await new Promise<string>((res, rej) => {
689
702
  let childProcess = exec(
690
703
  `${JSON.stringify(
@@ -695,14 +708,17 @@ export async function extractPropControlsUnsafe(
695
708
  {
696
709
  env: {
697
710
  // ...process.env,
698
- UNFRAMER_RUNTIME_PATH,
711
+ UNFRAMER_MAP_PACKAGES,
699
712
  },
700
713
  },
701
- (err, stdout) => {
714
+ (err, stdout, stderr) => {
702
715
  clearTimeout(timer)
703
716
  if (err) {
717
+ spinner.error(`error extracting types for ${name}`)
718
+ spinner.error(stderr)
704
719
  return rej(err)
705
720
  }
721
+
706
722
  res(stdout)
707
723
  },
708
724
  )
@@ -716,12 +732,15 @@ export async function extractPropControlsUnsafe(
716
732
  )
717
733
  }, TIMEOUT)
718
734
  }).catch((e) => {
719
- spinner.error(`error extracting types for ${name}`)
720
735
  logger.log(e.stack)
721
- throw e
736
+ return ''
722
737
  })
723
738
 
724
- stdout = stdout.split(delimiter)[1]
739
+ stdout = stdout.split(delimiter)[1] || ''
740
+ if (!stdout) {
741
+ return {}
742
+ }
743
+
725
744
  // console.log(stdout)
726
745
  return safeJsonParse(stdout)
727
746
  }
@@ -1000,7 +1019,7 @@ export function propCamelCase(str: string) {
1000
1019
  }
1001
1020
  // Convert dashes to camelCase (e.g. foo-bar -> fooBar)
1002
1021
  str = str.replace(/-([\w])/g, (g) => g[1].toUpperCase())
1003
- // Convert underscores to camelCase (e.g. foo_bar -> fooBar)
1022
+ // Convert underscores to camelCase (e.g. foo_bar -> fooBar)
1004
1023
  str = str.replace(/_([a-z])/g, (g) => g[1].toUpperCase())
1005
1024
  // Remove spaces (e.g. "Foo Bar" -> "fooBar")
1006
1025
  str = str.replace(/\s+(.)/g, (_, c) => c.toUpperCase())
package/src/index.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './framer.js'
2
+
3
+
2
4
  export {
3
5
  FramerStyles,
4
6
  UnframerBreakpoint,
@@ -1,10 +1,14 @@
1
+ const mapPackages = JSON.parse(process.env.UNFRAMER_MAP_PACKAGES || '{}')
2
+
1
3
  export async function resolve(specifier, context, defaultResolve) {
2
- if (specifier === 'unframer') {
4
+ if (mapPackages[specifier]) {
3
5
  return {
4
- url: process.env.UNFRAMER_RUNTIME_PATH,
5
- format: 'module', // Specify that unframer is an ES module
6
+ url: mapPackages[specifier],
7
+ // format: 'module', // Specify that unframer is an ES module
6
8
  shortCircuit: true, // Signal that we're intentionally not calling next hook
7
9
  }
8
10
  }
11
+
12
+
9
13
  return defaultResolve(specifier, context, defaultResolve)
10
14
  }