├── example.png ├── playground ├── assets │ ├── boat.png │ ├── ant_man.png │ └── favicon.png ├── babel.config.js ├── .gitignore ├── metro.config.js ├── app.json ├── package.json └── App.js ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── release.yml ├── SECURITY.md ├── .releaserc ├── LICENSE ├── src ├── config.ts ├── index.ts └── utils.ts ├── package.json ├── README-CN.md ├── README.md ├── CHANGELOG.md └── tsconfig.json /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChengbo/react-native-imagemin-asset-plugin/HEAD/example.png -------------------------------------------------------------------------------- /playground/assets/boat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChengbo/react-native-imagemin-asset-plugin/HEAD/playground/assets/boat.png -------------------------------------------------------------------------------- /playground/assets/ant_man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChengbo/react-native-imagemin-asset-plugin/HEAD/playground/assets/ant_man.png -------------------------------------------------------------------------------- /playground/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChengbo/react-native-imagemin-asset-plugin/HEAD/playground/assets/favicon.png -------------------------------------------------------------------------------- /playground/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules 8 | yarn.lock 9 | npm-debug.log 10 | yarn-error.log 11 | package-lock.json 12 | 13 | # temp 14 | .compressed-images 15 | 16 | # build 17 | dist -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: 'npm' 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: '/' 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: 'daily' -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 2.x | :white_check_mark: | 11 | | 1.5.x | :white_check_mark: | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | People are welcomed to report a vulnerability by sending an Email to chengbo_x@163.com. Also PR or Issues are welcomed too. 16 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | -------------------------------------------------------------------------------- /playground/metro.config.js: -------------------------------------------------------------------------------- 1 | // Learn more https://docs.expo.io/guides/customizing-metro 2 | const { getDefaultConfig } = require('expo/metro-config'); 3 | 4 | /** @type {import('expo/metro-config').MetroConfig} */ 5 | const config = getDefaultConfig(__dirname); 6 | 7 | // use plugin to compress assets 8 | config.transformer.assetPlugins.push('react-native-imagemin-asset-plugin') 9 | config.transformer.imageminAssetPlugin = { 10 | minimizer: { 11 | implementation: 'imagemin', 12 | options: { 13 | plugins: [ 14 | ['pngquant'], 15 | ] 16 | } 17 | } 18 | } 19 | module.exports = config; 20 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "master", 4 | { 5 | "name": "beta", 6 | "prerelease": true 7 | }, 8 | { 9 | "name": "alpha", 10 | "prerelease": true 11 | } 12 | ], 13 | "plugins": [ 14 | "@semantic-release/commit-analyzer", 15 | "@semantic-release/release-notes-generator", 16 | "@semantic-release/changelog", 17 | "@semantic-release/npm", 18 | [ 19 | "@semantic-release/git", 20 | { 21 | "assets": [ 22 | "package.json", 23 | "CHANGELOG.md" 24 | ], 25 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 26 | } 27 | ], 28 | "@semantic-release/github" 29 | ] 30 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - alpha 7 | - beta 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 18 21 | - name: Install dependencies 22 | run: npm install 23 | - name: Build 24 | run: npm run build 25 | - name: Release 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | run: npx semantic-release -------------------------------------------------------------------------------- /playground/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "playground", 4 | "slug": "playground", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "@expo/webpack-config": "^19.0.0", 13 | "expo": "~49.0.13", 14 | "expo-status-bar": "~1.6.0", 15 | "react": "18.2.0", 16 | "react-dom": "18.2.0", 17 | "react-native": "0.72.6", 18 | "react-native-web": "~0.19.6" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.20.0", 22 | "imagemin": "^8.0.1", 23 | "imagemin-pngquant": "^9.0.2", 24 | "react-native-imagemin-asset-plugin": "file:.." 25 | }, 26 | "private": true 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 iChengbo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import type { Plugin } from 'imagemin'; 4 | 5 | export interface ImageminMinimizer { 6 | implementation: 'imagemin', 7 | options: { 8 | plugins: Plugin[] 9 | } 10 | } 11 | 12 | export interface SharpMinimizer { 13 | implementation: 'sharp', 14 | options: {} 15 | } 16 | 17 | export interface IConfig { 18 | test: RegExp 19 | include?: RegExp | string 20 | exclude?: RegExp | string 21 | cacheDir: string 22 | minimizer?: ImageminMinimizer | SharpMinimizer 23 | } 24 | 25 | export const defaultConfig: IConfig = { 26 | test: /\.(png|jpg|jpeg)$/, // TODO:RegExp, minimatch glob ... 27 | cacheDir: '.assets-cache', 28 | }; 29 | 30 | /** 31 | * Builds the configuration object. 32 | * @returns {Promise} 33 | */ 34 | export const buildConfig = async (): Promise => { 35 | const metroConfigPath = path.join(process.cwd(), 'metro.config.js') 36 | let metroConfig; 37 | try { 38 | metroConfig = require(metroConfigPath); 39 | } catch { 40 | metroConfig = {}; 41 | } 42 | const transformerOptions = metroConfig.transformer || {}; 43 | const imageminAssetPluginOptions = transformerOptions.imageminAssetPlugin || {}; 44 | 45 | const config = { 46 | ...defaultConfig, 47 | ...imageminAssetPluginOptions, 48 | } 49 | 50 | return config; 51 | } 52 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import type { AssetData } from 'metro'; 4 | 5 | import type { IConfig, ImageminMinimizer } from './config'; 6 | import { buildConfig } from './config'; 7 | import { generateGitignoreFile, imageminGenerate } from './utils'; 8 | 9 | let _config: IConfig 10 | 11 | /** 12 | * imagemin assetPlugin 13 | * @param {Object} assetData 14 | * @returns {Object} 15 | */ 16 | const _imageminAssetPlugin = async (assetData: AssetData): Promise => { 17 | try { 18 | if (!_config) { 19 | _config = await buildConfig() 20 | await generateGitignoreFile(_config.cacheDir) 21 | } 22 | 23 | const { 24 | cacheDir, 25 | test, 26 | minimizer, 27 | include, 28 | exclude = 'node_modules' 29 | } = _config 30 | 31 | const excludeRegexp = new RegExp(exclude) 32 | 33 | if (!excludeRegexp.test(assetData.fileSystemLocation)) { 34 | const outputDirPath = path.join(process.cwd(), cacheDir); 35 | 36 | if (test.test(assetData.files[0])) { 37 | const { implementation = 'imagemin', options = [] } = minimizer ?? {} 38 | 39 | if (implementation === 'imagemin') { 40 | const _assetData = await imageminGenerate(assetData, outputDirPath, options as ImageminMinimizer['options']); 41 | return _assetData 42 | } 43 | // TODO: handle sharp implementation 44 | } 45 | } 46 | } catch (error) { 47 | return assetData; 48 | } 49 | return assetData; 50 | } 51 | 52 | module.exports = _imageminAssetPlugin; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-imagemin-asset-plugin", 3 | "version": "2.0.0", 4 | "description": "Asset plugin for compress/convert images in React Native", 5 | "main": "dist/lib/index.js", 6 | "types": "dist/types", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "tsc", 10 | "pretest": "npm run build", 11 | "test": "cd playground && npx expo export" 12 | }, 13 | "files": [ 14 | "dist" 15 | ], 16 | "keywords": [ 17 | "react-native", 18 | "metro", 19 | "compress", 20 | "image", 21 | "assetPlugin", 22 | "imagemin" 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/iChengbo/react-native-imagemin-asset-plugin.git" 27 | }, 28 | "author": "iChengbo ", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/iChengbo/react-native-imagemin-asset-plugin/issues" 32 | }, 33 | "homepage": "https://github.com/iChengbo/react-native-imagemin-asset-plugin#readme", 34 | "devDependencies": { 35 | "@semantic-release/changelog": "^6.0.3", 36 | "@semantic-release/git": "^10.0.1", 37 | "@types/imagemin": "^8.0.1", 38 | "imagemin": "^7.0.1", 39 | "imagemin-gifsicle": "^7.0.0", 40 | "imagemin-jpegtran": "^7.0.0", 41 | "imagemin-mozjpeg": "^9.0.0", 42 | "imagemin-optipng": "^8.0.0", 43 | "imagemin-pngquant": "^9.0.2", 44 | "imagemin-svgo": "^9.0.0", 45 | "imagemin-webp": "^6.1.0", 46 | "metro": "^0.80.2", 47 | "rimraf": "^5.0.5", 48 | "semantic-release": "^23.0.5", 49 | "typescript": "^5.3.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /playground/App.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-native/no-inline-styles */ 2 | /** 3 | * Sample React Native App 4 | * https://github.com/facebook/react-native 5 | * 6 | * @format 7 | * @flow strict-local 8 | */ 9 | 10 | import React from 'react'; 11 | import { 12 | SafeAreaView, 13 | StatusBar, 14 | Text, 15 | useColorScheme, 16 | View, 17 | Image, 18 | StyleSheet, 19 | Dimensions, 20 | } from 'react-native'; 21 | 22 | const windowWidth = Dimensions.get('window').width; 23 | 24 | const App = () => { 25 | const isDarkMode = useColorScheme() === 'dark'; 26 | 27 | return ( 28 | 29 | 30 | 31 | Metro Asset plugin for compressing images in React Native. 32 | 36 | 37 | ORIGINAL PNG  38 | 1.1 MB 39 | 40 | {/* 41 | TINIFY PNG  42 | 302 KB 43 | */} 44 | 45 | 46 | Okay, the first thing we should do...is give a Star. 47 | 48 | 53 | 54 | 55 | ); 56 | }; 57 | 58 | const styles = StyleSheet.create({ 59 | intro: { 60 | fontSize: 20, 61 | textAlign: 'center', 62 | marginBottom: 16, 63 | color: '#000', 64 | fontWeight: 'bold', 65 | marginHorizontal: 10, 66 | }, 67 | boatImage: { 68 | borderWidth: 5, 69 | borderColor: '#fff', 70 | width: windowWidth - 10, 71 | height: 260, 72 | marginBottom: 5, 73 | }, 74 | boatText: { 75 | color: '#000', 76 | fontWeight: 'bold', 77 | }, 78 | antText: { 79 | color: '#666', 80 | width: 220, 81 | borderColor: '#fff', 82 | borderWidth: 5, 83 | borderRadius: 5, 84 | backgroundColor: '#fff' 85 | }, 86 | antImage: { 87 | height: 160, 88 | width: 100 89 | }, 90 | }) 91 | 92 | export default App; 93 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # react-native-imagemin-asset-plugin 2 | 3 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 4 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue)](./LICENSE) 5 | [![npm-version](https://img.shields.io/npm/v/react-native-imagemin-asset-plugin)](https://www.npmjs.com/package/react-native-imagemin-asset-plugin) 6 | [![npm](https://img.shields.io/npm/dm/react-native-imagemin-asset-plugin.svg)](https://www.npmjs.com/package/react-native-imagemin-asset-plugin) 7 | 8 | **用于压缩 React-Native 图片资源的 metro 插件** 9 | 10 | > 使用 [imagemin](https://github.com/imagemin/imagemin) 完美压缩 PNG, JPG, JPEG 格式图片或者将其转为 WEBP 格式图片。 11 | 12 | example 13 | 14 | ## 安装 15 | 16 | ```sh 17 | yarn add -D react-native-imagemin-asset-plugin imagemin 18 | ``` 19 | 20 | or 21 | 22 | ```sh 23 | npm install --save-dev react-native-imagemin-asset-plugin imagemin 24 | ``` 25 | 26 | ## 配置 27 | 28 | 你可以通过修改 `metro.config.js` 文件,在 `transformer` 中添加 `assetPlugins` 属性,并设置为 `['react-native-imagemin-asset-plugin']` 29 | 30 | 根据需求选择不同的压缩方式,有以下两种搭配供参考: 31 | 32 | **推荐用于无损优化的 imagemin 插件** 33 | 34 | ```sh 35 | npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev 36 | ``` 37 | 38 | **推荐用于有损优化的 imagemin 插件** 39 | 40 | ```sh 41 | npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev 42 | ``` 43 | 44 | 对于 `imagemin-svgo` v9.0.0+ 需要使用 svgo [配置](https://github.com/svg/svgo#configuration) 45 | 46 | 47 | **metro.config.js (React Native Cli)** 48 | 49 | ```js 50 | module.exports = { 51 | transformer: { 52 | // ... 53 | assetPlugins: ['react-native-imagemin-asset-plugin'], 54 | imageminAssetPlugin: { 55 | cacheDir: '.compressed-images', 56 | minimizer: { 57 | implementation: 'imagemin', 58 | options: { 59 | // Lossless optimization with custom option 60 | // Feel free to experiment with options for better result for you 61 | plugins: [ 62 | ["gifsicle", { interlaced: true }], 63 | ["jpegtran", { progressive: true }], 64 | ['pngquant'], 65 | ] 66 | } 67 | } 68 | }, 69 | }, 70 | }; 71 | ``` 72 | **metro.config.js (Expo Go)** 73 | 74 | ```js 75 | // Learn more https://docs.expo.io/guides/customizing-metro 76 | const { getDefaultConfig } = require('expo/metro-config'); 77 | 78 | /** @type {import('expo/metro-config').MetroConfig} */ 79 | const config = getDefaultConfig(__dirname); 80 | 81 | // use plugin to compress assets 82 | config.transformer.assetPlugins.push('react-native-imagemin-asset-plugin'); 83 | config.transformer.imageminAssetPlugin = { 84 | cacheDir: '.compressed-images', 85 | minimizer: { 86 | implementation: 'imagemin', 87 | options: { 88 | // Lossless optimization with custom option 89 | // Feel free to experiment with options for better result for you 90 | plugins: [ 91 | ["gifsicle", { interlaced: true }], 92 | ["jpegtran", { progressive: true }], 93 | ['pngquant'], 94 | ] 95 | } 96 | } 97 | } 98 | 99 | module.exports = config; 100 | ``` 101 | 102 | ## LICENSE 103 | 104 | [MIT](./LICENSE) 105 | 106 | ## 其他 107 | 108 | ... 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-imagemin-asset-plugin 2 | 3 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 4 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue)](./LICENSE) 5 | [![npm-version](https://img.shields.io/npm/v/react-native-imagemin-asset-plugin)](https://www.npmjs.com/package/react-native-imagemin-asset-plugin) 6 | [![npm](https://img.shields.io/npm/dm/react-native-imagemin-asset-plugin.svg)](https://www.npmjs.com/package/react-native-imagemin-asset-plugin) 7 | 8 | [简体中文](https://github.com/iChengbo/react-native-imagemin-asset-plugin/blob/next/README-CN.md) 9 | 10 | [v1.x README](https://github.com/iChengbo/react-native-imagemin-asset-plugin/blob/1.x/README.md) 11 | 12 | **Metro Asset plugin for compressing images in React Native.** 13 | 14 | > Minify PNG, JPG, JPEG images or convert them to WEBP image with [imagemin](https://github.com/imagemin/imagemin) 15 | 16 | example 17 | 18 | ## Install 19 | 20 | ```sh 21 | yarn add -D react-native-imagemin-asset-plugin imagemin 22 | ``` 23 | 24 | or 25 | 26 | ```sh 27 | npm install --save-dev react-native-imagemin-asset-plugin imagemin 28 | ``` 29 | 30 | > **Warning** 31 | > 32 | > imagemin uses plugin to optimize/generate images, so you need to install them too 33 | 34 | ## Configuration 35 | 36 | You can configure the plugin behaviour through the optional `assetPlugins` field in your `metro.config.js` file under the `transformer` section. 37 | 38 | Explore the options to get the best result for you. 39 | 40 | **Recommended imagemin plugins for lossless optimization** 41 | 42 | ```sh 43 | npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev 44 | ``` 45 | 46 | **Recommended imagemin plugins for lossy optimization** 47 | 48 | ```sh 49 | npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev 50 | ``` 51 | 52 | For `imagemin-svgo` v9.0.0+ need use svgo [configuration](https://github.com/svg/svgo#configuration) 53 | 54 | 55 | **metro.config.js (React Native Cli)** 56 | 57 | ```js 58 | module.exports = { 59 | transformer: { 60 | // ... 61 | assetPlugins: ['react-native-imagemin-asset-plugin'], 62 | imageminAssetPlugin: { 63 | cacheDir: '.compressed-images', 64 | minimizer: { 65 | implementation: 'imagemin', 66 | options: { 67 | // Lossless optimization with custom option 68 | // Feel free to experiment with options for better result for you 69 | plugins: [ 70 | ["gifsicle", { interlaced: true }], 71 | ["jpegtran", { progressive: true }], 72 | ['pngquant'], 73 | ] 74 | } 75 | } 76 | }, 77 | }, 78 | }; 79 | ``` 80 | **metro.config.js (Expo Go)** 81 | 82 | ```js 83 | // Learn more https://docs.expo.io/guides/customizing-metro 84 | const { getDefaultConfig } = require('expo/metro-config'); 85 | 86 | /** @type {import('expo/metro-config').MetroConfig} */ 87 | const config = getDefaultConfig(__dirname); 88 | 89 | // use plugin to compress assets 90 | config.transformer.assetPlugins.push('react-native-imagemin-asset-plugin'); 91 | config.transformer.imageminAssetPlugin = { 92 | cacheDir: '.compressed-images', 93 | minimizer: { 94 | implementation: 'imagemin', 95 | options: { 96 | // Lossless optimization with custom option 97 | // Feel free to experiment with options for better result for you 98 | plugins: [ 99 | ["gifsicle", { interlaced: true }], 100 | ["jpegtran", { progressive: true }], 101 | ['pngquant'], 102 | ] 103 | } 104 | } 105 | } 106 | 107 | module.exports = config; 108 | ``` 109 | 110 | ## LICENSE 111 | 112 | [MIT](./LICENSE) 113 | 114 | ## Other 115 | 116 | ... 117 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs/promises'; 3 | import type { Plugin } from 'imagemin'; 4 | import { AssetData } from 'metro'; 5 | 6 | import { ImageminMinimizer } from './config'; 7 | 8 | /** 9 | * Generate a .gitignore file in the specified directory if it doesn't already exist. 10 | * @param dir - The directory where the .gitignore file should be created. 11 | */ 12 | export const generateGitignoreFile = async (dir: string) => { 13 | const gitignorePath = path.join(process.cwd(), dir, '.gitignore') 14 | try { 15 | await fs.access(gitignorePath); 16 | } catch (error) { 17 | const directoryPath = path.join(process.cwd(), dir); 18 | await fs.mkdir(directoryPath) 19 | await fs.writeFile(gitignorePath, '*') 20 | } 21 | } 22 | 23 | /** 24 | * Runs imagemin on the given asset data and generates optimized image files. 25 | * 26 | * @param assetData - The asset data containing the files to be optimized. 27 | * @param outputDirPath - The output directory path where the optimized files will be saved. 28 | * @param minimizerOptions - The options for the imagemin minimizer. 29 | * @returns The modified asset data with the optimized files. 30 | */ 31 | export const imageminGenerate = async (assetData: AssetData, outputDirPath: string, minimizerOptions: ImageminMinimizer['options']) => { 32 | const plugins = await imageminNormalizeConfig(minimizerOptions || {}); 33 | 34 | let outFiles = assetData.files; 35 | try { 36 | const imagemin = (await import("imagemin")).default; 37 | const tmpFiles = await imagemin(assetData.files, { 38 | destination: outputDirPath, 39 | plugins, 40 | }); 41 | outFiles = tmpFiles.map(file => file.destinationPath); 42 | } catch (error) { 43 | // FIXME: Error [ERR_REQUIRE_ESM]: require() of ES Module ... 44 | logError(error) 45 | } 46 | 47 | return { 48 | ...assetData, 49 | files: outFiles 50 | } 51 | } 52 | 53 | /** 54 | * Normalize the imagemin configuration by importing the required plugins and their options. 55 | * @param imageminConfig - The imagemin configuration. 56 | * @returns An array of plugins. 57 | * @throws If an unknown plugin is encountered or if the plugin configuration is invalid. 58 | */ 59 | export const imageminNormalizeConfig = async (imageminConfig: ImageminMinimizer['options']): Promise => { 60 | if ( 61 | !imageminConfig || 62 | !imageminConfig.plugins || 63 | (imageminConfig.plugins && imageminConfig.plugins.length === 0) 64 | ) { 65 | const message = 'No plugins found for `imagemin`, please read documentation'; 66 | logError(message); 67 | return [] 68 | } 69 | 70 | const plugins: Plugin[] = []; 71 | for (const plugin of imageminConfig.plugins) { 72 | const isPluginArray = Array.isArray(plugin); 73 | 74 | if (typeof plugin === 'string' || isPluginArray) { 75 | const pluginName = isPluginArray ? plugin[0] : plugin; 76 | const pluginOptions = isPluginArray ? plugin[1] : undefined; 77 | 78 | let requiredPlugin = null; 79 | let requiredPluginName = pluginName.startsWith('imagemin-') ? pluginName : `imagemin-${pluginName}`; 80 | 81 | try { 82 | requiredPlugin = (await import(requiredPluginName)).default( 83 | pluginOptions 84 | ); 85 | } catch { 86 | const message = `Unknown plugin: ${requiredPluginName}\n\nDid you forget to install the plugin?\nYou can install it with:\n\n$ npm install ${requiredPluginName} --save-dev\n$ yarn add ${requiredPluginName} --dev` 87 | logError(message) 88 | } 89 | plugins.push(requiredPlugin); 90 | } else { 91 | const message = `Invalid plugin configuration '${JSON.stringify(plugin)}', plugin configuration should be 'string' or '[string, object]'"` 92 | logError(message) 93 | } 94 | } 95 | return plugins; 96 | } 97 | 98 | /** 99 | * Returns a debounced version of the input function. 100 | * The debounced function will wait for a specified delay before invoking the original function. 101 | * 102 | * @param func - The function to be debounced. 103 | * @param delay - The delay in milliseconds. 104 | * @returns The debounced function. 105 | */ 106 | const debounce = (func: (...args: any[]) => void, delay: number): ((...args: any[]) => void) => { 107 | let timeoutId: ReturnType; 108 | 109 | return (...args: any[]) => { 110 | clearTimeout(timeoutId); 111 | timeoutId = setTimeout(() => { 112 | func(...args); 113 | }, delay); 114 | }; 115 | }; 116 | 117 | const logError = debounce((message: string) => { 118 | console.log('\x1B[31m%s\x1B[0m', '\n> An exception occurs in "react-native-imagemin-asset-plugin", which may cause compression of assets to fail.'); 119 | console.log('\x1B[31m%s\x1B[0m', message) 120 | }, 2000); 121 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.0.0](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.5.1...v2.0.0) (2024-01-03) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add example.png to build files ([3a8e963](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/3a8e96362646f46b79412e3be717137a3ecd13af)) 7 | * move imagemin to dependencies ([0c8d0f1](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/0c8d0f195679547f657da905015ab4da96a603a3)) 8 | * print error message ([cd8b64c](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/cd8b64ce70614a06aac7630fad4e7611dfd33bb3)) 9 | * remove example.png from pkg files ([9d2fba9](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/9d2fba9a42453192459aa22314bfb4adc5d9a51b)) 10 | 11 | 12 | ### Code Refactoring 13 | 14 | * modify usage and implementation ([859dfee](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/859dfee857f06a053c6eae7d57d4244a3c9e86b5)) 15 | 16 | 17 | ### Performance Improvements 18 | 19 | * remove the dependency on imagemin ([053431e](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/053431ec4fcd9c41c335441c6804279cc93d3ced)) 20 | 21 | 22 | ### BREAKING CHANGES 23 | 24 | * the dependency on imagemin is changed to be imported from outside 25 | * modify the defined configuration and remove the dependencies of imagemin-related plugins from within 26 | 27 | # [2.0.0-alpha.1](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.5.0...v2.0.0-alpha.1) (2023-12-26) 28 | 29 | ### Bug Fixes 30 | 31 | * print error message ([be37d53](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/be37d531b2c018e7837c96584abeac03de50093c)) 32 | 33 | 34 | ### Code Refactoring 35 | 36 | * modify usage and implementation ([287d8bf](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/287d8bf881ad5988866578f2bece52d55d24bb34)) 37 | 38 | 39 | ### BREAKING CHANGES 40 | 41 | * modify the defined configuration and remove the dependencies of imagemin-related plugins from within 42 | 43 | ## [1.5.1](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.5.0...v1.5.1) (2023-12-27) 44 | 45 | ### Bug Fixes 46 | 47 | * build package before executing semantic-release ([403d26b](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/403d26b862bd695c30219eb926dd21264ed68b9b)) 48 | 49 | # [1.5.0](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.4.1...v1.5.0) (2023-12-26) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * remove warning ([67cb025](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/67cb025891d6f899ef52facb81280cfe430262a8)) 55 | 56 | 57 | ### Features 58 | 59 | * deprecate giflossy & update readme ([98e382b](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/98e382b9ca17be7da45f7c74819753dc6284e2fe)) 60 | 61 | ## [1.4.1](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.4.0...v1.4.1) (2023-10-07) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * **action:** install dependencies ([45da12d](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/45da12defe439524f4b388ab2a6f89b8efe7de19)) 67 | * **action:** the version of ubuntu ([b0a375c](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/b0a375cb8ff99a614d495e48ee8499400fcbc267)) 68 | * readme-cn ([626ee9a](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/626ee9a6f0c6c91be475f3ce954389351a846b40)) 69 | * the version of imagemin-webp ([7bd2cb7](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/7bd2cb70aebdb9e7ab96fd2df2ae9a9fd99cf6a4)) 70 | 71 | # [1.4.0](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.3.2...v1.4.0) (2021-10-29) 72 | 73 | 74 | ### Features 75 | 76 | * modify the default temporary directory during compression & add 'gitignore' to ignore the directory ([9a642b2](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/9a642b2c595f194a2ff82392961dc64ae61028a3)) 77 | 78 | ## [1.3.2](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.3.1...v1.3.2) (2021-10-25) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * readme ([2fd03d2](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/2fd03d2143712fb2b018c9be15655a59085471c6)) 84 | 85 | ## [1.3.1](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.3.0...v1.3.1) (2021-08-24) 86 | 87 | 88 | ### Bug Fixes 89 | 90 | * install ([e843053](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/e843053c9176fed5ffbec3c374f2cf4eb70f24f4)) 91 | 92 | # [1.3.0](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.2.0...v1.3.0) (2021-08-23) 93 | 94 | 95 | ### Features 96 | 97 | * support more imagemin plugins ([3afcc32](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/3afcc3216e780d9908f6d1ef3c162dfde2c49bfa)) 98 | 99 | # [1.2.0](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.1.1...v1.2.0) (2021-08-17) 100 | 101 | 102 | ### Features 103 | 104 | * support custom folders for storing compressed images ([b86a428](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/b86a428725dfc3b358be0a104e342ba7a2157c9c)) 105 | 106 | ## [1.1.1](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.1.0...v1.1.1) (2021-08-16) 107 | 108 | 109 | ### Bug Fixes 110 | 111 | * load metro config ([b8e97b1](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/b8e97b1cf51e4b49408a334928e65fe7e92d0767)) 112 | 113 | # [1.1.0](https://github.com/iChengbo/react-native-imagemin-asset-plugin/compare/v1.0.3...v1.1.0) (2021-08-15) 114 | 115 | 116 | ### Features 117 | 118 | * test ([cc6e727](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/cc6e727070c17b0e37d283956472acca30daada9)) 119 | * test ([e9ebb8f](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/e9ebb8fd9cc81de3fca95796d299f800888fd417)) 120 | * test ci ([eef9b76](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/eef9b7640fae27502423aaa71d698066b7edcf51)) 121 | 122 | # 1.0.0 (2021-08-15) 123 | 124 | 125 | ### Features 126 | 127 | * test ([cc6e727](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/cc6e727070c17b0e37d283956472acca30daada9)) 128 | * test ([e9ebb8f](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/e9ebb8fd9cc81de3fca95796d299f800888fd417)) 129 | 130 | # 1.0.0 (2021-08-15) 131 | 132 | 133 | ### Features 134 | 135 | * test ([e9ebb8f](https://github.com/iChengbo/react-native-imagemin-asset-plugin/commit/e9ebb8fd9cc81de3fca95796d299f800888fd417)) 136 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "CommonJS", /* Specify what module code is generated. */ 29 | "rootDir": "./src", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | "outDir": "dist/lib", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | "declarationDir": "dist/types", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | }, 109 | "include": [ 110 | "src" 111 | ] 112 | } 113 | --------------------------------------------------------------------------------