From e9e522f4f5398249fcf05c48f0bcd9ac7df6b369 Mon Sep 17 00:00:00 2001 From: mikha Date: Tue, 16 Apr 2024 22:11:27 +0200 Subject: [PATCH] added custom svelte preprocessor to provide dynamic source --- .gitignore | 1 + README.md | 13 + package-lock.json | 165 ++++----- package.json | 9 +- src/lib/preprocessors/dynamicSource.js | 331 +++++++++++++++++++ src/lib/preprocessors/types.d.ts | 25 ++ src/stories/SVG/Close.story.svelte | 7 +- src/stories/SVG/Command.story.svelte | 7 +- src/stories/SVG/Dilk.story.svelte | 7 +- src/stories/SVG/Download.story.svelte | 12 +- src/stories/SVG/HotAirBalloon.story.svelte | 190 +++++++++++ src/stories/SVG/Maximize.story.svelte | 19 +- src/stories/SVG/Peacediscipline.story.svelte | 39 +-- src/stories/SVG/Warning.story.svelte | 9 +- vite.config.js | 9 +- 15 files changed, 667 insertions(+), 176 deletions(-) create mode 100644 src/lib/preprocessors/dynamicSource.js create mode 100644 src/lib/preprocessors/types.d.ts create mode 100644 src/stories/SVG/HotAirBalloon.story.svelte diff --git a/.gitignore b/.gitignore index fd6fc11..ffe7409 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ dist dist-ssr *.local LoadersToMake.html +TestPreprocessor.story.svelte # Editor directories and files .vscode/* diff --git a/README.md b/README.md index 6a12259..d223720 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ # Svelte Component Library A growing collection of all my reusable Svelte components and SVGs. +# Preprocessor +This project uses a custom made svelte pre-processor located in src/lib/preprocessors. The pre-processor adds dynamic source code to all the Histoire components and variants, replacing variable names with reactive values to make them easier to use in other projects. + +Unfortunately there is currently a bug in Histoire that causes variants to all share the source of the last declared variant. + +To use the pre processor it needs to be placed in the `preprocess` array after `vitePreprocess` in the svelte config. The available options will be displayed when you add an empty object as a parameter to the preprocessor. + +## Shortcomings +- It does not replace variables with values in expressions that are not Identifiers +- It does not recognize variables declared with the @const directive +- It has not been tested with multiple stories in a file +- The only multiline thing that indents correctly is text. Everything else is missing indentation + ## Components to add - Tabs - Loading animations from LoadersToMake diff --git a/package-lock.json b/package-lock.json index b102bdd..14c9ecb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,10 @@ "@histoire/plugin-svelte": "^0.17.17", "@sveltejs/vite-plugin-svelte": "^3.1.0", "@tsconfig/svelte": "^5.0.4", + "defu": "^6.1.4", "histoire": "^0.17.17", - "sass": "^1.74.1", - "svelte": "^4.2.13", + "sass": "^1.75.0", + "svelte": "^4.2.14", "svelte-check": "^3.6.9", "tslib": "^2.6.2", "typescript": "^5.4.5", @@ -108,9 +109,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.2.tgz", - "integrity": "sha512-j6V48PlFC/O7ERAR5vRW5QKDdchzmyyfojDdt+zPsB0YXoWgcjlC1IWjmlYfx08aQZ3HN5BtALcgGgtSKGMe7A==", + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz", + "integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==", "dev": true, "dependencies": { "@codemirror/state": "^6.4.0", @@ -684,9 +685,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz", - "integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz", + "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==", "cpu": [ "arm" ], @@ -697,9 +698,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz", - "integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz", + "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==", "cpu": [ "arm64" ], @@ -710,9 +711,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz", - "integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz", + "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==", "cpu": [ "arm64" ], @@ -723,9 +724,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz", - "integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz", + "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==", "cpu": [ "x64" ], @@ -736,9 +737,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz", - "integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz", + "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz", + "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==", "cpu": [ "arm" ], @@ -749,9 +763,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz", - "integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz", + "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==", "cpu": [ "arm64" ], @@ -762,9 +776,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz", - "integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz", + "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==", "cpu": [ "arm64" ], @@ -775,11 +789,11 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz", - "integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz", + "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==", "cpu": [ - "ppc64le" + "ppc64" ], "dev": true, "optional": true, @@ -788,9 +802,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz", - "integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz", + "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==", "cpu": [ "riscv64" ], @@ -801,9 +815,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz", - "integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz", + "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==", "cpu": [ "s390x" ], @@ -814,9 +828,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz", - "integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz", + "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==", "cpu": [ "x64" ], @@ -827,9 +841,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz", - "integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz", + "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==", "cpu": [ "x64" ], @@ -840,9 +854,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz", - "integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz", + "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==", "cpu": [ "arm64" ], @@ -853,9 +867,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz", - "integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz", + "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==", "cpu": [ "ia32" ], @@ -866,9 +880,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz", - "integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz", + "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==", "cpu": [ "x64" ], @@ -2678,9 +2692,9 @@ } }, "node_modules/rollup": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz", - "integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz", + "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -2693,21 +2707,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.14.1", - "@rollup/rollup-android-arm64": "4.14.1", - "@rollup/rollup-darwin-arm64": "4.14.1", - "@rollup/rollup-darwin-x64": "4.14.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.14.1", - "@rollup/rollup-linux-arm64-gnu": "4.14.1", - "@rollup/rollup-linux-arm64-musl": "4.14.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.14.1", - "@rollup/rollup-linux-riscv64-gnu": "4.14.1", - "@rollup/rollup-linux-s390x-gnu": "4.14.1", - "@rollup/rollup-linux-x64-gnu": "4.14.1", - "@rollup/rollup-linux-x64-musl": "4.14.1", - "@rollup/rollup-win32-arm64-msvc": "4.14.1", - "@rollup/rollup-win32-ia32-msvc": "4.14.1", - "@rollup/rollup-win32-x64-msvc": "4.14.1", + "@rollup/rollup-android-arm-eabi": "4.14.3", + "@rollup/rollup-android-arm64": "4.14.3", + "@rollup/rollup-darwin-arm64": "4.14.3", + "@rollup/rollup-darwin-x64": "4.14.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.3", + "@rollup/rollup-linux-arm-musleabihf": "4.14.3", + "@rollup/rollup-linux-arm64-gnu": "4.14.3", + "@rollup/rollup-linux-arm64-musl": "4.14.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3", + "@rollup/rollup-linux-riscv64-gnu": "4.14.3", + "@rollup/rollup-linux-s390x-gnu": "4.14.3", + "@rollup/rollup-linux-x64-gnu": "4.14.3", + "@rollup/rollup-linux-x64-musl": "4.14.3", + "@rollup/rollup-win32-arm64-msvc": "4.14.3", + "@rollup/rollup-win32-ia32-msvc": "4.14.3", + "@rollup/rollup-win32-x64-msvc": "4.14.3", "fsevents": "~2.3.2" } }, @@ -2765,9 +2780,9 @@ } }, "node_modules/sass": { - "version": "1.74.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz", - "integrity": "sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==", + "version": "1.75.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", + "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -2955,9 +2970,9 @@ "dev": true }, "node_modules/svelte": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.13.tgz", - "integrity": "sha512-jtVt2KXLbQnsWN93Zd7EVboNh8Tqexes4rZfXNP7nYRjd9+JjubTD8BXloUmU1OUYpc6pdd1aKBhCV+b2ZKoMg==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.14.tgz", + "integrity": "sha512-ry3+YlWqZpHxLy45MW4MZIxNdvB+Wl7p2nnstWKbOAewaJyNJuOtivSbRChcfIej6wFBjWqyKmf/NgK1uW2JAA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", diff --git a/package.json b/package.json index ed30d88..a69f949 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,16 @@ "preview": "histoire preview" }, "devDependencies": { - "histoire": "^0.17.17", "@histoire/plugin-svelte": "^0.17.17", "@sveltejs/vite-plugin-svelte": "^3.1.0", "@tsconfig/svelte": "^5.0.4", - "sass": "^1.74.1", - "svelte": "^4.2.13", + "defu": "^6.1.4", + "histoire": "^0.17.17", + "sass": "^1.75.0", + "svelte": "^4.2.14", "svelte-check": "^3.6.9", "tslib": "^2.6.2", "typescript": "^5.4.5", "vite": "^5.2.8" } -} \ No newline at end of file +} diff --git a/src/lib/preprocessors/dynamicSource.js b/src/lib/preprocessors/dynamicSource.js new file mode 100644 index 0000000..cf1ad21 --- /dev/null +++ b/src/lib/preprocessors/dynamicSource.js @@ -0,0 +1,331 @@ +import { parse } from "svelte/compiler"; +import defu from "defu"; + +/** + * Find the start and end indices of an attribute in a node if it exists. If not, return a start and + * end a position where an attribute could be inserted. + * @param {TemplateNode} node - The node to find the position of the attribute in + * @param {string} attribute - The attribute to find the position of + */ +function findAttribute(node, attribute) { + const attribute_object = node.attributes.find((/** @type {Attribute} */ attr) => attr.name === attribute); + let start = node.start + node.name.length + 1; + let end = start; + if (attribute_object) { + start = attribute_object.start; + end = attribute_object.end; + } + return { start, end }; +} + +/** + * Return the position where the node's children start and end. + * @param {TemplateNode} node + */ +function findChildren(node) { + if (node.children && node.children.length > 0) { + const startChild = node.children[0]; + const endChild = node.children?.at(-1) || startChild; + return { start: startChild.start, end: endChild.end }; + } + return { start: 0, end: 0 }; +} + +/** + * Return true if the node matches the provided type and the expression is a variable. + * @param {TemplateNode} node - The node to check + * @param {string} type - The type of node to check for + * @param {Set} variables - A set of variable names + */ +function relevantIdentifier(node, type, variables) { + return (node.type === type && node.expression?.type === "Identifier" && variables.has(node.expression?.name)); +} + +/** + * Remove a section of a string and replace it with another string. + * @param {string} str - The string to splice + * @param {number} index - The index at which to start the splice + * @param {number} count - The number of characters to remove + * @param {string} add - The value to insert in the space + */ +function splice(str, index, count, add) { + return str.slice(0, index) + add + str.slice(index + count); +} + +/** + * Format the given svelte code by standardizing the indentation and removing unnecessary elements. + * @param {string} src + * @param {InternalOptions} options +*/ +function formatSrc(src, options) { + const /** @type {ReplaceEntry[]} */ changes = []; + + /** @param {TemplateNode} node */ + function processChildren(node, depth = 0) { + if (!node.children) return; + for (let i = 0; i < node.children.length; ++i) { + const child = node.children[i]; + if (!options.keepNode(child)) { + changes.unshift({ start: child.start, end: child.end, replace: "" }); + } + else if (child.type === "Text") { + if (i < node.children.length - 1 && !options.keepNode(node.children[i + 1])) { + changes.unshift({ start: child.start, end: child.end, replace: "" }); + continue; + } + if (child.data === "\n") { + let indent_str = "\n" + options.indent.repeat(i === node.children.length - 1 ? depth - 1 : depth); + changes.unshift({ start: child.start, end: child.end, replace: indent_str }); + } else { + const lines = child.data.split("\n"); + let stop = lines.length; + if (lines.length > 1 && lines.at(-1) === "") { + stop = lines.length - 1; + lines[lines.length - 1] = options.indent.repeat(depth - 1); + } + for (let l = 1; l < stop; ++l) { + lines[l] = options.indent.repeat(depth) + lines[l]; + } + changes.unshift({ start: child.start, end: child.end, replace: lines.join("\n") }); + } + } else { + processChildren(child, depth + 1); + } + } + } + + src = src.split("\n").map((line) => line.trim()).join("\n"); + const ast = parse(src); + + processChildren(ast.html); + for (const { start, end, replace } of changes) { + src = splice(src, start, end - start, replace); + } + + return src; +} + +/** + * Format a value into a string that can be interpolated into a Svelte component. If the value is a + * string, it will be wrapped in double quotes. Otherwise it will be wrapped in curly braces. + * @param {string} str + */ +function formatReactiveValue(str) { + return `\${typeof ${str} === "string" ? \`"\${${str}}"\` : \`{\${${str}}}\`}`; +} + +/** + * Substitute variables in the attributes of a node with reactive values. + * @param {TemplateNode} node + * @param {Set} variables + * @param {string} src + * @param {InternalOptions} options +*/ +function subInReactiveValues(node, variables, src, options) { + const /** @type {ReplaceEntry[]} */ changes = []; + + /** @param {TemplateNode} node */ + function sub(node) { + if (node.hasOwnProperty("attributes")) { + for (const attr of node.attributes) { + if (options.replaceWithReactiveValues.bind && relevantIdentifier(attr, "Binding", variables)) { + changes.unshift({ start: attr.start, end: attr.end, replace: `${attr.name}=${formatReactiveValue(attr.expression.name)}` }); + } + if (options.replaceWithReactiveValues.attribute && attr.type === "Attribute") { + for (const val of attr.value) { + if (relevantIdentifier(val, "MustacheTag", variables)) { + const name = val.expression.name; + const inString = ["\"", "'"].includes(src.charAt(val.start - 1)); + const insert = attr.value.length > 1 || inString ? `\${${name}}` : formatReactiveValue(name); + changes.unshift({ start: val.start, end: val.end, replace: insert }); + } + if (options.replaceWithReactiveValues.attributeShorthand && relevantIdentifier(val, "AttributeShorthand", variables)) { + const name = val.expression.name; + changes.unshift({ start: attr.start, end: attr.end, replace: `${name}=${formatReactiveValue(name)}` }); + } + } + } + } + } + if (node.hasOwnProperty("children")) { + // @ts-ignore + for (const child of node.children) { + sub(child); + } + } + } + sub(node); + for (const { start, end, replace } of changes) { + src = splice(src, start, end - start, replace); + } + return src; +} + +/** + * Return a set of the variable names declared in the script tag of a Svelte component, accounting + * for array and object destructuring & svelte reactive declarations (using $:). + * @param {{[key: string]: any}[] | undefined} nodes - The content body array of the script tag. + */ +function variableSet(nodes) { + const variables = new Set(); + if (!nodes) return variables; + /** + * Find all identifiers in a node and add them to the variables set. + * @param {{[key: string]: any}} obj + */ + function findIdentifiers(obj) { + if (obj.type === "Identifier") { + variables.add(obj.name); + } else if (obj.type === "ArrayPattern" || obj.type === "ObjectPattern") { + for (let element of obj.elements || obj.properties) { + if (element?.type === "Identifier") { + variables.add(element.name); + } + } + } + } + for (let node of nodes) { + if (node.type === "VariableDeclaration") { + for (let declarator of node.declarations) { + findIdentifiers(declarator.id); + } + // If it is a reactive declaration or array/object destructuring + } else if (node.type === "LabeledStatement" && node.label.name === "$") { + if (node.body.type === "ExpressionStatement" && node.body.expression.type === "AssignmentExpression") { + findIdentifiers(node.body.expression.left); + } + } + } + return variables; +} + +/** + * + * @param {TemplateNode} node + * @param {string} src + * @param {Script | undefined} script + * @param {InternalOptions} options + */ +function generateSourceChange(node, src, script, options) { + const loc = findChildren(node); + + let component = formatSrc(src.slice(loc.start, loc.end), options); + + //! not going to work with constants declared outside of script tag + if (Object.values(options.replaceWithReactiveValues).some(val => val)) { + const variables = variableSet(script?.content.body) + if (variables.size > 0) { + component = subInReactiveValues(parse(component).html, variables, component, options); + } + } + + return ` source={\`${component}\`}`; +} + +/** + * Take a Svelte Histoire story file and insert the contents of the each Story or Variant into the + * source attribute of the component, replacing variables with reactive values. + * @param {string} src + * @param {InternalOptions} options + */ +function preprocess(src, options) { + const ast = parse(src); + + const /** @type {ReplaceEntry[]} */ changes = []; + + for (const node of ast.html.children || []) { + if (node.name === "Hst.Story" || node.name === "Hst.Variant") { + let variants = false; + for (const child of node.children || []) { + if (child.name === "Hst.Variant") { + variants = true; + const { start, end } = findAttribute(child, "source"); + if (start === end && !options.override) continue; + + changes.unshift({ start, end, replace: generateSourceChange(child, src, ast.instance, options) }); + } + } + if (variants) continue; + const { start, end } = findAttribute(node, "source"); + if (start === end && !options.override) continue; + + changes.unshift({ start, end, replace: generateSourceChange(node, src, ast.instance, options) }); + } + } + for (const { start, end, replace } of changes) { + src = splice(src, start, end - start, replace); + } + return src; +} + +// Declared here so the types are available when using the preprocessor. All other types are non user facing. +/** + * @typedef {Object} replaceWithReactiveValues + * @property {boolean} bind + * @property {boolean} attribute + * @property {boolean} attributeShorthand + */ +/** + * @typedef {Object} UserOptions + * @property {boolean} override - Whether to override the source attribute if it already exists + * @property {string | string[]} extensions + * @property {replaceWithReactiveValues} replaceWithReactiveValues + * @property {string | number} indent + * @property {boolean} removeComments + * @property {boolean} removeControlsSlot + */ + +const /** @type {UserOptions} */ defaultOptions = { + override: true, + extensions: [".story.svelte"], + replaceWithReactiveValues: { + bind: true, + attribute: true, + attributeShorthand: true + }, + indent: 4, + removeComments: true, + removeControlsSlot: true, +}; + +/** + * Preprocessor for Svelte components that provides dynamic source code for Histoire stories & variants. + * @param {UserOptions} options + * @returns {PreprocessorGroup} + */ +export default function preprocessor(options) { + options = defu(options, defaultOptions); // merge options with default options + + const KeepComponents = new Set(["InlineComponent", "Element", "Text", "SlotTemplate"]); + if (!options.removeComments) KeepComponents.add("Comment"); + + /** @type {InternalOptions} */ + const final_options = { + override: options.override, + extensions: options.extensions instanceof Array ? options.extensions : [options.extensions], + replaceWithReactiveValues: options.replaceWithReactiveValues, + indent: typeof options.indent === "number" ? " ".repeat(options.indent) : options.indent, + keepNode: (node) => { + if (KeepComponents.has(node.type)) { + if (options.removeControlsSlot && node.attributes) { + const slot = node.attributes.find((/** @type {Attribute} */ attr) => attr.name === "slot"); + if (slot?.value.length === 1) { + return slot.value[0].data !== "controls"; + } + } + return true; + } + return false; + } + }; + + return { + name: "Histoire source provider", + markup: function({ content, filename }) { + if (final_options.extensions.some((ext) => filename?.endsWith(ext))) { + content = preprocess(content, final_options); + } + return { code: content }; + } + }; +} \ No newline at end of file diff --git a/src/lib/preprocessors/types.d.ts b/src/lib/preprocessors/types.d.ts new file mode 100644 index 0000000..9d39d48 --- /dev/null +++ b/src/lib/preprocessors/types.d.ts @@ -0,0 +1,25 @@ +interface InternalOptions { + override: boolean; + extensions: string[]; + replaceWithReactiveValues: replaceWithReactiveValues + indent: string; + keepNode: (n: TemplateNode) => boolean; +} + +interface replaceWithReactiveValues { + bind: boolean; + attribute: boolean; + attributeShorthand: boolean; +} + +interface ReplaceEntry { + start: number, + end: number, + replace: string, +} + +type PreprocessorGroup = import("svelte/types/compiler/preprocess").PreprocessorGroup; +type Script = import("svelte/types/compiler/interfaces").Script; +type TemplateNode = import("svelte/types/compiler/interfaces").TemplateNode; +type Element = import("svelte/types/compiler/interfaces").Element; +type Attribute = import("svelte/types/compiler/interfaces").Attribute; \ No newline at end of file diff --git a/src/stories/SVG/Close.story.svelte b/src/stories/SVG/Close.story.svelte index abb4637..8bccc92 100644 --- a/src/stories/SVG/Close.story.svelte +++ b/src/stories/SVG/Close.story.svelte @@ -5,14 +5,9 @@ export let Hst: Hst; let color = "#000"; - let source = ` - - - - `; - + diff --git a/src/stories/SVG/Command.story.svelte b/src/stories/SVG/Command.story.svelte index 0b3eed5..436c25f 100644 --- a/src/stories/SVG/Command.story.svelte +++ b/src/stories/SVG/Command.story.svelte @@ -5,14 +5,9 @@ export let Hst: Hst; let color = "#000"; - let source = ` - - - - `; - + - - - `; - + diff --git a/src/stories/SVG/Download.story.svelte b/src/stories/SVG/Download.story.svelte index 6d7664b..c6fb7d3 100644 --- a/src/stories/SVG/Download.story.svelte +++ b/src/stories/SVG/Download.story.svelte @@ -5,19 +5,9 @@ export let Hst: Hst; let color = "#000"; - let source = ` - - - - - `; - + + import type { Hst } from "@histoire/plugin-svelte"; + + export let Hst: Hst; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/stories/SVG/Maximize.story.svelte b/src/stories/SVG/Maximize.story.svelte index a0651ff..6757f59 100644 --- a/src/stories/SVG/Maximize.story.svelte +++ b/src/stories/SVG/Maximize.story.svelte @@ -5,26 +5,9 @@ export let Hst: Hst; let color = "#000"; - let source = ` - - - - - - - -`; - +