├── .changeset └── config.json ├── .editorconfig ├── .eslintrc.js ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── renovate.json5 └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .prettierrc.cjs ├── LICENSE ├── README.md ├── package.json ├── packages ├── playground │ ├── README.md │ ├── benchmark │ │ ├── .gitignore │ │ ├── README.md │ │ ├── index.html │ │ ├── jsconfig.json │ │ ├── package.json │ │ ├── src │ │ │ ├── App.svelte │ │ │ ├── main.js │ │ │ └── vite-env.d.ts │ │ └── vite.config.js │ └── inline-svg │ │ ├── README.md │ │ ├── index.html │ │ ├── jsconfig.json │ │ ├── package.json │ │ ├── public │ │ └── favicon.ico │ │ ├── src │ │ ├── App.svelte │ │ ├── main.js │ │ └── vite-env.d.ts │ │ ├── svelte.config.js │ │ └── vite.config.js └── svelte-preprocess-svg │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ ├── index.ts │ ├── interfaces.ts │ ├── parsers │ │ ├── string-parse.ts │ │ └── svelte-parse.ts │ └── transforms │ │ ├── at-html.ts │ │ └── svgo.ts │ ├── tests │ ├── fixtures │ │ ├── .editorconfig │ │ ├── custom.svelte │ │ ├── custom.svelte.expected │ │ ├── escape.svelte │ │ ├── escape.svelte.expected │ │ ├── multiple.svelte │ │ ├── multiple.svelte.expected │ │ ├── simple.svelte │ │ ├── simple.svelte.expected │ │ ├── strict.svelte │ │ ├── strict.svelte.expected │ │ ├── svgo.svelte │ │ └── svgo.svelte.expected │ ├── options │ │ ├── filter.ts │ │ └── skip-transform.ts │ ├── parse │ │ ├── string-parse.ts │ │ └── svelte-parse.ts │ └── transform │ │ ├── at-html.ts │ │ ├── custom.ts │ │ ├── multiple.ts │ │ └── svgo.ts │ ├── tsconfig.json │ └── tsup.config.js ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.5.0/schema.json", 3 | "changelog": ["@svitejs/changesets-changelog-github-compact", { "repo": "svitejs/svelte-preprocess-svg" }], 4 | "commit": false, 5 | "linked": [], 6 | "access": "public", 7 | "baseBranch": "main", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } 11 | 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 2 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | 11 | [package.json] 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:node/recommended', 6 | 'plugin:@typescript-eslint/eslint-recommended', 7 | 'prettier' 8 | ], 9 | globals: { 10 | Atomics: 'readonly', 11 | SharedArrayBuffer: 'readonly' 12 | }, 13 | plugins: ['@typescript-eslint', 'svelte3', 'html', 'markdown'], 14 | settings: { 15 | 'svelte3/typescript': require('typescript'), 16 | 'svelte3/ignore-styles': (attrs) => 17 | (attrs.type && attrs.type !== 'text/css') || (attrs.lang && attrs.lang !== 'css') 18 | }, 19 | parser: '@typescript-eslint/parser', 20 | parserOptions: { 21 | sourceType: 'module', 22 | ecmaVersion: 2020 23 | }, 24 | rules: { 25 | 'no-console': 'off', 26 | 'no-debugger': 'error', 27 | 'node/no-deprecated-api': 'off', 28 | 'node/no-unpublished-import': 'off', 29 | 'node/no-unpublished-require': 'off', 30 | 'node/no-unsupported-features/es-syntax': 'off', 31 | 'no-process-exit': 'off', 32 | 'node/no-missing-import': [ 33 | 'error', 34 | { 35 | tryExtensions: ['.js', '.ts', '.json', '.node'], 36 | allowModules: ['@sveltejs/vite-plugin-svelte'] 37 | } 38 | ] 39 | }, 40 | overrides: [ 41 | { 42 | files: ['**/*.svelte'], 43 | env: { 44 | es6: true, 45 | browser: true, 46 | node: false 47 | }, 48 | processor: 'svelte3/svelte3', 49 | rules: { 50 | 'import/first': 'off', 51 | 'import/no-duplicates': 'off', 52 | 'import/no-mutable-exports': 'off', 53 | 'import/no-unresolved': 'off' 54 | } 55 | }, 56 | { 57 | files: ['**/*.svx', '**/*.md'], 58 | processor: 'markdown/markdown', 59 | rules: { 60 | 'no-undef': 'off', 61 | 'no-unused-vars': 'off', 62 | 'no-unused-labels': 'off', 63 | 'no-console': 'off', 64 | 'padded-blocks': 'off', 65 | 'node/no-missing-import': 'off', 66 | 'node/no-extraneous-require': 'off', 67 | 'import/no-unresolved': 'off', 68 | 'node/no-missing-require': 'off' 69 | } 70 | }, 71 | { 72 | files: ['**/*.svx/*.**', '**/*.md/*.**'], 73 | rules: { 74 | 'no-undef': 'off', 75 | 'no-unused-vars': 'off', 76 | 'no-unused-labels': 'off', 77 | 'no-console': 'off', 78 | 'padded-blocks': 'off', 79 | 'node/no-missing-import': 'off', 80 | 'node/no-extraneous-require': 'off', 81 | 'import/no-unresolved': 'off', 82 | 'node/no-missing-require': 'off' 83 | } 84 | }, 85 | { 86 | files: ['scripts/**'], 87 | env: { 88 | node: true, 89 | browser: false 90 | } 91 | }, 92 | { 93 | files: ['packages/e2e-tests/**', 'packages/playground/**'], 94 | rules: { 95 | 'node/no-extraneous-import': 'off', 96 | 'node/no-extraneous-require': 'off', 97 | 'no-unused-vars': 'off' 98 | }, 99 | env: { 100 | browser: true 101 | } 102 | }, 103 | { 104 | files: ['packages/svelte-preprocess-svg/tests/**/*.ts'], 105 | rules: { 106 | 'node/no-missing-import': 'off' 107 | } 108 | } 109 | ], 110 | ignorePatterns: ['packages/svelte-preprocess-svg/tests/fixtures/**/*.svelte'] 111 | }; 112 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # open_collective: 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41E Bug report" 2 | description: Report an issue with svelte-preprocess-svg 3 | labels: ["bug", "triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for participating in svelte-preprocess-svg! Please check for existing reports before creating a new one. 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 and step by step instructions to reproduce the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it may be closed when no reproduction is provided within a reasonable time-frame. 22 | placeholder: Reproduction 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: logs 27 | attributes: 28 | label: Logs 29 | description: "Please include relevant logs for the issue, eg browser console or terminal output. No screenshots." 30 | render: shell 31 | - type: textarea 32 | id: system-info 33 | attributes: 34 | label: System Info 35 | description: Output of `npx envinfo --system --binaries --browsers --npmPackages "{@svitejs/svelte-preprocess-svg}"` 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 svelte-preprocess-svg 49 | validations: 50 | required: true 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: GitHub Community 4 | url: https://github.com/svitejs/svelte-preprocess-svg/discussions 5 | about: Please ask and answer questions here. 6 | # - name: Discord 7 | # url: https://discord.gg/nzgMZJD 8 | # about: If you want to chat, join our discord. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 Feature Request" 2 | description: Request a new svelte-preprocess-svg feature 3 | labels: [enhancement, triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to request this feature! Please check if a similar request already exists before opening a new one. 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 svelte-preprocess-svg without it 41 | validations: 42 | required: true 43 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base","group:allNonMajor",":semanticCommits"], 3 | "schedule": ["before 5am on the 3rd day of the month"], 4 | "labels": ["dependencies"], 5 | "pin": false, 6 | "rangeStrategy": "bump", 7 | "node": false, 8 | "packageRules": [ 9 | { 10 | "depTypeList": ["peerDependencies"], 11 | "enabled": false 12 | } 13 | ], 14 | "ignoreDeps": [ 15 | // add deps that need manual attention here 16 | "pnpm" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # build and test on linux, windows, mac with node 12, 14, 16, 17 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | # "checks" job runs on linux + node14 only and checks that install, build, lint and audit work 14 | # it also primes the pnpm store cache for linux, important for downstream tests 15 | checks: 16 | timeout-minutes: 5 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | # pseudo-matrix for convenience, NEVER use more than a single combination 21 | node: [16] 22 | os: [ubuntu-latest] 23 | outputs: 24 | build_successful: ${{ steps.build.outcome == 'success' }} 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node }} 30 | - name: install pnpm 31 | shell: bash 32 | run: | 33 | PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json) 34 | echo installing pnpm version $PNPM_VER 35 | npm i -g pnpm@$PNPM_VER 36 | - uses: actions/setup-node@v3 37 | with: 38 | node-version: ${{ matrix.node }} 39 | cache: 'pnpm' 40 | cache-dependency-path: '**/pnpm-lock.yaml' 41 | - name: install 42 | run: | 43 | pnpm install --frozen-lockfile --prefer-offline --ignore-scripts 44 | - name: build 45 | id: build 46 | run: pnpm run build 47 | - name: lint 48 | if: (${{ success() }} || ${{ failure() }}) 49 | run: pnpm run lint 50 | - name: audit 51 | if: (${{ success() }} || ${{ failure() }}) 52 | run: pnpm audit --prod 53 | 54 | # this is the test matrix, it runs with node14 on linux,windows,macos + node12,16,17 on linux 55 | # it is skipped if the build step of the checks job wasn't successful (still runs if lint or audit fail) 56 | test: 57 | needs: checks 58 | if: (${{ success() }} || ${{ failure() }}) && (${{ needs.checks.output.build_successful }}) 59 | timeout-minutes: 10 60 | runs-on: ${{ matrix.os }} 61 | strategy: 62 | fail-fast: false 63 | matrix: 64 | node: [16] 65 | os: [ubuntu-latest, macos-latest, windows-latest] 66 | include: 67 | - node: 18 68 | os: ubuntu-latest 69 | - node: 20 70 | os: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v3 73 | - uses: actions/setup-node@v3 74 | with: 75 | node-version: ${{ matrix.node }} 76 | - name: install pnpm 77 | shell: bash 78 | run: | 79 | PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json) 80 | echo installing pnpm version $PNPM_VER 81 | npm i -g pnpm@$PNPM_VER 82 | - uses: actions/setup-node@v3 83 | with: 84 | node-version: ${{ matrix.node }} 85 | cache: 'pnpm' 86 | cache-dependency-path: '**/pnpm-lock.yaml' 87 | - name: install 88 | run: | 89 | pnpm install --frozen-lockfile --prefer-offline --ignore-scripts 90 | - name: build 91 | run: pnpm run build 92 | - name: run tests 93 | run: pnpm test 94 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | # prevents this action from running on forks 11 | if: github.repository == 'svitejs/svelte-preprocess-svg' 12 | name: Release 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | # pseudo-matrix for convenience, NEVER use more than a single combination 17 | node: [16] 18 | os: [ubuntu-latest] 19 | steps: 20 | - name: checkout 21 | uses: actions/checkout@v3 22 | with: 23 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 24 | fetch-depth: 0 25 | - uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node }} 28 | - name: install pnpm 29 | shell: bash 30 | run: | 31 | PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json) 32 | echo installing pnpm version $PNPM_VER 33 | npm i -g pnpm@$PNPM_VER 34 | - uses: actions/setup-node@v3 35 | with: 36 | node-version: ${{ matrix.node }} 37 | cache: 'pnpm' 38 | cache-dependency-path: '**/pnpm-lock.yaml' 39 | - name: install 40 | run: | 41 | pnpm install --frozen-lockfile --prefer-offline --ignore-scripts 42 | 43 | - name: Creating .npmrc 44 | run: | 45 | cat << EOF > "$HOME/.npmrc" 46 | //registry.npmjs.org/:_authToken=$NPM_TOKEN 47 | EOF 48 | env: 49 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 50 | - name: Create Release Pull Request or Publish to npm 51 | id: changesets 52 | uses: changesets/action@v1 53 | with: 54 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 55 | publish: pnpm release 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 59 | 60 | # TODO alert discord 61 | # - name: Send a Slack notification if a publish happens 62 | # if: steps.changesets.outputs.published == 'true' 63 | # # You can do something when a publish happens. 64 | # run: my-slack-bot send-notification --message "A new version of ${GITHUB_REPOSITORY} was published!" 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # logs and temp 2 | *.log 3 | *.cpuprofile 4 | temp 5 | *.tmp 6 | 7 | # build and dist 8 | build 9 | dist 10 | .svelte 11 | .svelte-kit 12 | 13 | # env and local 14 | *.local 15 | .env 16 | 17 | # node_modules and pnpm 18 | node_modules 19 | pnpm-lock.yaml 20 | !/pnpm-lock.yaml 21 | .pnpm-store 22 | 23 | # ide 24 | .idea 25 | .vscode 26 | 27 | # macos 28 | .DS_Store 29 | ._.DS_Store 30 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm lint-staged --concurrent false 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | useTabs: true, 3 | singleQuote: true, 4 | trailingComma: 'none', 5 | printWidth: 100, 6 | plugins: [require('prettier-plugin-svelte')], 7 | overrides: [ 8 | { 9 | files: '**/*.svx', 10 | options: { parser: 'markdown' } 11 | }, 12 | { 13 | files: '**/*.ts', 14 | options: { parser: 'typescript' } 15 | }, 16 | { 17 | files: '**/CHANGELOG.md', 18 | options: { 19 | requirePragma: true 20 | } 21 | }, 22 | { 23 | files: '**/package.json', 24 | options: { 25 | useTabs: false, 26 | tabWidth: 2 27 | } 28 | } 29 | ] 30 | }; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 dominikg and [these people](https://github.com/svitejs/svelte-preprocess-svg/graphs/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @svitejs/svelte-preprocess-svg 2 | 3 | [![npm version](https://img.shields.io/npm/v/@svitejs/svelte-preprocess-svg)](https://www.npmjs.com/package/@svitejs/svelte-preprocess-svg) 4 | [![CI](https://github.com/svitejs/svelte-preprocess-svg/actions/workflows/ci.yml/badge.svg)](https://github.com/svitejs/svelte-preprocess-svg/actions/workflows/ci.yml) 5 | 6 | Optimize inline svg in svelte components 7 | 8 | ## Features 9 | 10 | - wrap svg content in `@html` to reduce component size 11 | - svgo transform 12 | - custom transforms 13 | - include, exclude and skipTransform options 14 | 15 | ## Why 16 | 17 | Inline svg - especially when they contain many nodes and attributes - can lead to pretty large compiler output with lots of function calls. 18 | Wrapping the content of the svg in an `{@html content}` directive results in a single string variable inserted with innerHTML. 19 | 20 | Check the compiler output in [this repl](https://svelte.dev/repl/b885420f332941f0b9cf47dc05f8ce88?version=3.44.1) for an example 21 | and also the [benchmark playground project](packages/playground/benchmark), which is bundling all svelte-feather-icons. 22 | 23 | | benchmark vendor.js bytes | uncompressed | gzip | brotli | 24 | | ------------------------------ | ------------ | ----- | ------ | 25 | | **regular** | 327975 | 27323 | 19420 | 26 | | **with svelte-preprocess-svg** | 296567 | 21575 | 15991 | 27 | | **delta** | -10% | -21% | -18% | 28 | 29 | ## Caveats 30 | 31 | The `@html` transform does not work with dynamic svg. It's useful for icons and other static svg that don't contain any svelte directives. 32 | Dynamic attributes on the `` itself are ok. 33 | 34 | ## Documentation 35 | 36 | [see here](packages/svelte-preprocess-svg/README.md) 37 | 38 | ## Packages 39 | 40 | | Package | Changelog | 41 | | ---------------------------------------------------------------- | -------------------------------------------------------- | 42 | | [@svitejs/svelte-preprocess-svg](packages/svelte-preprocess-svg) | [Changelog](packages/svelte-preprocess-svg/CHANGELOG.md) | 43 | 44 | ## Development 45 | 46 | - `pnpm i` to install dependencies 47 | - `pnpm dev` to run development build 48 | - `pnpm test` to run tests 49 | - `pnpm build` to run build 50 | 51 | ## License 52 | 53 | [MIT](./LICENSE) 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-preprocess-svg-monorepo", 3 | "private": true, 4 | "license": "MIT", 5 | "scripts": { 6 | "dev": "pnpm --dir packages/svelte-preprocess-svg dev", 7 | "build": "pnpm --dir packages/svelte-preprocess-svg build", 8 | "test": "pnpm --dir packages/svelte-preprocess-svg test", 9 | "lint": "eslint --ignore-path .gitignore '**/*.{js,ts,svelte,html,svx,md}'", 10 | "lint:fix": "pnpm run lint --fix", 11 | "format": "prettier --ignore-path .gitignore '**/*.{css,scss,svelte,html,js,ts,svx,md}' --check", 12 | "format:fix": "pnpm run format --write", 13 | "fixup": "run-s lint:fix format:fix", 14 | "release": "pnpm run build && pnpm changeset publish", 15 | "prepare": "husky install" 16 | }, 17 | "devDependencies": { 18 | "@changesets/cli": "^2.26.1", 19 | "@changesets/get-github-info": "^0.5.2", 20 | "@svitejs/changesets-changelog-github-compact": "^1.1.0", 21 | "@types/node": "^18.16.16", 22 | "@typescript-eslint/eslint-plugin": "^6.12.0", 23 | "@typescript-eslint/parser": "^6.12.0", 24 | "cross-env": "^7.0.3", 25 | "eslint": "^8.54.0", 26 | "eslint-config-prettier": "^8.8.0", 27 | "eslint-plugin-html": "^7.1.0", 28 | "eslint-plugin-markdown": "^3.0.0", 29 | "eslint-plugin-node": "^11.1.0", 30 | "eslint-plugin-prettier": "^4.2.1", 31 | "eslint-plugin-svelte3": "^4.0.0", 32 | "husky": "^8.0.3", 33 | "lint-staged": "^13.2.2", 34 | "npm-run-all": "^4.1.5", 35 | "prettier": "^2.8.8", 36 | "prettier-plugin-svelte": "^2.10.1", 37 | "rimraf": "^5.0.1", 38 | "svelte": "^3.59.1", 39 | "tsm": "^2.3.0", 40 | "tsup": "^6.7.0", 41 | "typescript": "^5.1.3" 42 | }, 43 | "lint-staged": { 44 | "*.{js,ts,svelte,html,md,svx}": "eslint --fix", 45 | "*.{css,scss,svelte,html,js,ts,svx,md}": [ 46 | "prettier --write" 47 | ] 48 | }, 49 | "packageManager": "pnpm@8.10.2", 50 | "engines": { 51 | "pnpm": "^8.0.0", 52 | "yarn": "forbidden, use pnpm", 53 | "npm": "forbidden, use pnpm", 54 | "node": "^12.20 || ^14.13.1 || >= 16" 55 | }, 56 | "pnpm": { 57 | "overrides": { 58 | "@svitejs/svelte-preprocess-svg": "workspace:*", 59 | "ansi-regex@>2.1.1 <5.0.1": "^5.0.1" 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/playground/README.md: -------------------------------------------------------------------------------- 1 | # playground 2 | 3 | create playground projects in this directory. 4 | 5 | Use `"svitejs/svelte-preprocess-svg":"workspace:*"` in package.json deps to reference a workspace package 6 | -------------------------------------------------------------------------------- /packages/playground/benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /.vscode/ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /packages/playground/benchmark/README.md: -------------------------------------------------------------------------------- 1 | # benchmark for svelte-preprocess-svg 2 | 3 | App.svelte uses all svelte-feather-icon svgs. 4 | 5 | run `pnpm run benchmark` to build it without and with svelte-preprocess-svg. 6 | 7 | output is in dist/regular and dist/svg 8 | 9 | example: 10 | 11 | ```shell 12 | # regular 13 | vite v2.6.13 building for production... 14 | ✓ 293 modules transformed. 15 | dist/regular/index.html 0.47 KiB 16 | dist/regular/assets/index.82118662.js 2.17 KiB / gzip: 1.13 KiB 17 | dist/regular/assets/index.cc22df50.css 0.09 KiB / gzip: 0.10 KiB 18 | dist/regular/assets/vendor.332709fc.js 320.29 KiB / gzip: 27.46 KiB 19 | 20 | # with svelte-preprocess-svg 21 | vite v2.6.13 building for production... 22 | ✓ 293 modules transformed. 23 | dist/svg/index.html 0.47 KiB 24 | dist/svg/assets/index.6868e2bd.js 2.17 KiB / gzip: 1.13 KiB 25 | dist/svg/assets/index.cc22df50.css 0.09 KiB / gzip: 0.10 KiB 26 | dist/svg/assets/vendor.2dddde27.js 289.62 KiB / gzip: 21.23 KiB 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/playground/benchmark/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte + Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/playground/benchmark/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "esnext", 5 | "module": "esnext", 6 | /** 7 | * svelte-preprocess cannot figure out whether you have 8 | * a value or a type, so tell TypeScript to enforce using 9 | * `import type` instead of `import` for Types. 10 | */ 11 | "importsNotUsedAsValues": "error", 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | * To have warnings / errors of the Svelte compiler at the 16 | * correct position, enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "baseUrl": ".", 23 | /** 24 | * Typecheck JS in `.svelte` and `.js` files by default. 25 | * Disable this if you'd like to use dynamic types. 26 | */ 27 | "checkJs": true 28 | }, 29 | /** 30 | * Use global.d.ts instead of compilerOptions.types 31 | * to avoid limiting type declarations. 32 | */ 33 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] 34 | } 35 | -------------------------------------------------------------------------------- /packages/playground/benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benchmark", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite", 8 | "dev:svg": "USE_SVG=1 vite", 9 | "benchmark": "run-s build:regular build:svg", 10 | "build:regular": "vite build", 11 | "build:svg": "USE_SVG=1 vite build" 12 | }, 13 | "devDependencies": { 14 | "@sveltejs/vite-plugin-svelte": "2.4.1", 15 | "@svitejs/svelte-preprocess-svg": "workspace:2.0.0", 16 | "npm-run-all": "^4.1.5", 17 | "svelte": "^3.59.1", 18 | "svelte-feather-icons": "^4.0.1", 19 | "vite": "^4.5.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/playground/benchmark/src/App.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |

{icons.length} feather icon svgs

7 |
8 | {#each icons as icon} 9 | 10 | {/each} 11 |
12 | 13 | 20 | -------------------------------------------------------------------------------- /packages/playground/benchmark/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.getElementById('app') 5 | }); 6 | 7 | export default app; 8 | -------------------------------------------------------------------------------- /packages/playground/benchmark/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/playground/benchmark/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | import { sveltePreprocessSvg } from '@svitejs/svelte-preprocess-svg'; 4 | 5 | const svg = !!process.env.USE_SVG; 6 | 7 | export default defineConfig({ 8 | plugins: [ 9 | svelte({ 10 | preprocess: svg ? [sveltePreprocessSvg({ svgo: true, useSimpleStringParser: true })] : [], 11 | experimental: { 12 | prebundleSvelteLibraries: true 13 | } 14 | }) 15 | ], 16 | build: { 17 | outDir: svg ? 'dist/svg' : 'dist/regular' 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/README.md: -------------------------------------------------------------------------------- 1 | # inline-svg 2 | 3 | just a simple playground with an inline svg for quick prototyping 4 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte + Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "esnext", 5 | "module": "esnext", 6 | /** 7 | * svelte-preprocess cannot figure out whether you have 8 | * a value or a type, so tell TypeScript to enforce using 9 | * `import type` instead of `import` for Types. 10 | */ 11 | "importsNotUsedAsValues": "error", 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | * To have warnings / errors of the Svelte compiler at the 16 | * correct position, enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "baseUrl": ".", 23 | /** 24 | * Typecheck JS in `.svelte` and `.js` files by default. 25 | * Disable this if you'd like to use dynamic types. 26 | */ 27 | "checkJs": true 28 | }, 29 | /** 30 | * Use global.d.ts instead of compilerOptions.types 31 | * to avoid limiting type declarations. 32 | */ 33 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] 34 | } 35 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inline-svg", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/vite-plugin-svelte": "^2.4.1", 13 | "@svitejs/svelte-preprocess-svg": "workspace:2.0.0", 14 | "svelte": "^3.59.1", 15 | "vite": "^4.5.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svitejs/svelte-preprocess-svg/499b8e252094fdb227a460c63423d0d58713ac19/packages/playground/inline-svg/public/favicon.ico -------------------------------------------------------------------------------- /packages/playground/inline-svg/src/App.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

Svelte inline svg

11 | 26 |
27 | 28 | 34 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.getElementById('app') 5 | }); 6 | 7 | export default app; 8 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { sveltePreprocessSvg } from '@svitejs/svelte-preprocess-svg'; 2 | export default { 3 | preprocess: [sveltePreprocessSvg()] 4 | }; 5 | -------------------------------------------------------------------------------- /packages/playground/inline-svg/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()] 7 | }); 8 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @svitejs/svelte-preprocess-svg 2 | 3 | ## 2.0.0 4 | 5 | ### Major Changes 6 | 7 | - update to svgo 3.0 ([#58](https://github.com/svitejs/svelte-preprocess-svg/pull/58)) 8 | 9 | ## 1.0.2 10 | 11 | ### Patch Changes 12 | 13 | - update PreprocessorInput interface to match new svelte version ([#13](https://github.com/svitejs/svelte-preprocess-svg/pull/13)) 14 | 15 | ## 1.0.1 16 | 17 | ### Patch Changes 18 | 19 | - fix useSimpleStringParser off-by-one error with closing svg tag ([#8](https://github.com/svitejs/svelte-preprocess-svg/pull/8)) 20 | 21 | ## 1.0.0 22 | 23 | ### Major Changes 24 | 25 | - drop support for node12 ([#4](https://github.com/svitejs/svelte-preprocess-svg/pull/4)) 26 | 27 | * Add options for svgo and custom transforms ([#4](https://github.com/svitejs/svelte-preprocess-svg/pull/4)) 28 | 29 | - Some signature changes in config options ([#4](https://github.com/svitejs/svelte-preprocess-svg/pull/4)) 30 | 31 | ## 0.1.0 32 | 33 | ### Minor Changes 34 | 35 | - Initial implementation ([#1](https://github.com/svitejs/svelte-preprocess-svg/pull/1)) 36 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 dominikg and [these people](https://github.com/svitejs/svelte-preprocess-svg/graphs/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/README.md: -------------------------------------------------------------------------------- 1 | # @svitejs/svelte-preprocess-svg 2 | 3 | Optimize inline svg in svelte components 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install --save-dev @svitejs/svelte-preprocess-svg 9 | ``` 10 | 11 | ## Usage 12 | 13 | svelte.config.js 14 | 15 | ```js 16 | import { sveltePreprocessSvg } from '@svitejs/svelte-preprocess-svg'; 17 | //... 18 | export default { 19 | //... 20 | preprocess: [ 21 | //... 22 | // sveltePreprocessSvg must be used AFTER other markup preprocessors like mdsvex 23 | sveltePreprocessSvg({ 24 | /* options */ 25 | }) 26 | ] 27 | //... 28 | }; 29 | ``` 30 | 31 | ## Documentation 32 | 33 | ### options 34 | 35 | ```ts 36 | export interface SveltePreprocessSvgOptions { 37 | /** 38 | * Filter function to only include some files 39 | * 40 | * @param {PreprocessorInput} input - filename and content of the file to process 41 | * @return boolean true to include the file 42 | */ 43 | include?: (input: PreprocessorInput) => boolean; 44 | 45 | /** 46 | * Filter function to exclude some files 47 | * 48 | * @param {PreprocessorInput} input - filename and content of the file to process 49 | * @return boolean true to exclude the file 50 | */ 51 | exclude?: (input: PreprocessorInput) => boolean; 52 | 53 | /** 54 | * Array of transformers to apply. 55 | * name: unique name of the transform - this is passed to skipTransform 56 | * transform: function to manipulate svg 57 | * 58 | */ 59 | transforms?: SvgTransform[]; 60 | 61 | /** 62 | * enable svgo transform, use object for custom svgo options. 63 | * Without custom options it tries to load svgo config or uses defaults 64 | * 65 | * requires svgo to be installed as devDependency 66 | */ 67 | svgo?: boolean | object; 68 | 69 | /** 70 | * do not wrap svg content in {@html ``} 71 | */ 72 | disableAtHtml?: boolean; 73 | 74 | /** 75 | * skip a transform by name 76 | * @param {PreprocessorInput} input - filename and content of the file to process 77 | * @param {string} transform - name of the transform 78 | * @param {string} svg - the svg to transform (previous transforms are already applied!) 79 | */ 80 | skipTransform?: (input: PreprocessorInput, transform: string, svg: string) => boolean; 81 | 82 | /** 83 | * Set to true to use a simple string search instead of svelte.parse. 84 | * This is faster but not fault tolerant. misaligned html comments or svg inside string literals will trip it up 85 | */ 86 | useSimpleStringParser?: boolean; 87 | } 88 | ``` 89 | 90 | ## License 91 | 92 | [MIT](./LICENSE) 93 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@svitejs/svelte-preprocess-svg", 3 | "version": "2.0.0", 4 | "license": "MIT", 5 | "author": "dominikg", 6 | "type": "module", 7 | "main": "dist/index.cjs", 8 | "module": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": "./dist/index.js", 13 | "require": "./dist/index.cjs" 14 | }, 15 | "./package.json": "./package.json" 16 | }, 17 | "scripts": { 18 | "dev": "tsup-node --watch src", 19 | "build": "tsup-node --dts", 20 | "test": "tsm node_modules/uvu/bin.js tests -i fixtures" 21 | }, 22 | "engines": { 23 | "node": "^14.13.1 || >= 16" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/svitejs/svelte-preprocess-svg.git", 28 | "directory": "packages/svelte-preprocess-svg" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/svitejs/svelte-preprocess-svg/issues" 32 | }, 33 | "homepage": "https://github.com/svitejs/svelte-preprocess-svg#readme", 34 | "keywords": [ 35 | "svelte", 36 | "svelte-preprocess", 37 | "svg" 38 | ], 39 | "peerDependencies": { 40 | "svelte": "^3.44.1", 41 | "svgo": "^3.0.0" 42 | }, 43 | "peerDependenciesMeta": { 44 | "svgo": { 45 | "optional": true 46 | } 47 | }, 48 | "devDependencies": { 49 | "@types/svgo": "^2.6.4", 50 | "magic-string": "^0.30.0", 51 | "svelte": "^3.59.1", 52 | "svgo": "^3.0.2", 53 | "uvu": "^0.5.6" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/src/index.ts: -------------------------------------------------------------------------------- 1 | import MagicString from 'magic-string'; 2 | 3 | // eslint-disable-next-line node/no-missing-import 4 | import { PreprocessorGroup } from 'svelte/types/compiler/preprocess'; 5 | import { PreprocessorInput, SveltePreprocessSvgOptions } from './interfaces'; 6 | import { createAtHtmlTransform } from './transforms/at-html'; 7 | import { createSvgoTransform } from './transforms/svgo'; 8 | import { stringParse } from './parsers/string-parse'; 9 | import { svelteParse } from './parsers/svelte-parse'; 10 | 11 | const RESERVED_TRANSFORM_NAMES = ['svgo', '@html']; 12 | 13 | function skip(input: PreprocessorInput, options?: SveltePreprocessSvgOptions) { 14 | if (options?.include && !options.include(input)) { 15 | return true; 16 | } 17 | if (options?.exclude && options.exclude(input)) { 18 | return true; 19 | } 20 | if (!input.content.includes(' x.name === reserved)) { 29 | throw new Error(`transform name "${reserved}" is reserved for internal use`); 30 | } 31 | } 32 | if (options?.svgo) { 33 | transforms.push(createSvgoTransform(options.svgo)); 34 | } 35 | if (!options?.disableAtHtml) { 36 | transforms.push(createAtHtmlTransform()); 37 | } 38 | 39 | return transforms; 40 | } 41 | 42 | export function sveltePreprocessSvg( 43 | options?: SveltePreprocessSvgOptions 44 | ): Pick { 45 | const transforms = createTransforms(options); 46 | return { 47 | // @ts-ignore 48 | markup: async (input) => { 49 | if (skip(input, options)) { 50 | return; 51 | } 52 | const parsedSvgs = options?.useSimpleStringParser ? stringParse(input) : svelteParse(input); 53 | if (!parsedSvgs?.length) { 54 | return; 55 | } 56 | const s = new MagicString(input.content); 57 | for (const parsedSvg of parsedSvgs) { 58 | let svg = parsedSvg.svg; 59 | for (const transform of transforms) { 60 | if (!options?.skipTransform?.(input, transform.name, svg)) { 61 | const result = await transform.transform(svg); 62 | if (result != null) { 63 | svg = result; 64 | } 65 | } 66 | } 67 | s.overwrite(parsedSvg.start, parsedSvg.end, svg); 68 | } 69 | return { 70 | code: s.toString(), 71 | map: s.generateDecodedMap({ source: input.filename, hires: true }) 72 | }; 73 | } 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface SvgTransform { 2 | name: string; 3 | // eslint-disable-next-line no-unused-vars 4 | transform: (svg: string) => string | Promise; 5 | } 6 | export interface ParsedSvg { 7 | svg: string; 8 | start: number; 9 | end: number; 10 | } 11 | 12 | export interface PreprocessorInput { 13 | content: string; 14 | filename?: string; 15 | } 16 | 17 | export interface SveltePreprocessSvgOptions { 18 | /** 19 | * Filter function to only include some files 20 | * 21 | * @param {PreprocessorInput} input - filename and content of the file to process 22 | * @return boolean true to include the file 23 | */ 24 | // eslint-disable-next-line no-unused-vars 25 | include?: (input: PreprocessorInput) => boolean; 26 | 27 | /** 28 | * Filter function to exclude some files 29 | * 30 | * @param {PreprocessorInput} input - filename and content of the file to process 31 | * @return boolean true to exclude the file 32 | */ 33 | // eslint-disable-next-line no-unused-vars 34 | exclude?: (input: PreprocessorInput) => boolean; 35 | 36 | /** 37 | * Array of transformers to apply. 38 | * name: unique name of the transform - this is passed to skipTransform 39 | * transform: function to manipulate svg 40 | * 41 | */ 42 | // eslint-disable-next-line no-unused-vars 43 | transforms?: SvgTransform[]; 44 | 45 | /** 46 | * enable svgo transform, use object for custom svgo options 47 | * 48 | * requires svgo to be installed as devDependency 49 | */ 50 | svgo?: boolean | object; 51 | 52 | /** 53 | * do not wrap svg content in {@html ``} 54 | */ 55 | disableAtHtml?: boolean; 56 | 57 | /** 58 | * skip a transform by name 59 | * @param {PreprocessorInput} input - filename and content of the file to process 60 | * @param {string} transform - name of the transform 61 | * @param {string} svg - the svg to transform (previous transforms are already applied!) 62 | */ 63 | // eslint-disable-next-line no-unused-vars 64 | skipTransform?: (input: PreprocessorInput, transform: string, svg: string) => boolean; 65 | 66 | /** 67 | * Set to true to use a simple string search instead of svelte.parse. 68 | * This is faster but not fault tolerant. misaligned html comments or svg inside string literals will trip it up 69 | */ 70 | useSimpleStringParser?: boolean; 71 | } 72 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/src/parsers/string-parse.ts: -------------------------------------------------------------------------------- 1 | import { ParsedSvg, PreprocessorInput } from '../interfaces'; 2 | 3 | export function stringParse(input: PreprocessorInput): ParsedSvg[] { 4 | const result: ParsedSvg[] = []; 5 | let pos = 0; 6 | let svg; 7 | while ((svg = nextSvg(input.content, pos)) != null) { 8 | result.push(svg); 9 | pos = svg.end; 10 | } 11 | return result; 12 | } 13 | 14 | function nextSvg(content: string, pos: number = 0) { 15 | const start = content.indexOf('', closeTagStart + 4) + 1; 25 | if (end < 1) { 26 | return; 27 | } 28 | return { 29 | start, 30 | end, 31 | svg: content.slice(start, end) 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/src/parsers/svelte-parse.ts: -------------------------------------------------------------------------------- 1 | import { parse, walk } from 'svelte/compiler'; 2 | import { ParsedSvg, PreprocessorInput } from '../interfaces'; 3 | 4 | export function svelteParse(input: PreprocessorInput): ParsedSvg[] { 5 | const parsed = parse(input.content, { filename: input.filename }); 6 | const result: ParsedSvg[] = []; 7 | walk(parsed.html, { 8 | enter: function (node: any) { 9 | if (node.type === 'Element' && node.name === 'svg') { 10 | const { start, end } = node; 11 | const svg = input.content.slice(start, end); 12 | result.push({ 13 | start, 14 | end, 15 | svg 16 | }); 17 | this.skip(); 18 | } 19 | } 20 | }); 21 | return result; 22 | } 23 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/src/transforms/at-html.ts: -------------------------------------------------------------------------------- 1 | import { SvgTransform } from '../interfaces'; 2 | 3 | function atHtml(svg: string): string { 4 | const contentStart = svg.indexOf('>') + 1; 5 | const contentEnd = svg.lastIndexOf('<'); 6 | const openTag = svg.slice(0, contentStart); 7 | const content = svg.slice(contentStart, contentEnd); 8 | const closeTag = svg.substring(contentEnd); 9 | return `${openTag}{@html \`${content.replace(/`/g, '`')}\`}${closeTag}`; 10 | } 11 | 12 | export function createAtHtmlTransform(): SvgTransform { 13 | return { 14 | name: '@html', 15 | transform: atHtml 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/src/transforms/svgo.ts: -------------------------------------------------------------------------------- 1 | import { SvgTransform } from '../interfaces'; 2 | export function createSvgoTransform(options: object | boolean): SvgTransform { 3 | let svgo: any = null; 4 | let config: any; 5 | return { 6 | name: 'svgo', 7 | transform: async (svg: string) => { 8 | // lazy init svgo 9 | if (svgo === null) { 10 | try { 11 | svgo = await import('svgo'); 12 | } catch (e) { 13 | console.error('failed to import "svgo". Please install it', e); 14 | throw e; 15 | } 16 | try { 17 | config = options === true ? await svgo.loadConfig() : options; 18 | } catch (e) { 19 | console.error('failed to load svgo config', e); 20 | throw e; 21 | } 22 | } 23 | 24 | try { 25 | // ensure attributes are quoted, svgo doesn't support unquoted, see https://github.com/svg/svgo/issues/678 26 | svg = svg.replace(/=(\{[^}]*})(\s)/g, '="$1"$2'); 27 | const result = await svgo.optimize(svg, config); 28 | if (result.error) { 29 | throw result.error; 30 | } 31 | return result.data || svg; 32 | } catch (e) { 33 | console.error('svgo failed', e); 34 | return svg; 35 | } 36 | } 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/.editorconfig: -------------------------------------------------------------------------------- 1 | # disable mangling of fixtures 2 | [*.expected] 3 | trim_trailing_whitespace = false 4 | indent_size = unset 5 | indent_style = unset 6 | insert_final_newline = unset 7 | 8 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/custom.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

logo {foo} {bar}

11 | 17 |
18 | 19 | 25 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/custom.svelte.expected: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

logo {foo} {bar}

11 | 17 |
18 | 19 | 25 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/escape.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | ` 8 | {foo} 9 | } 10 | 11 |
12 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/escape.svelte.expected: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | {@html ``{foo}}`} 7 |
8 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/multiple.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

logo {foo} {bar}

11 | 18 |
19 | 20 | 26 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/multiple.svelte.expected: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

logo {foo} {bar}

11 | 12 |
13 | 14 | 20 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/simple.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

logo {foo} {bar}

11 | 17 |
18 | 19 | 25 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/simple.svelte.expected: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

logo {foo} {bar}

11 | 12 |
13 | 14 | 20 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/strict.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 |
12 |

logo {foo} {bar}

13 | 19 | 22 |
23 | 24 | 30 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/strict.svelte.expected: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 |
12 |

logo {foo} {bar}

13 | 19 | 22 |
23 | 24 | 30 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/svgo.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

logo {foo} {bar}

11 | 18 |
19 | 20 | 26 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/fixtures/svgo.svelte.expected: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 |
10 |

logo {foo} {bar}

11 | 12 |
13 | 14 | 20 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/options/filter.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as path from 'path'; 3 | import { promises as fs } from 'fs'; 4 | import { sveltePreprocessSvg } from '../../src/index.js'; 5 | import * as assert from 'uvu/assert'; 6 | 7 | test('include', async () => { 8 | const filename = path.resolve('tests/fixtures/strict.svelte'); 9 | const content = await fs.readFile(filename, 'utf-8'); 10 | const preprocess = await sveltePreprocessSvg({ 11 | include: ({ filename }) => { 12 | return filename.includes('strict.svelte'); 13 | } 14 | }); 15 | const result = await preprocess.markup({ content, filename }); 16 | assert.type(result, 'object'); 17 | const result2 = await preprocess.markup({ content, filename: 'some/other/file.svelte' }); 18 | assert.type(result2, 'undefined'); 19 | }); 20 | 21 | test('exclude', async () => { 22 | const filename = path.resolve('tests/fixtures/strict.svelte'); 23 | const content = await fs.readFile(filename, 'utf-8'); 24 | const preprocess = await sveltePreprocessSvg({ 25 | exclude: ({ filename }) => { 26 | return filename.includes('strict.svelte'); 27 | } 28 | }); 29 | const result = await preprocess.markup({ content, filename }); 30 | assert.type(result, 'undefined'); 31 | const result2 = await preprocess.markup({ content, filename: 'some/other/file.svelte' }); 32 | assert.type(result2, 'object'); 33 | }); 34 | 35 | test.run(); 36 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/options/skip-transform.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as path from 'path'; 3 | import { promises as fs } from 'fs'; 4 | import { sveltePreprocessSvg } from '../../src/index.js'; 5 | import * as assert from 'uvu/assert'; 6 | 7 | test('skip-transform', async () => { 8 | const filename = path.resolve('tests/fixtures/multiple.svelte'); 9 | const content = await fs.readFile(filename, 'utf-8'); 10 | const preprocess = await sveltePreprocessSvg({ 11 | svgo: true, 12 | transforms: [ 13 | { 14 | name: 'custom', 15 | transform: (svg) => svg.replace('green', 'red') 16 | } 17 | ], 18 | skipTransform: () => true 19 | }); 20 | const result = await preprocess.markup({ content, filename }); 21 | 22 | assert.fixture(result.code, content); 23 | }); 24 | 25 | test.run(); 26 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/parse/string-parse.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as path from 'path'; 3 | import { promises as fs } from 'fs'; 4 | import { sveltePreprocessSvg } from '../../src/index.js'; 5 | import { svelteParse } from '../../src/parsers/svelte-parse.js'; 6 | import { stringParse } from '../../src/parsers/string-parse.js'; 7 | import * as assert from 'uvu/assert'; 8 | 9 | test('string-parse result', async () => { 10 | const filename = path.resolve('tests/fixtures/simple.svelte'); 11 | const content = await fs.readFile(filename, 'utf-8'); 12 | const preprocess = await sveltePreprocessSvg({ useSimpleStringParser: true, svgo: true }); 13 | const result = await preprocess.markup({ content, filename }); 14 | const expected = await fs.readFile(filename + '.expected', 'utf-8'); 15 | assert.fixture(result.code, expected); 16 | }); 17 | 18 | test('string-parse equal', async () => { 19 | const filename = path.resolve('tests/fixtures/simple.svelte'); 20 | const content = await fs.readFile(filename, 'utf-8'); 21 | 22 | const parsed = stringParse({ filename, content }); 23 | const parsedStrict = svelteParse({ filename, content }); 24 | assert.equal(parsed, parsedStrict); 25 | }); 26 | 27 | test.run(); 28 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/parse/svelte-parse.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as path from 'path'; 3 | import { promises as fs } from 'fs'; 4 | import { sveltePreprocessSvg } from '../../src/index.js'; 5 | import * as assert from 'uvu/assert'; 6 | 7 | test('svelte-parse', async () => { 8 | const filename = path.resolve('tests/fixtures/strict.svelte'); 9 | const content = await fs.readFile(filename, 'utf-8'); 10 | const preprocess = sveltePreprocessSvg(); 11 | const result = await preprocess.markup({ content, filename }); 12 | const expected = await fs.readFile(filename + '.expected', 'utf-8'); 13 | assert.fixture(result.code, expected); 14 | }); 15 | 16 | test.run(); 17 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/transform/at-html.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as path from 'path'; 3 | import { promises as fs } from 'fs'; 4 | import { sveltePreprocessSvg } from '../../src/index.js'; 5 | import * as assert from 'uvu/assert'; 6 | 7 | test('at-html-transform', async () => { 8 | const filename = path.resolve('tests/fixtures/escape.svelte'); 9 | const content = await fs.readFile(filename, 'utf-8'); 10 | const preprocess = await sveltePreprocessSvg({ svgo: true }); 11 | const result = await preprocess.markup({ content, filename }); 12 | const expected = await fs.readFile(filename + '.expected', 'utf-8'); 13 | assert.fixture(result.code, expected); 14 | }); 15 | 16 | test.run(); 17 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/transform/custom.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as path from 'path'; 3 | import { promises as fs } from 'fs'; 4 | import { sveltePreprocessSvg } from '../../src/index.js'; 5 | import * as assert from 'uvu/assert'; 6 | 7 | test('custom-transform', async () => { 8 | const filename = path.resolve('tests/fixtures/custom.svelte'); 9 | const content = await fs.readFile(filename, 'utf-8'); 10 | const preprocess = await sveltePreprocessSvg({ 11 | svgo: false, 12 | disableAtHtml: true, 13 | transforms: [ 14 | { 15 | name: 'custom', 16 | transform: (svg) => svg.replace('green', 'red') 17 | } 18 | ] 19 | }); 20 | const result = await preprocess.markup({ content, filename }); 21 | const expected = await fs.readFile(filename + '.expected', 'utf-8'); 22 | assert.fixture(result.code, expected); 23 | }); 24 | 25 | test.run(); 26 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/transform/multiple.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as path from 'path'; 3 | import { promises as fs } from 'fs'; 4 | import { sveltePreprocessSvg } from '../../src/index.js'; 5 | import * as assert from 'uvu/assert'; 6 | 7 | test('svgo-transform', async () => { 8 | const filename = path.resolve('tests/fixtures/multiple.svelte'); 9 | const content = await fs.readFile(filename, 'utf-8'); 10 | const preprocess = await sveltePreprocessSvg({ 11 | svgo: true, 12 | transforms: [ 13 | { 14 | name: 'custom', 15 | transform: (svg) => svg.replace('green', 'red') 16 | } 17 | ] 18 | }); 19 | const result = await preprocess.markup({ content, filename }); 20 | const expected = await fs.readFile(filename + '.expected', 'utf-8'); 21 | assert.fixture(result.code, expected); 22 | }); 23 | 24 | test.run(); 25 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tests/transform/svgo.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as path from 'path'; 3 | import { promises as fs } from 'fs'; 4 | import { sveltePreprocessSvg } from '../../src/index.js'; 5 | import * as assert from 'uvu/assert'; 6 | 7 | test('svgo-transform', async () => { 8 | const filename = path.resolve('tests/fixtures/svgo.svelte'); 9 | const content = await fs.readFile(filename, 'utf-8'); 10 | const preprocess = await sveltePreprocessSvg({ svgo: true, disableAtHtml: true }); 11 | const result = await preprocess.markup({ content, filename }); 12 | const expected = await fs.readFile(filename + '.expected', 'utf-8'); 13 | assert.fixture(result.code, expected); 14 | }); 15 | 16 | test.run(); 17 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "target": "ES2020", 6 | "module": "ES2020", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "noUnusedLocals": true, 12 | "baseUrl": ".", 13 | "resolveJsonModule": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/svelte-preprocess-svg/tsup.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("tsup").Options} */ 2 | export const tsup = { 3 | entryPoints: ['src/index.ts'], 4 | format: ['esm', 'cjs'], 5 | sourcemap: true, 6 | splitting: false, 7 | clean: true, 8 | target: 'node14' 9 | }; 10 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'packages/playground/*' 4 | --------------------------------------------------------------------------------