mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2025-01-03 05:10:13 -05:00
Add image library to optimize performance (#133)
* Add image library to optimize performance * fix and improve * Add changelog and improve naming * refactor * improve * fix todos
This commit is contained in:
parent
a4113382bb
commit
f726dc9cec
16 changed files with 122 additions and 32 deletions
5
.changeset/tasty-geckos-eat.md
Normal file
5
.changeset/tasty-geckos-eat.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Optimize image exporting when there are multiple copies of the same image in the file
|
23
plugin-src/ImageLibrary.ts
Normal file
23
plugin-src/ImageLibrary.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { ImageColor } from '@ui/lib/types/utils/imageColor';
|
||||||
|
|
||||||
|
class ImageLibrary {
|
||||||
|
private images: Record<string, ImageColor> = {};
|
||||||
|
|
||||||
|
public register(hash: string, image: ImageColor) {
|
||||||
|
this.images[hash] = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(hash: string): ImageColor | undefined {
|
||||||
|
return this.images[hash];
|
||||||
|
}
|
||||||
|
|
||||||
|
public all(): Record<string, ImageColor> {
|
||||||
|
return this.images;
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(images: Record<string, ImageColor>): void {
|
||||||
|
this.images = images;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const imagesLibrary = new ImageLibrary();
|
|
@ -1,4 +1,5 @@
|
||||||
import { componentsLibrary } from '@plugin/ComponentLibrary';
|
import { componentsLibrary } from '@plugin/ComponentLibrary';
|
||||||
|
import { imagesLibrary } from '@plugin/ImageLibrary';
|
||||||
|
|
||||||
import { PenpotDocument } from '@ui/types';
|
import { PenpotDocument } from '@ui/types';
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ export const transformDocumentNode = async (node: DocumentNode): Promise<PenpotD
|
||||||
return {
|
return {
|
||||||
name: node.name,
|
name: node.name,
|
||||||
children,
|
children,
|
||||||
components: componentsLibrary.all()
|
components: componentsLibrary.all(),
|
||||||
|
images: imagesLibrary.all()
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { imagesLibrary } from '@plugin/ImageLibrary';
|
||||||
import { detectMimeType } from '@plugin/utils';
|
import { detectMimeType } from '@plugin/utils';
|
||||||
|
|
||||||
import { Fill } from '@ui/lib/types/utils/fill';
|
import { Fill } from '@ui/lib/types/utils/fill';
|
||||||
|
@ -9,28 +10,46 @@ export const translateImageFill = async (fill: ImagePaint): Promise<Fill | undef
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fillOpacity: !fill.visible ? 0 : fill.opacity,
|
fillOpacity: !fill.visible ? 0 : fill.opacity,
|
||||||
fillImage: fillImage
|
fillImage
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const translateImage = async (imageHash: string | null): Promise<ImageColor | undefined> => {
|
const translateImage = async (imageHash: string | null): Promise<ImageColor | undefined> => {
|
||||||
if (!imageHash) return;
|
if (!imageHash) return;
|
||||||
|
|
||||||
|
const imageColor = imagesLibrary.get(imageHash) ?? (await generateAndRegister(imageHash));
|
||||||
|
|
||||||
|
if (!imageColor) return;
|
||||||
|
|
||||||
|
const { dataUri, ...rest } = imageColor;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
imageHash
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateAndRegister = async (imageHash: string) => {
|
||||||
const image = figma.getImageByHash(imageHash);
|
const image = figma.getImageByHash(imageHash);
|
||||||
|
|
||||||
if (!image) return;
|
if (!image) return;
|
||||||
|
|
||||||
const bytes = await image.getBytesAsync();
|
const bytes = await image.getBytesAsync();
|
||||||
const size = await image.getSizeAsync();
|
const { width, height } = await image.getSizeAsync();
|
||||||
const b64 = figma.base64Encode(bytes);
|
const b64 = figma.base64Encode(bytes);
|
||||||
const mimeType = detectMimeType(b64);
|
const mtype = detectMimeType(b64);
|
||||||
const dataUri = `data:${mimeType};base64,${b64}`;
|
const dataUri = `data:${mtype};base64,${b64}`;
|
||||||
|
|
||||||
return {
|
const imageColor: ImageColor = {
|
||||||
width: size.width,
|
width,
|
||||||
height: size.height,
|
height,
|
||||||
mtype: mimeType,
|
mtype,
|
||||||
|
dataUri,
|
||||||
keepAspectRatio: true,
|
keepAspectRatio: true,
|
||||||
dataUri: dataUri,
|
|
||||||
id: '00000000-0000-0000-0000-000000000000'
|
id: '00000000-0000-0000-0000-000000000000'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
imagesLibrary.register(imageHash, imageColor);
|
||||||
|
|
||||||
|
return imageColor;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Uuid } from './uuid';
|
import { Uuid } from './uuid';
|
||||||
|
|
||||||
//@TODO: check how this exports the image through a dataUri
|
|
||||||
export type ImageColor = {
|
export type ImageColor = {
|
||||||
name?: string;
|
name?: string;
|
||||||
width: number;
|
width: number;
|
||||||
|
@ -9,4 +8,5 @@ export type ImageColor = {
|
||||||
id?: Uuid;
|
id?: Uuid;
|
||||||
keepAspectRatio?: boolean;
|
keepAspectRatio?: boolean;
|
||||||
dataUri?: string;
|
dataUri?: string;
|
||||||
|
imageHash?: string; // @TODO: move to any other place
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
|
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
|
||||||
import { Uuid } from '@ui/lib/types/utils/uuid';
|
import { Uuid } from '@ui/lib/types/utils/uuid';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolBlendMode, symbolFillGradients } from '@ui/parser/creators/symbols';
|
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
import { createItems } from '.';
|
import { createItems } from '.';
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export const createArtboard = (
|
||||||
file.addArtboard({
|
file.addArtboard({
|
||||||
id,
|
id,
|
||||||
shapeRef: shapeRef ?? parseFigmaId(file, figmaRelatedId, true),
|
shapeRef: shapeRef ?? parseFigmaId(file, figmaRelatedId, true),
|
||||||
fills: symbolFillGradients(fills),
|
fills: symbolFills(fills),
|
||||||
blendMode: symbolBlendMode(blendMode),
|
blendMode: symbolBlendMode(blendMode),
|
||||||
...rest
|
...rest
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
|
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolBlendMode, symbolBoolType, symbolFillGradients } from '@ui/parser/creators/symbols';
|
import { symbolBlendMode, symbolBoolType, symbolFills } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
import { createItems } from '.';
|
import { createItems } from '.';
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ export const createBool = (
|
||||||
file.addBool({
|
file.addBool({
|
||||||
id: parseFigmaId(file, figmaId),
|
id: parseFigmaId(file, figmaId),
|
||||||
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
||||||
fills: symbolFillGradients(fills),
|
fills: symbolFills(fills),
|
||||||
blendMode: symbolBlendMode(blendMode),
|
blendMode: symbolBlendMode(blendMode),
|
||||||
boolType: symbolBoolType(boolType),
|
boolType: symbolBoolType(boolType),
|
||||||
...rest
|
...rest
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
|
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolBlendMode, symbolFillGradients } from '@ui/parser/creators/symbols';
|
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
export const createCircle = (
|
export const createCircle = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
|
@ -10,7 +10,7 @@ export const createCircle = (
|
||||||
file.createCircle({
|
file.createCircle({
|
||||||
id: parseFigmaId(file, figmaId),
|
id: parseFigmaId(file, figmaId),
|
||||||
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
||||||
fills: symbolFillGradients(fills),
|
fills: symbolFills(fills),
|
||||||
blendMode: symbolBlendMode(blendMode),
|
blendMode: symbolBlendMode(blendMode),
|
||||||
...rest
|
...rest
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { componentsLibrary } from '@plugin/ComponentLibrary';
|
import { componentsLibrary } from '@plugin/ComponentLibrary';
|
||||||
|
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
|
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
|
||||||
import { uiComponents } from '@ui/parser/libraries';
|
import { uiComponents } from '@ui/parser/libraries';
|
||||||
|
|
||||||
import { createItems } from '.';
|
import { createItems } from '.';
|
||||||
|
@ -12,10 +13,12 @@ export const createComponentLibrary = (file: PenpotFile) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { children = [], ...rest } = component;
|
const { children = [], fills, blendMode, ...rest } = component;
|
||||||
|
|
||||||
file.startComponent({
|
file.startComponent({
|
||||||
...rest,
|
...rest,
|
||||||
|
fills: symbolFills(fills),
|
||||||
|
blendMode: symbolBlendMode(blendMode),
|
||||||
id: uiComponent.componentId,
|
id: uiComponent.componentId,
|
||||||
componentId: uiComponent.componentId,
|
componentId: uiComponent.componentId,
|
||||||
mainInstancePage: uiComponent.mainInstancePage,
|
mainInstancePage: uiComponent.mainInstancePage,
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import {
|
import { symbolBlendMode, symbolFills, symbolPathContent } from '@ui/parser/creators/symbols';
|
||||||
symbolBlendMode,
|
|
||||||
symbolFillGradients,
|
|
||||||
symbolPathContent
|
|
||||||
} from '@ui/parser/creators/symbols';
|
|
||||||
|
|
||||||
export const createPath = (
|
export const createPath = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
|
@ -14,7 +10,7 @@ export const createPath = (
|
||||||
file.createPath({
|
file.createPath({
|
||||||
id: parseFigmaId(file, figmaId),
|
id: parseFigmaId(file, figmaId),
|
||||||
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
||||||
fills: symbolFillGradients(fills),
|
fills: symbolFills(fills),
|
||||||
blendMode: symbolBlendMode(blendMode),
|
blendMode: symbolBlendMode(blendMode),
|
||||||
content: symbolPathContent(content),
|
content: symbolPathContent(content),
|
||||||
...rest
|
...rest
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { RectShape } from '@ui/lib/types/shapes/rectShape';
|
import { RectShape } from '@ui/lib/types/shapes/rectShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolBlendMode, symbolFillGradients } from '@ui/parser/creators/symbols';
|
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
export const createRectangle = (
|
export const createRectangle = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
|
@ -10,7 +10,7 @@ export const createRectangle = (
|
||||||
file.createRect({
|
file.createRect({
|
||||||
id: parseFigmaId(file, figmaId),
|
id: parseFigmaId(file, figmaId),
|
||||||
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
||||||
fills: symbolFillGradients(fills),
|
fills: symbolFills(fills),
|
||||||
blendMode: symbolBlendMode(blendMode),
|
blendMode: symbolBlendMode(blendMode),
|
||||||
...rest
|
...rest
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,33 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { TextShape } from '@ui/lib/types/shapes/textShape';
|
import { TextContent, TextShape } from '@ui/lib/types/shapes/textShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolBlendMode } from '@ui/parser/creators/symbols';
|
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
export const createText = (
|
export const createText = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
{ type, blendMode, figmaId, figmaRelatedId, ...rest }: TextShape
|
{ type, blendMode, figmaId, content, figmaRelatedId, ...rest }: TextShape
|
||||||
) => {
|
) => {
|
||||||
file.createText({
|
file.createText({
|
||||||
id: parseFigmaId(file, figmaId),
|
id: parseFigmaId(file, figmaId),
|
||||||
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
shapeRef: parseFigmaId(file, figmaRelatedId, true),
|
||||||
|
content: parseContent(content),
|
||||||
blendMode: symbolBlendMode(blendMode),
|
blendMode: symbolBlendMode(blendMode),
|
||||||
...rest
|
...rest
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseContent = (content: TextContent | undefined): TextContent | undefined => {
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
content.children?.forEach(paragraphSet => {
|
||||||
|
paragraphSet.children.forEach(paragraph => {
|
||||||
|
paragraph.children.forEach(textNode => {
|
||||||
|
textNode.fills = symbolFills(textNode.fills);
|
||||||
|
});
|
||||||
|
|
||||||
|
paragraph.fills = symbolFills(paragraph.fills);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export * from './symbolBlendMode';
|
export * from './symbolBlendMode';
|
||||||
export * from './symbolBoolType';
|
export * from './symbolBoolType';
|
||||||
export * from './symbolFillGradients';
|
export * from './symbolFills';
|
||||||
export * from './symbolPathContent';
|
export * from './symbolPathContent';
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import { imagesLibrary } from '@plugin/ImageLibrary';
|
||||||
|
|
||||||
import { Fill } from '@ui/lib/types/utils/fill';
|
import { Fill } from '@ui/lib/types/utils/fill';
|
||||||
import { Gradient, LINEAR_TYPE, RADIAL_TYPE } from '@ui/lib/types/utils/gradient';
|
import { Gradient, LINEAR_TYPE, RADIAL_TYPE } from '@ui/lib/types/utils/gradient';
|
||||||
|
import { ImageColor } from '@ui/lib/types/utils/imageColor';
|
||||||
|
|
||||||
export const symbolFillGradients = (fills?: Fill[]): Fill[] | undefined => {
|
export const symbolFills = (fills?: Fill[]): Fill[] | undefined => {
|
||||||
if (!fills) return;
|
if (!fills) return;
|
||||||
|
|
||||||
return fills.map(fill => {
|
return fills.map(fill => {
|
||||||
|
@ -9,6 +12,10 @@ export const symbolFillGradients = (fills?: Fill[]): Fill[] | undefined => {
|
||||||
fill.fillColorGradient = symbolFillGradient(fill.fillColorGradient);
|
fill.fillColorGradient = symbolFillGradient(fill.fillColorGradient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fill.fillImage) {
|
||||||
|
fill.fillImage = symbolFillImage(fill.fillImage);
|
||||||
|
}
|
||||||
|
|
||||||
return fill;
|
return fill;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -29,3 +36,16 @@ const symbolFillGradient = ({ type, ...rest }: Gradient): Gradient | undefined =
|
||||||
|
|
||||||
console.error(`Unsupported gradient type: ${String(type)}`);
|
console.error(`Unsupported gradient type: ${String(type)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const symbolFillImage = ({ imageHash, ...rest }: ImageColor): ImageColor | undefined => {
|
||||||
|
if (!imageHash) return;
|
||||||
|
|
||||||
|
const imageColor = imagesLibrary.get(imageHash);
|
||||||
|
|
||||||
|
if (!imageColor) return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
dataUri: imageColor?.dataUri
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
import { componentsLibrary } from '@plugin/ComponentLibrary';
|
import { componentsLibrary } from '@plugin/ComponentLibrary';
|
||||||
|
import { imagesLibrary } from '@plugin/ImageLibrary';
|
||||||
|
|
||||||
import { createFile } from '@ui/lib/penpot';
|
import { createFile } from '@ui/lib/penpot';
|
||||||
import { createComponentLibrary, createPage } from '@ui/parser/creators';
|
import { createComponentLibrary, createPage } from '@ui/parser/creators';
|
||||||
|
@ -7,8 +8,10 @@ import { PenpotDocument } from '@ui/types';
|
||||||
|
|
||||||
import { idLibrary } from '.';
|
import { idLibrary } from '.';
|
||||||
|
|
||||||
export const parse = ({ name, children = [], components }: PenpotDocument) => {
|
export const parse = ({ name, children = [], components, images }: PenpotDocument) => {
|
||||||
componentsLibrary.init(components);
|
componentsLibrary.init(components);
|
||||||
|
imagesLibrary.init(images);
|
||||||
|
|
||||||
uiComponents.init();
|
uiComponents.init();
|
||||||
idLibrary.init();
|
idLibrary.init();
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { PenpotPage } from '@ui/lib/types/penpotPage';
|
import { PenpotPage } from '@ui/lib/types/penpotPage';
|
||||||
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
|
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
|
||||||
|
import { ImageColor } from '@ui/lib/types/utils/imageColor';
|
||||||
|
|
||||||
export type PenpotDocument = {
|
export type PenpotDocument = {
|
||||||
name: string;
|
name: string;
|
||||||
children?: PenpotPage[];
|
children?: PenpotPage[];
|
||||||
components: Record<string, ComponentShape>;
|
components: Record<string, ComponentShape>;
|
||||||
|
images: Record<string, ImageColor>;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue