Skip to content

Commit

Permalink
chore: finish resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
vtrbo committed Aug 2, 2023
1 parent 3ed9e47 commit 788048d
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 106 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -78,3 +78,6 @@ dist
# IDE
.idea
.vscode

# generate
**/components.d.ts
9 changes: 6 additions & 3 deletions examples/vite-vue2/App.vue
@@ -1,5 +1,5 @@
<script lang="ts">
import Account from '~images:others/account.png'
import AccountP from '~images:others/account.png'

Check warning on line 2 in examples/vite-vue2/App.vue

View workflow job for this annotation

GitHub Actions / lint

'~images:others/account.png' imported multiple times
import AccountS from '~images/account.svg'
import Password from '~images:normal/password.png'
import OA from '~images:others/account.png'

Check warning on line 5 in examples/vite-vue2/App.vue

View workflow job for this annotation

GitHub Actions / lint

'~images:others/account.png' imported multiple times
Expand All @@ -11,7 +11,7 @@ import Test2Password from '~images/test/test/password.png?gif&width=100&height=1
export default {
name: 'App',
components: {
Account,
AccountP,
AccountS,
Password,
OA,
Expand All @@ -30,7 +30,10 @@ export default {

<template>
<div>
<Account width="150" @click="handleClick" />
<account-svg />
<account-png />
<normal-account />
<AccountP width="150" @click="() => console.log('on click')" />
<AccountS />
<OA />
<Password />
Expand Down
3 changes: 2 additions & 1 deletion examples/vite-vue2/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"scripts": {
"dev": "cross-env DEBUG=unplugin-vue-images:* vite --port 9999",
"dev": "cross-env DEBUG=unplugin-vue-images:resolver vite --port 9999",
"build": "vite build"
},
"dependencies": {
Expand All @@ -10,6 +10,7 @@
"devDependencies": {
"@vitejs/plugin-vue2": "^2.2.0",
"typescript": "^5.1.6",
"unplugin-vue-components": "^0.25.1",
"unplugin-vue-images": "workspace:*",
"vite": "^4.4.7"
}
Expand Down
17 changes: 16 additions & 1 deletion examples/vite-vue2/vite.config.ts
@@ -1,14 +1,29 @@
import type { UserConfig } from 'vite'
import Vue from '@vitejs/plugin-vue2'
import Components from 'unplugin-vue-components/vite'
import VueImages from 'unplugin-vue-images/vite'
import { ImagesResolver } from 'unplugin-vue-images/resolver'

const collectionDirs = [
'src/assets/images',
{ others: 'src/assets/others' },
]

const config: UserConfig = {
plugins: [
Vue(),
VueImages({
dirs: ['src/assets/images', { others: 'src/assets/others' }],
dirs: collectionDirs,
compiler: 'vue2',
}),
Components({
resolvers: [
ImagesResolver({
prefix: false,
dirs: collectionDirs,
}),
],
}),
],
}

Expand Down
2 changes: 1 addition & 1 deletion examples/vite-vue3/App.vue
Expand Up @@ -11,7 +11,7 @@ import Test2Password from '~images/test/test/password.png?gif&width=100&height=1

<template>
<div>
<account />
<account-svg />
<account-png />
<normal-account />
<AccountP width="150" @click="() => console.log('on click')" />
Expand Down
2 changes: 1 addition & 1 deletion examples/vite-vue3/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"scripts": {
"dev": "cross-env DEBUG=unplugin-vue-images:* vite --port 9999",
"dev": "cross-env DEBUG=unplugin-vue-images:resolver vite --port 9999",
"build": "vite build"
},
"dependencies": {
Expand Down
32 changes: 32 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

235 changes: 235 additions & 0 deletions src/core/content.ts
@@ -0,0 +1,235 @@
import process from 'node:process'
import type fs from 'node:fs'
import { parse } from 'node:path'
import { ensurePrefix, toCamelCase, toLinesCase } from '@vtrbo/utils/string'
import { isArray, isObject, isString } from '@vtrbo/utils/fn'
import fg from 'fast-glob'
import createDebugger from 'debug'
import { deepClone } from '@vtrbo/utils/object'
import type { Dir, Options } from '../types'
import { DEFAULT_ALIAS, DEFAULT_EXTENSIONS, DEFAULT_PATH, DEFAULT_PREFIX, UNPLUGIN_NAME } from './constants'
import { getAlias, getName, getNames, pathsEqual, removeSlash } from './utils'

const debug = createDebugger(`${UNPLUGIN_NAME}:content`)

export interface ImagesResolverOptions extends Omit<Options, 'compiler'> {
/**
* Prefix for resolving components name.
* Set '' to disable prefix.
*
* @default 'img'
*/
prefix?: string | false
}

export interface ResolvedImagesResolverOptions extends Omit<Required<ImagesResolverOptions>, 'dirs' | 'prefix'> {
dirs: Dir[]
prefix: string
}

interface Image {
file: string
alias: string
name: string
ext: string
prefix: string
names: string[]
}

export class Content {
root = process.cwd()

options: ResolvedImagesResolverOptions

private _images: Record<string, Record<string, Image[]>> = {}

private _imageFiles: string[] = []

constructor(rawOptions: ImagesResolverOptions) {
this.options = resolveOptions(rawOptions)
}

collectImage(path: string) {
const alias = getAlias(path, this.options)
const camelCaseAlias = toCamelCase(alias, true)

const name = getName(path, this.options)
const camelCaseName = toCamelCase(name, true)

let aliasImages: Record<string, Image[]> = {}
if (camelCaseAlias in this._images) {
const cacheAliasImages = this._images[camelCaseAlias]
if (cacheAliasImages)
aliasImages = deepClone(cacheAliasImages)
}

const ext = parse(`/${path}`).ext.slice(1)

const prefix = this.options.prefix

aliasImages[camelCaseName] = [
...(aliasImages[camelCaseName] || []),
{
file: ensurePrefix(path, '/'),
alias,
name,
ext,
prefix,
names: getNames(prefix, alias, name, ext),
},
]

this._images[camelCaseAlias] = deepClone(aliasImages)
}

addImage(path: string) {
const ext = parse(`/${path}`).ext.slice(1)
if (!this.options.extensions.includes(ext))
return

this.collectImage(path)
}

delImage(path: string) {
const ext = parse(`/${path}`).ext.slice(1)
if (!this.options.extensions.includes(ext))
return

const alias = getAlias(path, this.options)
const camelCaseAlias = toCamelCase(alias, true)

if (!(camelCaseAlias in this._images))
return

const aliasImages = this._images[camelCaseAlias]
if (!aliasImages)
return

const name = getName(path, this.options)
const camelCaseName = toCamelCase(name, true)

if (!(camelCaseName in aliasImages))
return

const nameImages = aliasImages[camelCaseName]
if (!nameImages)
return

const imageIndex = nameImages.findIndex(image => image.name === name && image.ext === ext)
if (imageIndex === -1)
return

nameImages.splice(imageIndex, 1)

aliasImages[camelCaseName] = deepClone(nameImages)

this._images[camelCaseAlias] = deepClone(aliasImages)
}

searchImages() {
const dirs = this.options.dirs
const extensions = this.options.extensions.join(',')
const sources = dirs.map(dir => `${dir.path}/**/*.{${extensions}}`)

const imageFiles = fg.sync(sources, {
ignore: ['**/node_modules/**'],
onlyFiles: true,
cwd: this.root,
})

if (pathsEqual(imageFiles, this._imageFiles))
return

debug('image files =>', imageFiles)

for (const imageFile of imageFiles)
this.collectImage(imageFile)
}

searchImage(name: string) {
const images = []
for (const aliasKey in this._images) {
const aliasImages = this._images[aliasKey]
for (const nameKey in aliasImages)
images.push(...aliasImages[nameKey])
}
for (const image of images) {
if (image.names.includes(name)) {
debug('image =>', image)
return image
}
}
return null
}

setWatcher(watcher: fs.FSWatcher) {
watcher
.on('add', (path) => {
debug('add path =>', path)
this.addImage(path)
})
.on('unlink', (path) => {
debug('unlink path =>', path)
this.delImage(path)
})
}
}

export function resolveOptions(options: ImagesResolverOptions = {}): ResolvedImagesResolverOptions {
const rawPrefix = options.prefix ?? DEFAULT_PREFIX
const prefix = rawPrefix ? `${toLinesCase(rawPrefix)}-` : ''

const dirs: Dir[] = []

if (options?.dirs) {
if (isString(options?.dirs)) {
dirs.push({
alias: DEFAULT_ALIAS,
path: removeSlash(options?.dirs),
})
}
else if (isArray(options?.dirs)) {
options?.dirs.forEach((dir) => {
if (isString(dir)) {
dirs.push({
alias: DEFAULT_ALIAS,
path: removeSlash(dir),
})
}
else {
for (const alias in dir) {
dirs.push({
alias,
path: removeSlash(dir[alias]),
})
}
}
})
}
else if (isObject(options?.dirs)) {
for (const alias in options?.dirs) {
dirs.push({
alias,
path: removeSlash(options?.dirs[alias]),
})
}
}
}

if (!dirs.length) {
dirs.push({
alias: DEFAULT_ALIAS,
path: removeSlash(DEFAULT_PATH),
})
}

const extensions = options?.extensions || DEFAULT_EXTENSIONS

debug('resolver options =>', { prefix, dirs, extensions })

return {
prefix,
dirs,
extensions,
}
}

0 comments on commit 788048d

Please sign in to comment.