From e2d379ab0d545ff6ffbcb9fe8ef03a6b30a52c52 Mon Sep 17 00:00:00 2001 From: Daniel Leroux Date: Thu, 26 Sep 2024 09:09:18 +0200 Subject: [PATCH] feat: remove bundle svg data (#64) * feat: add legacy cache map * fix asset path * changelog * remove import * remove legacy code part * fix import path * fix: remove comment * fix: expose all asset utils * fix: remove commonjs support * fix: make fallback to window origin * fix: types * update to latest stencil version * test: fix build env for e2e test * docs: adapt review comments --- .changeset/little-baboons-cry.md | 5 ++ .gitignore | 3 ++ BREAKING_CHANGES.md | 10 +++- e2e/all-icon.e2e.ts | 2 +- package.json | 4 +- pnpm-lock.yaml | 47 +++++++++-------- scripts/build-icons.ts | 25 +-------- src/components/icon/meta-tag.ts | 36 ------------- src/components/icon/resolveIcon.ts | 55 ++++++-------------- src/components/icon/test/resolveIcon.spec.ts | 52 ++++++++++++------ src/index.html | 1 - src/index.ts | 1 + stencil.config.ts | 5 +- 13 files changed, 104 insertions(+), 142 deletions(-) create mode 100644 .changeset/little-baboons-cry.md diff --git a/.changeset/little-baboons-cry.md b/.changeset/little-baboons-cry.md new file mode 100644 index 0000000..1d4f0db --- /dev/null +++ b/.changeset/little-baboons-cry.md @@ -0,0 +1,5 @@ +--- +'@siemens/ix-icons': major +--- + +feat: remove prebundled icons diff --git a/.gitignore b/.gitignore index 6879500..bac4c9a 100755 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ icons/ /playwright-report/ /playwright/.cache/ /svg +/components +/e2e/sample.json +src/components/icon/svg/*.svg diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 1d1af96..e53985d 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -1,3 +1,11 @@ +# v3.0.0 + +## Loading icon svg as external resource + +Previously, all icons were included within the icon package itself. This approach was chosen to simplify setup and eliminate the need for additional configurations. However, this implementation has led to an increase in bundle size. + +With the release of the main libraries @siemens/ix and @siemens/ix-icons, you now need to provide the icons as a static resource. This significantly reduces the bundle size. + # v2.0.0 -Icon web fonts are removed. \ No newline at end of file +Icon web fonts are removed. diff --git a/e2e/all-icon.e2e.ts b/e2e/all-icon.e2e.ts index 3d47047..0b55e87 100644 --- a/e2e/all-icon.e2e.ts +++ b/e2e/all-icon.e2e.ts @@ -8,7 +8,7 @@ */ import { ConsoleMessage, expect, test } from '@playwright/test'; -import * as iconsFile from './../dist/sample.json'; +import * as iconsFile from './sample.json'; import * as icons from './../icons'; function convertToCamelCase(value: string) { diff --git a/package.json b/package.json index b72ab1c..81c91ee 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,13 @@ "module": "dist/index.js", "es2015": "dist/esm/index.mjs", "es2017": "dist/esm/index.mjs", + "jsnext:main": "dist/esm/index.js", "types": "dist/types/index.d.ts", "collection": "dist/collection/collection-manifest.json", "collection:main": "dist/collection/index.js", "unpkg": "dist/ix-icons/ix-icons.esm.js", "files": [ + "components/", "dist/", "loader/", "icons/", @@ -43,7 +45,7 @@ "ci:publish": "pnpm changeset publish" }, "dependencies": { - "@stencil/core": "^4.12.6" + "@stencil/core": "^4.21.0" }, "devDependencies": { "@changesets/changelog-github": "^0.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b31b4a..35f966f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@stencil/core': - specifier: ^4.12.6 - version: 4.16.0 + specifier: ^4.21.0 + version: 4.21.0 devDependencies: '@changesets/changelog-github': specifier: ^0.5.0 @@ -23,7 +23,7 @@ importers: version: 1.43.1 '@stencil/sass': specifier: ^3.0.10 - version: 3.0.11(@stencil/core@4.16.0) + version: 3.0.11(@stencil/core@4.21.0) '@types/fs-extra': specifier: ^9.0.13 version: 9.0.13 @@ -50,10 +50,10 @@ importers: version: 14.1.1 jest: specifier: ^27.5.1 - version: 27.5.1(ts-node@10.9.2) + version: 27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)) jest-cli: specifier: ^27.5.1 - version: 27.5.1(ts-node@10.9.2) + version: 27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)) lodash: specifier: ^4.17.2 version: 4.17.21 @@ -436,8 +436,8 @@ packages: '@sinonjs/fake-timers@8.1.0': resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} - '@stencil/core@4.16.0': - resolution: {integrity: sha512-gXaC5IrquV/Hw5JIZTCWkM5lJEbBQtnvHLhDebjar6A6+YBqxah04dardS+YUNVuRbnE6Hcja7KKiAXT3oVsvw==} + '@stencil/core@4.21.0': + resolution: {integrity: sha512-v50lnVbzS8mpMSnEVxR+G75XpvxHKtkJaQrNPE8+/fF6Ppr5z4bcdcBhcP8LPfEW+4BZcic6VifMXRwTopc+kw==} engines: {node: '>=16.0.0', npm: '>=7.10.0'} hasBin: true @@ -4114,7 +4114,7 @@ snapshots: jest-util: 27.5.1 slash: 3.0.0 - '@jest/core@27.5.1(ts-node@10.9.2)': + '@jest/core@27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5))': dependencies: '@jest/console': 27.5.1 '@jest/reporters': 27.5.1 @@ -4128,7 +4128,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 27.5.1 - jest-config: 27.5.1(ts-node@10.9.2) + jest-config: 27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)) jest-haste-map: 27.5.1 jest-message-util: 27.5.1 jest-regex-util: 27.5.1 @@ -4318,9 +4318,10 @@ snapshots: progress: 2.0.3 proxy-from-env: 1.1.0 tar-fs: 2.1.1 - typescript: 4.9.5 unbzip2-stream: 1.4.3 yargs: 17.7.1 + optionalDependencies: + typescript: 4.9.5 transitivePeerDependencies: - supports-color @@ -4332,11 +4333,11 @@ snapshots: dependencies: '@sinonjs/commons': 1.8.6 - '@stencil/core@4.16.0': {} + '@stencil/core@4.21.0': {} - '@stencil/sass@3.0.11(@stencil/core@4.16.0)': + '@stencil/sass@3.0.11(@stencil/core@4.21.0)': dependencies: - '@stencil/core': 4.16.0 + '@stencil/core': 4.21.0 '@tootallnate/once@1.1.2': {} @@ -6183,16 +6184,16 @@ snapshots: transitivePeerDependencies: - supports-color - jest-cli@27.5.1(ts-node@10.9.2): + jest-cli@27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)): dependencies: - '@jest/core': 27.5.1(ts-node@10.9.2) + '@jest/core': 27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)) '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 27.5.1(ts-node@10.9.2) + jest-config: 27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)) jest-util: 27.5.1 jest-validate: 27.5.1 prompts: 2.4.2 @@ -6204,7 +6205,7 @@ snapshots: - ts-node - utf-8-validate - jest-config@27.5.1(ts-node@10.9.2): + jest-config@27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)): dependencies: '@babel/core': 7.24.4 '@jest/test-sequencer': 27.5.1 @@ -6230,6 +6231,7 @@ snapshots: pretty-format: 27.5.1 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: ts-node: 10.9.2(@types/node@16.18.96)(typescript@4.9.5) transitivePeerDependencies: - bufferutil @@ -6351,7 +6353,7 @@ snapshots: '@types/node': 16.18.96 jest-pnp-resolver@1.2.3(jest-resolve@27.5.1): - dependencies: + optionalDependencies: jest-resolve: 27.5.1 jest-regex-util@27.5.1: {} @@ -6499,11 +6501,11 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@27.5.1(ts-node@10.9.2): + jest@27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)): dependencies: - '@jest/core': 27.5.1(ts-node@10.9.2) + '@jest/core': 27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)) import-local: 3.1.0 - jest-cli: 27.5.1(ts-node@10.9.2) + jest-cli: 27.5.1(ts-node@10.9.2(@types/node@16.18.96)(typescript@4.9.5)) transitivePeerDependencies: - bufferutil - canvas @@ -7136,9 +7138,10 @@ snapshots: https-proxy-agent: 5.0.1 proxy-from-env: 1.1.0 tar-fs: 2.1.1 - typescript: 4.9.5 unbzip2-stream: 1.4.3 ws: 8.13.0 + optionalDependencies: + typescript: 4.9.5 transitivePeerDependencies: - bufferutil - encoding diff --git a/scripts/build-icons.ts b/scripts/build-icons.ts index e391849..f4fd5ae 100644 --- a/scripts/build-icons.ts +++ b/scripts/build-icons.ts @@ -175,8 +175,7 @@ async function buildIcons() { version, ), - writeIconSampleJson(iconCollection, path.join(rootPath, 'build-dist'), version), - writeGlobalCSSFile(path.join(rootPath, 'build-dist', 'css', 'ix-icons.css')), + writeIconSampleJson(iconCollection, path.join(rootPath, 'e2e'), version), fs.writeFile( iconsPkgPath, JSON.stringify( @@ -250,26 +249,4 @@ function getDataUrl(svgData: string) { return `"data:image/svg+xml;utf8,${svg}"`; } -async function writeGlobalCSSFile(targetPath: string) { - // Write the global css file to keep the application compiling after update to 2.0.0 - fs.ensureDirSync(path.join(targetPath, '..')); - - return fs.writeFile( - targetPath, - ` -/* -* SPDX-FileCopyrightText: 2023 Siemens AG -* -* SPDX-License-Identifier: MIT -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ -/* -* Deprecated since 2.0.0 no global css file is necessary. -*/ - `, - ); -} - buildIcons(); diff --git a/src/components/icon/meta-tag.ts b/src/components/icon/meta-tag.ts index a68516b..868d2fa 100644 --- a/src/components/icon/meta-tag.ts +++ b/src/components/icon/meta-tag.ts @@ -1,20 +1,3 @@ -/* - * COPYRIGHT (c) Siemens AG 2018-2024 ALL RIGHTS RESERVED. - */ - -function getV3PreviewMetaElement() { - return document.querySelector("meta[name='ix-icons:v3-preview']"); -} - -function getV3PreviewMetaContent() { - const v3PreviewMetaElement = getV3PreviewMetaElement(); - if (v3PreviewMetaElement) { - return v3PreviewMetaElement.getAttribute('content').split(';'); - } - - return null; -} - /** * Provide custom SVG path for icons * @@ -29,22 +12,3 @@ export function getCustomAssetUrl() { return false; } - -/** - * Enable v3 preview features - * - * - */ -export function isV3PreviewEnabled() { - const features = getV3PreviewMetaContent(); - - if (!features) { - return false; - } - - if (features.includes('svg-path-loading')) { - return true; - } - - return false; -} diff --git a/src/components/icon/resolveIcon.ts b/src/components/icon/resolveIcon.ts index 9a859d4..ba4ece7 100644 --- a/src/components/icon/resolveIcon.ts +++ b/src/components/icon/resolveIcon.ts @@ -6,8 +6,8 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import { getAssetPath } from '@stencil/core'; -import { getCustomAssetUrl, isV3PreviewEnabled } from './meta-tag'; +import { getAssetPath, setAssetPath } from '@stencil/core'; +import { getCustomAssetUrl } from './meta-tag'; declare global { interface Window { @@ -19,25 +19,6 @@ let fetchCache: Map; const requests = new Map>(); let parser = null; -function toCamelCase(value: string) { - value = value.replace(/[\(\)\[\]\{\}\=\?\!\.\:,\-_\+\\\"#~\/]/g, ' '); - let returnValue = ''; - let makeNextUppercase = true; - value = value.toLowerCase(); - for (let i = 0; value.length > i; i++) { - let c = value.charAt(i); - if (c.match(/^\s+$/g) || c.match(/[\(\)\[\]\{\}\\\/]/g)) { - makeNextUppercase = true; - } else if (makeNextUppercase) { - c = c.toUpperCase(); - makeNextUppercase = false; - } - returnValue += c; - } - const normalized = returnValue.replace(/\s+/g, ''); - return normalized.charAt(0).toUpperCase() + normalized.slice(1); -} - export const getIconCacheMap = (): Map => { if (typeof window === 'undefined') { return new Map(); @@ -116,21 +97,22 @@ function isValidUrl(url: string) { return urlRegex.test(url); } -function getAssetUrl(name: string) { +export function getIconUrl(name: string) { const customAssetUrl = getCustomAssetUrl(); if (customAssetUrl) { return `${customAssetUrl}/${name}.svg`; } - return getAssetPath(`svg/${name}.svg`); -} - -async function getESMIcon(name: string) { - const esmIcon = await import('./icons'); - let iconName = toCamelCase(name); - iconName = `icon${iconName}`; + let url: string = `svg/${name}.svg`; + try { + url = getAssetPath(url); + } catch (error) { + console.warn(error); + setAssetPath(`${window.location.origin}/`); + url = getAssetPath(url); + } - return parseSVGDataContent(esmIcon[iconName]); + return url; } export async function resolveIcon(iconName: string) { @@ -150,14 +132,9 @@ export async function resolveIcon(iconName: string) { } } - if (isV3PreviewEnabled()) { - console.warn('Using V3 preview feature for loading icons.'); - try { - return fetchSVG(getAssetUrl(iconName)); - } catch (error) { - throw Error('Cannot resolve any icon'); - } + try { + return fetchSVG(getIconUrl(iconName)); + } catch (error) { + throw Error('Cannot resolve any icon'); } - - return getESMIcon(iconName); } diff --git a/src/components/icon/test/resolveIcon.spec.ts b/src/components/icon/test/resolveIcon.spec.ts index 450ee4a..9d22b29 100644 --- a/src/components/icon/test/resolveIcon.spec.ts +++ b/src/components/icon/test/resolveIcon.spec.ts @@ -1,8 +1,8 @@ /* * COPYRIGHT (c) Siemens AG 2018-2023 ALL RIGHTS RESERVED. */ -import * as metaTag from './../meta-tag'; -import { resolveIcon } from '../resolveIcon'; +import { iconStar } from '../icons'; +import { resolveIcon, getIconCacheMap, getIconUrl, parseSVGDataContent } from '../resolveIcon'; const exampleSvg = ` @@ -28,6 +28,14 @@ jest.mock('../icons', () => ({ })); let fetch = (global.fetch = jest.fn((url: string) => { console.log(url); + + if (url === '/svg/star.svg') { + return Promise.resolve({ + text: () => Promise.resolve(iconStar), + ok: true, + }); + } + if (url === '/svg/bulb.svg') { return Promise.resolve({ text: () => Promise.resolve(exampleSvg), @@ -56,19 +64,7 @@ describe('resolve icon', () => { }); it('should resolve svg from name', async () => { - const icon = 'star'; - const data = await resolveIcon(icon); - - expect(data).toBe( - ` add `, - ); - }); - - it('resolve by v3 preview feature flat', async () => { - (metaTag as any).isV3PreviewEnabled = jest.fn(() => true); - - const icon = 'bulb'; - const data = await resolveIcon(icon); + const data = await resolveIcon('bulb'); expect(data).toBe( ` add `, @@ -89,3 +85,29 @@ describe('resolve icon', () => { await expect(resolveIcon(icon)).rejects.toThrow('No valid svg data provided'); }); }); + +test('fill cache map if not loaded', async () => { + fetch.mockClear(); + + const cacheMap = getIconCacheMap(); + cacheMap.clear(); + + expect(cacheMap.size).toBe(0); + + const data = await resolveIcon('star'); + + expect(data).toBe(parseSVGDataContent(iconStar)); + expect(cacheMap.get(getIconUrl('star'))).toBe(parseSVGDataContent(iconStar)); +}); + +test('preload custom icon', async () => { + fetch.mockClear(); + + const cacheMap = getIconCacheMap(); + cacheMap.clear(); + + cacheMap.set(getIconUrl('star'), 'Test'); + + const data = await resolveIcon('star'); + expect(data).toBe('Test'); +}); diff --git a/src/index.html b/src/index.html index 4da018d..e02ddf7 100644 --- a/src/index.html +++ b/src/index.html @@ -4,7 +4,6 @@ Stencil Component Starter - diff --git a/src/index.ts b/src/index.ts index 2467c26..df11feb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export type { Components, JSX } from './components'; export * from './components/icon/icon'; +export { setAssetPath } from '@stencil/core'; diff --git a/stencil.config.ts b/stencil.config.ts index e7e8137..8d55433 100644 --- a/stencil.config.ts +++ b/stencil.config.ts @@ -14,13 +14,14 @@ export const config: Config = { esmLoaderPath: '../loader', copy: [ { - src: path.join(__dirname, 'build-dist'), - dest: path.join(__dirname, 'dist'), + src: path.join(__dirname, 'svg'), + dest: path.join(__dirname, 'dist', 'ix-icons', 'svg'), }, ], }, { type: 'dist-custom-elements', + dir: './components', }, { type: 'www',