Skip to content

Commit

Permalink
chore: rewrite cache fn
Browse files Browse the repository at this point in the history
  • Loading branch information
vtrbo committed Aug 1, 2023
1 parent b717257 commit 74fd394
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 65 deletions.
6 changes: 3 additions & 3 deletions examples/vite-vue2/App.vue
@@ -1,11 +1,11 @@
<script lang="ts">
import Account from '~images/account.png'
import Account 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
import TestAccount from '~images/test/account?png'
import TestAccount from '~images/test-account?png'
import TestPassword from '~images/test/password.png?width=100'
import Test2Account from '~images/test/test/account?png&height=100'
import Test2Account from '~images:normal/test-test-account?png&height=100'
import Test2Password from '~images/test/test/password.png?gif&width=100&height=100'
export default {
Expand Down
8 changes: 4 additions & 4 deletions examples/vite-vue3/App.vue
@@ -1,17 +1,17 @@
<script lang="ts" setup>
// import Account from '~images/account.png'
import Account from '~images:others/account.png'

Check warning on line 2 in examples/vite-vue3/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-vue3/App.vue

View workflow job for this annotation

GitHub Actions / lint

'~images:others/account.png' imported multiple times
import TestAccount from '~images/test/account?png'
import TestAccount from '~images/test-account?png'
import TestPassword from '~images/test/password.png?width=100'
import Test2Account from '~images/test/test/account?png&height=100'
import Test2Account from '~images:normal/test-test-account?png&height=100'
import Test2Password from '~images/test/test/password.png?gif&width=100&height=100'
</script>

<template>
<div>
<img-normal-account width="150" @click="() => console.log('on click')" />
<Account width="150" @click="() => console.log('on click')" />
<AccountS />
<OA />
<Password />
Expand Down
4 changes: 2 additions & 2 deletions examples/vite-vue3/vite.config.ts
Expand Up @@ -19,7 +19,7 @@ const config: UserConfig = {
Components({
resolvers: [
(name: string) => {

Check warning on line 21 in examples/vite-vue3/vite.config.ts

View workflow job for this annotation

GitHub Actions / lint

'name' is defined but never used. Allowed unused args must match /^_/u
debug('name =>', name)
// debug('name =>', name)
const dirs = [{ alias: 'normal', path: 'src/assets/images' }, { alias: 'others', path: 'src/assets/others' }]
const extensions = ['jpg', 'jpeg', 'png', 'svg', 'gif', 'webp'].join(',')
const globs = dirs.map(dir => `${dir.path}/**/*.{${extensions}}`)
Expand All @@ -28,7 +28,7 @@ const config: UserConfig = {
onlyFiles: true,
cwd: process.cwd(),
})
debug('files => ', files)
// debug('files => ', files)
return ''
},
],
Expand Down
79 changes: 60 additions & 19 deletions src/core/context.ts
Expand Up @@ -4,9 +4,13 @@ import { parse } from 'node:path'
import fg from 'fast-glob'
import type { ViteDevServer } from 'vite'
import { ensurePrefix, toCamelCase } from '@vtrbo/utils/string'
import createDebugger from 'debug'
import type { Options, ResolvedOptions } from '../types'
import { UNPLUGIN_NAME } from '../constants'
import { resolveOptions } from './options'
import { getAlias, getImageName } from './utils'
import { getAlias, getName } from './utils'

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

export interface Image {
file: string
Expand All @@ -22,7 +26,7 @@ export class Context {

private _searched = false

private _cache = new Map<string, Image[]>()
private _images = new Map<string, Map<string, Image[]>>()

private _server: ViteDevServer | undefined

Expand All @@ -32,42 +36,79 @@ export class Context {
this.options = resolveOptions(rawOptions)
}

searchGlob() {
searchImages() {
if (this._searched)
return

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

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

for (const imageFile of imageFiles) {
const alias = getAlias(imageFile, this.options)
const camelCaseAlias = toCamelCase(alias, true)

for (const file of files) {
const alias = getAlias(file, this.options)
const name = getImageName(file, this.options)
const ext = parse(`/${file}`).ext.slice(1)
const name = getName(imageFile, this.options)
const camelCaseName = toCamelCase(name, true)
this._cache.set(camelCaseName, [
...(this._cache.get(camelCaseName) || []),

let nameMap = new Map<string, Image[]>()
if (this._images.has(camelCaseAlias)) {
const cacheNameMap = this._images.get(camelCaseAlias)
if (cacheNameMap)
nameMap = cacheNameMap
}

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

nameMap.set(camelCaseName, [
...(nameMap.get(camelCaseName) || []),
{
file: ensurePrefix(file, '/'),
file: ensurePrefix(imageFile, '/'),
alias,
name,
ext,
},
])
}
debug('searchImages nameMap =>', nameMap)

this._searched = true
this._images.set(camelCaseAlias, nameMap)
debug('searchImages this._images =>', this._images)

this._searched = true
}
}

searchByName(name: string) {
searchImage(alias: string, name: string, extension: string) {
const camelCaseAlias = toCamelCase(alias, true)
if (!this._images.has(camelCaseAlias))
return null

const camelCaseName = toCamelCase(name, true)
if (this._cache.has(camelCaseName))
return this._cache.get(camelCaseName)
const aliasImages = this._images.get(camelCaseAlias)
if (!aliasImages || !aliasImages.has(camelCaseName))
return null

const nameImages = aliasImages.get(camelCaseName)
if (!nameImages || !nameImages.length)
return null

debug('searchImage nameImages =>', nameImages)

if (!extension)
return nameImages[0]

const extImage = nameImages.find(image => image.ext === extension)
if (!extImage)
return null // TODO 这里当后缀无法匹配的时候,是不是可以考虑返回第一个

return extImage
}

setRoot(root: string) {
Expand All @@ -89,10 +130,10 @@ export class Context {
setupWatcher(watcher: fs.FSWatcher) {
watcher
.on('add', (path) => {

debug('setupWatcher add path =>', path)
})
.on('unlink', (path) => {

debug('setupWatcher unlink path =>', path)
})
}
}
37 changes: 11 additions & 26 deletions src/core/loader.ts
@@ -1,8 +1,11 @@
import { DEFAULT_ALIAS, DEFAULT_PATH } from '../constants'
import createDebugger from 'debug'
import { DEFAULT_ALIAS, DEFAULT_PATH, UNPLUGIN_NAME } from '../constants'
import type { ResolvedOptions } from '../types'
import type { Context, Image } from './context'
import type { Context } from './context'
import { compilers } from './compilers'

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

const URL_PREFIXES = [
'/~images/',
'/~images:',
Expand Down Expand Up @@ -113,31 +116,12 @@ export function resolveImagePath(imagePath: string, options: ResolvedOptions): R
}
}

export async function generateComponent(resolved: ResolvedImagePath, options: ResolvedOptions, context: Context) {
let images = context.searchByName(resolved.name)
if (!images)
throw new Error('Cannot find images')

images = images.filter(image => image.alias === resolved.alias)
if (!images.length)
throw new Error('Cannot find image')

let image: Image
export function generateComponent(resolved: ResolvedImagePath, options: ResolvedOptions, context: Context) {
const image = context.searchImage(resolved.alias, resolved.name, resolved.ext)
debug('generateComponent image =>', image)

if (images.length === 1) {
image = images[0]
}
else {
if (resolved.ext) {
const tmpImage = images.find(image => image.ext === resolved.ext)
if (!tmpImage)
throw new Error('Cannot find image')
image = tmpImage
}
else {
throw new Error('Cannot find image')
}
}
if (!image)
return null

return compilers[options.compiler](image, resolved)
}
Expand All @@ -146,5 +130,6 @@ export function generateComponentFromPath(path: string, options: ResolvedOptions
const resolved = resolveImagePath(path, options)
if (!resolved)
return null
debug('generateComponentFromPath resolved =>', resolved)
return generateComponent(resolved, options, context)
}
12 changes: 8 additions & 4 deletions src/core/options.ts
Expand Up @@ -44,10 +44,14 @@ export function resolveOptions(options: Options): ResolvedOptions {
}
}

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

debug('resolveOptions dirs =>', dirs)

const extensions = options?.extensions || DEFAULT_EXTENSIONS

Expand Down
2 changes: 1 addition & 1 deletion src/core/utils.ts
Expand Up @@ -4,7 +4,7 @@ import { DEFAULT_ALIAS, UNPLUGIN_NAME } from '../constants'

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

Check warning on line 5 in src/core/utils.ts

View workflow job for this annotation

GitHub Actions / lint

'debug' is assigned a value but never used. Allowed unused vars must match /^_/u

export function getImageName(path: string, options: ResolvedOptions) {
export function getName(path: string, options: ResolvedOptions) {
let name = ''
for (const dir of options.dirs) {
if (path.startsWith(dir.path)) {
Expand Down
18 changes: 12 additions & 6 deletions src/index.ts
@@ -1,30 +1,36 @@
import { createUnplugin } from 'unplugin'
import chokidar from 'chokidar'

import createDebugger from 'debug'
import type { ResolvedConfig, ViteDevServer } from 'vite'
import type { Options } from './types'
import { UNPLUGIN_NAME } from './constants'
import { resolveOptions } from './core/options'
import { generateComponentFromPath, generateImagePath, isImagePath, normalizeImagePath } from './core/loader'
import { Context } from './core/context'

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

const unplugin = createUnplugin<Options>((options = {}) => {
const ctx = new Context(options)

return {
name: UNPLUGIN_NAME,
enforce: 'pre',
resolveId(id) {
if (isImagePath(id))
return generateImagePath(normalizeImagePath(id))
if (isImagePath(id)) {
const generateId = generateImagePath(normalizeImagePath(id))
debug('resolve id =>', generateId)
return generateId
}
return null
},
loadInclude(id) {
return isImagePath(id)
},
load(id) {
if (isImagePath(id)) {
const config = resolveOptions(options)
debug('load id =>', id)
const config = ctx.options
debug('load config =>', config)
return generateComponentFromPath(id, config, ctx)
}
return null
Expand All @@ -33,7 +39,7 @@ const unplugin = createUnplugin<Options>((options = {}) => {
configResolved(config: ResolvedConfig) {
ctx.setRoot(config.root)

ctx.searchGlob()
ctx.searchImages()

if (config.build.watch && config.command === 'build')
ctx.setupWatcher(chokidar.watch(ctx.options.dirs.map(m => m.path)))
Expand Down

0 comments on commit 74fd394

Please sign in to comment.