@omnicajs/icons 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +113 -0
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/build.js +1 -13
- package/dist/extracted/bundler/component-loader.cjs +26 -0
- package/dist/extracted/bundler/component-loader.d.ts +3 -0
- package/dist/extracted/bundler/plugin.cjs +125 -0
- package/dist/extracted/bundler/plugin.d.ts +21 -0
- package/dist/extracted/bundler/template-assets-loader.cjs +8 -0
- package/dist/extracted/bundler/template-assets-loader.d.ts +3 -0
- package/dist/extracted/core.d.ts +14 -0
- package/dist/extracted/core.js +64 -0
- package/dist/extracted/manifest.cjs +23 -0
- package/dist/extracted/manifest.d.ts +11 -0
- package/dist/extracted/manifest.js +20 -0
- package/dist/extracted/vite.d.ts +8 -0
- package/dist/extracted/vite.js +174 -0
- package/dist/manifest.cjs +1 -1
- package/dist/manifest.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/rspack.cjs +14 -0
- package/dist/rspack.d.ts +16 -0
- package/dist/shared/sprite.d.ts +2 -0
- package/dist/shared/sprite.js +16 -0
- package/dist/{adapter-output.d.ts → subset/adapter-output.d.ts} +1 -1
- package/dist/{adapter-output.js → subset/adapter-output.js} +1 -1
- package/dist/subset/vite.d.ts +7 -0
- package/dist/subset/vite.js +61 -0
- package/dist/{webpack-loader.cjs → subset/webpack-loader.cjs} +5 -9
- package/dist/{webpack-loader.d.ts → subset/webpack-loader.d.ts} +1 -1
- package/dist/{webpack-runtime.cjs → subset/webpack-runtime.cjs} +1 -1
- package/dist/subset/webpack.cjs +27 -0
- package/dist/subset/webpack.d.ts +14 -0
- package/dist/vite.d.ts +4 -7
- package/dist/vite.js +3 -61
- package/dist/webpack.cjs +9 -21
- package/dist/webpack.d.ts +12 -7
- package/package.json +14 -2
- package/vue.d.ts +36 -0
- /package/dist/{webpack-runtime.d.ts → subset/webpack-runtime.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* Added import-driven Vue icon components ([5fed0d0](https://github.com/omnicajs/icons/commit/5fed0d0724bafc5d60ba43ab4510c3f1856e944c))
|
|
8
|
+
|
|
3
9
|
## 0.3.0
|
|
4
10
|
|
|
5
11
|
### ⚠ BREAKING CHANGE* **icons:** Added missing icons and standardized clear circle
|
package/README.md
CHANGED
|
@@ -119,6 +119,119 @@ const brainKeywords = manifest.variants.filled.groups.ai.icons['brain-circuit'].
|
|
|
119
119
|
// ['ai/brain-ai']
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
## Import-driven Vue icons
|
|
123
|
+
|
|
124
|
+
The opt-in Vue adapter complements the existing full, group, and configured subset APIs. It supports Vue 3 and transforms only unqualified `filled` and `outlined` package assets.
|
|
125
|
+
|
|
126
|
+
### Vite
|
|
127
|
+
|
|
128
|
+
Add the adapter before the regular Vue plugin:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import vue from '@vitejs/plugin-vue'
|
|
132
|
+
import { defineConfig } from 'vite'
|
|
133
|
+
import { omnicaIconComponents } from '@omnicajs/icons/vite'
|
|
134
|
+
|
|
135
|
+
export default defineConfig({
|
|
136
|
+
plugins: [
|
|
137
|
+
omnicaIconComponents(),
|
|
138
|
+
vue(),
|
|
139
|
+
],
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Webpack
|
|
144
|
+
|
|
145
|
+
Add the icon plugin before `VueLoaderPlugin`. The adapter injects its package-scoped SVG rule and reuses the application's `vue-loader`:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
const { VueLoaderPlugin } = require('vue-loader')
|
|
149
|
+
const { OmnicaIconComponentsPlugin } = require('@omnicajs/icons/webpack')
|
|
150
|
+
|
|
151
|
+
module.exports = {
|
|
152
|
+
output: {
|
|
153
|
+
publicPath: '/',
|
|
154
|
+
},
|
|
155
|
+
module: {
|
|
156
|
+
rules: [
|
|
157
|
+
{ test: /\.vue$/, loader: 'vue-loader' },
|
|
158
|
+
{ test: /\.svg$/, type: 'asset/resource' },
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
plugins: [
|
|
162
|
+
new OmnicaIconComponentsPlugin(),
|
|
163
|
+
new VueLoaderPlugin(),
|
|
164
|
+
],
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Rspack
|
|
169
|
+
|
|
170
|
+
Rspack uses the same contract and its own typed entrypoint:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const { VueLoaderPlugin } = require('vue-loader')
|
|
174
|
+
const { OmnicaIconComponentsPlugin } = require('@omnicajs/icons/rspack')
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
output: {
|
|
178
|
+
publicPath: '/',
|
|
179
|
+
},
|
|
180
|
+
module: {
|
|
181
|
+
rules: [
|
|
182
|
+
{ test: /\.vue$/, loader: 'vue-loader' },
|
|
183
|
+
{ test: /\.svg$/, type: 'asset/resource' },
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
plugins: [
|
|
187
|
+
new OmnicaIconComponentsPlugin(),
|
|
188
|
+
new VueLoaderPlugin(),
|
|
189
|
+
],
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Both configurations require the normal Vue 3 SFC toolchain (`vue`, `vue-loader`, and `@vue/compiler-sfc`) in the application. The package does not declare a root Vue peer because the adapter is opt-in and the sprite/URL APIs remain compatible with non-Vue and Vue 2 consumers. If an application already has another SVG loader, constrain that rule with a query or exclude `@omnicajs/icons/assets/icons` so one unqualified import is not processed by two component loaders.
|
|
194
|
+
|
|
195
|
+
An unqualified monochrome SVG import then resolves to a Vue component:
|
|
196
|
+
|
|
197
|
+
```vue
|
|
198
|
+
<script setup lang="ts">
|
|
199
|
+
import IconClearCircle from '@omnicajs/icons/assets/icons/filled/actions/clear-circle.svg'
|
|
200
|
+
</script>
|
|
201
|
+
|
|
202
|
+
<template>
|
|
203
|
+
<IconClearCircle width="24" height="24" aria-hidden="true" />
|
|
204
|
+
</template>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Load the opt-in declarations from a project declaration file included by the consumer `tsconfig`, for example `src/omnica-icons.d.ts`:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import '@omnicajs/icons/vue'
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The `/vue` entrypoint exports `OmnicaIconProps` as Vue's `SVGAttributes` plus typed `data-*` attributes, and `OmnicaIconComponent` as `DefineComponent<OmnicaIconProps>`. Every transformed asset uses that component type, so TypeScript checks standard SVG presentation, sizing, accessibility, data, and event attributes instead of exposing an unqualified generic Vue component.
|
|
214
|
+
|
|
215
|
+
Production builds emit one content-hashed sprite containing the imported `filled` and `outlined` symbols. Generated components preserve the source `viewBox`, forward attributes to their root `svg`, and inherit `currentColor`. The symbol fragment includes the variant, for example `#filled/actions/clear-circle`, so filled and outlined names cannot collide. Vite honours its configured root-relative `base`; Webpack and Rspack use their root-relative `output.publicPath`.
|
|
216
|
+
|
|
217
|
+
For SSR, run the client build first. It emits `.omnica/omnica-icons-imported.json`; the Vite, Webpack, and Rspack SSR adapters read that manifest and reference the exact client sprite instead of hashing the server graph independently. Server-rendered icons must be a subset of the client graph, otherwise the SSR build fails with the missing symbol names instead of producing a URL that would return 404.
|
|
218
|
+
|
|
219
|
+
The default client output is `<root>/dist` for Vite and `<context>/dist` for Webpack/Rspack. When the client uses another output directory, pass it to both configurations:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
omnicaIconComponents({ clientOutputDirectory: 'dist/client' })
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
new OmnicaIconComponentsPlugin({ clientOutputDirectory: 'dist/client' })
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
All production builds require a root-relative public path such as `/app/`: configure it through Vite `base` or Webpack/Rspack `output.publicPath`. Absolute HTTP(S), protocol-relative, automatic, empty, and relative paths are rejected. The generated components use external SVG `<use>` references, which browsers restrict to the page origin, so CDN sprite URLs are not supported. SSR builds reuse the public sprite URL recorded by the client manifest even when the server bundler has another root-relative public path.
|
|
230
|
+
|
|
231
|
+
Vite development serves content-addressed one-symbol sprites so HMR can add or remove imports without stale combined-sprite state. Vite watch builds and Webpack/Rspack watch compilations recalculate the production sprite from the current module graph.
|
|
232
|
+
|
|
233
|
+
Imports with `?url` keep the bundler's standard string URL behavior. Static asset URLs in Vue templates, CSS `url()` references, and JavaScript `new URL(..., import.meta.url)` dependencies also remain in the normal asset pipeline; only module imports become components. Flags and logos are not transformed by this adapter and remain available through their existing URL and sprite APIs.
|
|
234
|
+
|
|
122
235
|
## Custom subsets with the CLI
|
|
123
236
|
|
|
124
237
|
The package can generate one sprite per selected variant. Create `omnica-icons.config.mjs`:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
exports.__toESM = __toESM;
|
package/dist/build.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractSymbol, serializeSprite } from "./shared/sprite.js";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
//#region tooling/build.ts
|
|
@@ -10,13 +11,6 @@ var hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
|
|
10
11
|
var assertObject = (value, label) => {
|
|
11
12
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
|
|
12
13
|
};
|
|
13
|
-
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14
|
-
var serializeSprite = (symbols) => [
|
|
15
|
-
"<svg xmlns=\"http://www.w3.org/2000/svg\">",
|
|
16
|
-
symbols.join("\n"),
|
|
17
|
-
"</svg>",
|
|
18
|
-
""
|
|
19
|
-
].join("\n");
|
|
20
14
|
var selectionNames = (selection, group) => selection === "*" ? Object.keys(group.icons) : selection;
|
|
21
15
|
var runtimeModule = ({ variant, groups }, spriteImport) => {
|
|
22
16
|
return `const hasOwn = (object: object, key: PropertyKey): key is keyof typeof object => Object.prototype.hasOwnProperty.call(object, key)
|
|
@@ -92,12 +86,6 @@ var resolveSelection = (config, manifest) => {
|
|
|
92
86
|
})
|
|
93
87
|
}));
|
|
94
88
|
};
|
|
95
|
-
var extractSymbol = (sprite, symbolId, filename) => {
|
|
96
|
-
const expression = new RegExp(`<symbol\\b[^>]*\\bid=(["'])${escapeRegExp(symbolId)}\\1[\\s\\S]*?<\\/symbol>`);
|
|
97
|
-
const match = sprite.match(expression);
|
|
98
|
-
if (!match) throw new Error(`Unable to find symbol ${symbolId} in ${filename}`);
|
|
99
|
-
return match[0];
|
|
100
|
-
};
|
|
101
89
|
var loadIconSymbols = (resolvedVariants) => Promise.all(resolvedVariants.map(async (variant) => ({
|
|
102
90
|
...variant,
|
|
103
91
|
groups: await Promise.all(variant.groups.map(async (group) => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const require_extracted_bundler_plugin = require("./plugin.cjs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
let node_url = require("node:url");
|
|
4
|
+
//#region tooling/extracted/bundler/component-loader.ts
|
|
5
|
+
var omnicaIconComponentsLoader = function() {
|
|
6
|
+
const callback = this.async();
|
|
7
|
+
const { ssr = false } = this.getOptions();
|
|
8
|
+
const assetsPath = (0, node_path.resolve)(__dirname, "../../../assets/icons");
|
|
9
|
+
const assetPath = (0, node_path.relative)(assetsPath, this.resourcePath).split(node_path.sep).join("/");
|
|
10
|
+
this.cacheable(true);
|
|
11
|
+
if (assetPath.startsWith("../") || assetPath === "..") {
|
|
12
|
+
callback(/* @__PURE__ */ new Error(`Imported icon is outside ${assetsPath}: ${this.resourcePath}`));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const source = `@omnicajs/icons/assets/icons/${assetPath}`;
|
|
16
|
+
import((0, node_url.pathToFileURL)((0, node_path.join)(__dirname, "../core.js")).href).then(async ({ createVueIconSfc, loadImportedIcon, loadManifest }) => {
|
|
17
|
+
const icon = await loadImportedIcon(source, await loadManifest());
|
|
18
|
+
if (!icon) throw new Error(`Unable to resolve imported icon ${source}`);
|
|
19
|
+
this.emitFile(require_extracted_bundler_plugin.importedSymbolAssetFilename(icon), require_extracted_bundler_plugin.createImportedSymbolAsset(icon));
|
|
20
|
+
callback(null, createVueIconSfc(icon, ssr ? JSON.stringify(require_extracted_bundler_plugin.importedSpriteMarker) : `__webpack_public_path__ + ${JSON.stringify(require_extracted_bundler_plugin.importedSpriteMarker)}`));
|
|
21
|
+
}).catch((error) => {
|
|
22
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
module.exports = omnicaIconComponentsLoader;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_manifest = require("../manifest.cjs");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
let node_url = require("node:url");
|
|
6
|
+
let node_crypto = require("node:crypto");
|
|
7
|
+
let node_fs_promises = require("node:fs/promises");
|
|
8
|
+
node_fs_promises = require_runtime.__toESM(node_fs_promises, 1);
|
|
9
|
+
//#region tooling/extracted/bundler/plugin.ts
|
|
10
|
+
var pluginName = "OmnicaIconComponentsPlugin";
|
|
11
|
+
var temporaryAssetPrefix = "__omnica_icons_imported__/";
|
|
12
|
+
var importedSpriteMarker = "__OMNICA_IMPORTED_SPRITE_URL__";
|
|
13
|
+
var targetsNode = (compiler) => {
|
|
14
|
+
const target = compiler.options.target;
|
|
15
|
+
return (Array.isArray(target) ? target : [target]).some((candidate) => typeof candidate === "string" && candidate.startsWith("node")) || compiler.options.externalsPresets?.node === true;
|
|
16
|
+
};
|
|
17
|
+
var assertRootRelativePublicPath = (publicPath) => {
|
|
18
|
+
if (typeof publicPath !== "string" || !publicPath.startsWith("/") || publicPath.startsWith("//") || !publicPath.endsWith("/")) throw new Error(`Import-driven icon builds require output.publicPath to be root-relative and end in "/". Absolute, protocol-relative, automatic, and relative public paths are not supported. Received ${typeof publicPath === "string" ? JSON.stringify(publicPath) : typeof publicPath}.`);
|
|
19
|
+
return publicPath;
|
|
20
|
+
};
|
|
21
|
+
var renderContentHashFilename = (template, source) => {
|
|
22
|
+
const hash = (0, node_crypto.createHash)("sha256").update(source).digest("hex");
|
|
23
|
+
return template.replace(/\[contenthash(?::(\d+))?\]/g, (_match, length) => length ? hash.slice(0, Number(length)) : hash);
|
|
24
|
+
};
|
|
25
|
+
var createImportedSymbolAsset = (symbol) => JSON.stringify({
|
|
26
|
+
schemaVersion: 1,
|
|
27
|
+
importedSymbol: symbol.importedSymbol,
|
|
28
|
+
symbolSource: symbol.symbolSource
|
|
29
|
+
});
|
|
30
|
+
var importedSymbolAssetFilename = (symbol) => {
|
|
31
|
+
return `${temporaryAssetPrefix}${(0, node_crypto.createHash)("sha256").update(symbol.importedSymbol).digest("hex")}.json`;
|
|
32
|
+
};
|
|
33
|
+
var parseImportedSymbolAsset = (source, filename) => {
|
|
34
|
+
const parsed = JSON.parse(source);
|
|
35
|
+
if (parsed.schemaVersion !== 1 || typeof parsed.importedSymbol !== "string" || typeof parsed.symbolSource !== "string") throw new Error(`Invalid imported icon metadata in ${filename}`);
|
|
36
|
+
return parsed;
|
|
37
|
+
};
|
|
38
|
+
var replaceSpriteMarker = (compiler, compilation, filename) => {
|
|
39
|
+
const { ReplaceSource } = compiler.webpack.sources;
|
|
40
|
+
for (const asset of compilation.getAssets()) {
|
|
41
|
+
const source = asset.source.source().toString();
|
|
42
|
+
if (!source.includes("__OMNICA_IMPORTED_SPRITE_URL__")) continue;
|
|
43
|
+
const replaced = new ReplaceSource(asset.source, asset.name);
|
|
44
|
+
let position = source.indexOf(importedSpriteMarker);
|
|
45
|
+
while (position !== -1) {
|
|
46
|
+
replaced.replace(position, position + 30 - 1, filename);
|
|
47
|
+
position = source.indexOf(importedSpriteMarker, position + 30);
|
|
48
|
+
}
|
|
49
|
+
compilation.updateAsset(asset.name, replaced);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var BundlerIconComponentsPlugin = class {
|
|
53
|
+
#options;
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
this.#options = options;
|
|
56
|
+
}
|
|
57
|
+
apply(compiler) {
|
|
58
|
+
const loaderPath = (0, node_path.join)(__dirname, "component-loader.cjs");
|
|
59
|
+
const templateAssetsLoaderPath = (0, node_path.join)(__dirname, "template-assets-loader.cjs");
|
|
60
|
+
const assetsPath = (0, node_path.resolve)(__dirname, "../../../assets/icons");
|
|
61
|
+
const clientOutputDirectory = (0, node_path.resolve)(compiler.context, this.#options.clientOutputDirectory ?? "dist");
|
|
62
|
+
const ssr = this.#options.ssr ?? targetsNode(compiler);
|
|
63
|
+
const publicPath = assertRootRelativePublicPath(compiler.options.output.publicPath);
|
|
64
|
+
const vueLoader = {
|
|
65
|
+
loader: this.#options.vueLoader ?? "vue-loader",
|
|
66
|
+
options: { experimentalInlineMatchResource: true }
|
|
67
|
+
};
|
|
68
|
+
compiler.options.module.rules.push({
|
|
69
|
+
dependency: { not: ["url"] },
|
|
70
|
+
include: assetsPath,
|
|
71
|
+
issuer: /\.(?:[cm]?[jt]sx?|vue)$/,
|
|
72
|
+
resourceQuery: /^(?:$|\?vue&type=(?:script|template)\b)/,
|
|
73
|
+
test: /\.svg$/,
|
|
74
|
+
type: "javascript/auto",
|
|
75
|
+
use: [vueLoader, {
|
|
76
|
+
loader: loaderPath,
|
|
77
|
+
options: { ssr }
|
|
78
|
+
}]
|
|
79
|
+
});
|
|
80
|
+
compiler.options.module.rules.push({
|
|
81
|
+
enforce: "post",
|
|
82
|
+
resourceQuery: /\bvue&type=(?:script|template)\b/,
|
|
83
|
+
test: /\.vue$/,
|
|
84
|
+
use: [{ loader: templateAssetsLoaderPath }]
|
|
85
|
+
});
|
|
86
|
+
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
|
|
87
|
+
compilation.hooks.processAssets.tapPromise({
|
|
88
|
+
name: pluginName,
|
|
89
|
+
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE
|
|
90
|
+
}, async () => {
|
|
91
|
+
const temporaryAssets = compilation.getAssets().filter((asset) => asset.name.startsWith(temporaryAssetPrefix));
|
|
92
|
+
if (temporaryAssets.length === 0) return;
|
|
93
|
+
const symbols = temporaryAssets.map((asset) => parseImportedSymbolAsset(asset.source.source().toString(), asset.name));
|
|
94
|
+
for (const asset of temporaryAssets) compilation.deleteAsset(asset.name);
|
|
95
|
+
let filename;
|
|
96
|
+
if (ssr) {
|
|
97
|
+
const manifestPath = (0, node_path.join)(clientOutputDirectory, require_manifest.importedSpriteManifestFilename);
|
|
98
|
+
let clientManifest;
|
|
99
|
+
try {
|
|
100
|
+
clientManifest = require_manifest.parseImportedSpriteManifest(await node_fs_promises.default.readFile(manifestPath, "utf8"), manifestPath);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
103
|
+
throw new Error(`Unable to load the import-driven icon client manifest at ${manifestPath}. Build the client before SSR or configure clientOutputDirectory.${detail}`);
|
|
104
|
+
}
|
|
105
|
+
require_manifest.assertImportedSymbolsAvailable(clientManifest, symbols);
|
|
106
|
+
filename = clientManifest.spriteUrl;
|
|
107
|
+
} else {
|
|
108
|
+
const { createImportedSprite } = await import((0, node_url.pathToFileURL)((0, node_path.join)(__dirname, "../core.js")).href);
|
|
109
|
+
const spriteSource = createImportedSprite(symbols);
|
|
110
|
+
const filenameTemplate = this.#options.filename ?? "assets/omnica-icons-imported.[contenthash:8].svg";
|
|
111
|
+
filename = renderContentHashFilename(filenameTemplate, spriteSource);
|
|
112
|
+
compilation.emitAsset(filename, new compiler.webpack.sources.RawSource(spriteSource), { immutable: /\[contenthash(?::\d+)?\]/.test(filenameTemplate) });
|
|
113
|
+
compilation.emitAsset(require_manifest.importedSpriteManifestFilename, new compiler.webpack.sources.RawSource(require_manifest.createImportedSpriteManifest(filename, `${publicPath}${filename}`, symbols)));
|
|
114
|
+
}
|
|
115
|
+
replaceSpriteMarker(compiler, compilation, filename);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
//#endregion
|
|
121
|
+
exports.BundlerIconComponentsPlugin = BundlerIconComponentsPlugin;
|
|
122
|
+
exports.createImportedSymbolAsset = createImportedSymbolAsset;
|
|
123
|
+
exports.importedSpriteMarker = importedSpriteMarker;
|
|
124
|
+
exports.importedSymbolAssetFilename = importedSymbolAssetFilename;
|
|
125
|
+
exports.renderContentHashFilename = renderContentHashFilename;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Compiler } from 'webpack';
|
|
2
|
+
import type { ImportedSymbol } from '../core.js';
|
|
3
|
+
export interface BundlerIconComponentsOptions {
|
|
4
|
+
/** Supports `[contenthash]` and `[contenthash:N]`. */
|
|
5
|
+
readonly filename?: string;
|
|
6
|
+
/** Overrides the loader used to compile the generated Vue SFC. */
|
|
7
|
+
readonly vueLoader?: string;
|
|
8
|
+
/** Client build output read by a later SSR build. Defaults to `<context>/dist`. */
|
|
9
|
+
readonly clientOutputDirectory?: string;
|
|
10
|
+
/** Overrides automatic Node-target SSR detection. */
|
|
11
|
+
readonly ssr?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const importedSpriteMarker = "__OMNICA_IMPORTED_SPRITE_URL__";
|
|
14
|
+
export declare const renderContentHashFilename: (template: string, source: string) => string;
|
|
15
|
+
export declare const createImportedSymbolAsset: (symbol: ImportedSymbol) => string;
|
|
16
|
+
export declare const importedSymbolAssetFilename: (symbol: ImportedSymbol) => string;
|
|
17
|
+
export declare class BundlerIconComponentsPlugin {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(options?: BundlerIconComponentsOptions);
|
|
20
|
+
apply(compiler: Compiler): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region tooling/extracted/bundler/template-assets-loader.ts
|
|
2
|
+
var vueTemplateAssetImport = /(\bimport\s+_imports_\d+\s+from\s+)(["'])(@omnicajs\/icons\/assets\/icons\/(?:filled|outlined)\/[^"'?#]+\.svg)\2/g;
|
|
3
|
+
var bundlerTemplateAssetsLoader = function(source, sourceMap) {
|
|
4
|
+
const transformed = source.replace(vueTemplateAssetImport, (_match, prefix, quote, request) => `${prefix}${quote}${request}?url${quote}`);
|
|
5
|
+
this.callback(null, transformed, sourceMap);
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
module.exports = bundlerTemplateAssetsLoader;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IconManifest, ManifestIcon } from '../build.js';
|
|
2
|
+
export { loadManifest } from '../build.js';
|
|
3
|
+
export interface ImportedIcon extends ManifestIcon {
|
|
4
|
+
readonly variant: 'filled' | 'outlined';
|
|
5
|
+
readonly group: string;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly importedSymbol: string;
|
|
8
|
+
readonly symbolSource: string;
|
|
9
|
+
}
|
|
10
|
+
export type ImportedSymbol = Pick<ImportedIcon, 'importedSymbol' | 'symbolSource'>;
|
|
11
|
+
export declare const resolveImportedIcon: (source: string, manifest: IconManifest) => Omit<ImportedIcon, "symbolSource"> | null;
|
|
12
|
+
export declare const loadImportedIcon: (source: string, manifest: IconManifest) => Promise<ImportedIcon | null>;
|
|
13
|
+
export declare const createImportedSprite: (icons: readonly ImportedSymbol[]) => string;
|
|
14
|
+
export declare const createVueIconSfc: (icon: ImportedIcon, spriteExpression: string) => string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { serializeSprite } from "../shared/sprite.js";
|
|
2
|
+
import { loadIconSymbols, loadManifest } from "../build.js";
|
|
3
|
+
//#region tooling/extracted/core.ts
|
|
4
|
+
var importedAssetExpression = /^@omnicajs\/icons\/(assets\/icons\/(filled|outlined)\/([^/]+)\/([^/]+)\.svg)$/;
|
|
5
|
+
var importedSymbolId = (variant, group, name) => `${variant}/${group}/${name}`;
|
|
6
|
+
var resolveImportedIcon = (source, manifest) => {
|
|
7
|
+
if (source.includes("?") || source.includes("#")) return null;
|
|
8
|
+
const match = source.match(importedAssetExpression);
|
|
9
|
+
if (!match) return null;
|
|
10
|
+
const [, assetPath, variant, group, name] = match;
|
|
11
|
+
const icon = manifest.variants[variant]?.groups[group]?.icons[name];
|
|
12
|
+
if (!icon || icon.source !== assetPath) throw new Error(`Unknown imported icon ${variant}/${group}/${name}`);
|
|
13
|
+
return {
|
|
14
|
+
...icon,
|
|
15
|
+
variant,
|
|
16
|
+
group,
|
|
17
|
+
name,
|
|
18
|
+
importedSymbol: importedSymbolId(variant, group, name)
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
var loadImportedIcon = async (source, manifest) => {
|
|
22
|
+
const icon = resolveImportedIcon(source, manifest);
|
|
23
|
+
if (!icon) return null;
|
|
24
|
+
const symbolSource = (await loadIconSymbols([{
|
|
25
|
+
variant: icon.variant,
|
|
26
|
+
groups: [{
|
|
27
|
+
name: icon.group,
|
|
28
|
+
sprite: manifest.variants[icon.variant].groups[icon.group].sprite,
|
|
29
|
+
icons: [{
|
|
30
|
+
name: icon.name,
|
|
31
|
+
source: icon.source,
|
|
32
|
+
symbol: icon.symbol,
|
|
33
|
+
viewBox: icon.viewBox,
|
|
34
|
+
keywords: icon.keywords
|
|
35
|
+
}]
|
|
36
|
+
}]
|
|
37
|
+
}]))[0]?.groups[0]?.icons[0]?.symbolSource;
|
|
38
|
+
if (!symbolSource) throw new Error(`Missing symbol source for ${icon.importedSymbol}`);
|
|
39
|
+
return {
|
|
40
|
+
...icon,
|
|
41
|
+
symbolSource
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
var createImportedSprite = (icons) => {
|
|
45
|
+
return serializeSprite([...new Map(icons.map((icon) => [icon.importedSymbol, icon])).values()].sort((left, right) => left.importedSymbol.localeCompare(right.importedSymbol)).map((icon) => icon.symbolSource.replace(/(<symbol\b[^>]*\bid=)(["'])([^"']+)\2/, (_match, prefix, quote) => `${prefix}${quote}${icon.importedSymbol}${quote}`)));
|
|
46
|
+
};
|
|
47
|
+
var componentName = (icon) => `Omnica-${icon.variant}-${icon.group}-${icon.name}-icon`.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => `${part[0].toUpperCase()}${part.slice(1)}`).join("");
|
|
48
|
+
var createVueIconSfc = (icon, spriteExpression) => `<template>
|
|
49
|
+
<svg viewBox="${icon.viewBox}">
|
|
50
|
+
<use :href="href" />
|
|
51
|
+
</svg>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script>
|
|
55
|
+
const href = ${spriteExpression} + ${JSON.stringify(`#${icon.importedSymbol}`)}
|
|
56
|
+
|
|
57
|
+
export default {
|
|
58
|
+
name: ${JSON.stringify(componentName(icon))},
|
|
59
|
+
data: () => ({ href }),
|
|
60
|
+
}
|
|
61
|
+
<\/script>
|
|
62
|
+
`;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { createImportedSprite, createVueIconSfc, loadImportedIcon, loadManifest, resolveImportedIcon };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region tooling/extracted/manifest.ts
|
|
2
|
+
var importedSpriteManifestFilename = ".omnica/omnica-icons-imported.json";
|
|
3
|
+
var createImportedSpriteManifest = (spriteFilename, spriteUrl, icons) => `${JSON.stringify({
|
|
4
|
+
schemaVersion: 1,
|
|
5
|
+
spriteFilename,
|
|
6
|
+
spriteUrl,
|
|
7
|
+
symbols: [...new Set(icons.map((icon) => icon.importedSymbol))].sort()
|
|
8
|
+
}, null, 2)}\n`;
|
|
9
|
+
var parseImportedSpriteManifest = (source, filename) => {
|
|
10
|
+
const parsed = JSON.parse(source);
|
|
11
|
+
if (parsed.schemaVersion !== 1 || typeof parsed.spriteFilename !== "string" || parsed.spriteFilename.length === 0 || typeof parsed.spriteUrl !== "string" || !parsed.spriteUrl.startsWith("/") || parsed.spriteUrl.startsWith("//") || !Array.isArray(parsed.symbols) || parsed.symbols.some((symbol) => typeof symbol !== "string") || new Set(parsed.symbols).size !== parsed.symbols.length) throw new Error(`Invalid import-driven icon manifest in ${filename}`);
|
|
12
|
+
return parsed;
|
|
13
|
+
};
|
|
14
|
+
var assertImportedSymbolsAvailable = (manifest, icons) => {
|
|
15
|
+
const clientSymbols = new Set(manifest.symbols);
|
|
16
|
+
const missing = [...new Set(icons.map((icon) => icon.importedSymbol).filter((symbol) => !clientSymbols.has(symbol)))].sort();
|
|
17
|
+
if (missing.length > 0) throw new Error(`SSR imports icons missing from the client sprite: ${missing.join(", ")}. Build the client first and ensure its icon graph contains every server-rendered icon.`);
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
exports.assertImportedSymbolsAvailable = assertImportedSymbolsAvailable;
|
|
21
|
+
exports.createImportedSpriteManifest = createImportedSpriteManifest;
|
|
22
|
+
exports.importedSpriteManifestFilename = importedSpriteManifestFilename;
|
|
23
|
+
exports.parseImportedSpriteManifest = parseImportedSpriteManifest;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ImportedSymbol } from './core.js';
|
|
2
|
+
export interface ImportedSpriteManifest {
|
|
3
|
+
readonly schemaVersion: 1;
|
|
4
|
+
readonly spriteFilename: string;
|
|
5
|
+
readonly spriteUrl: string;
|
|
6
|
+
readonly symbols: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const importedSpriteManifestFilename = ".omnica/omnica-icons-imported.json";
|
|
9
|
+
export declare const createImportedSpriteManifest: (spriteFilename: string, spriteUrl: string, icons: readonly ImportedSymbol[]) => string;
|
|
10
|
+
export declare const parseImportedSpriteManifest: (source: string, filename: string) => ImportedSpriteManifest;
|
|
11
|
+
export declare const assertImportedSymbolsAvailable: (manifest: ImportedSpriteManifest, icons: readonly ImportedSymbol[]) => void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region tooling/extracted/manifest.ts
|
|
2
|
+
var importedSpriteManifestFilename = ".omnica/omnica-icons-imported.json";
|
|
3
|
+
var createImportedSpriteManifest = (spriteFilename, spriteUrl, icons) => `${JSON.stringify({
|
|
4
|
+
schemaVersion: 1,
|
|
5
|
+
spriteFilename,
|
|
6
|
+
spriteUrl,
|
|
7
|
+
symbols: [...new Set(icons.map((icon) => icon.importedSymbol))].sort()
|
|
8
|
+
}, null, 2)}\n`;
|
|
9
|
+
var parseImportedSpriteManifest = (source, filename) => {
|
|
10
|
+
const parsed = JSON.parse(source);
|
|
11
|
+
if (parsed.schemaVersion !== 1 || typeof parsed.spriteFilename !== "string" || parsed.spriteFilename.length === 0 || typeof parsed.spriteUrl !== "string" || !parsed.spriteUrl.startsWith("/") || parsed.spriteUrl.startsWith("//") || !Array.isArray(parsed.symbols) || parsed.symbols.some((symbol) => typeof symbol !== "string") || new Set(parsed.symbols).size !== parsed.symbols.length) throw new Error(`Invalid import-driven icon manifest in ${filename}`);
|
|
12
|
+
return parsed;
|
|
13
|
+
};
|
|
14
|
+
var assertImportedSymbolsAvailable = (manifest, icons) => {
|
|
15
|
+
const clientSymbols = new Set(manifest.symbols);
|
|
16
|
+
const missing = [...new Set(icons.map((icon) => icon.importedSymbol).filter((symbol) => !clientSymbols.has(symbol)))].sort();
|
|
17
|
+
if (missing.length > 0) throw new Error(`SSR imports icons missing from the client sprite: ${missing.join(", ")}. Build the client first and ensure its icon graph contains every server-rendered icon.`);
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { assertImportedSymbolsAvailable, createImportedSpriteManifest, importedSpriteManifestFilename, parseImportedSpriteManifest };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export interface OmnicaIconComponentsOptions {
|
|
3
|
+
/** Asset name before Vite adds its content hash. */
|
|
4
|
+
readonly spriteName?: string;
|
|
5
|
+
/** Client build output read by a later SSR build. Defaults to `<root>/dist`. */
|
|
6
|
+
readonly clientOutputDirectory?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const omnicaIconComponents: (options?: OmnicaIconComponentsOptions) => Plugin;
|