├── .changeset ├── README.md └── config.json ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── lefthook.yml ├── manifest.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── rollup.config.mjs ├── src ├── App.svelte ├── app.css ├── app.js ├── img │ ├── Icon.png │ ├── coverArt.png │ ├── jewel.svg │ └── wordmark.svg ├── index.html ├── lib │ ├── components │ │ ├── Controls │ │ │ ├── File.svelte │ │ │ ├── Images.svelte │ │ │ ├── Page.svelte │ │ │ ├── Preview.svelte │ │ │ ├── Text.svelte │ │ │ └── Variables.svelte │ │ ├── DatafaceLogo.svelte │ │ ├── ErrorMessage.svelte │ │ ├── Footer.svelte │ │ ├── Inputs │ │ │ ├── Button.svelte │ │ │ ├── Checkbox.svelte │ │ │ ├── Input.svelte │ │ │ ├── Select.svelte │ │ │ └── TextArea.svelte │ │ ├── Layout │ │ │ ├── Panel.svelte │ │ │ └── PreviewCard.svelte │ │ ├── Overlay.svelte │ │ └── WindowResize.svelte │ ├── generator │ │ ├── convertTextFrames.ts │ │ ├── createSettingsBlock.ts │ │ ├── css │ │ │ ├── frame.ts │ │ │ ├── index.ts │ │ │ ├── page.ts │ │ │ └── textEffect.ts │ │ ├── group.ts │ │ ├── html │ │ │ ├── frame.ts │ │ │ ├── index.ts │ │ │ ├── span.ts │ │ │ └── wrapper.ts │ │ ├── js │ │ │ ├── fonts.ts │ │ │ ├── index.ts │ │ │ └── resizer.ts │ │ ├── styleProps.ts │ │ └── widthRange.ts │ ├── pkg.js │ └── utils │ │ ├── autotype.ts │ │ ├── camelize.ts │ │ ├── dashify.ts │ │ ├── extractTextFromHTML.ts │ │ ├── isFigma2htmlFrame.ts │ │ ├── isNodeVisible.ts │ │ ├── log.ts │ │ ├── roundTo.ts │ │ ├── slugify.ts │ │ ├── stringify.ts │ │ ├── textNodeVariable.ts │ │ ├── timestamp.ts │ │ ├── trim.ts │ │ └── zeroPad.ts ├── main.ts └── types.d.ts ├── tailwind.config.js └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.5.0/schema.json", 3 | "changelog": [ 4 | "@svitejs/changesets-changelog-github-compact", 5 | { "repo": "the-dataface/figma2html" } 6 | ], 7 | "commit": false, 8 | "fixed": [], 9 | "linked": [], 10 | "access": "restricted", 11 | "privatePackages": { "version": true, "tag": true }, 12 | "baseBranch": "main", 13 | "updateInternalDependencies": "patch", 14 | "ignore": [] 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | node_modules 4 | dist 5 | build 6 | coverage 7 | .md 8 | *.d.ts 9 | !**/*.{ts,tsx,js,jsx,svelte} 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | // https://github.com/sveltejs/eslint-plugin-svelte3 2 | { 3 | "root": true, 4 | "parser": "@typescript-eslint/parser", 5 | "extends": ["eslint:recommended", "prettier"], 6 | "plugins": ["svelte3", "@typescript-eslint"], 7 | "overrides": [{ "files": ["*.svelte"], "processor": "svelte3/svelte3" }], 8 | "parserOptions": { 9 | "sourceType": "module", 10 | "ecmaVersion": 2020 11 | }, 12 | "env": { 13 | "browser": true, 14 | "es6": true, 15 | "node": true 16 | }, 17 | "rules": { 18 | "no-undef": "off" 19 | }, 20 | "settings": { 21 | "svelte3/typescript": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F6A8 Bug report" 2 | description: Report an issue with figma2html 3 | labels: ['bug'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: bug-description 11 | attributes: 12 | label: Describe the bug 13 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 14 | placeholder: Bug description 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: reproduction 19 | attributes: 20 | label: Reproduction 21 | description: Please provide a link to a repo or Figma file that can reproduce the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided within a reasonable time-frame, the issue will be closed. 22 | placeholder: Reproduction 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: logs 27 | attributes: 28 | label: Logs 29 | description: 'Please include browser console and server logs around the time this bug occurred. Optional if provided reproduction. Please try not to insert an image but copy paste the log text.' 30 | render: Shell 31 | - type: textarea 32 | id: system-info 33 | attributes: 34 | label: System Info 35 | description: Figma version, OS version, Browser version, etc. 36 | render: Shell 37 | placeholder: System, Binaries, Browsers 38 | validations: 39 | required: true 40 | - type: dropdown 41 | id: severity 42 | attributes: 43 | label: Severity 44 | description: Select the severity of this issue 45 | options: 46 | - annoyance 47 | - blocking an upgrade 48 | - blocking all usage of figma2html 49 | validations: 50 | required: true 51 | - type: checkboxes 52 | id: terms 53 | attributes: 54 | label: Code of Conduct 55 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/the-dataface/figma2html/blob/main/CODE_OF_CONDUCT.md) 56 | options: 57 | - label: I agree to follow figma2html's Code of Conduct 58 | required: true 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 Feature Request" 2 | description: Request a new figma2html feature 3 | labels: [enhancement] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to request this feature! A maintainer will review your request and respond as soon as possible. Feel free to open a PR if you'd like to implement this feature yourself. 9 | - type: textarea 10 | id: problem 11 | attributes: 12 | label: Describe the problem 13 | description: Please provide a clear and concise description the problem this feature would solve. The more information you can provide here, the better. 14 | placeholder: I'm always frustrated when... 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: solution 19 | attributes: 20 | label: Describe the proposed solution 21 | description: Please provide a clear and concise description of what you would like to happen. 22 | placeholder: I would like to see... 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: alternatives 27 | attributes: 28 | label: Alternatives considered 29 | description: "Please provide a clear and concise description of any alternative solutions or features you've considered." 30 | validations: 31 | required: true 32 | - type: dropdown 33 | id: importance 34 | attributes: 35 | label: Importance 36 | description: How important is this feature to you? 37 | options: 38 | - nice to have 39 | - would make my life easier 40 | - i cannot use figma2html without it 41 | validations: 42 | required: true 43 | - type: checkboxes 44 | id: terms 45 | attributes: 46 | label: Code of Conduct 47 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/the-dataface/figma2html/blob/main/CODE_OF_CONDUCT.md) 48 | options: 49 | - label: I agree to follow figma2html's Code of Conduct 50 | required: true 51 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Before submitting the PR, please make sure you do the following 2 | 3 | - [ ] It's really useful if your PR references an issue where it is discussed ahead of time. 4 | - [ ] Prefix your PR title with `[feat]`, `[fix]`, `[chore]`, `[docs]` or another appropriate prefix from the [Contributing guide](https://github.com/the-dataface/figma2html/blob/main/CONTRIBUTING.md). 5 | - [ ] This message body should clearly illustrate what problems it solves. 6 | - [ ] Ideally, link a repo or Figma file that fails export without this PR but passes with it. 7 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | if: github.repository == 'the-dataface/figma2html' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup Node.js 16 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 16.x 23 | cache: npm 24 | 25 | - name: Install Dependencies 26 | run: npm ci 27 | 28 | - name: Create Release Pull Request 29 | uses: changesets/action@v1 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | node_modules 4 | /build 5 | .env 6 | .env.* 7 | !.env.example 8 | package-lock.json -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/gallium -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.github 5 | /.githooks 6 | /.vscode 7 | .env 8 | .env.* 9 | !.env.example 10 | .d.ts 11 | .md 12 | lefthook.yml -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "pluginSearchDirs": false, 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # figma2html 2 | 3 | ## 1.1.0 4 | 5 | ### Minor Changes 6 | 7 | - Support multi-line variable text ([#100](https://github.com/the-dataface/figma2html/pull/100)) 8 | 9 | - - Alter `variables` workflows by adding UI panel and making entries async to config block ([#100](https://github.com/the-dataface/figma2html/pull/100)) 10 | - Edit figma2html settings block, grouping together (migration is built in) 11 | - Update reset/load/save functionality 12 | 13 | ### Patch Changes 14 | 15 | - Fix f2h-variables frame duplication ([#100](https://github.com/the-dataface/figma2html/pull/100)) 16 | 17 | - Fix Google Fonts family variant detection ([#111](https://github.com/the-dataface/figma2html/pull/111)) 18 | 19 | ## 1.0.3 20 | 21 | ### Patch Changes 22 | 23 | - Fix custom `` ending tag ([#106](https://github.com/the-dataface/figma2html/pull/106)) 24 | 25 | ## 1.0.3-beta 26 | 27 | ### Patch Changes 28 | 29 | - Notify users of malformatted YAML ([#101](https://github.com/the-dataface/figma2html/pull/101)) 30 | 31 | - Remove duplicate variables frame + fix placement ([#101](https://github.com/the-dataface/figma2html/pull/101)) 32 | 33 | ## 1.0.2-beta 34 | 35 | ### Patch Changes 36 | 37 | - Fix text node visibility issue by recursively checking parent visibility. See [Figma's visible docs](https://www.figma.com/plugin-docs/api/properties/nodes-visible/#remarks) for more info. Added a isNodeVisible utils to perform recursion and isFigma2htmlFrame to check if passed node is a valid figma2html frame. ([#98](https://github.com/the-dataface/figma2html/pull/98)) 38 | 39 | ## 1.0.1-beta 40 | 41 | ### Patch Changes 42 | 43 | - Fix text styling #92. Text styles were broken during a prior refactor, causing `font-family` and `font-size` and other critical styles to not be assigned in the resulting files. ([#94](https://github.com/the-dataface/figma2html/pull/94)) 44 | 45 | - - Frames that were acting as shapes (ie with fills and strokes) were being converted to groups and thus losing their styling. That’s addressed. ([#96](https://github.com/the-dataface/figma2html/pull/96)) 46 | - Missing font family names and missing segments were causing an error in a specific debugging file. I defaulted those cases to ‘Inter Normal’; should be fixed. 47 | - `Apply header tags` feature has been removed in favor of a more robust attribute application. Now you can set custom attributes via a typical `key="value"` syntax, with `tag="TAGNAME"` reserved for changing the tag from `p` to the desired entry. 48 | - Fluid width was stretching images (#86). Fixed in #87 and reflected here, by setting image height to `null` when `Fluid container width` is turned on. 49 | - Text in components and component instances was not being recognized as text and instead was exporting as part of the image. I’ve made it so the `tempFrame` converts frames (that have text children), auto layouts, and components to groups and detaches instances, which means text nodes are have correct positioning, relative to the base frame. 50 | - We were still seeing invisible text nodes in the final export. I’ve updated `withModificationsForExport` to just remove those nodes from the `tempFrame`. 51 | - Text positioning was way off. Converting frames with text children to groups helped with part of that and correcting the artboard height and width fixed the rest... I hope? 52 | 53 | ## 1.0.1-beta 54 | 55 | ### Patch Changes 56 | 57 | - - Ignore empty text nodes. Previously would error when trying to access textNode.styles, which is false. I could not find a way for the Figma plugin API to ignore empty nodes by default so included safeguards in convertTextFrames.ts. ([#88](https://github.com/the-dataface/figma2html/pull/88)) 58 | - Ensure textNode.textStyleId is not a symbol. Text nodes with mixed styles get a symbol as their style id. I added a conditional to ignore that ID and proceed as if none were found. 59 | 60 | ## 1.0.0-beta 61 | 62 | ### Major changes 63 | 64 | - Initial beta release ([#84](https://github.com/the-dataface/figma2html/pull/84)) 65 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Figma2html Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at [sam@thedataface.com](mailto:sam@thedataface.com?subject=Figma2html%20-%20Code%20of%20Conduct) and [sawyer@thedataface.com](mailto:sawyer@thedataface.com?subject=Figma2html%20-%20Code%20of%20Conduct). 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 125 | [https://www.contributor-covenant.org/translations][translations]. 126 | 127 | [homepage]: https://www.contributor-covenant.org 128 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 129 | [Mozilla CoC]: https://github.com/mozilla/diversity 130 | [FAQ]: https://www.contributor-covenant.org/faq 131 | [translations]: https://www.contributor-covenant.org/translations 132 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Table of Contents 4 | 5 | - [Contributing](#contributing) 6 | - [Table of Contents](#table-of-contents) 7 | - [Requirements](#requirements) 8 | - [Getting started](#getting-started) 9 | - [Clone this repo](#clone-this-repo) 10 | - [Install dependences](#install-dependences) 11 | - [Build plugin](#build-plugin) 12 | - [Import plugin](#import-plugin) 13 | - [Run plugin](#run-plugin) 14 | - [Making a Pull Request](#making-a-pull-request) 15 | 16 | ## Requirements 17 | 18 | - [git](https://www.github.com/git-guides/install-git) - A version manager. 19 | - [Node (Gallium)](nodejs.org/download/release/latest-gallium) - The JavaScript runtime, versioned at ^16. 20 | - [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - A package manager. 21 | - [Figma Desktop](https://www.figma.com/downloads/) - Required for running plug-ins in development. 22 | - Knowledge of the [Figma API](https://www.figma.com/plugin-docs) and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html). 23 | - (Optional) Knowledge of [Tailwind](https://tailwindcss.com) 24 | 25 | ## Getting started 26 | 27 | ### Clone this repo 28 | 29 | ```bash 30 | git clone https://github.com/the-dataface/figma2html.git 31 | ``` 32 | 33 | ### Install dependences 34 | 35 | The following command with move you to the repository's local directory, set up the correct Node version, and install the dependencies: 36 | 37 | ```bash 38 | cd figma2html 39 | nvm use 40 | npm install 41 | ``` 42 | 43 | ### Build plugin 44 | 45 | After making updates locally, run `npm run build` in a terminal to bundle the project. There should be a `build` folder with three files: `bundle.js`, `index.html`, `main.js`. There are required to run the plugin. 46 | 47 | To watch for updates, run `npm run dev` in a terminal. This will start a server that will livereload the aforementioned files and let you develop while testing your Figma file. 48 | 49 | ### Import plugin 50 | 51 | 1. From Figma's top menu bar: `Plugins` > `Development` > `Import plugin from manifest`. 52 | 2. Select the `manifest.json` file in the cloned `figma2html` directory. 53 | 54 | ### Run plugin 55 | 56 | Via shortcut: 57 | 58 | 1. `cmd + /` to open the quick actions bar 59 | 2. Type `figma2html` and hit enter on the plugin. You will see a _development_ tag next to the version in development. 60 | 61 | Via menu: 62 | 63 | 1. From Figma's top menu: `Plugins` > `Development` > `figma2html` 64 | 65 | Via resources: 66 | 67 | 1. `shift + I` top open the resources panel 68 | 2. Click `Plugins` 69 | 3. Search for `figma2html` and click run 70 | 71 | ## Making a Pull Request 72 | 73 | - All pull requests should be made against the `main` branch. 74 | - Code should be in working condition before a review is requested unless help is needed. Ensure that the code is formatted with `npm run format` and that there are no linting errors with `npm run lint`. 75 | - Commits should follow the [conventional commits style](https://www.conventionalcommits.org/), which helps generate changelogs and ensure proper versioning. 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 [these people](https://github.com/the-dataface/figma2html/graphs/contributors) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![figma2html in action](/src/img/coverArt.png) 2 | 3 | # [figma2html](https://www.figma.com/community/plugin/1109185297790825980/figma2html) 4 | 5 | Export Figma frames to responsive HTML and CSS. A Figma version of [ai2html](http://ai2html.org), with additional features. Created by [The DataFace](https://www.thedataface.com). 6 | 7 | [Install the plugin](https://www.figma.com/community/plugin/1109185297790825980/figma2html) on Figma. 8 | 9 | ## Table of Contents 10 | 11 | - [figma2html](#figma2html) 12 | - [Table of Contents](#table-of-contents) 13 | - [Instructions](#instructions) 14 | - [How it Works](#how-it-works) 15 | - [Options + Features](#options--features) 16 | - [File settings](#file-settings) 17 | - [Image settings](#image-settings) 18 | - [Page settings](#page-settings) 19 | - [Text settings](#text-settings) 20 | - [Variable Text](#variable-text) 21 | - [Contributing + Feedback](#contributing--feedback) 22 | - [Acknowledgements](#acknowledgements) 23 | - [License](#license) 24 | 25 | ## Instructions 26 | 27 | 1. Create your artwork 28 | - Size frames according to the desired breakpoints. (note: breakpoints are based on width and not height). 29 | - Name frames using the following format: `#[width]px` (e.g. `#640px`). 30 | - If you're using a Google Font that's built in to Figma, you'll have the option to automatically include the appropriate Google Fonts tag in your exported HTML. Otherwise, you can add your webfonts to your project yourself or include a custom script or link any custom CSS in the `Page settings` panel.' 31 | 2. Run the plugin and configure your settings 32 | - In the plugin dialog, you can you load your preconfigured settings from a YAML text node named `f2h-settings` on the current page in your Figma doc, or you can write your current settings to a YAML text node. 33 | 3. Click Export and voila, you can save your exported images and HTML file as a zipped folder. 34 | 35 | Check out [this demo Figma file](https://www.figma.com/file/THVkWmLhe7TJD16hj0IDIR/figma2html-Demo?node-id=0%3A1) if you need a reference to get started! 36 | 37 | ## How it Works 38 | 39 | - The plugin grabs all frames named `#[width]px` on your current page, unframes all children (turns frames into groups to get positions relative to the parent frame), hides all text layers, and exports as a `PNG`, `JPG`, or `SVG`, at a specified scale. 40 | - All text elements are added via absolutely positioned divs. 41 | - Text is rendered as `

` (or header) elements. A style is applied based on the most frequently used style within that frame, with inline styles applied to text that differs. This should match what you see in Figma. 42 | - Text frames with auto widths are rendered with `width=auto`, while text frames with set widths include that width as inline css. 43 | - Text is rendered with any visible drop shadows and layer blurs as inline css. 44 | - The plugin supports rotated text, as well as left/right/center-aligned text. 45 | - If `Include Resizer Script` is turned on, the outputted HTML will include JS to show and hide the correct image at the appropriate size, based on the named width of the frame. 46 | 47 | ## Options + Features 48 | 49 | ### File settings 50 | 51 | - The inputted Filename will be the name of the outputted zipped folder and HTML file. You can customize this, but will default to the current page name in Figma. This field is required to export. 52 | - Filetype option so far is just `html`. `svelte` is coming soon! 53 | - Turn on `Testing Mode` to keep text visible on exported images. Useful for comparing layouts in the exported HTML vs the designs in Figma. 54 | 55 | ### Image settings 56 | 57 | - Input an `Image scale` to export images at a specified scale. 58 | - `Image format` options include `PNG`, `JPG`, and `SVG`. 59 | - The `Path` field allows you to specify an image path, which will be referenced in the image paths in the outputted HTML. Useful if you will be dropping your outputted files into a larger codebase. 60 | - `Alt text` allows you to add an alt text tag to images in your outputted HTML. This field is required to export. 61 | 62 | ### Page settings 63 | 64 | - Turn on `Include resizer script` to include JS in your outputted file which will show and hide the appropriate image at the appropriate size. 65 | - Turn on `Center HTML output` to center your page content in the outputted HTML. 66 | - Turn on `Fluid` to have the f2h containers fill the width of the screen. If off, f2h containers will maintain the widths at which they were designed in Figma. 67 | - Input a `Max width` in pixels to apply a max width to your page content. 68 | - Include `Custom scripts`, like Typekit tags, to your outputted HTML. 69 | 70 | ### Text settings 71 | 72 | - Turn on `Style text elements` to include inline styles to match the text styles you see in Figma. 73 | - Turn on `Include figma styles` as Classes to include any named styles in Figma as classes on your text elements. Ex. `Desktop/Utility Sans` will be included as `class="utility-sans"`. 74 | - Turn on `Include Google Fonts` to include a Google Fonts tag for included Google Fonts in the outputted HTML. 75 | - **Including custom attributes:** you can include custom attributes on a text layer by naming the layer `[f2h]` followed by a list of attributes in HTMl attribute format. For example, a text layer named `[f2h]class="text-sm font-sans" id="my-text-layer"` will render as `

`. This can be useful for including Tailwind and other utility classes. You also can use a reserved `tag` attribute to change the default `

` tag to one of your choosing (ex: `[f2h] tag="h1" id="hed" class="text-xl font-bold"` will render `

`). Note that naming your layers like this will not turn off `Style text elements` for that node, and will still include inline styling for child spans. To rely purely on class names for styling, turn off the `Style text elements` option. 76 | 77 | ### Variable Text 78 | 79 | Variable text is supported for exportable frames (frames prefixed with `#`). Variables can be added in the UI under the `Variables` panel. All variables values are strings and can be used in any text node in your Figma file. 80 | 81 | To use a variable, edit any desired _visible_ text node's name to be the key (e.g. `hed`) in double curly brackets (e.g. `{{hed}}`) in a _visible_ text node. The exported files with replace the key with the appropriate value (e.g. `{{hed}}` -> `figma2html`). 82 | 83 | This replacement occurs on the exported files and on Figma itself everytime a key or value changes. 84 | 85 | ## Contributing + Feedback 86 | 87 | We welcome feedback, bug reports and feature requests in the form of [Issues](https://github.com/the-dataface/figma2html/issues) or [PRs](https://www.github.com/the-dataface/figma2html/pulls). Alternatively, comments can be left on the Figma plugin community page. 88 | 89 | Further information on contributing can be found in [CONTRIBUTING.md](https://www.github.com/the-dataface/figma2html/blob/main/CONTRIBUTING.md). 90 | 91 | ## Acknowledgements 92 | 93 | Figma2html is not possible without [ai2html](https://www.ai2html.org), created by [Archie Tse](https://twitter.com/archietse) at _The New York Times_. Figma2html sprung from the same codebase, modified in time to add additional features specific to the Figma API. The plugin was inspired by [Thomas Lowry](https://www.github.com/thomas-lowry)'s [Figma Plugin DS Svelte](https://www.github.com/thomas-lowry/figma-plugin-ds-svelte) library. 94 | 95 | The core team from The DataFace features [Sam Vickars](https://www.twitter.com/samvickars), [Sawyer Click](https://www.sawyer.codes), and [Michael Hester](https://www.twitter.com/immichaelhester). 96 | 97 | ## License 98 | 99 | [MIT](https://www.github.com/the-dataface/figma2html/blob/main/LICENSE) 100 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | # EXAMPLE USAGE: 2 | # 3 | # Refer for explanation to following link: 4 | # https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md 5 | 6 | pre-commit: 7 | commands: 8 | prettier: 9 | glob: '*' 10 | run: npx prettier --ignore-unknown --write {staged_files} && git add {staged_files} 11 | 12 | pre-push: 13 | commands: 14 | lint: 15 | glob: '*' 16 | run: npx eslint --fix {push_files} --ignore-pattern "**/*.md" --ignore-pattern "**/*.yml" && git add {push_files} 17 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma2html", 3 | "api": "1.0.0", 4 | "id": "1109185297790825980", 5 | "main": "build/main.js", 6 | "ui": "build/index.html", 7 | "editorType": ["figma"] 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma2html", 3 | "version": "1.1.0", 4 | "private": true, 5 | "description": "Export Figma frames as images with text elements rendered as HTML", 6 | "homepage": "https://www.github.com/the-dataface/figma2html", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://www.github.com/the-dataface/figma2html.git" 10 | }, 11 | "bugs": { 12 | "email": "sam@thedataface.com", 13 | "url": "https://www.github.com/the-dataface/figma2html/issues/new/choose" 14 | }, 15 | "license": "MIT", 16 | "keywords": [ 17 | "ai2html", 18 | "figma", 19 | "figma2html", 20 | "figma html" 21 | ], 22 | "scripts": { 23 | "dev": "rollup -c -w", 24 | "build": "rollup -c && rm ./build/bundle.js", 25 | "ci:version": "changeset version", 26 | "ci:publish": "changeset publish && git push --follow-tags" 27 | }, 28 | "author": { 29 | "name": "Sam Vickars", 30 | "email": "sam.vickars@gmail.com", 31 | "url": "https://www.samvickars.com" 32 | }, 33 | "contributors": [ 34 | { 35 | "name": "Sawyer Click", 36 | "email": "sawyerclick@gmail.com", 37 | "url": "https://www.sawyer.codes" 38 | }, 39 | { 40 | "name": "Michael Hester" 41 | } 42 | ], 43 | "devDependencies": { 44 | "@changesets/cli": "^2.26.0", 45 | "@figma/plugin-typings": "^1.77.0", 46 | "@rollup/plugin-alias": "^4.0.2", 47 | "@rollup/plugin-babel": "^6.0.3", 48 | "@rollup/plugin-commonjs": "24.0.0", 49 | "@rollup/plugin-dsv": "^3.0.1", 50 | "@rollup/plugin-html": "^1.0.1", 51 | "@rollup/plugin-image": "3.0.1", 52 | "@rollup/plugin-json": "^6.0.1", 53 | "@rollup/plugin-node-resolve": "^15.0.1", 54 | "@rollup/plugin-terser": "^0.2.1", 55 | "@rollup/plugin-typescript": "10.0.1", 56 | "@svitejs/changesets-changelog-github-compact": "^1.1.0", 57 | "@typescript-eslint/eslint-plugin": "^5.48.2", 58 | "@typescript-eslint/parser": "^5.48.2", 59 | "autoprefixer": "^10.4.13", 60 | "caniuse-lite": "1.0.30001441", 61 | "cssnano": "^5.1.14", 62 | "eslint": "^8.30.0", 63 | "eslint-config-prettier": "^8.5.0", 64 | "eslint-plugin-svelte3": "^4.0.0", 65 | "htmlparser2": "^9.0.0", 66 | "lefthook": "^1.2.7", 67 | "postcss": "^8.4.20", 68 | "postcss-load-config": "^4.0.1", 69 | "prettier": "^2.8.3", 70 | "prettier-plugin-svelte": "^2.9.0", 71 | "prettier-plugin-tailwindcss": "^0.2.1", 72 | "rollup": "3.8.1", 73 | "rollup-plugin-livereload": "2.0.5", 74 | "rollup-plugin-postcss": "^4.0.2", 75 | "rollup-plugin-svelte": "7.1.0", 76 | "rollup-plugin-svg": "2.0.0", 77 | "sirv-cli": "2.0.2", 78 | "slugify": "^1.6.5", 79 | "svelte": "3.55.0", 80 | "svelte-feather-icons": "^4.0.0", 81 | "svelte-preprocess": "5.0.0", 82 | "tailwindcss": "^3.2.4", 83 | "tslib": "2.4.1", 84 | "typescript": "4.9.4" 85 | }, 86 | "dependencies": { 87 | "js-convert-case": "^4.2.0", 88 | "jszip": "3.10.1" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import dsv from '@rollup/plugin-dsv'; 4 | import htmlBundle, { makeHtmlAttributes } from '@rollup/plugin-html'; 5 | import json from '@rollup/plugin-json'; 6 | import resolve from '@rollup/plugin-node-resolve'; 7 | import terser from '@rollup/plugin-terser'; 8 | import typescript from '@rollup/plugin-typescript'; 9 | import cssnano from 'cssnano'; 10 | import { readFile } from 'node:fs/promises'; 11 | import livereload from 'rollup-plugin-livereload'; 12 | import postcss from 'rollup-plugin-postcss'; 13 | import svelte from 'rollup-plugin-svelte'; 14 | import svg from 'rollup-plugin-svg'; 15 | import sveltePreprocess from 'svelte-preprocess'; 16 | 17 | const fileUrl = new URL('./package.json', import.meta.url); 18 | const pkg = JSON.parse(await readFile(fileUrl, 'utf8')); 19 | 20 | const production = !process.env.ROLLUP_WATCH; 21 | 22 | export default [ 23 | { 24 | input: 'src/app.js', 25 | output: { 26 | format: 'iife', 27 | name: 'ui', 28 | file: 'build/bundle.js' 29 | }, 30 | plugins: [ 31 | svelte({ 32 | preprocess: sveltePreprocess({ 33 | sourceMap: !production, 34 | postcss: true 35 | }), 36 | compilerOptions: { 37 | dev: !production 38 | } 39 | }), 40 | resolve({ 41 | browser: true, 42 | dedupe: (importee) => importee === 'svelte' || importee.startsWith('svelte/'), 43 | extensions: ['.svelte', '.mjs', '.js', '.json', '.node'] 44 | }), 45 | typescript(), 46 | commonjs(), 47 | babel({ babelHelpers: 'bundled' }), 48 | svg(), 49 | dsv(), 50 | json(), 51 | postcss({ 52 | extensions: ['.css'], 53 | plugins: [cssnano()] 54 | }), 55 | htmlBundle({ 56 | fileName: 'index.html', 57 | target: 'build/index.html', 58 | title: pkg.name, 59 | attributes: { 60 | html: { lang: 'en' }, 61 | link: null, 62 | script: null 63 | }, 64 | meta: [ 65 | { charset: 'utf-8' }, 66 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 67 | { name: 'description', content: `${pkg.description} - v${pkg.version}` } 68 | ], 69 | template: ({ attributes, files, publicPath, title, meta }) => { 70 | // inline all script to fit figma's one-file rule 71 | const scripts = (files.js || []).map(({ code }) => ``).join('\n'); 72 | 73 | const links = (files.css || []) 74 | .map( 75 | ({ fileName }) => 76 | `` 79 | ) 80 | .join('\n'); 81 | 82 | const metas = meta.map((input) => ``).join('\n'); 83 | 84 | const html = makeHtmlAttributes(attributes.html); 85 | 86 | return `${title}${metas}${links}
${scripts}`; 87 | } 88 | }), 89 | !production && livereload('build'), 90 | production && terser() 91 | ], 92 | watch: { 93 | clearScreen: false 94 | } 95 | }, 96 | { 97 | input: 'src/main.ts', 98 | output: { 99 | file: 'build/main.js', 100 | format: 'cjs', 101 | name: 'main', 102 | inlineDynamicImports: false 103 | }, 104 | plugins: [ 105 | typescript(), 106 | resolve(), 107 | commonjs(), 108 | json(), 109 | babel({ babelHelpers: 'bundled' }), 110 | production && terser() 111 | ] 112 | } 113 | ]; 114 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 261 | 262 |

{name}

263 | 264 |
265 |
266 |
269 | {#if $panels} 270 | 271 | setErrorMessage($error.message)} /> 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 288 | 289 | {/if} 290 |
291 | 292 |
293 | 294 |
295 |
296 | 297 |
303 |
304 | 305 | {#if $error.message} 306 | 307 | {/if} 308 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --footer: 3rem; 7 | } 8 | 9 | summary.no-marker { 10 | @apply list-none outline-none; 11 | } 12 | summary.no-marker::-webkit-details-marker { 13 | @apply hidden; 14 | } 15 | 16 | input[type='text'], 17 | select, 18 | textarea { 19 | @apply appearance-none rounded border border-solid border-figma-bg-secondary bg-figma-bg-secondary p-2 text-sm text-figma-text !outline-none selection:bg-figma-bg-selected selection:text-figma-text-selected placeholder:border-transparent placeholder:text-2xs placeholder:text-figma-text-tertiary placeholder-shown:border-figma-border placeholder-shown:text-figma-text invalid:border-figma-border-danger-strong invalid:required:border-figma-border-danger hover:text-figma-text-hover placeholder-shown:hover:text-figma-text-hover focus:border-figma-border-selected focus:text-figma-text focus:placeholder-shown:border-figma-border-selected active:border-figma-border-selected active:text-figma-text disabled:text-figma-text-disabled disabled:hover:border-transparent; 20 | } 21 | 22 | option { 23 | @apply w-full; 24 | } 25 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import './app.css'; 2 | import App from './App.svelte'; 3 | 4 | const app = new App({ 5 | target: document.getElementById('app') 6 | }); 7 | 8 | export default app; 9 | -------------------------------------------------------------------------------- /src/img/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-dataface/figma2html/2344e1b1069113e3a5358876e67aec9ef26d88bc/src/img/Icon.png -------------------------------------------------------------------------------- /src/img/coverArt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-dataface/figma2html/2344e1b1069113e3a5358876e67aec9ef26d88bc/src/img/coverArt.png -------------------------------------------------------------------------------- /src/img/jewel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/img/wordmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | figma2html 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/Controls/File.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 |
22 |
23 |

File name

24 | {#if !$filename || $filename === ''} 25 |
29 | 30 |

Required

31 |
32 | {/if} 33 |
34 |
35 |
36 | { 42 | if ($filename === '') { 43 | $error.message = 'File name cannot be empty'; 44 | dispatch('error'); 45 | return; 46 | } 47 | // if (fileName.includes("/")) { 48 | // errorMessage = "File name cannot contain '/'"; 49 | // dispatch("error"); 50 | // return; 51 | // } 52 | filename.set( 53 | slugify($filename, { lower: true, strict: true, remove: /[*+~.()'"!:@]/g }) 54 | ); 55 | dispatch('change'); 56 | return; 57 | }} 58 | /> 59 |
60 |
61 |
62 | 63 |
64 |
65 |

File type

66 |
67 |
68 |
69 | 25 |
26 |
27 |
28 |
29 |
30 |

Format

31 |
32 |
33 |
34 | 51 |
52 |
53 |
54 | 55 |
56 |
57 |

Alt text

58 | {#if !$alt || $alt === ''} 59 |
63 | 64 |

Required

65 |
66 | {/if} 67 |
68 |
69 |
70 |