unframer 2.7.6 → 2.7.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.
- package/README.md +5 -6
- package/dist/babel-plugin-imports.d.ts +21 -0
- package/dist/babel-plugin-imports.d.ts.map +1 -0
- package/dist/babel-plugin-imports.js +375 -0
- package/dist/babel-plugin-imports.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -42
- package/dist/cli.js.map +1 -1
- package/dist/css.d.ts.map +1 -1
- package/dist/css.js +4 -3
- package/dist/css.js.map +1 -1
- package/dist/esbuild.d.ts +7 -0
- package/dist/esbuild.d.ts.map +1 -1
- package/dist/esbuild.js +15 -1
- package/dist/esbuild.js.map +1 -1
- package/dist/exporter.d.ts +6 -14
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +79 -38
- package/dist/exporter.js.map +1 -1
- package/dist/exporter.test.js +48 -0
- package/dist/exporter.test.js.map +1 -1
- package/dist/framer.d.ts.map +1 -1
- package/dist/framer.js +26 -1788
- package/dist/framer.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/renamer.d.ts +12 -0
- package/dist/renamer.d.ts.map +1 -0
- package/dist/renamer.js +169 -0
- package/dist/renamer.js.map +1 -0
- package/dist/unframer-loader.d.ts.map +1 -1
- package/dist/unframer-loader.js +4 -3
- package/dist/unframer-loader.js.map +1 -1
- package/esm/babel-plugin-imports.d.ts +21 -0
- package/esm/babel-plugin-imports.d.ts.map +1 -0
- package/esm/babel-plugin-imports.js +344 -0
- package/esm/babel-plugin-imports.js.map +1 -0
- package/esm/cli.d.ts +14 -0
- package/esm/cli.d.ts.map +1 -1
- package/esm/cli.js +9 -42
- package/esm/cli.js.map +1 -1
- package/esm/css.d.ts.map +1 -1
- package/esm/css.js +3 -2
- package/esm/css.js.map +1 -1
- package/esm/esbuild.d.ts +7 -0
- package/esm/esbuild.d.ts.map +1 -1
- package/esm/esbuild.js +13 -0
- package/esm/esbuild.js.map +1 -1
- package/esm/exporter.d.ts +6 -14
- package/esm/exporter.d.ts.map +1 -1
- package/esm/exporter.js +78 -37
- package/esm/exporter.js.map +1 -1
- package/esm/exporter.test.js +48 -0
- package/esm/exporter.test.js.map +1 -1
- package/esm/framer.d.ts.map +1 -1
- package/esm/framer.js +27 -1788
- package/esm/framer.js.map +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js.map +1 -1
- package/esm/renamer.d.ts +12 -0
- package/esm/renamer.d.ts.map +1 -0
- package/esm/renamer.js +140 -0
- package/esm/renamer.js.map +1 -0
- package/esm/unframer-loader.d.ts.map +1 -1
- package/esm/unframer-loader.js +4 -3
- package/esm/unframer-loader.js.map +1 -1
- package/package.json +6 -4
- package/src/babel-plugin-imports.ts +441 -0
- package/src/cli.tsx +11 -52
- package/src/css.ts +3 -2
- package/src/esbuild.ts +24 -2
- package/src/exporter.test.ts +66 -0
- package/src/exporter.ts +95 -42
- package/src/framer.js +25 -1827
- package/src/index.ts +2 -0
- package/src/renamer.ts +184 -0
- package/src/unframer-loader.ts +7 -3
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import * as BabelTypes from '@babel/types'
|
|
2
|
+
|
|
3
|
+
import { PluginObj } from '@babel/core'
|
|
4
|
+
import { ImportDeclaration, ImportSpecifier, Identifier } from '@babel/types'
|
|
5
|
+
import BatchRenamer from './renamer'
|
|
6
|
+
import { logger } from './utils'
|
|
7
|
+
|
|
8
|
+
export function babelPluginDeduplicateImports({
|
|
9
|
+
types: t,
|
|
10
|
+
}: {
|
|
11
|
+
types: typeof BabelTypes
|
|
12
|
+
}): PluginObj {
|
|
13
|
+
const importAliasMap = new Map<
|
|
14
|
+
string,
|
|
15
|
+
Map<
|
|
16
|
+
string,
|
|
17
|
+
{ consolidated: string; importName; path: ImportSpecifier; source }
|
|
18
|
+
>
|
|
19
|
+
>()
|
|
20
|
+
|
|
21
|
+
function addImport({
|
|
22
|
+
source,
|
|
23
|
+
local,
|
|
24
|
+
consolidated,
|
|
25
|
+
importName,
|
|
26
|
+
path,
|
|
27
|
+
}: {
|
|
28
|
+
source: string
|
|
29
|
+
local: string
|
|
30
|
+
consolidated: string
|
|
31
|
+
importName: string
|
|
32
|
+
path
|
|
33
|
+
}) {
|
|
34
|
+
if (!importAliasMap.has(source)) {
|
|
35
|
+
importAliasMap.set(source, new Map())
|
|
36
|
+
}
|
|
37
|
+
importAliasMap
|
|
38
|
+
.get(source)
|
|
39
|
+
?.set(local, { consolidated, importName, path, source })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getConsolidatedName({ source, importName, defaultOne }) {
|
|
43
|
+
const allSpecifiers = [...importAliasMap.values()].flatMap((x) => [
|
|
44
|
+
...x.values(),
|
|
45
|
+
])
|
|
46
|
+
const first = allSpecifiers.find((x) => {
|
|
47
|
+
if (importName === 'default' || importName === 'namespace') {
|
|
48
|
+
if (source !== x.source) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return x.importName === importName
|
|
53
|
+
})
|
|
54
|
+
return first?.consolidated || defaultOne
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
visitor: {
|
|
59
|
+
ImportDeclaration(path) {
|
|
60
|
+
const source = path.node.source.value
|
|
61
|
+
|
|
62
|
+
path.node.specifiers.forEach((specifier) => {
|
|
63
|
+
if (
|
|
64
|
+
t.isImportSpecifier(specifier) &&
|
|
65
|
+
BabelTypes.isIdentifier(specifier.imported)
|
|
66
|
+
) {
|
|
67
|
+
const importName = specifier.imported.name
|
|
68
|
+
|
|
69
|
+
const consolidatedName = getConsolidatedName({
|
|
70
|
+
source,
|
|
71
|
+
importName,
|
|
72
|
+
defaultOne: specifier.local.name,
|
|
73
|
+
})
|
|
74
|
+
addImport({
|
|
75
|
+
source,
|
|
76
|
+
local: specifier.local.name,
|
|
77
|
+
importName,
|
|
78
|
+
consolidated: consolidatedName,
|
|
79
|
+
path,
|
|
80
|
+
})
|
|
81
|
+
} else if (t.isImportDefaultSpecifier(specifier)) {
|
|
82
|
+
const importName = 'default'
|
|
83
|
+
|
|
84
|
+
const consolidatedName = getConsolidatedName({
|
|
85
|
+
source,
|
|
86
|
+
importName,
|
|
87
|
+
defaultOne: specifier.local.name,
|
|
88
|
+
})
|
|
89
|
+
addImport({
|
|
90
|
+
source,
|
|
91
|
+
local: specifier.local.name,
|
|
92
|
+
importName,
|
|
93
|
+
consolidated: consolidatedName,
|
|
94
|
+
path,
|
|
95
|
+
})
|
|
96
|
+
} else if (t.isImportNamespaceSpecifier(specifier)) {
|
|
97
|
+
const importName = 'namespace'
|
|
98
|
+
|
|
99
|
+
const consolidatedName = getConsolidatedName({
|
|
100
|
+
source,
|
|
101
|
+
importName,
|
|
102
|
+
defaultOne: specifier.local.name,
|
|
103
|
+
})
|
|
104
|
+
addImport({
|
|
105
|
+
source,
|
|
106
|
+
local: specifier.local.name,
|
|
107
|
+
importName,
|
|
108
|
+
consolidated: consolidatedName,
|
|
109
|
+
path,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Remove the current import declaration to later add the consolidated one
|
|
115
|
+
// path.remove()
|
|
116
|
+
},
|
|
117
|
+
Program: {
|
|
118
|
+
exit(path) {
|
|
119
|
+
console.log(`renaming imports...`)
|
|
120
|
+
for (const [source, modMap] of importAliasMap) {
|
|
121
|
+
// rename import names to consolidated names
|
|
122
|
+
for (let [local, { consolidated, path: p }] of modMap) {
|
|
123
|
+
logger.log(
|
|
124
|
+
`renaming ${local} to ${consolidated}...`,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const map = new Map<string, string>(
|
|
130
|
+
[...importAliasMap.values()].flatMap((x) => {
|
|
131
|
+
return [...x.entries()]
|
|
132
|
+
.map(([prev, x]) => {
|
|
133
|
+
if (prev === x.consolidated) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
return [prev, x.consolidated]
|
|
137
|
+
})
|
|
138
|
+
.filter((x) => x !== undefined) as Array<
|
|
139
|
+
[string, string]
|
|
140
|
+
>
|
|
141
|
+
}),
|
|
142
|
+
)
|
|
143
|
+
// console.log([...map.entries()])
|
|
144
|
+
const renamer = new BatchRenamer(path.scope, map)
|
|
145
|
+
renamer.rename()
|
|
146
|
+
|
|
147
|
+
const importDecs = path.node.body.filter((node) =>
|
|
148
|
+
t.isImportDeclaration(node),
|
|
149
|
+
) as ImportDeclaration[]
|
|
150
|
+
|
|
151
|
+
const definedImports = new Set<string>()
|
|
152
|
+
const later = [] as Function[]
|
|
153
|
+
console.log(`removing duplicates...`)
|
|
154
|
+
for (let importDec of importDecs) {
|
|
155
|
+
const source = importDec.source.value
|
|
156
|
+
|
|
157
|
+
const specifiers = importDec.specifiers
|
|
158
|
+
for (let specifier of specifiers) {
|
|
159
|
+
if (
|
|
160
|
+
!BabelTypes.isImportSpecifier(specifier) &&
|
|
161
|
+
!BabelTypes.isImportDefaultSpecifier(
|
|
162
|
+
specifier,
|
|
163
|
+
) &&
|
|
164
|
+
!BabelTypes.isImportNamespaceSpecifier(
|
|
165
|
+
specifier,
|
|
166
|
+
)
|
|
167
|
+
) {
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
let importKey = ''
|
|
171
|
+
if (
|
|
172
|
+
BabelTypes.isImportDefaultSpecifier(specifier)
|
|
173
|
+
) {
|
|
174
|
+
importKey = source + 'default'
|
|
175
|
+
} else if (
|
|
176
|
+
BabelTypes.isImportNamespaceSpecifier(specifier)
|
|
177
|
+
) {
|
|
178
|
+
importKey = source + 'namespace'
|
|
179
|
+
} else {
|
|
180
|
+
if (
|
|
181
|
+
!BabelTypes.isIdentifier(specifier.imported)
|
|
182
|
+
) {
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
importKey = specifier.imported.name
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (definedImports.has(importKey)) {
|
|
189
|
+
later.push(() => {
|
|
190
|
+
console.log(
|
|
191
|
+
`removing ${importKey} from ${source}...`,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
importDec.specifiers =
|
|
195
|
+
importDec.specifiers.filter(
|
|
196
|
+
(x) => x !== specifier,
|
|
197
|
+
)
|
|
198
|
+
if (!importDec.specifiers.length) {
|
|
199
|
+
const index = path.node.body.findIndex(
|
|
200
|
+
(x) => x === importDec,
|
|
201
|
+
)
|
|
202
|
+
path.node.body.splice(index, 1)
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
definedImports.add(importKey)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
for (let fn of later) {
|
|
210
|
+
fn()
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
// Identifier(path) {
|
|
215
|
+
// const name = path.node.name
|
|
216
|
+
// const binding = path.scope.getBinding(name)
|
|
217
|
+
|
|
218
|
+
// if (binding && t.isImportSpecifier(binding.path.node)) {
|
|
219
|
+
// const source = (binding.path.parent as ImportDeclaration)
|
|
220
|
+
// .source.value
|
|
221
|
+
// const localName = getLocalImportName(source, name)
|
|
222
|
+
|
|
223
|
+
// if (localName && localName !== name) {
|
|
224
|
+
// path.node.name = localName
|
|
225
|
+
// }
|
|
226
|
+
// }
|
|
227
|
+
// },
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function babelPluginRenameExports({
|
|
233
|
+
map,
|
|
234
|
+
}: {
|
|
235
|
+
map: Map<string, string>
|
|
236
|
+
}) {
|
|
237
|
+
return {
|
|
238
|
+
name: 'rename-exports',
|
|
239
|
+
visitor: {
|
|
240
|
+
ExportNamedDeclaration(path) {
|
|
241
|
+
const { specifiers, declaration } = path.node
|
|
242
|
+
// Handle export specifiers like: export { something as oldName }
|
|
243
|
+
for (const specifier of specifiers) {
|
|
244
|
+
if (!BabelTypes.isExportSpecifier(specifier)) continue
|
|
245
|
+
const exported = specifier.exported
|
|
246
|
+
if (!BabelTypes.isIdentifier(exported)) continue
|
|
247
|
+
const oldName = exported.name
|
|
248
|
+
const newName = map.get(oldName)
|
|
249
|
+
if (newName) {
|
|
250
|
+
exported.name = newName
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Handle function declarations like: export function oldName1() {}
|
|
254
|
+
if (BabelTypes.isFunctionDeclaration(declaration)) {
|
|
255
|
+
const oldName = declaration.id?.name
|
|
256
|
+
if (oldName) {
|
|
257
|
+
const newName = map.get(oldName)
|
|
258
|
+
if (newName && declaration?.id) {
|
|
259
|
+
declaration.id.name = newName
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
ExportDefaultDeclaration(path) {
|
|
265
|
+
const { declaration } = path.node
|
|
266
|
+
if (BabelTypes.isIdentifier(declaration)) {
|
|
267
|
+
const oldName = declaration.name
|
|
268
|
+
const newName = map.get(oldName)
|
|
269
|
+
if (newName) {
|
|
270
|
+
declaration.name = newName
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Set of types that don't need expression containers
|
|
279
|
+
const noContainerTypes = new Set([
|
|
280
|
+
'JSXElement',
|
|
281
|
+
// 'StringLiteral',
|
|
282
|
+
'NumericLiteral',
|
|
283
|
+
])
|
|
284
|
+
|
|
285
|
+
export function babelPluginJsxTransform() {
|
|
286
|
+
return {
|
|
287
|
+
name: 'jsx-transform',
|
|
288
|
+
visitor: {
|
|
289
|
+
CallExpression(path) {
|
|
290
|
+
// Check if it's a _jsx or _jsxs call
|
|
291
|
+
if (
|
|
292
|
+
!path.node.callee ||
|
|
293
|
+
!path.node.callee.name?.startsWith('_jsx')
|
|
294
|
+
) {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Remove /* @__PURE__ */ comments
|
|
299
|
+
if (path.node.leadingComments) {
|
|
300
|
+
path.node.leadingComments =
|
|
301
|
+
path.node.leadingComments.filter(
|
|
302
|
+
(comment) => !comment.value.includes('@__PURE__'),
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const [elementArg, propsArg] = path.node.arguments
|
|
307
|
+
|
|
308
|
+
// Get the element type name
|
|
309
|
+
let elementName = ''
|
|
310
|
+
if (elementArg.type === 'MemberExpression') {
|
|
311
|
+
elementName = `${elementArg.object.name}.${elementArg.property.name}`
|
|
312
|
+
} else if (elementArg.type === 'StringLiteral') {
|
|
313
|
+
elementName = elementArg.value
|
|
314
|
+
} else if (elementArg.type === 'Identifier') {
|
|
315
|
+
elementName = elementArg.name
|
|
316
|
+
} else {
|
|
317
|
+
// Skip if we can't determine element name
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Convert to JSX element
|
|
322
|
+
const jsxElement: BabelTypes.JSXElement = {
|
|
323
|
+
type: 'JSXElement',
|
|
324
|
+
openingElement: {
|
|
325
|
+
type: 'JSXOpeningElement',
|
|
326
|
+
name: {
|
|
327
|
+
type: 'JSXIdentifier',
|
|
328
|
+
name: elementName,
|
|
329
|
+
},
|
|
330
|
+
attributes: [],
|
|
331
|
+
selfClosing: !propsArg.properties.find(
|
|
332
|
+
(p) => p.key?.name === 'children',
|
|
333
|
+
),
|
|
334
|
+
},
|
|
335
|
+
closingElement: propsArg.properties.find(
|
|
336
|
+
(p) => p.key?.name === 'children',
|
|
337
|
+
)
|
|
338
|
+
? {
|
|
339
|
+
type: 'JSXClosingElement',
|
|
340
|
+
name: {
|
|
341
|
+
type: 'JSXIdentifier',
|
|
342
|
+
name: elementName,
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
: null,
|
|
346
|
+
children: [],
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Add attributes
|
|
350
|
+
if (propsArg && propsArg.properties) {
|
|
351
|
+
propsArg.properties.forEach((prop) => {
|
|
352
|
+
if (prop.type === 'SpreadElement') {
|
|
353
|
+
jsxElement.openingElement.attributes.push({
|
|
354
|
+
type: 'JSXSpreadAttribute',
|
|
355
|
+
argument: prop.argument,
|
|
356
|
+
})
|
|
357
|
+
} else if (prop.key?.name === 'children') {
|
|
358
|
+
if (prop.value.type === 'ArrayExpression') {
|
|
359
|
+
jsxElement.children = prop.value.elements.map(
|
|
360
|
+
(element) => {
|
|
361
|
+
if (
|
|
362
|
+
noContainerTypes.has(
|
|
363
|
+
element.type,
|
|
364
|
+
) ||
|
|
365
|
+
(element.type ===
|
|
366
|
+
'CallExpression' &&
|
|
367
|
+
element.callee?.name?.startsWith(
|
|
368
|
+
'_jsx',
|
|
369
|
+
))
|
|
370
|
+
) {
|
|
371
|
+
return element
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
type: 'JSXExpressionContainer',
|
|
375
|
+
expression: element,
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
} else {
|
|
380
|
+
if (
|
|
381
|
+
noContainerTypes.has(prop.value.type) ||
|
|
382
|
+
(prop.value.type === 'CallExpression' &&
|
|
383
|
+
prop.value.callee?.name?.startsWith(
|
|
384
|
+
'_jsx',
|
|
385
|
+
))
|
|
386
|
+
) {
|
|
387
|
+
jsxElement.children = [prop.value]
|
|
388
|
+
} else {
|
|
389
|
+
jsxElement.children = [
|
|
390
|
+
{
|
|
391
|
+
type: 'JSXExpressionContainer',
|
|
392
|
+
expression: prop.value,
|
|
393
|
+
},
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
let attrName = prop.key?.name
|
|
399
|
+
if (
|
|
400
|
+
!attrName &&
|
|
401
|
+
prop.key?.type === 'StringLiteral'
|
|
402
|
+
) {
|
|
403
|
+
attrName = prop.key.value
|
|
404
|
+
}
|
|
405
|
+
if (!attrName) {
|
|
406
|
+
console.log(
|
|
407
|
+
`no prop.key?.name for ${JSON.stringify(
|
|
408
|
+
prop,
|
|
409
|
+
)}`,
|
|
410
|
+
)
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
jsxElement.openingElement.attributes.push({
|
|
415
|
+
type: 'JSXAttribute',
|
|
416
|
+
name: {
|
|
417
|
+
type: 'JSXIdentifier',
|
|
418
|
+
name: attrName,
|
|
419
|
+
},
|
|
420
|
+
value: {
|
|
421
|
+
type: 'JSXExpressionContainer',
|
|
422
|
+
expression: prop.value,
|
|
423
|
+
},
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
path.replaceWith(jsxElement)
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function jsonStringifyWithMaps(map) {
|
|
436
|
+
return JSON.stringify(
|
|
437
|
+
[...map],
|
|
438
|
+
(key, value) => (value instanceof Map ? [...value] : value),
|
|
439
|
+
2,
|
|
440
|
+
)
|
|
441
|
+
}
|
package/src/cli.tsx
CHANGED
|
@@ -9,19 +9,12 @@ import path, { basename } from 'path'
|
|
|
9
9
|
import { BreakpointSizes } from './css.js'
|
|
10
10
|
import { logger } from './utils.js'
|
|
11
11
|
const configNames = ['unframer.config.json', 'unframer.json']
|
|
12
|
+
import kebabCase from 'just-kebab-case'
|
|
12
13
|
|
|
13
14
|
export const cli = cac('unframer')
|
|
14
15
|
|
|
15
16
|
let defaultOutDir = 'framer'
|
|
16
17
|
|
|
17
|
-
function nameToFolder(name: string) {
|
|
18
|
-
return name
|
|
19
|
-
.replace(/[^a-zA-Z0-9]/g, '-') // Replace non-alphanumeric with dash
|
|
20
|
-
.replace(/-+/g, '-') // Replace multiple dashes with single dash
|
|
21
|
-
.replace(/^-|-$/g, '') // Remove leading/trailing dashes
|
|
22
|
-
.toLowerCase()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
18
|
cli.command('[projectId]', 'Run unframer with optional project ID')
|
|
26
19
|
.option('--outDir <dir>', 'Output directory', { default: defaultOutDir })
|
|
27
20
|
.action(async function main(projectId, options) {
|
|
@@ -40,20 +33,20 @@ cli.command('[projectId]', 'Run unframer with optional project ID')
|
|
|
40
33
|
return
|
|
41
34
|
}
|
|
42
35
|
const data = await response.json()
|
|
43
|
-
|
|
36
|
+
logger.log('unframer data', data)
|
|
37
|
+
let cwd = path.resolve(process.cwd(), outDir || 'framer')
|
|
38
|
+
return await bundle({
|
|
44
39
|
config: {
|
|
45
40
|
outDir,
|
|
46
41
|
components: Object.fromEntries(
|
|
47
|
-
data.components.map((c) => [
|
|
48
|
-
nameToFolder(c.name),
|
|
49
|
-
c.url,
|
|
50
|
-
]),
|
|
42
|
+
data.components.map((c) => [kebabCase(c.name), c.url]),
|
|
51
43
|
),
|
|
52
44
|
tokens: data.colorStyles,
|
|
45
|
+
framerWebPages: data.framerWebPages || [],
|
|
53
46
|
},
|
|
54
47
|
watch: false,
|
|
55
48
|
|
|
56
|
-
|
|
49
|
+
cwd,
|
|
57
50
|
signal: new AbortController().signal,
|
|
58
51
|
})
|
|
59
52
|
}
|
|
@@ -79,11 +72,11 @@ cli.command('[projectId]', 'Run unframer with optional project ID')
|
|
|
79
72
|
|
|
80
73
|
let controller = new AbortController()
|
|
81
74
|
setMaxListeners(0, controller.signal)
|
|
82
|
-
|
|
75
|
+
await bundle({
|
|
83
76
|
config,
|
|
84
77
|
watch: false,
|
|
85
78
|
signal: controller.signal,
|
|
86
|
-
|
|
79
|
+
cwd: path.resolve(process.cwd(), outDir || 'framer'),
|
|
87
80
|
})
|
|
88
81
|
})
|
|
89
82
|
|
|
@@ -154,46 +147,12 @@ function getNewNames(oldConfig: Config, newConfig: Config) {
|
|
|
154
147
|
return newNames
|
|
155
148
|
}
|
|
156
149
|
|
|
157
|
-
type Config = {
|
|
150
|
+
export type Config = {
|
|
158
151
|
components: {
|
|
159
152
|
[name: string]: string
|
|
160
153
|
}
|
|
154
|
+
framerWebPages?: { webPageId: string; path: string }[]
|
|
161
155
|
breakpoints?: BreakpointSizes
|
|
162
156
|
tokens?: StyleToken[]
|
|
163
157
|
outDir?: string
|
|
164
158
|
}
|
|
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/css.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import dedent from 'dedent'
|
|
1
|
+
import dedent from 'string-dedent'
|
|
2
2
|
import { ComponentFont } from './framer.js'
|
|
3
3
|
|
|
4
4
|
function deduplicateByKey<T>(arr: T[], key: (k: T) => string): T[] {
|
|
@@ -92,7 +92,8 @@ export function getFontsStyles(_fontsDefs: ComponentFontBundle[]) {
|
|
|
92
92
|
str += dedent`
|
|
93
93
|
@font-face {
|
|
94
94
|
font-family: '${x.family}';
|
|
95
|
-
src: url('${x.url}');\n
|
|
95
|
+
src: url('${x.url}');\n
|
|
96
|
+
`
|
|
96
97
|
if (x.style) {
|
|
97
98
|
str += ` font-style: ${x.style};\n`
|
|
98
99
|
}
|
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(
|
package/src/exporter.test.ts
CHANGED
|
@@ -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', () => {
|