diff --git a/README.md b/README.md index 0bf01323..cbe912a5 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ - Zero-config required - Auto-import Ionic components, composables and icons +- Support SVG Icons - Ionic Router integration - Pre-render routes - Mobile meta tags diff --git a/docs/content/0.index.md b/docs/content/0.index.md index c50a9af0..d9c892d9 100755 --- a/docs/content/0.index.md +++ b/docs/content/0.index.md @@ -25,6 +25,7 @@ Batteries-included [Ionic](https://ionicframework.com/) integration for Nuxt. ::list - Zero-config required - Auto-import Ionic components, composables and icons +- Support SVG Icons - Ionic Router integration - Pre-render routes - Mobile meta tags diff --git a/docs/content/1.get-started/1.introduction.md b/docs/content/1.get-started/1.introduction.md index d84cb10f..ad2ebaed 100644 --- a/docs/content/1.get-started/1.introduction.md +++ b/docs/content/1.get-started/1.introduction.md @@ -35,6 +35,7 @@ This module attempts to get you going with Nuxt + Ionic quickly, providing sane ::list{type=success} - **Ionic router integration:** continue defining routes based on the structure of your `~/pages` directory and using page-level utilities such as `definePageMeta()`. - **Auto-imports**: Ionic components, composables and icons are all [auto-imported](https://nuxt.com/docs/guide/concepts/auto-imports) for ease of use. +- **Support SVG Icons**: custom icons in the assets/ionic-icons path - **Helpful components and utilities**: This module provides components and utilities to accomplish common tasks more easily. - **PWA support**: out-of-the-box support for progressive web apps, using `nuxt-pwa-module`. - **Pre-render routes** diff --git a/docs/content/1.get-started/3.configuration.md b/docs/content/1.get-started/3.configuration.md index 34ed5b5d..315687f2 100644 --- a/docs/content/1.get-started/3.configuration.md +++ b/docs/content/1.get-started/3.configuration.md @@ -50,8 +50,29 @@ Integrations control which other modules this module should enable and setup fro - **icons** + - **ionicons** + Default: `true` - Disable to stop icons from being auto-imported. + Disable to stop ionic-icons from being auto-imported. + + - **svg** + + Custom SVG icons and automatic detection with nested folders and icon size optimization in assets/ionic-icons path. + ::alert{type="warning"} + ⚠️ Note: Please put all the necessary SVG files in the assets/ionic-icons folder before you run your project. Because when a file is added/deleted in this path, your project will be rerendered again. + :: + + - **enable** + + Default: `true` + + Disable to stop svg from being auto-imported. + + - **directoryAsNamespace** + + Default: `true` + + Disable to stop svg from being auto-imported. #### `css` diff --git a/docs/content/2.overview/5.icons.md b/docs/content/2.overview/5.icons.md index 2697f12c..41768358 100644 --- a/docs/content/2.overview/5.icons.md +++ b/docs/content/2.overview/5.icons.md @@ -6,35 +6,59 @@ navigation.icon: uil:illustration Icons are auto-imported from [`ionicons/icons`](https://github.com/ionic-team/ionicons) by default, following the pattern of camel case naming with `ionicons` in front of the original icon name, that you can find on the [official ionicons website](https://ionic.io/ionicons). +::alert{type="info"} +ℹ️ Note: Please put all the necessary SVG files in the assets/ionic-icons folder before you run your project. Because when a file is added/deleted in this path, your project will be rerendered again. +:: + +::alert{type="warning"} +⚠️ Note: When you use #import , change the name of the icon using "as" and the desired name. Because it doesn't interfere with auto-import. +:: + ::code-group ```vue [Auto-imported icons] ``` ```vue [Manual imports] ``` :: -You can opt-out of auto-importing icons by setting the `integrations.icons` module options in your `nuxt.config.ts` to `false`. +You can opt-out of auto-importing icons by setting the `integrations.icons.ionicons` module options in your `nuxt.config.ts` to `false`. ```js export default defineNuxtConfig({ ionic: { integrations: { - icons: false, + icons: { + ionicons: false + }, }, }, }) diff --git a/package.json b/package.json index 623e971f..b80bd316 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "ionicons": "^7.3.1", "pathe": "^1.1.2", "pkg-types": "^1.0.3", + "svgo": "^3.2.0", "ufo": "^1.5.3", "unimport": "^3.7.1" }, diff --git a/playground/composables/usePhotoGallery.ts b/playground/composables/usePhotoGallery.ts index a9c71659..10f3c809 100644 --- a/playground/composables/usePhotoGallery.ts +++ b/playground/composables/usePhotoGallery.ts @@ -4,7 +4,7 @@ import { Camera, CameraSource, CameraResultType } from '@capacitor/camera' import { Filesystem, Directory } from '@capacitor/filesystem' import { Preferences } from '@capacitor/preferences' -export function usePhotoGallery() { +export const usePhotoGallery = () => { const photos = ref([]) const PHOTO_STORAGE = 'photos' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f13d0f4..9c388f64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: pkg-types: specifier: ^1.0.3 version: 1.0.3 + svgo: + specifier: ^3.2.0 + version: 3.2.0 ufo: specifier: ^1.5.3 version: 1.5.3 @@ -8108,8 +8111,6 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} - '@trysound/sax@0.2.0': {} - '@tufjs/canonical-json@2.0.0': {} '@tufjs/models@2.0.0': @@ -8497,10 +8498,6 @@ snapshots: '@vue/compiler-core@3.4.24': dependencies: '@babel/parser': 7.24.4 - '@vue/shared': 3.4.24 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.0 '@vue/compiler-dom@3.4.24': dependencies: @@ -8851,8 +8848,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - boolbase@1.0.0: {} - bplist-parser@0.2.0: dependencies: big-integer: 1.6.51 @@ -9133,8 +9128,6 @@ snapshots: commander@2.20.3: {} - commander@7.2.0: {} - commander@8.3.0: {} commander@9.5.0: {} @@ -9223,8 +9216,6 @@ snapshots: mdn-data: 2.0.30 source-map-js: 1.2.0 - css-what@6.1.0: {} - cssesc@3.0.0: {} cssnano-preset-default@6.0.3(postcss@8.4.38): @@ -9443,8 +9434,6 @@ snapshots: domhandler: 5.0.3 entities: 4.5.0 - domelementtype@2.3.0: {} - domhandler@5.0.3: dependencies: domelementtype: 2.3.0 @@ -9522,8 +9511,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 - entities@4.5.0: {} - env-paths@2.2.1: {} err-code@2.0.3: {} @@ -11094,10 +11081,6 @@ snapshots: dependencies: '@types/mdast': 4.0.1 - mdn-data@2.0.28: {} - - mdn-data@2.0.30: {} - mdurl@1.0.1: {} merge-stream@2.0.0: {} @@ -12592,8 +12575,6 @@ snapshots: postcss-unique-selectors@7.0.0(postcss@8.4.38): dependencies: - postcss: 8.4.38 - postcss-selector-parser: 6.0.16 postcss-value-parser@4.2.0: {} @@ -13174,8 +13155,6 @@ snapshots: ip: 2.0.0 smart-buffer: 4.2.0 - source-map-js@1.2.0: {} - source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 diff --git a/src/module.ts b/src/module.ts index 11a35545..f1164aec 100644 --- a/src/module.ts +++ b/src/module.ts @@ -17,17 +17,28 @@ import { IonicBuiltInComponents, IonicHooks } from './imports' import { setupUtilityComponents } from './parts/components' import { useCSSSetup } from './parts/css' -import { setupIcons } from './parts/icons' +import { setupIonIcons } from './parts/icons/ionicons' +import { setupIonicIconsSvg } from './parts/icons/ionic-icons-svg' import { setupMeta } from './parts/meta' import { setupPWA } from './parts/pwa' import { setupRouter } from './parts/router' +export interface ModuleOptionIconSvg { + enable?: boolean + directoryAsNamespace?: boolean +} + +export interface ModuleOptionIcon { + ionicons?: boolean + svg?: ModuleOptionIconSvg +} + export interface ModuleOptions { integrations?: { router?: boolean pwa?: boolean meta?: boolean - icons?: boolean + icons?: ModuleOptionIcon } css?: { core?: boolean @@ -93,7 +104,13 @@ export default defineNuxtModule({ meta: true, pwa: true, router: true, - icons: true, + icons: { + ionicons: true, + svg: { + enable: false, + directoryAsNamespace: true, + }, + }, }, css: { core: true, @@ -205,8 +222,13 @@ export default defineNuxtModule({ } // Add auto-imported icons - if (options.integrations?.icons) { - await setupIcons() + if (options.integrations?.icons?.ionicons) { + await setupIonIcons() + } + + // Add auto-imported custom icons + if (options.integrations?.icons?.svg?.enable) { + setupIonicIconsSvg(options.integrations?.icons?.svg) } if (options.integrations?.meta) { diff --git a/src/parts/icons/ionic-icons-svg.ts b/src/parts/icons/ionic-icons-svg.ts new file mode 100644 index 00000000..83b9c099 --- /dev/null +++ b/src/parts/icons/ionic-icons-svg.ts @@ -0,0 +1,123 @@ +import fs from 'node:fs' +import { defineUnimportPreset } from 'unimport' +import { useNuxt, addTemplate, addImportsSources } from '@nuxt/kit' +import { join, resolve, basename, sep } from 'pathe' +import svgo from 'svgo' +import type { ModuleOptionIconSvg } from '../../module' + +const getIcons = (dir: string): string[] => { + const items = fs.readdirSync(dir) + const files: string[] = [] + + for (const item of items) { + const pathFile = join(dir, item) + if (fs.existsSync(pathFile) && fs.lstatSync(pathFile).isDirectory()) { + files.push(...getIcons(pathFile)) + } + else if (pathFile.endsWith(`.svg`)) { + files.push(pathFile) + } + } + return files +} + +export const setupIonicIconsSvg = async (options: ModuleOptionIconSvg) => { + // init + const nuxt = useNuxt() + const newIcons: Array<{ name: string, path: string }> = [] + const fileNameConfigIonicIcons = 'ionic-icons.ts' + const pathConfigIonicIcons = resolve(nuxt.options.buildDir, fileNameConfigIonicIcons) + const pathIonicIcons = join(nuxt?.options?.rootDir, `assets/ionic-icons`) + if (!fs.existsSync(pathIonicIcons)) { + fs.mkdir(pathIonicIcons, { recursive: true }, (err) => { + if (err) { + // note: this does NOT get triggered if the directory already existed + console.info( + 'Unfortunately, a problem has occurred. We cannot create the ionic-icons folder. Please create the ionic-icons folder manually in the assets path.', + ) + } + }) + } + if (fs.existsSync(pathIonicIcons)) { + const icons = getIcons(pathIonicIcons) + + // check length + if (Object.keys(icons).length > 0) { + for (const icon of icons) { + let fileName = basename(icon) + if (options.directoryAsNamespace) { + const segments = icon.replace(pathIonicIcons, '').split(sep) + segments.splice(0, 1) + fileName = segments.map((name: string) => name[0].toUpperCase() + name.slice(1)).join('').replace('.svg', '') + } + else { + fileName = fileName.replace('.svg', '').toUpperCase() + } + fileName = fileName + .split('-') + .map((name: string) => name[0].toUpperCase() + name.slice(1)) + .join('') + newIcons.push({ + name: fileName, + path: icon, + }) + } + + let fileText = `/** +* @description Generated by @nuxtjs/ionic +* @author rasool-deldar +*/\n` + let count = 0 + for (const newIcon of newIcons) { + fs.readFile(newIcon.path, 'utf8', async (err, data) => { + if (err) console.log(err) + const resultSvgo = svgo.optimize(data, { + multipass: true, + js2svg: { + indent: 1, + pretty: false, + }, + plugins: [ + 'cleanupAttrs', + 'minifyStyles', + 'removeDoctype', + 'removeEditorsNSData', + 'removeXMLProcInst', + { + name: 'removeComments', + params: { + preservePatterns: ['