Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/vite-vue3-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@vitejs/plugin-vue": "^3.1.2",
"typescript": "^4.8.4",
"vite": "^3.1.8",
"vite-imagetools": "^6.2.4",
"vite-plugin-image-presets": "^0.3.2",
"vue-tsc": "^1.0.8"
}
Expand Down
27 changes: 19 additions & 8 deletions apps/vite-vue3-project/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { extname } from 'node:path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { imagetools } from 'vite-imagetools';
import imagePresets, { formatPreset } from 'vite-plugin-image-presets';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
imagePresets({
modern: formatPreset({
formats: {
avif: {},
webp: {},
original: {},
// imagePresets({
// modern: formatPreset({
// formats: {
// avif: {},
// webp: {},
// original: {},
// },
// loading: 'lazy',
// }),
// }),
// 验证否兼容vite-imagetools
imagetools({
defaultDirectives: (url) => {
return new URLSearchParams({
format: 'avif;webp;' + extname(url.pathname).slice(1),
as: 'picture',
});
},
loading: 'lazy',
}),
}),
],
});
138 changes: 67 additions & 71 deletions packages/picture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,82 +4,60 @@

## 安装

`pnpm install @kwai-explore/picture.vue`
```shell
pnpm install @kwai-explore/picture.vue
```

## 使用

> 建议配合 (vite-plugin-image-presets) 使用
> 建议配合 [vite-imagetools](https://github.com/JonasKruckenberg/imagetools/tree/main/packages/vite) 使用

### 安装 [vite-plugin-image-presets](github.com/ElMassimo/vite-plugin-image-presets)
### 安装

`pnpm add -D vite-plugin-image-presets`
```shell
pnpm add -D vite-imagetools
```

### 建议配置

`vite.config.ts`
需要修改图片资源导出的数据结构。

```ts
// vite.config.ts

import { extname } from 'node:path';
import imagePresets, { formatPreset } from 'vite-plugin-image-presets';

// ...
plugins: [
vue(),
imagePresets({
modern: formatPreset({
formats: {
avif: {},
webp: {},
original: {},
},
loading: 'lazy',
}),
}),
],
export default {
// ...
plugins: [
vue(),
imagePresets({
defaultDirectives: (url) => {
if (url.searchParams.get('preset') === 'modern') {
return new URLSearchParams({
format: 'avif;webp;' + extname(url.pathname).slice(1),
as: 'picture',
});
}
return new URLSearchParams();
},
}),
],
}
```

根据上面的配置添加类型:

`vite-env.d.ts`

这里的文档需要换一下:理论上应该标记的是自己生成的类型

```ts
// xxx.d.ts

declare module '*?preset=modern' {
type SourceOption = {
type: string;
srcset: string;
};
type ImgOption = {
src: string;
// 这下面的属性需要与 vite config 里的 formatPreset 配置同步修改
loading: 'lazy';
};
type PictureOption = [...SourceOption[], ImgOption];

const src: PictureOption;
export default src;
import type { ImageToolsPictureOption } from '@kwai-explore/picture.vue';
const src: ImageToolsPictureOption;
export default src;
}
```

### 在代码中使用

> Picture 组件接受的属性跟 `img` 相同,唯一的例外是 `src` 接收一个数组,一个例子是

```json
[{
type: 'image/webp',
srcset: '/assets/logo.ffc730c4.webp 48w, /assets/logo.1f874174.webp 96w',
},
{
type: 'image/jpeg',
srcset: '/assets/logo.063759b1.jpeg 48w, /assets/logo.81d93491.jpeg 96w',
src: '/assets/logo.81d93491.jpeg',
class: 'img thumb',
loading: 'lazy',
]
```

在我们配置好 `vite-plugin-image-presets` 之后,可以直接在 import 图片的语句后面加一个 query,产出的数据就是上面需要的格式。
在我们配置好 `vite-imagetools` 之后,可以直接在 import 图片的语句后面加一个 query ,产出的数据就是组件需要的格式。

```vue
<script setup lang="ts">
Expand All @@ -92,33 +70,51 @@ import examplePic from './components/example.jpg?preset=modern';
</template>
```

完整支持的属性
### 完整支持的属性

```ts
interface PictureProp {
src: ImgHTMLAttributes[];
// 默认是empty。 color 会展示一个渐变色块的 loading 效果,加上 fade-in 的加载成功的渐变效果。
type PictureProp = {
src: ImageToolsPictureOption | ImageToolsPictureOptionOld;
// 默认是empty
// color 会展示一个渐变色块的 loading 效果,加上 fade-in 的加载成功的渐变效果。
placeholder: 'empty' | 'color';
// Picture 接收的属性会透传给img元素
} & ImgHTMLAttributes;
```

### 在代码中传入数据

Picture 组件接收的 `src` 数据结构示例:

```js
{
sources: {
avif: 'xxx.avif 5304w',
webp: 'xxx.webp 5304w',
},
img: {
src: 'xxx.jpg',
}
}
```

### 建议添加 eslint 规则

```
'vue/no-restricted-html-elements': [
'warn',
{
element: 'img',
message: 'Prefer use of our @kwai-explore/picture.vue component',
},
],
```js
'vue/no-restricted-html-elements': [
'warn',
{
element: 'img',
message: 'Prefer use of our @kwai-explore/picture.vue component',
},
],
```

### 对应的行为

开发环境(`vite dev`) 初次请求图片资源时需要进行格式转换,图片的加载时间比较长。
开发环境(`vite dev`初次请求图片资源时需要进行格式转换,图片的加载时间比较长。

生产环境(`vite build`) 会把需要的图片格式都构建出来。
生产环境(`vite build`会把需要的图片格式都构建出来。

## 兼容性

Expand Down
68 changes: 49 additions & 19 deletions packages/picture/src/components/Picture.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,43 @@ export type ImgOption = {
src: string;
} & SimpleImgHTMLAttributes;

export type ImageToolsPictureOptionImg = {
src: string;
w?: number;
} & SimpleImgHTMLAttributes;

// TODO: 封装 provider 来应对不同的接口
/** vite-imagetools 风格的 picture 数据格式 */
export type ImageToolsPictureOption = {
fallback: {
src: string;
w?: number;
} & SimpleImgHTMLAttributes;
// avif: [{src: 'xxx.avif'}], webp: [{src: xx.webp}]
img: ImageToolsPictureOptionImg;
// 帮助区分 ImageToolsPictureOptionOld
fallback: never;
// avif: 'xxx.avif', webp: 'xx.webp'
sources: {
[key: string]: string;
};
};

// v6.0.0以下
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

实际上是4.x,5.x 不是这样的

export type ImageToolsPictureOptionOld = {
// avif: [{src: 'xxx.avif'}], webp: [{src: xx.webp}]({
sources: {
[key: string]: {
src: string;
w?: number;
}[];
};
img: never;
fallback: ImageToolsPictureOptionImg;
} | {
sources: {
[key: string]: {
src: string;
w?: number;
}[];
};
img: ImageToolsPictureOptionImg;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个old是代表啥,之前支持这样吗?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ImageToolsPictureOptionOld 吗,是 4.x 和 5.x 的类型,跟最新的 6.x 区分了下

fallback: never;
};


Expand All @@ -44,11 +67,11 @@ export interface SimpleImgHTMLAttributes {
type?: string;
}

export type PictureOption = ImagePresetPictureOption | ImageToolsPictureOption;
export type PictureOption = ImagePresetPictureOption | ImageToolsPictureOption | ImageToolsPictureOptionOld;

// 这里的属性其实也有点奇怪...理论上大部分只需要放在最后一个就可以了
// 其实跟生产端不太一样
interface PictureProp {
export interface PictureProp {
src: PictureOption;
// color 会展示一个渐变色块的 loading 效果,加上 fade-in 的加载成功的渐变
placeholder?: 'empty' | 'color';
Expand Down Expand Up @@ -90,22 +113,29 @@ function assertNotNil<T>(v: T, message?: string): asserts v is NonNullable<T> {
throw new Error(message ?? 'Must not be null or undefined');
}
}
function isImageToolsPictureOption(sources: PictureOption): sources is ImageToolsPictureOption | ImageToolsPictureOptionOld {
if (!('fallback' in sources) && !('img' in sources)) {
return false;
}
return 'sources' in sources;
}
const allSources = computed(() => props.src);
const sources = computed<{srcset?: string; type?: string;}[]>(() =>
'fallback' in allSources.value
? Object.entries(allSources.value.sources ?? {}).map(([key, val]) => {
const sources = computed<{srcset?: string; type?: string;}[]>(() => {
if (isImageToolsPictureOption(allSources.value)) {
return Object.entries(allSources.value.sources ?? {}).map(([key, val]) => {
return {
type: `image/${key}`,
srcset: val[0]?.src,
}
})
: allSources.value.slice(0, -1),
);
srcset: typeof val === 'string' ? val : val[0]?.src,
};
});
}
return allSources.value.slice(0, -1);
});
const lastSource = computed(() => {
const res =
'fallback' in allSources.value
? allSources.value.fallback
: allSources.value[allSources.value.length - 1];
const res = isImageToolsPictureOption(allSources.value)
? allSources.value.img ?? allSources.value.fallback
: allSources.value[allSources.value.length - 1]

assertNotNil(res);
return res as ImgOption;
});
Expand Down
Loading