Skip to content

Commit

Permalink
Merge pull request #40 from marcomontalbano/i34-svgr
Browse files Browse the repository at this point in the history
Add new component outputter 'output-components-as-svgr'
  • Loading branch information
marcomontalbano authored Feb 28, 2020
2 parents fc4c9bc + d63063b commit 9da0e6a
Show file tree
Hide file tree
Showing 11 changed files with 761 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"node": ">= 8.3 <= 13.x"
},
"lint-staged": {
"*.{js,ts}": [
"!(*.d).{js,ts}": [
"npm test"
]
},
Expand Down
1 change: 1 addition & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,5 @@ or install an official outputter:
| [`@figma-export/output-components-as-es6`](/packages/output-components-as-es6) | [![npm](https://img.shields.io/npm/v/@figma-export/output-components-as-es6.svg?maxAge=3600)](https://www.npmjs.com/package/@figma-export/output-components-as-es6) |
| [`@figma-export/output-components-as-stdout`](/packages/output-components-as-stdout) | [![npm](https://img.shields.io/npm/v/@figma-export/output-components-as-stdout.svg?maxAge=3600)](https://www.npmjs.com/package/@figma-export/output-components-as-stdout) |
| [`@figma-export/output-components-as-svg`](/packages/output-components-as-svg) | [![npm](https://img.shields.io/npm/v/@figma-export/output-components-as-svg.svg?maxAge=3600)](https://www.npmjs.com/package/@figma-export/output-components-as-svg) |
| [`@figma-export/output-components-as-svgr`](/packages/output-components-as-svgr) | [![npm](https://img.shields.io/npm/v/@figma-export/output-components-as-svgr.svg?maxAge=3600)](https://www.npmjs.com/package/@figma-export/output-components-as-svgr) |
| [`@figma-export/output-components-as-svgstore`](/packages/output-components-as-svgstore) | [![npm](https://img.shields.io/npm/v/@figma-export/output-components-as-svgstore.svg?maxAge=3600)](https://www.npmjs.com/package/@figma-export/output-components-as-svgstore) |
1 change: 1 addition & 0 deletions packages/core/src/lib/_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const componentWithSlashedName = {
id: '9:10',
name: 'icon/eye',
type: 'COMPONENT',
svg: svg.content,
};

const component1: FigmaExport.ComponentNode = {
Expand Down
3 changes: 3 additions & 0 deletions packages/output-components-as-svgr/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src
tsconfig.json
tsconfig.tsbuildinfo
50 changes: 50 additions & 0 deletions packages/output-components-as-svgr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# @figma-export/output-components-as-svgr

> Outputter for [@figma-export](https://github.com/marcomontalbano/figma-export) that exports components as React components.
With this outputter you can export all Figma components as React components into the specified output folder.

This is a sample of the output from this [Figma file](https://www.figma.com/file/RSzpKJcnb6uBRQ3rOfLIyUs5):

```sh
$ tree output/

# output
# ├── icons
# │ ├── FigmaArrow.jsx
# │ ├── FigmaExport.jsx
# │ ├── FigmaLogo.jsx
# │ └── index.js
# ├── monochrome
# │ ├── FigmaBlue.jsx
# │ ├── FigmaGreen.jsx
# │ ├── FigmaPurple.jsx
# │ ├── FigmaRed.jsx
# │ └── index.js
# └── unit-test
# ├── figma
# │ ├── logo
# │ │ ├── Main.jsx
# │ │ ├── MainBright.jsx
# │ │ └── index.js
# │ ├── Logo.jsx
# │ └── index.js
# ├── FigmaDefaultLogo.jsx
# └── index.js
```

> **Tip**: A figma component named `icon/eye` will be exported as `Eye.jsx` inside the `icon` folder. Another `index.js` file will be created inside the `icon` folder and this will export directly the `Eye` component.
## Install

Using npm:

```sh
npm install --save-dev @figma-export/output-components-as-svgr
```

or using yarn:

```sh
yarn add @figma-export/output-components-as-svgr --dev
```
5 changes: 5 additions & 0 deletions packages/output-components-as-svgr/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module '@svgr/core' {
type config = {};
type state = {};
function sync(svg: string, config: config, state: state): string;
}
508 changes: 508 additions & 0 deletions packages/output-components-as-svgr/package-lock.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions packages/output-components-as-svgr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@figma-export/output-components-as-svgr",
"version": "2.0.0-alpha.1",
"description": "Outputter for @figma-export that exports components as React components using svgr",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/marcomontalbano/figma-exporter.git",
"directory": "packages/output-components-as-svgr"
},
"author": "Marco Montalbano <me@marcomontalbano.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@figma-export/output-components-utils": "^2.0.0-alpha.1",
"@figma-export/types": "^2.0.0-alpha.1",
"@svgr/core": "~5.2.0",
"make-dir": "~3.0.0"
},
"engines": {
"node": ">= 8.3 <= 13.x"
}
}
95 changes: 95 additions & 0 deletions packages/output-components-as-svgr/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import sinon from 'sinon';
import { expect } from 'chai';

import * as figmaDocument from '../../core/src/lib/_config.test';
import * as figma from '../../core/src/lib/figma';

import fs = require('fs');
import outputter = require('./index');

describe('outputter as svgr', () => {
afterEach(() => {
sinon.restore();
});

it('should export all components into jsx files plus one index.js for each folder', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');
const fakePage = figmaDocument.createPage([figmaDocument.component1, figmaDocument.component2]);
const pages = figma.getPages(fakePage);

await outputter({
output: 'output',
})(pages);

expect(writeFileSync).to.be.calledThrice;
expect(writeFileSync.firstCall).to.be.calledWithMatch('output/fakePage/FigmaLogo.jsx', 'function FigmaLogo(props) {');
expect(writeFileSync.secondCall).to.be.calledWithMatch('output/fakePage/Search.jsx', 'function Search(props) {');
expect(writeFileSync.thirdCall).to.be.calledWithMatch('output/fakePage/index.js', "export { default as FigmaLogo } from './FigmaLogo.jsx';");
});

it('should create folder if component names contain slashes', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');
const fakePage = figmaDocument.createPage([figmaDocument.componentWithSlashedName]);
const pages = figma.getPages(fakePage);

await outputter({
output: 'output',
})(pages);

expect(writeFileSync).to.be.calledTwice;
expect(writeFileSync.firstCall).to.be.calledWithMatch('output/fakePage/icon/Eye.jsx');
expect(writeFileSync.secondCall).to.be.calledWithMatch('output/fakePage/icon/index.js');
});

describe('options', () => {
const fakePage = figmaDocument.createPage([figmaDocument.componentWithSlashedName]);
const pages = figma.getPages(fakePage);

it('should be able to customize "dirname"', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');

await outputter({
output: 'output',
getDirname: (options) => `${options.dirname}`,
})(pages);

expect(writeFileSync.firstCall).to.be.calledWithMatch('output/icon/Eye.jsx', 'function Eye(props) {');
expect(writeFileSync.secondCall).to.be.calledWithMatch('output/icon/index.js', "from './Eye.jsx';");
});

it('should be able to customize "componentName"', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');

await outputter({
output: 'output',
getComponentName: (options) => `${options.basename}`,
})(pages);

expect(writeFileSync.firstCall).to.be.calledWithMatch('output/fakePage/icon/eye.jsx', 'function eye(props) {');
expect(writeFileSync.secondCall).to.be.calledWithMatch('output/fakePage/icon/index.js', "from './eye.jsx';");
});

it('should be able to customize "fileExtension"', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');

await outputter({
output: 'output',
getFileExtension: () => '.js',
})(pages);

expect(writeFileSync.firstCall).to.be.calledWithMatch('output/fakePage/icon/Eye.js', 'function Eye(props) {');
expect(writeFileSync.secondCall).to.be.calledWithMatch('output/fakePage/icon/index.js', "from './Eye.js';");
});

it('should be able to customize "svgrConfig"', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');

await outputter({
output: 'output',
getSvgrConfig: () => ({ native: true }),
})(pages);

expect(writeFileSync.firstCall).to.be.calledWithMatch('output/fakePage/icon/Eye.jsx', 'from "react-native-svg"');
});
});
});
60 changes: 60 additions & 0 deletions packages/output-components-as-svgr/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

import { FigmaExport } from '@figma-export/types';
import { pascalCase } from '@figma-export/output-components-utils';
import svgr from '@svgr/core';

import fs = require('fs');
import path = require('path');
import makeDir = require('make-dir');

type Options = {
output: string;
getDirname?: (options: FigmaExport.OutputterParamOption) => string;
getComponentName?: (options: FigmaExport.OutputterParamOption) => string;
getFileExtension?: (options: FigmaExport.OutputterParamOption) => string;
getSvgrConfig?: (options: FigmaExport.OutputterParamOption) => svgr.config;
}

type IndexJs = {
[key: string]: string[];
};

export = ({
output,
getDirname = (options): string => `${options.pageName}${path.sep}${options.dirname}`,
getComponentName = (options): string => `${pascalCase(options.basename)}`,
getFileExtension = (): string => '.jsx',
getSvgrConfig = (): svgr.config => ({}),
}: Options): FigmaExport.Outputter => {
makeDir.sync(output);
const indexJs: IndexJs = {};
return async (pages): Promise<void> => {
pages.forEach(({ name: pageName, components }) => {
components.forEach(({ name: componentName, svg, figmaExport }) => {
const options = {
pageName,
componentName,
...figmaExport,
};

const reactComponentName = getComponentName(options);
const basename = `${reactComponentName}${getFileExtension(options)}`;
const filePath = makeDir.sync(path.resolve(output, getDirname(options)));

indexJs[filePath] = indexJs[filePath] || [];
indexJs[filePath].push(`export { default as ${reactComponentName} } from './${basename}';`);

const svgrConfig = getSvgrConfig(options);
const svgrState = { componentName: reactComponentName };

const jsCode = svgr.sync(svg, svgrConfig, svgrState);

fs.writeFileSync(path.resolve(filePath, basename), jsCode);
});

Object.entries(indexJs).forEach(([filePath, exports]) => {
fs.writeFileSync(path.resolve(filePath, 'index.js'), exports.join('\n'));
});
});
};
};
11 changes: 11 additions & 0 deletions packages/output-components-as-svgr/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"references": [
{"path": "../types"},
{"path": "../output-components-utils"},
]
}

0 comments on commit 9da0e6a

Please sign in to comment.