diff --git a/example/public/images/static-test.gif b/example/public/images/static-test.gif
new file mode 100644
index 0000000..5b33f7e
Binary files /dev/null and b/example/public/images/static-test.gif differ
diff --git a/example/public/images/static-test.jpg b/example/public/images/static-test.jpg
new file mode 100644
index 0000000..f67eb23
Binary files /dev/null and b/example/public/images/static-test.jpg differ
diff --git a/example/public/images/static-test.png b/example/public/images/static-test.png
new file mode 100644
index 0000000..8597e68
Binary files /dev/null and b/example/public/images/static-test.png differ
diff --git a/example/public/images/static-test.svg b/example/public/images/static-test.svg
new file mode 100644
index 0000000..c8fcefb
--- /dev/null
+++ b/example/public/images/static-test.svg
@@ -0,0 +1,340 @@
+
+
+
\ No newline at end of file
diff --git a/example/src/App.vue b/example/src/App.vue
index f3da116..537a09b 100644
--- a/example/src/App.vue
+++ b/example/src/App.vue
@@ -1,9 +1,20 @@
@@ -14,6 +25,7 @@
import jpg from './assets/test.jpg';
import png from './assets/test.png';
import svg from './assets/test.svg';
+
export default defineComponent({
name: 'App',
components: {},
diff --git a/src/index.ts b/src/index.ts
index 48d2d3e..d683487 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,8 +1,8 @@
import type { Plugin, ResolvedConfig } from 'vite';
import type { VitePluginImageMin } from './types';
import path from 'path';
-import { normalizePath } from 'vite';
-import { isNotFalse, isBoolean, isRegExp, isFunction } from './utils';
+import fs from 'fs-extra';
+import { isNotFalse, isBoolean, isRegExp, isFunction, readAllFiles } from './utils';
import chalk from 'chalk';
import { debug as Debug } from 'debug';
@@ -21,6 +21,7 @@ const extRE = /\.(png|jpeg|gif|jpg|bmp|svg)$/i;
const exportFn = (options: VitePluginImageMin = {}): Plugin => {
let outputPath: string;
+ let publicDir: string;
let config: ResolvedConfig;
const emptyPlugin: Plugin = {
@@ -34,15 +35,46 @@ const exportFn = (options: VitePluginImageMin = {}): Plugin => {
}
debug('plugin options:', options);
+
+ const mtimeCache = new Map();
let tinyMap = new Map();
+ async function processFile(filePath: string, buffer: Buffer) {
+ let content:Buffer;
+
+ try {
+ content = await imagemin.buffer(buffer, {
+ plugins: getImageminPlugins(options),
+ });
+
+ const size = content.byteLength,
+ oldSize = buffer.byteLength;
+
+ tinyMap.set(filePath, {
+ size: size / 1024,
+ oldSize: oldSize / 1024,
+ ratio: size / oldSize - 1,
+ });
+
+ return content;
+ } catch (error) {
+ config.logger.error('imagemin error:' + filePath);
+ }
+ }
+
return {
...emptyPlugin,
apply: 'build',
enforce: 'post',
- configResolved(resolvedConfig) {
+ configResolved(resolvedConfig) {
config = resolvedConfig;
- outputPath = path.join(config.root, config.build.outDir);
+ outputPath = config.build.outDir;
+
+ // get public static assets directory: https://vitejs.dev/guide/assets.html#the-public-directory
+ if (typeof config.publicDir === 'string') {
+ publicDir = config.publicDir;
+ }
+
debug('resolvedConfig:', resolvedConfig);
},
async generateBundle(_options, bundler) {
@@ -60,25 +92,8 @@ const exportFn = (options: VitePluginImageMin = {}): Plugin => {
}
const handles = files.map(async (filePath: string) => {
- let source = (bundler[filePath] as any).source;
-
- const fullFilePath = path.resolve(outputPath, filePath);
- let content: Buffer | null = null;
- try {
- content = await imagemin.buffer(source, {
- plugins: getImageminPlugins(options),
- });
- } catch (error) {
- console.log(error);
- config.logger.error('imagemin error:' + fullFilePath);
- }
- const oldSize = source.length;
- const size = content?.byteLength ?? 0;
- tinyMap.set(fullFilePath, {
- size: size / 1024,
- oldSize: oldSize / 1024,
- ratio: size / oldSize - 1,
- });
+ let source = (bundler[filePath] as any).source;
+ const content = await processFile(filePath, source);
if (content) {
(bundler[filePath] as any).source = content;
}
@@ -86,7 +101,39 @@ const exportFn = (options: VitePluginImageMin = {}): Plugin => {
await Promise.all(handles);
},
- closeBundle() {
+ async closeBundle() {
+ if (publicDir) {
+ const files:string[] = [];
+
+ // try to find any static images in original static folder
+ readAllFiles(publicDir).forEach((file) => {
+ filterFile(file, filter) && files.push(file);
+ });
+
+ if (files.length) {
+ const handles = files.map(async (publicFilePath: string) => {
+ // now convert the path to the output folder
+ const filePath = publicFilePath.replace(publicDir + '/', '');
+ const fullFilePath = path.join(outputPath, filePath);
+
+ const { mtimeMs } = await fs.stat(fullFilePath);
+ if (mtimeMs <= (mtimeCache.get(filePath) || 0)) {
+ return;
+ }
+
+ const buffer = await fs.readFile(fullFilePath);
+ const content = await processFile(filePath, buffer);
+
+ if (content) {
+ await fs.writeFile(fullFilePath, content);
+ mtimeCache.set(filePath, Date.now());
+ }
+ });
+
+ await Promise.all(handles);
+ }
+ }
+
if (verbose) {
handleOutputLogger(config, tinyMap);
}
@@ -114,10 +161,6 @@ function handleOutputLogger(
recordMap.forEach((value, name) => {
let { ratio, size, oldSize } = value;
- const rName = normalizePath(name).replace(
- normalizePath(`${config.root}/${config.build.outDir}/`),
- ''
- );
ratio = Math.floor(100 * ratio);
const fr = `${ratio}`;
@@ -126,8 +169,8 @@ function handleOutputLogger(
const sizeStr = `${oldSize.toFixed(2)}kb / tiny: ${size.toFixed(2)}kb`;
config.logger.info(
- chalk.dim(path.basename(config.build.outDir) + '/') +
- chalk.blueBright(rName) +
+ chalk.dim(path.basename(config.build.outDir)) + '/' +
+ chalk.blueBright(name) +
' '.repeat(2 + maxKeyLength - name.length) +
chalk.gray(`${denseRatio} ${' '.repeat(valueKeyLength - fr.length)}`) +
' ' +
@@ -137,6 +180,7 @@ function handleOutputLogger(
config.logger.info('\n');
}
+
function filterFile(file: string, filter: RegExp | ((file: string) => boolean)) {
if (filter) {
const isRe = isRegExp(filter);
diff --git a/src/utils.ts b/src/utils.ts
index 6a5ce84..9022c3e 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,3 +1,6 @@
+import fs from 'fs';
+import path from 'path';
+
export const isFunction = (arg: unknown): arg is (...args: any[]) => any =>
typeof arg === 'function';
@@ -15,3 +18,37 @@ export const isNotFalse = (arg: unknown): arg is boolean => {
export const isRegExp = (arg: unknown): arg is RegExp =>
Object.prototype.toString.call(arg) === '[object RegExp]';
+
+
+ /*
+ * Read all files in the specified folder, filter through regular rules, and return file path array
+ * @param root Specify the folder path
+ * [@param] reg Regular expression for filtering files, optional parameters
+ * Note: It can also be deformed to check whether the file path conforms to regular rules. The path can be a folder or a file. The path that does not exist is also fault-tolerant.
+ */
+export function readAllFiles(root: string, reg?: RegExp) {
+ let resultArr: string[] = [];
+ try {
+ if (fs.existsSync(root)) {
+ const stat = fs.lstatSync(root);
+ if (stat.isDirectory()) {
+ // dir
+ const files = fs.readdirSync(root);
+ files.forEach(function (file) {
+ const t = readAllFiles(path.join(root, '/', file), reg);
+ resultArr = resultArr.concat(t);
+ });
+ } else {
+ if (reg !== undefined) {
+ if (isFunction(reg.test) && reg.test(root)) {
+ resultArr.push(root);
+ }
+ } else {
+ resultArr.push(root);
+ }
+ }
+ }
+ } catch (error) {}
+
+ return resultArr;
+}
\ No newline at end of file