Skip to content

Commit

Permalink
feat: add domInsertionStrategy option
Browse files Browse the repository at this point in the history
  • Loading branch information
Jevon617 committed May 23, 2024
1 parent e28ade7 commit 504dc41
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 107 deletions.
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,15 @@
### React
![image](./images/intellisense-react.jpg)


## Features

* **intelligent**: When using components, the name of the SVG file will be prompted with Typescript
* **HMR**: HMR for svg file
* **Vue & React**: Support Vue&React and automatically detect project types.
* **Tree-shaking**: Only bundle the icons you really use since **v0.5.0**.
* **SSR**: Support SSR since **v0.6.0**.


* **SSR**: Support SSR since **v0.6.0** with `option.domInsertionStrategy = replaceHtml`(default).

## Installation
## Installation

```shell
yarn add unplugin-svg-component -D
Expand Down Expand Up @@ -54,7 +51,6 @@ export default defineConfig({
```
<br></details>


<details>
<summary>Vue-cli config</summary><br>

Expand Down Expand Up @@ -139,7 +135,7 @@ import SvgIcon from '~virtual/svg-component'
function App() {
return (
<div className="logo">
<SvgIcon name='icon-react'></SvgIcon>
<SvgIcon name="icon-react"></SvgIcon>
</div>
)
}
Expand Down Expand Up @@ -174,6 +170,7 @@ import type { SvgName } from '~virtual/svg-component'
| treeShaking | `boolean` | false | whether enable tree-shaking |
| scanGlob | `string[]` | [look this way](./src/core/utils.ts/#L41) | the glob pattern used in tree-shaking |
| scanStrategy | `text \| component \| (code: string[], options: Options)=>string[]` | component | the strategy used for tree-shaking will not be bundled into the final bundle if it is not successfully matched. The `text` option indicates matching by svg name, so you should ensure the uniqueness of your svg name (you can consider customizing it with the 'symbolIdFormatter' option) to avoid erroneous tree-shaking, while the default value of the `component` option indicates matching by component as a whole, In addition, you can also implement a tree-shaking strategy by passing a function whose return value represents the set of SVG icons used. |
| domInsertionStrategy | `replaceHtml \| dynamic` | `replaceHtml` | controls the method of injecting SVG elements, `replaceHtml(default)`: Injects the SVG elements by replacing the HTML string in server. `dynamic`: Injects the SVG elements through JavaScript dynamically in client. **Warning: if you are in ssr mode, you should use `replaceHtml` strategy.**|

## Typescript support
```json
Expand All @@ -198,7 +195,6 @@ You can refer to [examples](./examples) as required. Note that the configuration
## License
MIT License © 2022-PRESENT [Jevon617](https://github.com/Jevon617)


<!-- Markdown link & img dfn's -->
[npm-image]: https://img.shields.io/npm/v/unplugin-svg-component.svg?style=flat-square
[npm-url]: https://npmjs.org/package/unplugin-svg-component
Expand Down
12 changes: 4 additions & 8 deletions README.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,15 @@
### React
![image](./images/intellisense-react.jpg)


## 功能

* **智能提示**: 使用组件时, 配合 Typescript 会提示出 svg 文件名称
* **热更新**: svg 文件的增删改操作, 都会实时显示于页面上, 无需刷新浏览器
* **Vue & React 支持**: 自动检测项目类型
* **Tree-shaking**: 从**v0.5.0**版本开始, 生产环境只会打包你用到的 svg 图标
* **SSR**: 从**v0.6.0**版本开始支持SSR


* **SSR**: 从**v0.6.0**版本开始支持SSR, 通过开启 `option.domInsertionStrategy = replaceHtml` 这个选项(默认开启).

## 安装
## 安装

```shell
yarn add unplugin-svg-component -D
Expand All @@ -53,7 +50,6 @@ export default defineConfig({
```
<br></details>


<details>
<summary>Vue-cli config</summary><br>

Expand Down Expand Up @@ -138,7 +134,7 @@ import SvgIcon from '~virtual/svg-component'
function App() {
return (
<div className="logo">
<SvgIcon name='icon-react'></SvgIcon>
<SvgIcon name="icon-react"></SvgIcon>
</div>
)
}
Expand Down Expand Up @@ -172,6 +168,7 @@ import type { SvgName } from '~virtual/svg-component'
| treeShaking | `boolean` | true | 是否开启tree-shaking |
| scanGlob | `string[]` | [code](./src/core/utils.ts/#L41) | 用于 tree-shaking 的模式匹配路径 |
| scanStrategy | `text \| component \| (code: string[], options: Options)=>string[]` | component | 用于 tree-shaking 的模式匹配策略, 未匹配成功则不会打包到最终的产物中去, `text`选项表示按图标名称匹配, 所以你应该保证你图标名称的唯一性(可以考虑用`symbolIdFormatter`选项定制),以此避免错误的tree-shaking, 而默认值`component`表示的是按组件这一整体进行匹配, 此外你也可以通过传递函数的方式来实现 tree-shaking 策略, 函数的返回值表示用到的 svg 图标合集。|
| domInsertionStrategy | `replaceHtml \| dynamic` | `replaceHtml` | 控制注入SVG元素的方法,`replaceHtml(默认)`:在服务端通过替换HTML字符串来注入SVG元素; `dynamic`:在浏览器端,通过JavaScript动态地注入SVG元素。**警告:如果您处于服务器端渲染(ssr)模式,您应该使用`replaceHtml`策略**|

## Typescript 支持
```json
Expand All @@ -196,7 +193,6 @@ import type { SvgName } from '~virtual/svg-component'
## License
MIT License © 2022-PRESENT [Jevon617](https://github.com/Jevon617)


<!-- Markdown link & img dfn's -->
[npm-image]: https://img.shields.io/npm/v/unplugin-svg-component.svg?style=flat-square
[npm-url]: https://npmjs.org/package/unplugin-svg-component
Expand Down
1 change: 1 addition & 0 deletions examples/vue3-vite/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default defineConfig({
vueVersion: 3,
scanStrategy: 'component',
treeShaking: true,
domInsertionStrategy: 'dynamic',
}),
],
base: './',
Expand Down
47 changes: 36 additions & 11 deletions src/core/generator.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import path from 'node:path'
import fs from 'node:fs/promises'
import { getPackageInfo, importModule, isPackageExists } from 'local-pkg'
import type { Options, VueVersion } from '../types'
import type { Options, SvgSpriteInfo, VueVersion } from '../types'
import { dts, golbalDts, reactDts, reactTemplate, template } from './snippets'
import { replace, transformStyleStrToObject } from './utils'
import { LOAD_EVENT } from './constants'

export async function genCode(options: Options, symbolIds: Set<string>, isDev = false) {
export async function genCode(options: Options, spriteInfo: SvgSpriteInfo, isDev = false) {
const { svgSpriteDomId } = options
const { symbolIds, sprite } = spriteInfo

const isDynamic = options.domInsertionStrategy === 'dynamic'

const insertSvgCode = isDynamic ? genInsertSvgCode(sprite) : ''
const componentCode = await genComponentCode(options)

// only generate dts in serve
Expand All @@ -28,14 +33,34 @@ if (import.meta.hot) {
const symbolIdsCode = `
export const svgNames = ["${[...symbolIds].join('","')}"]
`

const code = `
${componentCode}
${symbolIdsCode}
${insertSvgCode}
${isDev ? hmrCode : ''}
`
return code
}

export function genInsertSvgCode(sprite: string) {
return `
if (typeof window !== 'undefined') {
function mountSvg() {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = '${sprite.trim()}';
window.document.body.append(tempDiv.firstElementChild);
}
if(document.readyState === 'loading') {
window.document.addEventListener('DOMContentLoaded', mountSvg);
} else {
mountSvg();
}
}
`
}

export function genDts(symbolIds: Set<string>, options: Options) {
const isVue = isVueProject(options)
const dtsPath = path.resolve(options.dtsDir!, './svg-component.d.ts')
Expand Down Expand Up @@ -79,15 +104,15 @@ async function genComponentCode(options: Options) {

return `${templateCode}
\nexport default {
name: "${componentName}",
props: {
name: {
type: String,
required: true
}
},
render
}`
name: "${componentName}",
props: {
name: {
type: String,
required: true
}
},
render
}`
}

async function compileVueTemplate(template: string, vueVerison: string): Promise<string> {
Expand Down
11 changes: 8 additions & 3 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import watchIconDir from './watcher'

let isBuild = false
let isWebpack = false
let isDynamicStrategy = false
let spriteInfo: SvgSpriteInfo
let transformPluginContext: any

Expand All @@ -19,6 +20,7 @@ const unplugin = createUnplugin<Options>(options => ({
async buildStart() {
options = resolveOptions(options)
spriteInfo = await createSvgSprite(options, isBuild)
isDynamicStrategy = options.domInsertionStrategy === 'dynamic'
},
resolveId(id: string) {
if (id === MODULE_NAME)
Expand All @@ -29,10 +31,13 @@ const unplugin = createUnplugin<Options>(options => ({
},
async load() {
return isBuild || isWebpack
? (await genCode(options, spriteInfo.symbolIds))
? (await genCode(options, spriteInfo))
: ''
},
webpack(compiler) {
if (isDynamicStrategy)
return

isWebpack = true
isBuild = compiler.options.mode === 'production'

Expand Down Expand Up @@ -64,7 +69,7 @@ const unplugin = createUnplugin<Options>(options => ({
if (req.url?.endsWith(`/@id/${MODULE_NAME}`)) {
watchIconDir(options, server, spriteInfo)

const code = await genCode(options, spriteInfo.symbolIds, true)
const code = await genCode(options, spriteInfo, true)

const importAnalysisTransform = server.config.plugins.find(
plugin => plugin.name === 'vite:import-analysis',
Expand Down Expand Up @@ -96,7 +101,7 @@ const unplugin = createUnplugin<Options>(options => ({
})
},
async transformIndexHtml(html) {
if (html.includes(options.svgSpriteDomId!))
if (isDynamicStrategy || html.includes(options.svgSpriteDomId!))
return html
return html.replace(/<\/body>/, `${spriteInfo.sprite}</body>`)
},
Expand Down
7 changes: 2 additions & 5 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import process from 'node:process'
import path from 'node:path'
import Sprite from 'svg-sprite'
import fg from 'fast-glob'
import type { Options, SvgSpriteInfo } from '../types'
import { addSymbol } from './sprite'
import type { Options } from '../types'

export function debounce(fn: (...args: any) => void, delay: number) {
let timer: NodeJS.Timeout
Expand Down Expand Up @@ -57,6 +53,7 @@ export function resolveOptions(options: Options): Options {
'**/*.jsx',
],
scanStrategy: 'component',
domInsertionStrategy: 'replaceHtml',
}
return {
...defaultOptions,
Expand Down
1 change: 0 additions & 1 deletion src/core/watcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { sep } from 'node:path'
import type { ViteDevServer } from 'vite'
import fg from 'fast-glob'
import colors from 'picocolors'
import type { Options, SvgSpriteInfo } from '../types'
import { debounce } from './utils'
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export interface Options {
treeShaking?: boolean
scanGlob?: string[]
scanStrategy?: 'text' | 'component' | ((code: string[], options: Options) => string[])
/**
* Controls the method of injecting SVG elements. Possible values:
* 'replaceHtml'(default): Injects the SVG elements by replacing the HTML string.
* 'dynamic': Injects the SVG elements through JavaScript dynamically.
* Warning: if you are in ssr mode, you should use 'replaceHtml' strategy.
*/
domInsertionStrategy?: 'replaceHtml' | 'dynamic'
}

export type VueVersion = 2 | 3 | 'auto'
Expand Down
47 changes: 0 additions & 47 deletions test/__snapshots__/gen-module-code.test.ts.snap

This file was deleted.

24 changes: 0 additions & 24 deletions test/gen-module-code.test.ts

This file was deleted.

0 comments on commit 504dc41

Please sign in to comment.