mirror of
https://github.com/gromlab-ru/svg-sprites.git
synced 2026-07-22 20:50:19 +03:00
sync
This commit is contained in:
@@ -5,8 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const nextAppTurbopackAdapter: ModeAdapter<'next@app/turbopack'> = {
|
||||
mode: 'next@app/turbopack',
|
||||
contractVersion: 5,
|
||||
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, {
|
||||
rootViewBox: true,
|
||||
|
||||
@@ -151,7 +151,37 @@ function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact
|
||||
}
|
||||
|
||||
function manifestTypes(config: ResolvedSpriteConfig): string {
|
||||
return [header(config.generatedNotice), "import type { SpriteManifest } from '@gromlab/svg-sprites/react'", '', 'export declare const spriteManifest: SpriteManifest', 'export default spriteManifest', ''].join('\n')
|
||||
return [
|
||||
header(config.generatedNotice),
|
||||
'',
|
||||
'export type SpriteManifestColor = {',
|
||||
' variable: `--icon-color-${number}`',
|
||||
' fallback: string',
|
||||
'}',
|
||||
'export type SpriteManifestIcon = {',
|
||||
' name: string',
|
||||
' id: string',
|
||||
' viewBox: string | null',
|
||||
' colors: readonly SpriteManifestColor[]',
|
||||
'}',
|
||||
'export type SpriteManifest = {',
|
||||
' schemaVersion: 1',
|
||||
" generator: '@gromlab/svg-sprites'",
|
||||
' name: string',
|
||||
' description?: string',
|
||||
' componentName: string',
|
||||
" mode: 'next@app/turbopack'",
|
||||
" target: 'next@app/turbopack'",
|
||||
" format: 'stack' | 'symbol'",
|
||||
' iconCount: number',
|
||||
' spriteUrl: string',
|
||||
' icons: readonly SpriteManifestIcon[]',
|
||||
'}',
|
||||
'',
|
||||
'export declare const spriteManifest: SpriteManifest',
|
||||
'export default spriteManifest',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
export function generateOutputFiles(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): GeneratedFile[] {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const nextAppWebpackAdapter: ModeAdapter<'next@app/webpack'> = {
|
||||
mode: 'next@app/webpack',
|
||||
contractVersion: 5,
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, { rootViewBox: true })
|
||||
const artifact = createCompiledSpriteArtifact(bytes, context.prepared.iconNames, 'stack')
|
||||
|
||||
@@ -70,7 +70,37 @@ function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact
|
||||
return [header(config.generatedNotice), "const spriteUrl = new URL('./sprite.svg', import.meta.url).href", '', `export const spriteManifest = ${source}`, '', 'export default spriteManifest', ''].join('\n')
|
||||
}
|
||||
|
||||
const manifestTypes = (config: ResolvedSpriteConfig) => [header(config.generatedNotice), "import type { SpriteManifest } from '@gromlab/svg-sprites/react'", '', 'export declare const spriteManifest: SpriteManifest', 'export default spriteManifest', ''].join('\n')
|
||||
const manifestTypes = (config: ResolvedSpriteConfig) => [
|
||||
header(config.generatedNotice),
|
||||
'',
|
||||
'export type SpriteManifestColor = {',
|
||||
' variable: `--icon-color-${number}`',
|
||||
' fallback: string',
|
||||
'}',
|
||||
'export type SpriteManifestIcon = {',
|
||||
' name: string',
|
||||
' id: string',
|
||||
' viewBox: string | null',
|
||||
' colors: readonly SpriteManifestColor[]',
|
||||
'}',
|
||||
'export type SpriteManifest = {',
|
||||
' schemaVersion: 1',
|
||||
" generator: '@gromlab/svg-sprites'",
|
||||
' name: string',
|
||||
' description?: string',
|
||||
' componentName: string',
|
||||
" mode: 'next@app/webpack'",
|
||||
" target: 'next@app/webpack'",
|
||||
" format: 'stack' | 'symbol'",
|
||||
' iconCount: number',
|
||||
' spriteUrl: string',
|
||||
' icons: readonly SpriteManifestIcon[]',
|
||||
'}',
|
||||
'',
|
||||
'export declare const spriteManifest: SpriteManifest',
|
||||
'export default spriteManifest',
|
||||
'',
|
||||
].join('\n')
|
||||
|
||||
export function generateOutputFiles(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): GeneratedFile[] {
|
||||
return [
|
||||
|
||||
@@ -5,7 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const nextPagesTurbopackAdapter: ModeAdapter<'next@pages/turbopack'> = {
|
||||
mode: 'next@pages/turbopack',
|
||||
contractVersion: 5,
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, { rootViewBox: true })
|
||||
const artifact = createCompiledSpriteArtifact(bytes, context.prepared.iconNames, 'stack')
|
||||
|
||||
@@ -70,7 +70,37 @@ function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact
|
||||
return [header(config.generatedNotice), "const spriteUrl = new URL('./sprite.svg', import.meta.url).href", '', `export const spriteManifest = ${source}`, '', 'export default spriteManifest', ''].join('\n')
|
||||
}
|
||||
|
||||
const manifestTypes = (config: ResolvedSpriteConfig) => [header(config.generatedNotice), "import type { SpriteManifest } from '@gromlab/svg-sprites/react'", '', 'export declare const spriteManifest: SpriteManifest', 'export default spriteManifest', ''].join('\n')
|
||||
const manifestTypes = (config: ResolvedSpriteConfig) => [
|
||||
header(config.generatedNotice),
|
||||
'',
|
||||
'export type SpriteManifestColor = {',
|
||||
' variable: `--icon-color-${number}`',
|
||||
' fallback: string',
|
||||
'}',
|
||||
'export type SpriteManifestIcon = {',
|
||||
' name: string',
|
||||
' id: string',
|
||||
' viewBox: string | null',
|
||||
' colors: readonly SpriteManifestColor[]',
|
||||
'}',
|
||||
'export type SpriteManifest = {',
|
||||
' schemaVersion: 1',
|
||||
" generator: '@gromlab/svg-sprites'",
|
||||
' name: string',
|
||||
' description?: string',
|
||||
' componentName: string',
|
||||
" mode: 'next@pages/turbopack'",
|
||||
" target: 'next@pages/turbopack'",
|
||||
" format: 'stack' | 'symbol'",
|
||||
' iconCount: number',
|
||||
' spriteUrl: string',
|
||||
' icons: readonly SpriteManifestIcon[]',
|
||||
'}',
|
||||
'',
|
||||
'export declare const spriteManifest: SpriteManifest',
|
||||
'export default spriteManifest',
|
||||
'',
|
||||
].join('\n')
|
||||
|
||||
export function generateOutputFiles(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): GeneratedFile[] {
|
||||
return [
|
||||
|
||||
@@ -5,7 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const nextPagesWebpackAdapter: ModeAdapter<'next@pages/webpack'> = {
|
||||
mode: 'next@pages/webpack',
|
||||
contractVersion: 5,
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, { rootViewBox: true })
|
||||
const artifact = createCompiledSpriteArtifact(bytes, context.prepared.iconNames, 'stack')
|
||||
|
||||
@@ -70,7 +70,37 @@ function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact
|
||||
return [header(config.generatedNotice), "const spriteUrl = new URL('./sprite.svg', import.meta.url).href", '', `export const spriteManifest = ${source}`, '', 'export default spriteManifest', ''].join('\n')
|
||||
}
|
||||
|
||||
const manifestTypes = (config: ResolvedSpriteConfig) => [header(config.generatedNotice), "import type { SpriteManifest } from '@gromlab/svg-sprites/react'", '', 'export declare const spriteManifest: SpriteManifest', 'export default spriteManifest', ''].join('\n')
|
||||
const manifestTypes = (config: ResolvedSpriteConfig) => [
|
||||
header(config.generatedNotice),
|
||||
'',
|
||||
'export type SpriteManifestColor = {',
|
||||
' variable: `--icon-color-${number}`',
|
||||
' fallback: string',
|
||||
'}',
|
||||
'export type SpriteManifestIcon = {',
|
||||
' name: string',
|
||||
' id: string',
|
||||
' viewBox: string | null',
|
||||
' colors: readonly SpriteManifestColor[]',
|
||||
'}',
|
||||
'export type SpriteManifest = {',
|
||||
' schemaVersion: 1',
|
||||
" generator: '@gromlab/svg-sprites'",
|
||||
' name: string',
|
||||
' description?: string',
|
||||
' componentName: string',
|
||||
" mode: 'next@pages/webpack'",
|
||||
" target: 'next@pages/webpack'",
|
||||
" format: 'stack' | 'symbol'",
|
||||
' iconCount: number',
|
||||
' spriteUrl: string',
|
||||
' icons: readonly SpriteManifestIcon[]',
|
||||
'}',
|
||||
'',
|
||||
'export declare const spriteManifest: SpriteManifest',
|
||||
'export default spriteManifest',
|
||||
'',
|
||||
].join('\n')
|
||||
|
||||
export function generateOutputFiles(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): GeneratedFile[] {
|
||||
return [
|
||||
|
||||
@@ -5,8 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const reactViteAdapter: ModeAdapter<'react@vite'> = {
|
||||
mode: 'react@vite',
|
||||
contractVersion: 5,
|
||||
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(
|
||||
context.prepared.folder,
|
||||
|
||||
@@ -241,7 +241,30 @@ function generateManifest(config: ResolvedSpriteConfig, artifact: CompiledSprite
|
||||
function generateManifestDeclarations(config: ResolvedSpriteConfig): string {
|
||||
return [
|
||||
blockHeader(config.generatedNotice),
|
||||
"import type { SpriteManifest } from '@gromlab/svg-sprites/react'",
|
||||
'',
|
||||
'export type SpriteManifestColor = {',
|
||||
' variable: `--icon-color-${number}`',
|
||||
' fallback: string',
|
||||
'}',
|
||||
'export type SpriteManifestIcon = {',
|
||||
' name: string',
|
||||
' id: string',
|
||||
' viewBox: string | null',
|
||||
' colors: readonly SpriteManifestColor[]',
|
||||
'}',
|
||||
'export type SpriteManifest = {',
|
||||
' schemaVersion: 1',
|
||||
" generator: '@gromlab/svg-sprites'",
|
||||
' name: string',
|
||||
' description?: string',
|
||||
' componentName: string',
|
||||
" mode: 'react@vite'",
|
||||
" target: 'vite'",
|
||||
" format: 'stack' | 'symbol'",
|
||||
' iconCount: number',
|
||||
' spriteUrl: string',
|
||||
' icons: readonly SpriteManifestIcon[]',
|
||||
'}',
|
||||
'',
|
||||
'export declare const spriteManifest: SpriteManifest',
|
||||
'export default spriteManifest',
|
||||
|
||||
@@ -5,8 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const reactWebpackAdapter: ModeAdapter<'react@webpack'> = {
|
||||
mode: 'react@webpack',
|
||||
contractVersion: 5,
|
||||
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, {
|
||||
rootViewBox: false,
|
||||
|
||||
@@ -168,7 +168,37 @@ function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact
|
||||
}
|
||||
|
||||
function manifestTypes(config: ResolvedSpriteConfig): string {
|
||||
return [header(config.generatedNotice), "import type { SpriteManifest } from '@gromlab/svg-sprites/react'", '', 'export declare const spriteManifest: SpriteManifest', 'export default spriteManifest', ''].join('\n')
|
||||
return [
|
||||
header(config.generatedNotice),
|
||||
'',
|
||||
'export type SpriteManifestColor = {',
|
||||
' variable: `--icon-color-${number}`',
|
||||
' fallback: string',
|
||||
'}',
|
||||
'export type SpriteManifestIcon = {',
|
||||
' name: string',
|
||||
' id: string',
|
||||
' viewBox: string | null',
|
||||
' colors: readonly SpriteManifestColor[]',
|
||||
'}',
|
||||
'export type SpriteManifest = {',
|
||||
' schemaVersion: 1',
|
||||
" generator: '@gromlab/svg-sprites'",
|
||||
' name: string',
|
||||
' description?: string',
|
||||
' componentName: string',
|
||||
" mode: 'react@webpack'",
|
||||
" target: 'webpack'",
|
||||
" format: 'stack' | 'symbol'",
|
||||
' iconCount: number',
|
||||
' spriteUrl: string',
|
||||
' icons: readonly SpriteManifestIcon[]',
|
||||
'}',
|
||||
'',
|
||||
'export declare const spriteManifest: SpriteManifest',
|
||||
'export default spriteManifest',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
export function generateOutputFiles(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): GeneratedFile[] {
|
||||
|
||||
@@ -5,8 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const standaloneViteAdapter: ModeAdapter<'standalone@vite'> = {
|
||||
mode: 'standalone@vite',
|
||||
contractVersion: 1,
|
||||
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, {
|
||||
rootViewBox: false,
|
||||
|
||||
@@ -68,9 +68,10 @@ function iconDeclarations(config: ResolvedSpriteConfig, artifact: CompiledSprite
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function index(config: ResolvedSpriteConfig): string {
|
||||
function index(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string {
|
||||
const prefix = camel(config.name)
|
||||
const pascalName = pascal(config.name)
|
||||
const iconTagName = `${config.name}-icon`
|
||||
return [
|
||||
header(config.generatedNotice),
|
||||
"import spriteUrl from './sprite.svg?no-inline'",
|
||||
@@ -78,9 +79,92 @@ function index(config: ResolvedSpriteConfig): string {
|
||||
'',
|
||||
`export { ${prefix}IconIds, ${prefix}IconNames }`,
|
||||
`export const ${prefix}SpriteUrl = spriteUrl`,
|
||||
`export const ${prefix}IconTagName = ${JSON.stringify(iconTagName)}`,
|
||||
`const ${prefix}IconViewBoxes = ${JSON.stringify(Object.fromEntries(artifact.icons.map((icon) => [icon.name, icon.viewBox])), null, 2)}`,
|
||||
`const ${prefix}IconElementMarker = Symbol.for('@gromlab/svg-sprites/icon-element')`,
|
||||
`const ${prefix}IconElementSignature = ${prefix}IconTagName + '|' + ${prefix}SpriteUrl`,
|
||||
`function has${pascalName}Icon(icon) {`,
|
||||
` return Object.prototype.hasOwnProperty.call(${prefix}IconIds, icon)`,
|
||||
'}',
|
||||
`export function get${pascalName}IconHref(icon) {`,
|
||||
` return ${prefix}SpriteUrl + '#' + ${prefix}IconIds[icon]`,
|
||||
'}',
|
||||
`export function define${pascalName}IconElement() {`,
|
||||
` if (typeof globalThis.customElements === 'undefined' || typeof globalThis.HTMLElement === 'undefined') return`,
|
||||
` const existing = globalThis.customElements.get(${prefix}IconTagName)`,
|
||||
' if (existing) {',
|
||||
` if (existing[${prefix}IconElementMarker] === ${prefix}IconElementSignature) return`,
|
||||
` throw new Error('Cannot define <' + ${prefix}IconTagName + '>: this custom element name is already registered.')`,
|
||||
' }',
|
||||
` class ${pascalName}IconElement extends globalThis.HTMLElement {`,
|
||||
" static observedAttributes = ['icon']",
|
||||
' constructor() {',
|
||||
' super()',
|
||||
' const ownerDocument = this.ownerDocument',
|
||||
" const shadowRoot = this.attachShadow({ mode: 'open' })",
|
||||
" const style = ownerDocument.createElement('style')",
|
||||
" style.textContent = ':host{display:inline-block;width:1em;height:1em;vertical-align:-0.125em}:host([hidden]){display:none}svg{display:block;width:100%;height:100%}svg[hidden]{display:none}'",
|
||||
" this._svg = ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'svg')",
|
||||
" this._svg.setAttribute('part', 'svg')",
|
||||
" this._svg.setAttribute('aria-hidden', 'true')",
|
||||
" this._svg.setAttribute('focusable', 'false')",
|
||||
" this._svg.setAttribute('hidden', '')",
|
||||
" this._use = ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'use')",
|
||||
" this._use.setAttribute('part', 'use')",
|
||||
' this._svg.append(this._use)',
|
||||
' shadowRoot.append(style, this._svg)',
|
||||
' }',
|
||||
' connectedCallback() {',
|
||||
" if (Object.prototype.hasOwnProperty.call(this, 'icon')) {",
|
||||
' const icon = this.icon',
|
||||
' delete this.icon',
|
||||
' this.icon = icon',
|
||||
' }',
|
||||
' this._renderIcon()',
|
||||
' }',
|
||||
' attributeChangedCallback() {',
|
||||
' this._renderIcon()',
|
||||
' }',
|
||||
' get icon() {',
|
||||
" const icon = this.getAttribute('icon')",
|
||||
` return icon !== null && has${pascalName}Icon(icon) ? icon : null`,
|
||||
' }',
|
||||
' set icon(icon) {',
|
||||
' if (icon === null || icon === undefined) {',
|
||||
" this.removeAttribute('icon')",
|
||||
' return',
|
||||
' }',
|
||||
` if (!has${pascalName}Icon(icon)) throw new TypeError('Unknown ${config.name} icon: ' + icon)`,
|
||||
" this.setAttribute('icon', icon)",
|
||||
' }',
|
||||
' _renderIcon() {',
|
||||
" const icon = this.getAttribute('icon')",
|
||||
' if (icon === null) {',
|
||||
' this._clearIcon()',
|
||||
' return',
|
||||
' }',
|
||||
` if (!has${pascalName}Icon(icon)) {`,
|
||||
' this._clearIcon()',
|
||||
` if (this._invalidIcon !== icon) console.error('<' + ${prefix}IconTagName + '>: unknown icon "' + icon + '".')`,
|
||||
' this._invalidIcon = icon',
|
||||
' return',
|
||||
' }',
|
||||
' this._invalidIcon = undefined',
|
||||
` const viewBox = ${prefix}IconViewBoxes[icon]`,
|
||||
" if (viewBox === null) this._svg.removeAttribute('viewBox')",
|
||||
" else this._svg.setAttribute('viewBox', viewBox)",
|
||||
` this._use.setAttribute('href', get${pascalName}IconHref(icon))`,
|
||||
" this._svg.removeAttribute('hidden')",
|
||||
' }',
|
||||
' _clearIcon() {',
|
||||
" this._use.removeAttribute('href')",
|
||||
" this._svg.removeAttribute('viewBox')",
|
||||
" this._svg.setAttribute('hidden', '')",
|
||||
' }',
|
||||
' }',
|
||||
` Object.defineProperty(${pascalName}IconElement, ${prefix}IconElementMarker, { value: ${prefix}IconElementSignature })`,
|
||||
` globalThis.customElements.define(${prefix}IconTagName, ${pascalName}IconElement)`,
|
||||
'}',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
@@ -88,6 +172,7 @@ function index(config: ResolvedSpriteConfig): string {
|
||||
function indexDeclarations(config: ResolvedSpriteConfig): string {
|
||||
const prefix = camel(config.name)
|
||||
const pascalName = pascal(config.name)
|
||||
const iconTagName = `${config.name}-icon`
|
||||
return [
|
||||
header(config.generatedNotice),
|
||||
`import type { ${pascalName}IconName } from './icon-data.js'`,
|
||||
@@ -95,7 +180,17 @@ function indexDeclarations(config: ResolvedSpriteConfig): string {
|
||||
`export { ${prefix}IconIds, ${prefix}IconNames } from './icon-data.js'`,
|
||||
`export type { ${pascalName}IconName } from './icon-data.js'`,
|
||||
`export declare const ${prefix}SpriteUrl: string`,
|
||||
`export declare const ${prefix}IconTagName: ${JSON.stringify(iconTagName)}`,
|
||||
`export declare function get${pascalName}IconHref(icon: ${pascalName}IconName): string`,
|
||||
`export interface ${pascalName}IconElement extends HTMLElement {`,
|
||||
` icon: ${pascalName}IconName | null`,
|
||||
'}',
|
||||
`export declare function define${pascalName}IconElement(): void`,
|
||||
'declare global {',
|
||||
' interface HTMLElementTagNameMap {',
|
||||
` ${JSON.stringify(iconTagName)}: ${pascalName}IconElement`,
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
@@ -133,11 +228,33 @@ function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact
|
||||
function manifestTypes(config: ResolvedSpriteConfig): string {
|
||||
return [
|
||||
header(config.generatedNotice),
|
||||
"import type { StandaloneSpriteManifest, StandaloneSpriteManifestData } from '@gromlab/svg-sprites'",
|
||||
'',
|
||||
"export declare const spriteManifestData: StandaloneSpriteManifestData<'standalone@vite'>",
|
||||
"export declare function createSpriteManifest(spriteUrl: string): StandaloneSpriteManifest<'standalone@vite'>",
|
||||
"export declare const spriteManifest: StandaloneSpriteManifest<'standalone@vite'>",
|
||||
'export type SpriteManifestColor = {',
|
||||
' variable: `--icon-color-${number}`',
|
||||
' fallback: string',
|
||||
'}',
|
||||
'export type SpriteManifestIcon = {',
|
||||
' name: string',
|
||||
' id: string',
|
||||
' viewBox: string | null',
|
||||
' colors: readonly SpriteManifestColor[]',
|
||||
'}',
|
||||
'export type SpriteManifestData = {',
|
||||
' schemaVersion: 1',
|
||||
" generator: '@gromlab/svg-sprites'",
|
||||
' name: string',
|
||||
' description?: string',
|
||||
" mode: 'standalone@vite'",
|
||||
" target: 'vite'",
|
||||
" format: 'stack' | 'symbol'",
|
||||
' iconCount: number',
|
||||
' icons: readonly SpriteManifestIcon[]',
|
||||
'}',
|
||||
'export type SpriteManifest = SpriteManifestData & { spriteUrl: string }',
|
||||
'',
|
||||
'export declare const spriteManifestData: SpriteManifestData',
|
||||
'export declare function createSpriteManifest(spriteUrl: string): SpriteManifest',
|
||||
'export declare const spriteManifest: SpriteManifest',
|
||||
'export default spriteManifest',
|
||||
'',
|
||||
].join('\n')
|
||||
@@ -148,7 +265,7 @@ export function generateOutputFiles(
|
||||
artifact: CompiledSpriteArtifact,
|
||||
): GeneratedFile[] {
|
||||
return [
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'index.js'), content: index(config) },
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'index.js'), content: index(config, artifact) },
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'index.d.ts'), content: indexDeclarations(config) },
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'sprite.svg'), content: svg(artifact.bytes, config.generatedNotice) },
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'icon-data.js'), content: iconData(config, artifact) },
|
||||
|
||||
@@ -5,8 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const standaloneWebpackAdapter: ModeAdapter<'standalone@webpack'> = {
|
||||
mode: 'standalone@webpack',
|
||||
contractVersion: 1,
|
||||
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, {
|
||||
rootViewBox: false,
|
||||
|
||||
@@ -68,18 +68,102 @@ function iconDeclarations(config: ResolvedSpriteConfig, artifact: CompiledSprite
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function index(config: ResolvedSpriteConfig): string {
|
||||
function index(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string {
|
||||
const prefix = camel(config.name)
|
||||
const pascalName = pascal(config.name)
|
||||
const iconTagName = `${config.name}-icon`
|
||||
return [
|
||||
header(config.generatedNotice),
|
||||
`import { ${prefix}IconIds, ${prefix}IconNames } from './icon-data.js'`,
|
||||
'',
|
||||
`export { ${prefix}IconIds, ${prefix}IconNames }`,
|
||||
`export const ${prefix}SpriteUrl = new URL('./sprite.svg', import.meta.url).href`,
|
||||
`export const ${prefix}IconTagName = ${JSON.stringify(iconTagName)}`,
|
||||
`const ${prefix}IconViewBoxes = ${JSON.stringify(Object.fromEntries(artifact.icons.map((icon) => [icon.name, icon.viewBox])), null, 2)}`,
|
||||
`const ${prefix}IconElementMarker = Symbol.for('@gromlab/svg-sprites/icon-element')`,
|
||||
`const ${prefix}IconElementSignature = ${prefix}IconTagName + '|' + ${prefix}SpriteUrl`,
|
||||
`function has${pascalName}Icon(icon) {`,
|
||||
` return Object.prototype.hasOwnProperty.call(${prefix}IconIds, icon)`,
|
||||
'}',
|
||||
`export function get${pascalName}IconHref(icon) {`,
|
||||
` return ${prefix}SpriteUrl + '#' + ${prefix}IconIds[icon]`,
|
||||
'}',
|
||||
`export function define${pascalName}IconElement() {`,
|
||||
` if (typeof globalThis.customElements === 'undefined' || typeof globalThis.HTMLElement === 'undefined') return`,
|
||||
` const existing = globalThis.customElements.get(${prefix}IconTagName)`,
|
||||
' if (existing) {',
|
||||
` if (existing[${prefix}IconElementMarker] === ${prefix}IconElementSignature) return`,
|
||||
` throw new Error('Cannot define <' + ${prefix}IconTagName + '>: this custom element name is already registered.')`,
|
||||
' }',
|
||||
` class ${pascalName}IconElement extends globalThis.HTMLElement {`,
|
||||
" static observedAttributes = ['icon']",
|
||||
' constructor() {',
|
||||
' super()',
|
||||
' const ownerDocument = this.ownerDocument',
|
||||
" const shadowRoot = this.attachShadow({ mode: 'open' })",
|
||||
" const style = ownerDocument.createElement('style')",
|
||||
" style.textContent = ':host{display:inline-block;width:1em;height:1em;vertical-align:-0.125em}:host([hidden]){display:none}svg{display:block;width:100%;height:100%}svg[hidden]{display:none}'",
|
||||
" this._svg = ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'svg')",
|
||||
" this._svg.setAttribute('part', 'svg')",
|
||||
" this._svg.setAttribute('aria-hidden', 'true')",
|
||||
" this._svg.setAttribute('focusable', 'false')",
|
||||
" this._svg.setAttribute('hidden', '')",
|
||||
" this._use = ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'use')",
|
||||
" this._use.setAttribute('part', 'use')",
|
||||
' this._svg.append(this._use)',
|
||||
' shadowRoot.append(style, this._svg)',
|
||||
' }',
|
||||
' connectedCallback() {',
|
||||
" if (Object.prototype.hasOwnProperty.call(this, 'icon')) {",
|
||||
' const icon = this.icon',
|
||||
' delete this.icon',
|
||||
' this.icon = icon',
|
||||
' }',
|
||||
' this._renderIcon()',
|
||||
' }',
|
||||
' attributeChangedCallback() {',
|
||||
' this._renderIcon()',
|
||||
' }',
|
||||
' get icon() {',
|
||||
" const icon = this.getAttribute('icon')",
|
||||
` return icon !== null && has${pascalName}Icon(icon) ? icon : null`,
|
||||
' }',
|
||||
' set icon(icon) {',
|
||||
' if (icon === null || icon === undefined) {',
|
||||
" this.removeAttribute('icon')",
|
||||
' return',
|
||||
' }',
|
||||
` if (!has${pascalName}Icon(icon)) throw new TypeError('Unknown ${config.name} icon: ' + icon)`,
|
||||
" this.setAttribute('icon', icon)",
|
||||
' }',
|
||||
' _renderIcon() {',
|
||||
" const icon = this.getAttribute('icon')",
|
||||
' if (icon === null) {',
|
||||
' this._clearIcon()',
|
||||
' return',
|
||||
' }',
|
||||
` if (!has${pascalName}Icon(icon)) {`,
|
||||
' this._clearIcon()',
|
||||
` if (this._invalidIcon !== icon) console.error('<' + ${prefix}IconTagName + '>: unknown icon "' + icon + '".')`,
|
||||
' this._invalidIcon = icon',
|
||||
' return',
|
||||
' }',
|
||||
' this._invalidIcon = undefined',
|
||||
` const viewBox = ${prefix}IconViewBoxes[icon]`,
|
||||
" if (viewBox === null) this._svg.removeAttribute('viewBox')",
|
||||
" else this._svg.setAttribute('viewBox', viewBox)",
|
||||
` this._use.setAttribute('href', get${pascalName}IconHref(icon))`,
|
||||
" this._svg.removeAttribute('hidden')",
|
||||
' }',
|
||||
' _clearIcon() {',
|
||||
" this._use.removeAttribute('href')",
|
||||
" this._svg.removeAttribute('viewBox')",
|
||||
" this._svg.setAttribute('hidden', '')",
|
||||
' }',
|
||||
' }',
|
||||
` Object.defineProperty(${pascalName}IconElement, ${prefix}IconElementMarker, { value: ${prefix}IconElementSignature })`,
|
||||
` globalThis.customElements.define(${prefix}IconTagName, ${pascalName}IconElement)`,
|
||||
'}',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
@@ -87,6 +171,7 @@ function index(config: ResolvedSpriteConfig): string {
|
||||
function indexDeclarations(config: ResolvedSpriteConfig): string {
|
||||
const prefix = camel(config.name)
|
||||
const pascalName = pascal(config.name)
|
||||
const iconTagName = `${config.name}-icon`
|
||||
return [
|
||||
header(config.generatedNotice),
|
||||
`import type { ${pascalName}IconName } from './icon-data.js'`,
|
||||
@@ -94,7 +179,17 @@ function indexDeclarations(config: ResolvedSpriteConfig): string {
|
||||
`export { ${prefix}IconIds, ${prefix}IconNames } from './icon-data.js'`,
|
||||
`export type { ${pascalName}IconName } from './icon-data.js'`,
|
||||
`export declare const ${prefix}SpriteUrl: string`,
|
||||
`export declare const ${prefix}IconTagName: ${JSON.stringify(iconTagName)}`,
|
||||
`export declare function get${pascalName}IconHref(icon: ${pascalName}IconName): string`,
|
||||
`export interface ${pascalName}IconElement extends HTMLElement {`,
|
||||
` icon: ${pascalName}IconName | null`,
|
||||
'}',
|
||||
`export declare function define${pascalName}IconElement(): void`,
|
||||
'declare global {',
|
||||
' interface HTMLElementTagNameMap {',
|
||||
` ${JSON.stringify(iconTagName)}: ${pascalName}IconElement`,
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
].join('\n')
|
||||
}
|
||||
@@ -132,11 +227,33 @@ function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact
|
||||
function manifestTypes(config: ResolvedSpriteConfig): string {
|
||||
return [
|
||||
header(config.generatedNotice),
|
||||
"import type { StandaloneSpriteManifest, StandaloneSpriteManifestData } from '@gromlab/svg-sprites'",
|
||||
'',
|
||||
"export declare const spriteManifestData: StandaloneSpriteManifestData<'standalone@webpack'>",
|
||||
"export declare function createSpriteManifest(spriteUrl: string): StandaloneSpriteManifest<'standalone@webpack'>",
|
||||
"export declare const spriteManifest: StandaloneSpriteManifest<'standalone@webpack'>",
|
||||
'export type SpriteManifestColor = {',
|
||||
' variable: `--icon-color-${number}`',
|
||||
' fallback: string',
|
||||
'}',
|
||||
'export type SpriteManifestIcon = {',
|
||||
' name: string',
|
||||
' id: string',
|
||||
' viewBox: string | null',
|
||||
' colors: readonly SpriteManifestColor[]',
|
||||
'}',
|
||||
'export type SpriteManifestData = {',
|
||||
' schemaVersion: 1',
|
||||
" generator: '@gromlab/svg-sprites'",
|
||||
' name: string',
|
||||
' description?: string',
|
||||
" mode: 'standalone@webpack'",
|
||||
" target: 'webpack'",
|
||||
" format: 'stack' | 'symbol'",
|
||||
' iconCount: number',
|
||||
' icons: readonly SpriteManifestIcon[]',
|
||||
'}',
|
||||
'export type SpriteManifest = SpriteManifestData & { spriteUrl: string }',
|
||||
'',
|
||||
'export declare const spriteManifestData: SpriteManifestData',
|
||||
'export declare function createSpriteManifest(spriteUrl: string): SpriteManifest',
|
||||
'export declare const spriteManifest: SpriteManifest',
|
||||
'export default spriteManifest',
|
||||
'',
|
||||
].join('\n')
|
||||
@@ -147,7 +264,7 @@ export function generateOutputFiles(
|
||||
artifact: CompiledSpriteArtifact,
|
||||
): GeneratedFile[] {
|
||||
return [
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'index.js'), content: index(config) },
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'index.js'), content: index(config, artifact) },
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'index.d.ts'), content: indexDeclarations(config) },
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'sprite.svg'), content: svg(artifact.bytes, config.generatedNotice) },
|
||||
{ path: path.posix.join(OUTPUT_DIR, 'icon-data.js'), content: iconData(config, artifact) },
|
||||
|
||||
@@ -5,8 +5,6 @@ import { generateOutputFiles } from './output.js'
|
||||
|
||||
export const standaloneAdapter: ModeAdapter<'standalone'> = {
|
||||
mode: 'standalone',
|
||||
contractVersion: 2,
|
||||
|
||||
async generate(context) {
|
||||
const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, {
|
||||
rootViewBox: false,
|
||||
@@ -15,6 +13,7 @@ export const standaloneAdapter: ModeAdapter<'standalone'> = {
|
||||
|
||||
return {
|
||||
files: generateOutputFiles(context.config, artifact),
|
||||
createGitignore: false,
|
||||
paths: {
|
||||
generatedDir: '.svg-sprite',
|
||||
sprite: '.svg-sprite/sprite.svg',
|
||||
|
||||
Reference in New Issue
Block a user