Repository: dazuaz/responsive-loader
Branch: master
Commit: ef2c806fcd36
Files: 31
Total size: 91.3 KB
Directory structure:
gitextract_hh3w3uug/
├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .github/
│ └── workflows/
│ └── codeql-analysis.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── jimp.js
├── package.json
├── sharp.js
├── src/
│ ├── adapters/
│ │ ├── jimp.ts
│ │ └── sharp.ts
│ ├── cache.ts
│ ├── cjs.js
│ ├── index.ts
│ ├── loader-utils.d.ts
│ ├── parseQuery.ts
│ ├── schema.json
│ ├── types.d.ts
│ └── utils.ts
├── test/
│ ├── cjs.test.js
│ ├── jimp/
│ │ ├── build/
│ │ │ └── __snapshots__/
│ │ │ └── test.js.snap
│ │ ├── index.js
│ │ └── webpack.config.js
│ ├── sharp/
│ │ ├── build/
│ │ │ └── __snapshots__/
│ │ │ └── test.js.snap
│ │ ├── index.js
│ │ └── webpack.config.js
│ └── utils.test.js
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "12.22.1"
}
}
]
]
}
================================================
FILE: .eslintignore
================================================
# don't ever lint node_modules
node_modules
# don't lint build output
lib
# don't lint test
test
sharp.js
jimp.js
src/cjs.js
.eslintrc.js
================================================
FILE: .eslintrc.js
================================================
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
ignorePatterns: ["examples/*"],
rules: {
"@typescript-eslint/ban-ts-comment": "off",
},
}
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '20 4 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
================================================
FILE: .gitignore
================================================
node_modules
*.log
test/build
test/**/build/*.js
test/**/build/**/*.png
test/**/build/**/*.jpg
test/**/build/**/*.avif
test/**/build/**/*.jpeg
test/**/build/**/*.webp
.node-version
lib
.vscode
.envrc
.tool-versions
================================================
FILE: .travis.yml
================================================
os: osx
language: node_js
node_js:
- '12'
================================================
FILE: CHANGELOG.md
================================================
# Change Log
## v1.1.0
- Added `min` and `max` options to automatically generate a number of images, and `steps` option to say how many images ([#31](https://github.com/herrstucki/responsive-loader/pull/31)).
## v1.0.0
### New
- 🚀 Added support for [sharp](https://github.com/lovell/sharp) ([#19](https://github.com/herrstucki/responsive-loader/pull/29))
### Breaking
#### Webpack 2 support
Removed support for webpack 1! Please upgrade to webpack >= 2.
The syntax to import images has changed. The query part now comes _after_ the resource (the image) instead of the loader.
```diff
- require('responsive-loader?size=100!some-image.jpg')
+ require('responsive-loader!some-image.jpg?size=100')
```
That means if `responsive-loader` is configured in your webpack-config, it's possible to specify image-specific options without having to add the loader part to the import path. For example:
```js
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.jpg$/,
loader: 'responsive-loader',
options: {
size: 1000
//...
}
}
]
},
}
// some-file.js
const image1000 = require('some-image.jpg') // will have size 1000 from the config
const image500 = require('some-image.jpg?size=500')
```
#### Other breaking changes
- The `ext` option was removed, in favor of `format=jpg|png`. `[ext]` is now part of the `name` option like in other loaders (fixes [#13](https://github.com/herrstucki/responsive-loader/issues/13))
- Changed default JPEG `quality` to `85`
- The `pass` option is now called `disable`
## v0.7.0
- Add `placeholder` option ([#16](https://github.com/herrstucki/responsive-loader/pull/16))
- Add `width` and `height` attributes to output ([#19](https://github.com/herrstucki/responsive-loader/pull/19))
## v0.6.1
- Declare default `name`, `context`, `quality`, and `background` through webpack options when they're not specified in the loader query ([#12](https://github.com/herrstucki/responsive-loader/pull/12)).
## v0.6.0
- Add linting ([#7](https://github.com/herrstucki/responsive-loader/pull/7))
- Breaking (maybe): Require node >= v4
## v0.5.3
- Fix wrong callback being called on file load error ([#6](https://github.com/herrstucki/responsive-loader/pull/6))
## v0.5.2
- Added tests!
- Update `queue-async` to `d3-queue`
## v0.5.1
- Optimization: skip resizing images of the same size ([#5](https://github.com/herrstucki/responsive-loader/pull/5))
## v0.5.0
Using the `size` option for getting only one resized image no longer just returns a string but the same object structure as when using `sizes`. The difference is, that when `toString()` is called on that object, it will return the path of the first resized image.
Also, for pure convenience, the returned object also contains a `src` property, so it can be spread onto a React component (e.g. `<img {...resized} />`).
### Before
This worked:
```js
import resized from 'responsive?sizes[]=100,sizes[]=200';
<img srcSet={resized.srcSet} src={resized.images[0].path} />
```
```css
.foo { background-image: url('responsive?size=100'); }
```
But this didn't :sob::
```js
import resized from 'responsive?size=100';
// Whoops, error because `resized` ist just a string
<img srcSet={resized.srcSet} src={resized.images[0].path} />
```
```css
/* Whoops, `url('[object Object]')` */
.foo { background-image: url('responsive?sizes[]=100'); }
```
### After
All these work :v:
```js
import resized from 'responsive?sizes[]=100,sizes[]=200';
<img srcSet={resized.srcSet} src={resized.src} />
<img srcSet={resized.srcSet} src={resized} />
<img {...resized} />
```
```css
.foo { background-image: url('responsive?sizes[]=100,sizes[]=200'); }
.foo { background-image: url('responsive?sizes[]=100'); }
.foo { background-image: url('responsive?size=100'); }
```
================================================
FILE: LICENSE
================================================
Copyright (c) 2016, Jeremy Stucki
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of responsive-loader nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# responsive-loader
[![build][travis]][travis-url]
[![node][node]][node-url]
A webpack loader for responsive images. Creates multiple images from one source image, and returns a `srcset`. For more information on how to use `srcset`, read [Responsive Images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images). Browser support is [pretty good](http://caniuse.com/#search=srcset).
## Install
### With sharp
```
npm install responsive-loader sharp --save-dev
```
For [super-charged performance](http://sharp.dimens.io/en/stable/performance/) and webp and avif formats support, responsive-loader works with [sharp](https://github.com/lovell/sharp). It's recommended to use sharp if you have lots of images to transform.
```js
module.exports = {
// ...
module: {
rules: [
{
test: /\.(png|jpe?g)$/,
use: [
{
loader: 'responsive-loader',
options: {
// Set options for all transforms
},
},
],
type: 'javascript/auto',
},
],
},
}
```
### With jimp
```
npm install responsive-loader jimp --save-dev
```
Responsive-loader can be use with [jimp](https://github.com/oliver-moran/jimp) to transform images. which needs to be installed alongside responsive-loader. Because jimp is written entirely in JavaScript and doesn't have any native dependencies it will work anywhere. The main drawback is that it's pretty slow.
If you want to use jimp, you need to configure responsive-loader to use its adapter:
```diff
module.exports = {
// ...
module: {
rules: [
{
test: /\.(png|jpe?g)$/,
use: [
{
loader: 'responsive-loader',
options: {
+ adapter: require('responsive-loader/jimp')
},
},
],
type: 'javascript/auto',
}
]
},
}
```
### Typescript
```typescript
//declare a module to your type definitions files *.d.ts
interface ResponsiveImageOutput {
src: string
srcSet: string
placeholder: string | undefined
images: { path: string; width: number; height: number }[]
width: number
height: number
toString: () => string
}
declare module '*!rl' {
const src: ResponsiveImageOutput
export default src
}
```
```
import responsiveImage from 'img/myImage.jpg?sizes[]=300,sizes[]=600,sizes[]=1024,sizes[]=2048!rl';
import responsiveImageWebp from 'img/myImage.jpg?sizes[]=300,sizes[]=600,sizes[]=1024,sizes[]=2048&format=webp!rl';
...
```
---
Then import images in your JavaScript files:
```js
import responsiveImage from 'img/myImage.jpg?sizes[]=300,sizes[]=600,sizes[]=1024,sizes[]=2048';
import responsiveImageWebp from 'img/myImage.jpg?sizes[]=300,sizes[]=600,sizes[]=1024,sizes[]=2048&format=webp';
// Outputs
// responsiveImage.srcSet => '2fefae46cb857bc750fa5e5eed4a0cde-300.jpg 300w,2fefae46cb857bc750fa5e5eed4a0cde-600.jpg 600w,2fefae46cb857bc750fa5e5eed4a0cde-600.jpg 600w ...'
// responsiveImage.images => [{height: 150, path: '2fefae46cb857bc750fa5e5eed4a0cde-300.jpg', width: 300}, {height: 300, path: '2fefae46cb857bc750fa5e5eed4a0cde-600.jpg', width: 600} ...]
// responsiveImage.src => '2fefae46cb857bc750fa5e5eed4a0cde-2048.jpg'
// responsiveImage.toString() => '2fefae46cb857bc750fa5e5eed4a0cde-2048.jpg'
...
<picture>
<source srcSet={responsiveImageWebp.srcSet} type='image/webp' sizes='(min-width: 1024px) 1024px, 100vw'/>
<img
src={responsiveImage.src}
srcSet={responsiveImage.srcSet}
width={responsiveImage.width}
height={responsiveImage.height}
sizes='(min-width: 1024px) 1024px, 100vw'
loading="lazy"
/>
</picture>
...
```
Notes:
- `width` and `height` are intrinsic and are used to avoid layout shift, other techniques involve the use of aspect ratio and padding.
- `sizes`, without sizes, the browser assumes the image is always 100vw for any viewport.
- A helpful tool to determine proper sizes https://ausi.github.io/respimagelint/
- `loading` do not add loading lazy if the image is part of the initial rendering of the page or close to it.
- `srcset` Modern browsers will choose the closest best image depending on the pixel density of your screen.
- in the example above is your pixel density is `>1x` for a screen `>1024px` it will display the 2048 image.
Or use it in CSS (only the first resized image will be used, if you use multiple `sizes`):
```css
.myImage {
background: url('myImage.jpg?size=1140');
}
@media (max-width: 480px) {
.myImage {
background: url('myImage.jpg?size=480');
}
}
```
```js
// Outputs placeholder image as a data URI, and three images with 100, 200, and 300px widths
const responsiveImage = require('myImage.jpg?placeholder=true&sizes[]=100,sizes[]=200,sizes[]=300')
// responsiveImage.placeholder => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAIBAQE…'
ReactDOM.render(
<div
style={{
height: responsiveImage.height,
width: responsiveImage.width,
backgroundSize: 'cover',
backgroundImage: 'url("' + responsiveImage.placeholder + '")',
}}>
<img src={responsiveImage.src} srcSet={responsiveImage.srcSet} />
</div>,
el
)
```
You can also use [JSON5](https://json5.org/) notation:
```
<source srcSet={require('./image.jpg?{sizes:[50,100,200,300,400,500,600,700,800], format: "webp"}').srcSet} type='image/webp'/>
```
### Options
| Option | Type | Default | Description |
| --------------------------------------- | --------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | `[hash]-[width].[ext]` | Filename template for output files. |
| `outputPath` | `string \| Function` | `undefined` | Configure a custom output path for your file |
| `publicPath` | `string \| Function` | `undefined` | Configure a custom public path for your file. |
| `context` | `string` | `this.options.context` | Custom file context, defaults to webpack.config.js [context](https://webpack.js.org/configuration/entry-context/#context) |
| `sizes` | `array` | _original size_ | Specify all widths you want to use; if a specified size exceeds the original image's width, the latter will be used (i.e. images won't be scaled up). You may also declare a default `sizes` array in the loader options in your `webpack.config.js`. |
| `size` | `integer` | _original size_ | Specify one width you want to use; if the specified size exceeds the original image's width, the latter will be used (i.e. images won't be scaled up) |
| `min` | `integer` | | As an alternative to manually specifying `sizes`, you can specify `min`, `max` and `steps`, and the sizes will be generated for you. |
| `max` | `integer` | | See `min` above |
| `steps` | `integer` | `4` | Configure the number of images generated between `min` and `max` (inclusive) |
| `quality` | `integer` | `85` | JPEG and WEBP compression quality |
| `format` | `string` | _original format_ | Either `png` or `jpg`; use to convert to another format. `webp` and `avif` is also supported, but only by the sharp adapter |
| `placeholder` | `boolean` | `false` | A true or false value to specify wether to output a placeholder image as a data URI |
| `placeholderSize` | `integer` | `40` | A number value specifying the width of the placeholder image, if enabled with the option above |
| `adapter` | `Adapter` | JIMP | Specify which adapter to use. Can only be specified in the loader options. |
| `disable` | `boolean` | `false` | Disable processing of images by this loader (useful in development). `srcSet` and other attributes will still be generated but only for the original size. Note that the `width` and `height` attributes will both be set to `100` but the image will retain its original dimensions. |
| **[`esModule`](#esmodule)** | `boolean` | `false` | Use ES modules syntax. |
| `emitFile` | `boolean` | `true` | If `true`, emits a file (writes a file to the filesystem). If `false`, the loader will still return a object with the public URI but will not emit the file. It is often useful to disable this option for server-side packages. |
| **[`cacheDirectory`](#cachedirectory)** | `string` or `boolean` | `false` | Experimental: If `true`, this will cache the result object but not the image files. The images are only produced once, when they are not found in the results object cache, or when the options change (cache key). For Development you can set query parameter to `?cacheDirectory=false`. |
#### Adapter-specific options
##### jimp
- `background: number` — Background fill when converting transparent to opaque images. Make sure this is a valid hex number, e.g. `0xFFFFFFFF`)
##### sharp
- `background: string` — Background fill when converting transparent to opaque images. E.g. `#FFFFFF` or `%23FFFFFF` for webpack > 5
- `format: webp` — Conversion to the `image/webp` format. Recognizes the `quality` option.
- `format: avif` — Conversion to the `image/avif` format. Recognizes the `quality` option.
- `progressive: boolean` - Use progressive (interlace) scan for `image/jpeg` format.
- `rotate: number` - Rotates image [more here](https://sharp.pixelplumbing.com/api-operation#rotate)
### Examples
Set a default `sizes` array, so you don't have to declare them with each `require`.
```js
module.exports = {
entry: {...},
output: {...},
module: {
rules: [
{
test: /\.(jpe?g|png|webp)$/i,
use: [
{
loader: "responsive-loader",
options: {
adapter: require('responsive-loader/sharp'),
sizes: [320, 640, 960, 1200, 1800, 2400],
placeholder: true,
placeholderSize: 20
},
},
],
}
]
},
}
```
### `cacheDirectory`
Type: `Boolean` or `string`
Default: `false`
Experimental: If `true`, this will cache the result object but not the image files. The images are only produced once, when they are not found in the results object cache, or when the options change (cache key). For Development you can set query parameter to individual images by using `?cacheDirectory=false`.
Default cache directory might be `.node_modules/.cache/responsive-loader`
```js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png)$/i,
use: [
{
loader: 'responsive-loader',
options: {
esModule: true,
cacheDirectory: true,
publicPath: '/_next',
name: 'static/media/[name]-[hash:7]-[width].[ext]',
},
},
],
},
],
},
}
```
### `esModule`
Type: `Boolean`
Default: `false`
By default, `responsive-loader` generates JS modules that use the CommonJS syntax.
There are some cases in which using ES modules is beneficial, like in the case of [module concatenation](https://webpack.js.org/plugins/module-concatenation-plugin/) and [tree shaking](https://webpack.js.org/guides/tree-shaking/).
You can enable a ES module syntax using:
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png)$/i,
use: [
{
loader: 'responsive-loader',
options: {
esModule: true,
},
},
],
},
],
},
}
```
### Writing Your Own Adapter
Maybe you want to use another image processing library or you want to change an existing one's behavior. You can write your own adapter with the following signature:
```js
type Adapter = (imagePath: string) => {
metadata: () => Promise<{width: number, height: number}>
resize: (config: {width: number, mime: string, options: Object}) => Promise<{data: Buffer, width: number, height: number}>
}
```
The `resize` method takes a single argument which has a `width`, `mime` and `options` property (which receives all loader options)
In your webpack config, require your adapter
```js
{
test: /\.(jpe?g|png)$/i,
loader: 'responsive-loader',
options: {
adapter: require('./my-adapter')
foo: 'bar' // will get passed to adapter.resize({width, mime, options: {foo: 'bar}})
}
}
```
## Notes
- Doesn't support `1x`, `2x` sizes, but you probably don't need it.
## Usage Examples
### Next.js
- https://github.com/dazuaz/responsive-loader-example
### Pug
- [How to use responsive-loader with Pug](https://webdiscus.github.io/pug-plugin/responsive-image/). Thanks to the awesome [pug-loader](https://webdiscus.github.io/pug-plugin/hello-world/).
Please submit your own example to add here
[node]: https://img.shields.io/node/v/responsive-loader.svg
[node-url]: https://nodejs.org
[travis]: https://travis-ci.com/dazuaz/responsive-loader.svg?branch=master
[travis-url]: https://travis-ci.com/dazuaz/responsive-loader
================================================
FILE: jimp.js
================================================
module.exports = require('./lib/adapters/jimp');
================================================
FILE: package.json
================================================
{
"name": "responsive-loader",
"version": "3.1.2",
"description": "A webpack loader for responsive images",
"main": "lib/cjs.js",
"engines": {
"node": ">= 12.22.1"
},
"scripts": {
"build": "tsc",
"lint": "eslint",
"test:clean": "find test/**/build/ -name '*.jpg' -o -name '*.png' -o -name '*.avif' -o -name '*.webp' -o -name '*.jpeg' -o -name '*.js' | xargs rm -f",
"test": "npm run build && npm run test:clean && webpack --config=./test/jimp/webpack.config.js && webpack --config=./test/sharp/webpack.config.js && jest"
},
"np": {
"yarn": false,
"contents": "lib"
},
"files": [
"lib",
"jimp.js",
"sharp.js"
],
"repository": {
"type": "git",
"url": "git+https://github.com/dazuaz/responsive-loader.git"
},
"keywords": [
"webpack",
"responsive",
"loader",
"srcset"
],
"author": "Jeremy Stucki <jeremy@interactivethings.com>",
"contributors": [
"Daniel Zuloaga <daniel@staticprops.com> (https://staticprops.com/)"
],
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/dazuaz/responsive-loader/issues"
},
"homepage": "https://github.com/dazuaz/responsive-loader",
"peerDependencies": {
"webpack": "^5.73.0"
},
"peerDependenciesMeta": {
"jimp": {
"optional": true
},
"sharp": {
"optional": true
}
},
"dependencies": {
"@types/node": "^18.11.9",
"find-cache-dir": "^3.3.2",
"json5": "^2.2.1",
"loader-utils": "^3.2.1",
"make-dir": "^3.1.0",
"schema-utils": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.2",
"@babel/preset-env": "^7.20.2",
"@types/find-cache-dir": "^3.2.1",
"@types/jest": "^29.2.3",
"@types/json-schema": "^7.0.11",
"@types/sharp": "^0.31.0",
"@types/webpack": "^5.28.0",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"babel-jest": "^29.3.1",
"eslint": "^8.27.0",
"jest": "^29.3.1",
"jimp": "^0.16.2",
"prettier": "^2.7.1",
"prettier-eslint": "^15.0.1",
"sharp": "^0.31.2",
"typescript": "^4.9.3",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0"
},
"jest": {
"testEnvironment": "node"
}
}
================================================
FILE: sharp.js
================================================
module.exports = require('./lib/adapters/sharp');
================================================
FILE: src/adapters/jimp.ts
================================================
import * as jimp from "jimp"
type ResizeProps = {
width: number
mime: "image/jpeg" | "image/png" | "image/webp" | "image/avif"
options: {
background?: string
rotate: number
quality: number
progressive?: boolean
}
}
class JimpAdapter {
readImage: Promise<jimp>
constructor(imagePath: string) {
this.readImage = jimp.read(imagePath)
}
metadata(): Promise<{ height: number; width: number }> {
return this.readImage.then((image) => ({
width: image.bitmap.width,
height: image.bitmap.height,
}))
}
resize({
width,
mime,
options,
}: ResizeProps): Promise<{
width: number
height: number
data: Buffer
}> {
return new Promise((resolve, reject) => {
this.readImage.then((image) => {
image
.clone()
.resize(width, jimp.AUTO)
.quality(options.quality)
.background(parseInt(options.background + "", 16) || 0xffffffff)
.getBuffer(mime, function (err, data) {
// eslint-disable-line func-names
if (err) {
reject(err)
} else {
resolve({
data,
width,
height: this.bitmap.height,
})
}
})
})
})
}
}
module.exports = (imagePath: string): JimpAdapter => {
return new JimpAdapter(imagePath)
}
================================================
FILE: src/adapters/sharp.ts
================================================
import * as sharp from 'sharp'
type ResizeProps = {
width: number
mime: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/avif'
options: {
background?: string
rotate: number
quality: number
progressive?: boolean
}
}
class SharpAdapter {
image: sharp.Sharp
constructor(imagePath: string) {
this.image = sharp(imagePath)
}
metadata(): Promise<sharp.Metadata> {
return this.image.metadata()
}
resize({ width, mime, options }: ResizeProps): Promise<{ data: Buffer; width: number; height: number }> {
return new Promise((resolve, reject) => {
let resized = this.image.clone().resize(width, null)
if (!options.rotate) {
// .toBuffer() strips EXIF metadata like orientation, so portrait
// images will become landscape. This updates the image to reflect
// the EXIF metadata (if an EXIF orientation is set; otherwise unchanged).
resized.rotate()
}
if (options.background) {
resized = resized.flatten({
background: options.background,
})
}
if (mime === 'image/jpeg') {
resized = resized.jpeg({
quality: options.quality,
progressive: options.progressive,
})
}
if (mime === 'image/png') {
resized = resized.png({
quality: options.quality,
progressive: options.progressive,
})
}
if (mime === 'image/webp') {
resized = resized.webp({
quality: options.quality,
})
}
if (mime === 'image/avif') {
// @ts-ignore
resized = resized.avif({
quality: options.quality,
})
}
// rotate
if (options.rotate && options.rotate !== 0) {
resized = resized.rotate(options.rotate)
}
resized.toBuffer((err, data, { height }) => {
if (err) {
reject(err)
} else {
resolve({
data,
width,
height,
})
}
})
})
}
}
// export default SharpAdapter
module.exports = (imagePath: string): SharpAdapter => {
return new SharpAdapter(imagePath)
}
================================================
FILE: src/cache.ts
================================================
/**
* Filesystem Cache
*
* Given a file and a transform function, cache the result into files
* or retrieve the previously cached files if the given file is already known.
*
* @see https://github.com/babel/babel-loader/
*/
import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
import * as zlib from 'zlib'
import * as crypto from 'crypto'
import * as findCacheDir from 'find-cache-dir'
import * as makeDir from 'make-dir'
import { promisify } from 'util'
import { CacheOptions, TransformParams } from './types'
import { transform } from '.'
// Lazily instantiated when needed
let defaultCacheDirectory: string | null = null
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const gunzip = promisify(zlib.gunzip)
const gzip = promisify(zlib.gzip)
/**
* Read the contents from the compressed file.
*
* @async
* @params {String} filename
* @params {Boolean} compress
*/
const read = async function (filename: string, compress: boolean) {
const data = await readFile(filename + (compress ? '.gz' : ''))
const content = compress ? await gunzip(data) : data
return JSON.parse(content.toString())
}
/**
* Write contents into a compressed file.
*
* @async
* @params {String} filename
* @params {Boolean} compress
* @params {String} result
*/
const write = async function (filename: string, compress: boolean, result: string) {
const content = JSON.stringify(result)
const data = compress ? await gzip(content) : content
return await writeFile(filename + (compress ? '.gz' : ''), data)
}
/**
* Build the filename for the cached file
*
* @params {String} source File source code
* @params {Object} options Options used
*
* @return {String}
*/
const filename = function (source: string, identifier: string) {
const hash = crypto.createHash('md4')
const contents = JSON.stringify({ source, identifier })
hash.update(contents)
return hash.digest('hex') + '.json'
}
/**
* Handle the cache
*
* @params {String} directory
* @params {Object} params
*/
const handleCache = async function (
directory: string,
cacheOptions: CacheOptions,
params: TransformParams
): Promise<string> {
const { cacheIdentifier, cacheDirectory, cacheCompression } = cacheOptions
const file = path.join(directory, filename(params.resourcePath, cacheIdentifier))
try {
// No errors mean that the file was previously cached
// we just need to return it
return await read(file, cacheCompression)
} catch (err) {
// continue regardless of error
}
const fallback = typeof cacheDirectory !== 'string' && directory !== os.tmpdir()
// Make sure the directory exists.
try {
await makeDir(directory)
} catch (err) {
if (fallback) {
return handleCache(os.tmpdir(), cacheOptions, params)
}
throw err
}
// Otherwise just transform the file
// return it to the user asap and write it in cache
const result = await transform(params)
try {
await write(file, cacheCompression, result)
} catch (err) {
if (fallback) {
// Fallback to tmpdir if node_modules folder not writable
return handleCache(os.tmpdir(), cacheOptions, params)
}
throw err
}
return result
}
/**
* Retrieve file from cache, or create a new one for future reads
*
* @async
* @param {CacheOptions} cacheOptions
* @param {TransformParams} transformParams Options to be given to the transform fn
*
*/
export async function cache(cacheOptions: CacheOptions, transformParams: TransformParams): Promise<string> {
let directory
if (typeof cacheOptions.cacheDirectory === 'string') {
directory = cacheOptions.cacheDirectory
} else {
if (defaultCacheDirectory === null) {
defaultCacheDirectory = findCacheDir({ name: 'responsive-loader' }) || os.tmpdir()
}
directory = defaultCacheDirectory
}
return await handleCache(directory, cacheOptions, transformParams)
}
================================================
FILE: src/cjs.js
================================================
const loader = require('./index')
module.exports = loader.default
module.exports.raw = loader.raw
================================================
FILE: src/index.ts
================================================
import * as schema from './schema.json'
import { validate } from 'schema-utils'
import { JSONSchema7 } from 'schema-utils/declarations/ValidationError'
import { parseOptions, getOutputAndPublicPath, createPlaceholder } from './utils'
import { cache } from './cache'
import type { LoaderContext } from 'webpack'
import { interpolateName } from 'loader-utils'
import { parseQuery } from './parseQuery'
import type {
Adapter,
Options,
CacheOptions,
AdapterImplementation,
MimeType,
AdapterResizeResponse,
TransformParams,
} from './types'
const DEFAULTS = {
quality: 85,
placeholder: false,
placeholderSize: 40,
name: '[hash]-[width].[ext]',
steps: 4,
esModule: false,
emitFile: true,
rotate: 0,
cacheDirectory: false,
cacheCompression: true,
cacheIdentifier: '',
}
/**
* **Responsive Loader**
*
* Creates multiple images from one source image, and returns a srcset
* [Responsive Loader](https://github.com/dazuaz/responsive-loader)
*
* @param {Buffer} content Source
*
* @return {loaderCallback} loaderCallback Result
*/
export default function loader(this: LoaderContext<Options>, content: string): void {
const loaderCallback = this.async()
if (typeof loaderCallback == 'undefined') {
new Error('Responsive loader callback error')
return
}
// Parsers the query string and options
const parsedResourceQuery = this.resourceQuery ? parseQuery(this.resourceQuery) : {}
// Combines defaults, webpack options and query options,
const options = { ...DEFAULTS, ...this.getOptions(), ...parsedResourceQuery }
validate(schema as JSONSchema7, options, { name: 'Responsive Loader' })
const outputContext = options.context || this.rootContext
const { mime, ext, name, sizes, outputPlaceholder, placeholderSize, imageOptions, cacheOptions } = parseOptions(
this.resourcePath,
options
)
if (!mime) {
loaderCallback(new Error('No mime type for file with extension ' + ext + ' supported'))
return
}
const createFile = ({ data, width, height }: AdapterResizeResponse) => {
const fileName = interpolateName(this, name, {
context: outputContext,
content: data.toString(),
})
.replace(/\[width\]/gi, width + '')
.replace(/\[height\]/gi, height + '')
const { outputPath, publicPath } = getOutputAndPublicPath(fileName, {
outputPath: options.outputPath,
publicPath: options.publicPath,
})
if (options.emitFile) {
this.emitFile(outputPath, data)
}
return {
src: publicPath + `+${JSON.stringify(` ${width}w`)}`,
path: publicPath,
width: width,
height: height,
}
}
/**
* Disable processing of images by this loader (useful in development)
*/
if (options.disable) {
const { path } = createFile({ data: content, width: 100, height: 100 })
loaderCallback(
null,
`${options.esModule ? 'export default' : 'module.exports ='} {
srcSet: ${path},
images: [{path:${path},width:100,height:100}],
src: ${path},
toString: function(){return ${path}}
}`
)
return
}
// The full config is passed to the adapter, later sources' properties overwrite earlier ones.
const adapterOptions = Object.assign({}, options, imageOptions)
const transformParams = {
adapterModule: options.adapter,
resourcePath: this.resourcePath,
adapterOptions,
createFile,
outputPlaceholder,
placeholderSize,
mime,
sizes,
}
orchestrate({ cacheOptions, transformParams })
.then((result) => loaderCallback(null, result))
.catch((err) => loaderCallback(err))
}
interface OrchestrateParams {
cacheOptions: CacheOptions
transformParams: TransformParams
}
async function orchestrate(params: OrchestrateParams) {
// use cached, or create new image.
let result
const { transformParams, cacheOptions } = params
if (cacheOptions.cacheDirectory) {
result = await cache(cacheOptions, transformParams)
} else {
result = await transform(transformParams)
}
return result
}
// Transform based on the parameters
export async function transform({
adapterModule,
resourcePath,
createFile,
sizes,
mime,
outputPlaceholder,
placeholderSize,
adapterOptions,
}: TransformParams): Promise<string> {
const adapter: Adapter = adapterModule || require('./adapters/sharp')
const img = adapter(resourcePath)
const results = await transformations({ img, sizes, mime, outputPlaceholder, placeholderSize, adapterOptions })
let placeholder
let files
if (outputPlaceholder) {
files = results.slice(0, -1).map(createFile)
placeholder = createPlaceholder(results[results.length - 1], mime)
} else {
files = results.map(createFile)
}
const srcset = files.map((f) => f.src).join('+","+')
const images = files.map((f) => `{path: ${f.path},width: ${f.width},height: ${f.height}}`).join(',')
// default to the biggest image
const defaultImage = files[files.length - 1]
return `${adapterOptions.esModule ? 'export default' : 'module.exports ='} {
srcSet: ${srcset},
images: [${images}],
src: ${defaultImage.path},
toString: function(){return ${defaultImage.path}},
${placeholder ? 'placeholder: ' + placeholder + ',' : ''}
width: ${defaultImage.width},
height: ${defaultImage.height}
}`
}
interface TransformationParams {
img: AdapterImplementation
sizes: number[]
mime: MimeType
outputPlaceholder: boolean
placeholderSize: number
adapterOptions: Options
}
/**
* **Run Transformations**
*
* For each size defined in the parameters, resize an image via the adapter
*
*/
async function transformations({
img,
sizes,
mime,
outputPlaceholder,
placeholderSize,
adapterOptions,
}: TransformationParams): Promise<AdapterResizeResponse[]> {
const metadata = await img.metadata()
const promises = []
const widthsToGenerate = new Set()
sizes.forEach((size) => {
const width = Math.min(metadata.width, size)
// Only resize images if they aren't an exact copy of one already being resized...
if (!widthsToGenerate.has(width)) {
widthsToGenerate.add(width)
promises.push(
img.resize({
width,
mime,
options: adapterOptions,
})
)
}
})
if (outputPlaceholder) {
promises.push(
img.resize({
width: placeholderSize,
options: adapterOptions,
mime,
})
)
}
return Promise.all(promises)
}
export const raw = true
================================================
FILE: src/loader-utils.d.ts
================================================
declare module 'loader-utils' {
export function interpolateName(LoaderContext: any, name: string, options: any): any
}
================================================
FILE: src/parseQuery.ts
================================================
import * as JSON5 from 'json5'
interface LooseObject {
[key: string]: any
}
const specialValues: LooseObject = {
null: null,
true: true,
false: false,
}
function parseQuery(query: string): LooseObject {
if (query.slice(0, 1) !== '?') {
throw new Error("A valid query string passed to parseQuery should begin with '?'")
}
query = query.slice(1)
if (!query) {
return {}
}
if (query.slice(0, 1) === '{' && query.slice(-1) === '}') {
query = decodeURIComponent(query)
return JSON5.parse(query)
}
const queryArgs = query.split(/[,&]/g)
const result: LooseObject = {}
queryArgs.forEach((arg) => {
const idx = arg.indexOf('=')
if (idx >= 0) {
let name = arg.slice(0, idx)
let value = decodeURIComponent(arg.slice(idx + 1))
// const specialValues: LooseObject = {}
// eslint-disable-next-line no-prototype-builtins
if (specialValues.hasOwnProperty(value)) {
value = specialValues[value]
}
if (name.slice(-2) === '[]') {
name = decodeURIComponent(name.slice(0, name.length - 2))
if (!Array.isArray(result[name])) {
result[name] = []
}
result[name].push(value)
} else {
name = decodeURIComponent(name)
result[name] = value
}
} else {
if (arg.slice(0, 1) === '-') {
result[decodeURIComponent(arg.slice(1))] = false
} else if (arg.slice(0, 1) === '+') {
result[decodeURIComponent(arg.slice(1))] = true
} else {
result[decodeURIComponent(arg)] = true
}
}
})
return result
}
export { parseQuery }
================================================
FILE: src/schema.json
================================================
{
"title": "Responsive Loader options",
"type": "object",
"properties": {
"size": {
"anyOf": [{ "type": "string" }, { "type": "number" }]
},
"sizes": {
"type": "array",
"items": {
"description": "Array of strings or numbers",
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
]
}
},
"min": {
"anyOf": [{ "type": "string" }, { "type": "number" }]
},
"max": {
"anyOf": [{ "type": "string" }, { "type": "number" }]
},
"steps": {
"anyOf": [{ "type": "string" }, { "type": "number" }]
},
"name": {
"type": "string"
},
"outputPath": {
"anyOf": [{ "type": "string" }, { "instanceof": "Function" }]
},
"publicPath": {
"anyOf": [{ "type": "string" }, { "instanceof": "Function" }]
},
"context": { "type": "string" },
"placeholderSize": {
"anyOf": [{ "type": "string" }, { "type": "number" }]
},
"quality": { "anyOf": [{ "type": "string" }, { "type": "number" }] },
"background": { "anyOf": [{ "type": "string" }, { "type": "number" }] },
"rotate": {
"anyOf": [{ "type": "string" }, { "type": "number" }]
},
"progressive": { "type": "boolean" },
"placeholder": { "type": ["string", "boolean"] },
"adapter": {
"instanceof": "Function"
},
"format": { "type": "string", "enum": ["png", "jpg", "jpeg", "webp", "avif"] },
"disable": { "type": "boolean" },
"esModule": {
"description": "By default, responsive-loader generates JS modules that don't use the ES modules syntax.",
"type": "boolean"
},
"emitFile": {
"description": "Enables/Disables emit files.",
"type": "boolean"
},
"cacheDirectory": { "anyOf": [{ "type": "string" }, { "type": "boolean" }] },
"cacheIdentifier": { "type": "string" },
"cacheCompression": { "type": "boolean" }
},
"additionalProperties": true
}
================================================
FILE: src/types.d.ts
================================================
export type Options = {
size?: string | number
sizes?: [string | number]
min?: string | number
max?: string | number
steps: string | number
name: string
outputPath?: ((...args: Array<unknown>) => string) | string
publicPath?: ((...args: Array<unknown>) => string) | string
context?: string
placeholder: string | boolean
placeholderSize: string | number
quality: string | number
background?: string | number
progressive?: boolean
rotate: string | number
adapter?: Adapter
format?: Format
disable?: boolean | null
esModule: boolean
emitFile: boolean
cacheDirectory: string | boolean
cacheIdentifier: string
cacheCompression: boolean
}
export type Format = 'png' | 'jpg' | 'jpeg' | 'webp' | 'avif'
export type FileExt = 'jpg' | 'png' | 'webp' | 'avif'
export type MimeType = 'image/jpeg' | 'image/png' | 'image/webp' | 'image/avif'
export interface CacheOptions {
cacheDirectory: string | boolean
cacheIdentifier: string
cacheCompression: boolean
}
export type Adapter = (imagePath: string) => AdapterImplementation
export interface ImageOptions {
quality: number
background?: string | number
progressive: boolean
rotate?: number
}
export interface AdapterImplementation {
metadata: () => Promise<{ width: number; height: number }>
resize: (config: { width: number; mime: string; options: Options }) => Promise<AdapterResizeResponse>
}
export type AdapterResizeResponse = { data: string | Buffer; width: number; height: number }
export interface TransformParams {
adapterModule: Adapter | undefined
resourcePath: string
createFile: ({ data, width, height }: AdapterResizeResponse) => {
src: string
path: string
width: number
height: number
}
outputPlaceholder: boolean
placeholderSize: number
mime: MimeType
sizes: number[]
adapterOptions: Options & ImageOptions
}
================================================
FILE: src/utils.ts
================================================
import * as path from 'path'
import type { Options, MimeType, ImageOptions, CacheOptions } from './types'
const version = '3'
enum MIMES {
jpg = 'image/jpeg',
jpeg = 'image/jpeg',
png = 'image/png',
webp = 'image/webp',
avif = 'image/avif',
}
enum EXTS {
'image/jpeg' = 'jpg',
'image/png' = 'png',
'image/webp' = 'webp',
'image/avif' = 'avif',
}
type ParsedOptions = {
outputPlaceholder: boolean
placeholderSize: number
name: string
mime: MimeType | undefined
ext: string
sizes: number[]
imageOptions: ImageOptions
cacheOptions: CacheOptions
}
function parseOptions(resourcePath: string, options: Options): ParsedOptions {
const outputPlaceholder = Boolean(options.placeholder)
const placeholderSize: number = parseInt(options.placeholderSize + '', 10)
// Adapter compression options
const imageOptions: ImageOptions = {
quality: parseInt(options.quality + '', 10),
rotate: parseInt(options.rotate + '', 10),
background: options.background,
progressive: Boolean(options.progressive),
}
// let mime: MimeType | undefined
// let ext: FileExt | string
let mime
let ext
if (options.format) {
mime = MIMES[options.format]
ext = EXTS[mime]
} else {
ext = path.extname(resourcePath).replace(/\./, '')
switch (ext) {
case 'jpg':
case 'jpeg':
case 'png':
case 'webp':
case 'avif':
mime = MIMES[ext]
break
default:
mime = undefined
break
}
}
const name = options.name.replace(/\[ext\]/gi, ext)
const min: number | void = options.min !== undefined ? parseInt(options.min + '', 10) : undefined
const max: number | void = options.max !== undefined ? parseInt(options.max + '', 10) : undefined
const steps: number = parseInt(options.steps + '', 10)
let generatedSizes
if (typeof min === 'number' && max) {
generatedSizes = []
for (let step = 0; step < steps; step++) {
const size = min + ((max - min) / (steps - 1)) * step
generatedSizes.push(Math.ceil(size))
}
}
const size = parseInt(options.size + '', 10)
const sizes = size
? [size]
: options.sizes?.map((size) => parseInt(size + '', 10)) || generatedSizes || [Number.MAX_SAFE_INTEGER]
// Cache options
const cacheOptions: CacheOptions = {
cacheDirectory: options.cacheDirectory,
cacheIdentifier: JSON.stringify({
options,
'responsive-loader': version,
}),
cacheCompression: Boolean(options.cacheCompression),
}
return {
ext,
mime,
name,
sizes,
outputPlaceholder,
placeholderSize,
cacheOptions,
imageOptions,
}
}
const createPlaceholder = ({ data }: { data: any }, mime: string): string => {
return `"data:${mime};base64,${data.toString('base64')}"`
}
// return `"data:${mime};base64,${data.toString("base64")}"`
interface GetOutputAndPublicPath {
(
fileName: string,
{
outputPath,
publicPath,
}: {
outputPath?: ((...args: Array<unknown>) => string) | string
publicPath?: ((...args: Array<unknown>) => string) | string
}
): {
outputPath: string
publicPath: string
}
}
/**
* **Responsive Loader Paths**
*
* Returns the output and public path
*
* @method getOutputAndPublicPath
*
* @param {string} fileName
* @param {Config} outputPath
* @param {Config} publicPath
*
* @return {Config} Paths Result
*/
const getOutputAndPublicPath: GetOutputAndPublicPath = (
fileName: string,
{ outputPath: configOutputPath, publicPath: configPublicPath }
) => {
let outputPath = fileName
if (configOutputPath) {
if (typeof configOutputPath === 'function') {
outputPath = configOutputPath(fileName)
} else {
outputPath = path.posix.join(configOutputPath, fileName)
}
}
let publicPath = `__webpack_public_path__ + ${JSON.stringify(outputPath)}`
if (configPublicPath) {
if (typeof configPublicPath === 'function') {
publicPath = configPublicPath(fileName)
} else {
// publicPath can be a url or local path
// check if it's a valid url
if (isValidUrl(configPublicPath)) {
const url = new URL(configPublicPath)
url.pathname = path.posix.join(url.pathname, fileName)
publicPath = url.toString()
} else {
publicPath = path.posix.join(configPublicPath, fileName)
}
}
publicPath = JSON.stringify(publicPath)
}
return {
outputPath,
publicPath,
}
}
const isValidUrl = (urlString: string) => {
try {
return Boolean(new URL(urlString))
} catch (e) {
return false
}
}
export { parseOptions, getOutputAndPublicPath, createPlaceholder }
================================================
FILE: test/cjs.test.js
================================================
import src from '../lib'
import cjs from '../lib/cjs'
describe('CJS', () => {
it('should export loader', () => {
expect(cjs).toEqual(src)
})
it('should export "raw" flag', () => {
expect(cjs.raw).toEqual(true)
})
})
================================================
FILE: test/jimp/build/__snapshots__/test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`disable 1`] = `
{
"default": {
"images": [
{
"height": 100,
"path": "foobar/82e6d197b5ce433a-100.jpg",
"width": 100,
},
],
"src": "foobar/82e6d197b5ce433a-100.jpg",
"srcSet": "foobar/82e6d197b5ce433a-100.jpg",
"toString": [Function],
},
}
`;
exports[`doesn't emit file 1`] = `
{
"default": {
"height": 225,
"images": [
{
"height": 225,
"path": "foobar/445db0be601e84d6-250.jpg",
"width": 250,
},
],
"src": "foobar/445db0be601e84d6-250.jpg",
"srcSet": "foobar/445db0be601e84d6-250.jpg 250w",
"toString": [Function],
"width": 250,
},
}
`;
exports[`multiple sizes 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/c869fe04ebafd01d-500.jpg",
"width": 500,
},
{
"height": 900,
"path": "foobar/076d6ca8665b1d94-1000.jpg",
"width": 1000,
},
],
"src": "foobar/076d6ca8665b1d94-1000.jpg",
"srcSet": "foobar/c869fe04ebafd01d-500.jpg 500w,foobar/076d6ca8665b1d94-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
exports[`output first resized image height & width 1`] = `
{
"default": {
"height": 450,
"images": [
{
"height": 450,
"path": "foobar/c869fe04ebafd01d-500.jpg",
"width": 500,
},
],
"src": "foobar/c869fe04ebafd01d-500.jpg",
"srcSet": "foobar/c869fe04ebafd01d-500.jpg 500w",
"toString": [Function],
"width": 500,
},
}
`;
exports[`output should be in outputPath dir 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/img/c869fe04ebafd01d-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/img/76a7b8dd076af418-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/img/076d6ca8665b1d94-1000.jpg",
"width": 1000,
},
],
"src": "foobar/img/076d6ca8665b1d94-1000.jpg",
"srcSet": "foobar/img/c869fe04ebafd01d-500.jpg 500w,foobar/img/76a7b8dd076af418-750.jpg 750w,foobar/img/076d6ca8665b1d94-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
exports[`output should be relative to context 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/test/c869fe04ebafd01d-500x450.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/test/76a7b8dd076af418-750x675.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/test/076d6ca8665b1d94-1000x900.jpg",
"width": 1000,
},
],
"src": "foobar/test/076d6ca8665b1d94-1000x900.jpg",
"srcSet": "foobar/test/c869fe04ebafd01d-500x450.jpg 500w,foobar/test/76a7b8dd076af418-750x675.jpg 750w,foobar/test/076d6ca8665b1d94-1000x900.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
exports[`override min and max with size 1`] = `
{
"default": {
"height": 90,
"images": [
{
"height": 90,
"path": "foobar/f7443232972a8934-100.jpg",
"width": 100,
},
],
"src": "foobar/f7443232972a8934-100.jpg",
"srcSet": "foobar/f7443232972a8934-100.jpg 100w",
"toString": [Function],
"width": 100,
},
}
`;
exports[`override min and max with sizes 1`] = `
{
"default": {
"height": 180,
"images": [
{
"height": 90,
"path": "foobar/f7443232972a8934-100.jpg",
"width": 100,
},
{
"height": 180,
"path": "foobar/974831fb3cc31ef7-200.jpg",
"width": 200,
},
],
"src": "foobar/974831fb3cc31ef7-200.jpg",
"srcSet": "foobar/f7443232972a8934-100.jpg 100w,foobar/974831fb3cc31ef7-200.jpg 200w",
"toString": [Function],
"width": 200,
},
}
`;
exports[`parses json notation 1`] = `
{
"default": {
"height": 180,
"images": [
{
"height": 45,
"path": "foobar/843b57924a32e9ff-50.jpg",
"width": 50,
},
{
"height": 90,
"path": "foobar/f7443232972a8934-100.jpg",
"width": 100,
},
{
"height": 180,
"path": "foobar/974831fb3cc31ef7-200.jpg",
"width": 200,
},
],
"src": "foobar/974831fb3cc31ef7-200.jpg",
"srcSet": "foobar/843b57924a32e9ff-50.jpg 50w,foobar/f7443232972a8934-100.jpg 100w,foobar/974831fb3cc31ef7-200.jpg 200w",
"toString": [Function],
"width": 200,
},
}
`;
exports[`png 1`] = `
{
"default": {
"height": 595,
"images": [
{
"height": 580,
"path": "foobar/f64253666cd2fe13-500.png",
"width": 500,
},
{
"height": 595,
"path": "foobar/a44dff6b028c41f7-513.png",
"width": 513,
},
],
"src": "foobar/a44dff6b028c41f7-513.png",
"srcSet": "foobar/f64253666cd2fe13-500.png 500w,foobar/a44dff6b028c41f7-513.png 513w",
"toString": [Function],
"width": 513,
},
}
`;
exports[`png to jpeg with background color 1`] = `
{
"default": {
"height": 595,
"images": [
{
"height": 580,
"path": "foobar/940321b5b2ba9532-500.jpg",
"width": 500,
},
{
"height": 595,
"path": "foobar/07deb4eb4e131586-513.jpg",
"width": 513,
},
],
"src": "foobar/07deb4eb4e131586-513.jpg",
"srcSet": "foobar/940321b5b2ba9532-500.jpg 500w,foobar/07deb4eb4e131586-513.jpg 513w",
"toString": [Function],
"width": 513,
},
}
`;
exports[`png to jpeg with background color 2`] = `
{
"default": {
"height": 595,
"images": [
{
"height": 580,
"path": "foobar/940321b5b2ba9532-500.jpg",
"width": 500,
},
{
"height": 595,
"path": "foobar/07deb4eb4e131586-513.jpg",
"width": 513,
},
],
"src": "foobar/07deb4eb4e131586-513.jpg",
"srcSet": "foobar/940321b5b2ba9532-500.jpg 500w,foobar/07deb4eb4e131586-513.jpg 513w",
"toString": [Function],
"width": 513,
},
}
`;
exports[`public path should replace global publicPath 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "public/c869fe04ebafd01d-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "public/76a7b8dd076af418-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "public/076d6ca8665b1d94-1000.jpg",
"width": 1000,
},
],
"src": "public/076d6ca8665b1d94-1000.jpg",
"srcSet": "public/c869fe04ebafd01d-500.jpg 500w,public/76a7b8dd076af418-750.jpg 750w,public/076d6ca8665b1d94-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
exports[`public path should replace global publicPath absolute 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "/public/c869fe04ebafd01d-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "/public/76a7b8dd076af418-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "/public/076d6ca8665b1d94-1000.jpg",
"width": 1000,
},
],
"src": "/public/076d6ca8665b1d94-1000.jpg",
"srcSet": "/public/c869fe04ebafd01d-500.jpg 500w,/public/76a7b8dd076af418-750.jpg 750w,/public/076d6ca8665b1d94-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
exports[`single size 1`] = `
{
"default": {
"height": 450,
"images": [
{
"height": 450,
"path": "foobar/c869fe04ebafd01d-500.jpg",
"width": 500,
},
],
"src": "foobar/c869fe04ebafd01d-500.jpg",
"srcSet": "foobar/c869fe04ebafd01d-500.jpg 500w",
"toString": [Function],
"width": 500,
},
}
`;
exports[`with min and max sizes 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/c869fe04ebafd01d-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/76a7b8dd076af418-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/076d6ca8665b1d94-1000.jpg",
"width": 1000,
},
],
"src": "foobar/076d6ca8665b1d94-1000.jpg",
"srcSet": "foobar/c869fe04ebafd01d-500.jpg 500w,foobar/76a7b8dd076af418-750.jpg 750w,foobar/076d6ca8665b1d94-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
exports[`with min and max sizes options 1`] = `
{
"default": {
"height": 270,
"images": [
{
"height": 90,
"path": "foobar/f7443232972a8934-100.jpg",
"width": 100,
},
{
"height": 150,
"path": "foobar/fb90951ca5da54c2-167.jpg",
"width": 167,
},
{
"height": 211,
"path": "foobar/fe6debad717249e9-234.jpg",
"width": 234,
},
{
"height": 270,
"path": "foobar/a990e2f8f763852a-300.jpg",
"width": 300,
},
],
"src": "foobar/a990e2f8f763852a-300.jpg",
"srcSet": "foobar/f7443232972a8934-100.jpg 100w,foobar/fb90951ca5da54c2-167.jpg 167w,foobar/fe6debad717249e9-234.jpg 234w,foobar/a990e2f8f763852a-300.jpg 300w",
"toString": [Function],
"width": 300,
},
}
`;
exports[`with min and max sizes, and default steps 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/c869fe04ebafd01d-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/76a7b8dd076af418-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/076d6ca8665b1d94-1000.jpg",
"width": 1000,
},
],
"src": "foobar/076d6ca8665b1d94-1000.jpg",
"srcSet": "foobar/c869fe04ebafd01d-500.jpg 500w,foobar/76a7b8dd076af418-750.jpg 750w,foobar/076d6ca8665b1d94-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
exports[`with placeholder image 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/c869fe04ebafd01d-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/76a7b8dd076af418-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/076d6ca8665b1d94-1000.jpg",
"width": 1000,
},
],
"placeholder": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx4BBQUFBwYHDggIDh4UERQeHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHv/AABEIACQAKAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AMnW/wBnaO4v4hpWo2tjZFv3reWxkRfRVzhvqSMe9bukuhnzO1mUm/ZomXVIZF8Uxy2YbMiyWZD47AYYg/pU+yGpF3XP2aNIuYi+na7cWlwe5hDRn/gORj86p0kHMzl7j9nPxZo9za32karY6lJGcyxMDCf+A5JB49cVlUoOUWjajW9nNS7HfeDPg9ObqK+8VyQsiHcLKE5DEf327j2HX1rCjgrO8zvxGZuS5aa+Z7dPNBa20tzcyRwwRKXkkfhVUDJJ9K9A8g8i8TftB+D9MuXg0u1utXZODKn7mIn2LfMf++cVm6iQ1FkvgT48eG/EWsRaXe2E+kTXB2xSPKJIi3oTgEZ9cYoVVdQcWeqQ3thcY+z3dtNuO0bJFbJ9OD1rS6ESFAeOfoaAPFPFvw98dalod43iv4hNNYW8DyPHH+7jbaCfnwACOOpzWMozfUtOK6HzWbIu/wC7IlQAH5PmyOnPpWG6L2Z33w1+Geta217cBlsJ9PmEWy5Uq27GfTjginKN0JSsz1v4S/DS78N+NINakuIfJSNmuQX3GSYhh8vA4AYHPrmqpwfMmwlJWPbAVfkZA7iuoyM+70+0u7SW2u4FninUpJDJ8yOD1BB4NTuB5/rHwltZ/F9j4h0pbKxNsQJIBbDy3VfukKMYYcfp6VDp63Q0+5sa5pOssrxxqmbniS4jjw+4Dgkg5HAxntUzTYIs/C7Tdfg8O+X4ilea4S5kEczABpI88EgdO/4VVNO2o5W6HXeQVyQcitCSVVGc5NMBr5DcE1LASJiwYtzzTQErcKBng0wAdM9z1oA//9k=",
"src": "foobar/076d6ca8665b1d94-1000.jpg",
"srcSet": "foobar/c869fe04ebafd01d-500.jpg 500w,foobar/76a7b8dd076af418-750.jpg 750w,foobar/076d6ca8665b1d94-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
exports[`with size defined in webpack.config.js 1`] = `
{
"default": {
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/c869fe04ebafd01d-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/76a7b8dd076af418-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/076d6ca8665b1d94-1000.jpg",
"width": 1000,
},
],
"src": "foobar/076d6ca8665b1d94-1000.jpg",
"srcSet": "foobar/c869fe04ebafd01d-500.jpg 500w,foobar/76a7b8dd076af418-750.jpg 750w,foobar/076d6ca8665b1d94-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
},
}
`;
================================================
FILE: test/jimp/index.js
================================================
test('multiple sizes', () => {
const multi = require('../cat-1000.jpg?sizes[]=500&sizes[]=2000')
expect(multi).toMatchSnapshot()
expect(multi.default.toString()).toBe(multi.default.src)
})
test('parses json notation', () => {
const multi = require('../cat-1000.jpg?{sizes:[50,100,200]}')
expect(multi).toMatchSnapshot()
})
test('single size', () => {
const single = require('../cat-1000.jpg?size=500')
expect(single).toMatchSnapshot()
})
test('with size defined in webpack.config.js', () => {
const multi = require('../cat-1000.jpg')
expect(multi).toMatchSnapshot()
})
test('disable', () => {
const multi = require('../cat-1000.jpg?disable')
expect(multi).toMatchSnapshot()
})
test('output should be relative to context', () => {
const multi = require('../cat-1000.jpg?name=[path][hash]-[width]x[height].[ext]&context=./')
expect(multi).toMatchSnapshot()
})
test('output should be in outputPath dir', () => {
const multi = require('../cat-1000.jpg?outputPath=img/')
expect(multi).toMatchSnapshot()
})
test('public path should replace global publicPath', () => {
const multi = require('../cat-1000.jpg?outputPath=img/&publicPath=public/')
expect(multi).toMatchSnapshot()
})
test('public path should replace global publicPath absolute', () => {
const multi = require('../cat-1000.jpg?outputPath=/img2/&publicPath=/public/')
expect(multi).toMatchSnapshot()
})
test('with placeholder image', () => {
const output = require('../cat-1000.jpg?placeholder=true')
expect(output).toMatchSnapshot()
})
test('output first resized image height & width', () => {
const output = require('../cat-1000.jpg?size=500')
expect(output).toMatchSnapshot()
})
test('png', () => {
const output = require('../cat-transparent.png')
expect(output).toMatchSnapshot()
})
test('png to jpeg with background color', () => {
const output = require('../cat-transparent.png?background=0xFF0000FF&format=jpg')
expect(output).toMatchSnapshot()
})
test('png to jpeg with background color', () => {
const output = require('../cat-transparent.png?background=0xFF0000FF&format=jpg')
expect(output).toMatchSnapshot()
})
test('with min and max sizes', () => {
const output = require('../cat-1000.jpg?min=600&max=800&steps=3')
expect(output).toMatchSnapshot()
})
test('with min and max sizes, and default steps', () => {
const output = require('../cat-1000.jpg?min=500&max=1000')
expect(output).toMatchSnapshot()
})
test('with min and max sizes options', () => {
const output = require('../cat-1000.jpg?minmax')
expect(output).toMatchSnapshot()
})
test('override min and max with sizes', () => {
const output = require('../cat-1000.jpg?minmax&sizes[]=100&sizes[]=200')
expect(output).toMatchSnapshot()
})
test('override min and max with size', () => {
const output = require('../cat-1000.jpg?minmax&size=100')
expect(output).toMatchSnapshot()
})
test("doesn't emit file", () => {
const multi = require('../cat-1000.jpg?emitFile=false&sizes[]=250')
expect(multi).toMatchSnapshot()
})
================================================
FILE: test/jimp/webpack.config.js
================================================
const path = require('path')
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, 'index'),
module: {
rules: [
// This rule will be matched when the resourceQuery contains `minmax`, e.g. `cat-1000.jpg?minmax`
{
test: /\.(png|jpg)$/,
resourceQuery: /minmax/,
loader: require.resolve('../../lib/index'),
options: {
min: 100,
max: 300,
esModule: true,
adapter: require('../../jimp'),
},
type: 'javascript/auto',
},
{
test: /\.(png|jpg)$/,
loader: require.resolve('../../lib/index'),
options: {
sizes: [500, 750, 1000],
esModule: true,
adapter: require('../../jimp'),
},
type: 'javascript/auto',
},
],
},
output: {
path: path.resolve(__dirname, 'build'),
publicPath: 'foobar/',
filename: 'test.js',
},
target: 'node',
}
================================================
FILE: test/sharp/build/__snapshots__/test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Works with the cache 1`] = `
{
"height": 451,
"images": [
{
"height": 451,
"path": "foobar/ab790bc248982116-501.jpg",
"width": 501,
},
],
"src": "foobar/ab790bc248982116-501.jpg",
"srcSet": "foobar/ab790bc248982116-501.jpg 501w",
"toString": [Function],
"width": 501,
}
`;
exports[`creates jpeg extension 1`] = `
{
"height": 395,
"images": [
{
"height": 395,
"path": "foobar/f93d9af4a72634bc-439.jpeg",
"width": 439,
},
],
"src": "foobar/f93d9af4a72634bc-439.jpeg",
"srcSet": "foobar/f93d9af4a72634bc-439.jpeg 439w",
"toString": [Function],
"width": 439,
}
`;
exports[`disable 1`] = `
{
"images": [
{
"height": 100,
"path": "foobar/82e6d197b5ce433a-100.jpg",
"width": 100,
},
],
"src": "foobar/82e6d197b5ce433a-100.jpg",
"srcSet": "foobar/82e6d197b5ce433a-100.jpg",
"toString": [Function],
}
`;
exports[`doesn't emit file 1`] = `
{
"height": 225,
"images": [
{
"height": 225,
"path": "foobar/0a229ed85b2f5618-250.jpg",
"width": 250,
},
],
"src": "foobar/0a229ed85b2f5618-250.jpg",
"srcSet": "foobar/0a229ed85b2f5618-250.jpg 250w",
"toString": [Function],
"width": 250,
}
`;
exports[`hash lenght output should be relative to context 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/test/282d046d-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/test/b8e60128-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/test/3cf3c375-1000.jpg",
"width": 1000,
},
],
"src": "foobar/test/3cf3c375-1000.jpg",
"srcSet": "foobar/test/282d046d-500.jpg 500w,foobar/test/b8e60128-750.jpg 750w,foobar/test/3cf3c375-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`jpg to webp 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/8593c1f9da542c50-500.webp",
"width": 500,
},
{
"height": 675,
"path": "foobar/72befca36bd004a7-750.webp",
"width": 750,
},
{
"height": 900,
"path": "foobar/c66204d304cfe4fd-1000.webp",
"width": 1000,
},
],
"src": "foobar/c66204d304cfe4fd-1000.webp",
"srcSet": "foobar/8593c1f9da542c50-500.webp 500w,foobar/72befca36bd004a7-750.webp 750w,foobar/c66204d304cfe4fd-1000.webp 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`multiple sizes 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/282d046d550fa19c-500.jpg",
"width": 500,
},
{
"height": 900,
"path": "foobar/3cf3c37514578847-1000.jpg",
"width": 1000,
},
],
"src": "foobar/3cf3c37514578847-1000.jpg",
"srcSet": "foobar/282d046d550fa19c-500.jpg 500w,foobar/3cf3c37514578847-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`output first resized image height & width 1`] = `
{
"height": 450,
"images": [
{
"height": 450,
"path": "foobar/282d046d550fa19c-500.jpg",
"width": 500,
},
],
"src": "foobar/282d046d550fa19c-500.jpg",
"srcSet": "foobar/282d046d550fa19c-500.jpg 500w",
"toString": [Function],
"width": 500,
}
`;
exports[`output should be in outputPath dir 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/img/282d046d550fa19c-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/img/b8e6012830c98919-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/img/3cf3c37514578847-1000.jpg",
"width": 1000,
},
],
"src": "foobar/img/3cf3c37514578847-1000.jpg",
"srcSet": "foobar/img/282d046d550fa19c-500.jpg 500w,foobar/img/b8e6012830c98919-750.jpg 750w,foobar/img/3cf3c37514578847-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`output should be relative to context 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/test/282d046d550fa19c-500x450.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/test/b8e6012830c98919-750x675.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/test/3cf3c37514578847-1000x900.jpg",
"width": 1000,
},
],
"src": "foobar/test/3cf3c37514578847-1000x900.jpg",
"srcSet": "foobar/test/282d046d550fa19c-500x450.jpg 500w,foobar/test/b8e6012830c98919-750x675.jpg 750w,foobar/test/3cf3c37514578847-1000x900.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`override min and max with size 1`] = `
{
"height": 90,
"images": [
{
"height": 90,
"path": "foobar/661fbbbdbe7372a2-100.jpg",
"width": 100,
},
],
"src": "foobar/661fbbbdbe7372a2-100.jpg",
"srcSet": "foobar/661fbbbdbe7372a2-100.jpg 100w",
"toString": [Function],
"width": 100,
}
`;
exports[`override min and max with sizes 1`] = `
{
"height": 180,
"images": [
{
"height": 90,
"path": "foobar/661fbbbdbe7372a2-100.jpg",
"width": 100,
},
{
"height": 180,
"path": "foobar/daae2e5f7f65b269-200.jpg",
"width": 200,
},
],
"src": "foobar/daae2e5f7f65b269-200.jpg",
"srcSet": "foobar/661fbbbdbe7372a2-100.jpg 100w,foobar/daae2e5f7f65b269-200.jpg 200w",
"toString": [Function],
"width": 200,
}
`;
exports[`parses json notation 1`] = `
{
"height": 180,
"images": [
{
"height": 45,
"path": "foobar/b58d8935f3e689e3-50.webp",
"width": 50,
},
{
"height": 90,
"path": "foobar/fffbb70a2c269cf1-100.webp",
"width": 100,
},
{
"height": 180,
"path": "foobar/701bf549670b5294-200.webp",
"width": 200,
},
],
"src": "foobar/701bf549670b5294-200.webp",
"srcSet": "foobar/b58d8935f3e689e3-50.webp 50w,foobar/fffbb70a2c269cf1-100.webp 100w,foobar/701bf549670b5294-200.webp 200w",
"toString": [Function],
"width": 200,
}
`;
exports[`png 1`] = `
{
"height": 595,
"images": [
{
"height": 580,
"path": "foobar/ed6170e9b0b0edf0-500.png",
"width": 500,
},
{
"height": 595,
"path": "foobar/418c98c2d061efc6-513.png",
"width": 513,
},
],
"src": "foobar/418c98c2d061efc6-513.png",
"srcSet": "foobar/ed6170e9b0b0edf0-500.png 500w,foobar/418c98c2d061efc6-513.png 513w",
"toString": [Function],
"width": 513,
}
`;
exports[`png to avif 1`] = `
{
"height": 595,
"images": [
{
"height": 595,
"path": "foobar/54f4e72270566ed5-513.avif",
"width": 513,
},
],
"src": "foobar/54f4e72270566ed5-513.avif",
"srcSet": "foobar/54f4e72270566ed5-513.avif 513w",
"toString": [Function],
"width": 513,
}
`;
exports[`png to avif 2`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/99b6b84c23e8a9b4-500.avif",
"width": 500,
},
{
"height": 675,
"path": "foobar/d0b22cf68e1f393a-750.avif",
"width": 750,
},
{
"height": 900,
"path": "foobar/6f9cba284621e494-1000.avif",
"width": 1000,
},
],
"src": "foobar/6f9cba284621e494-1000.avif",
"srcSet": "foobar/99b6b84c23e8a9b4-500.avif 500w,foobar/d0b22cf68e1f393a-750.avif 750w,foobar/6f9cba284621e494-1000.avif 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`png to jpeg with background color 1`] = `
{
"height": 595,
"images": [
{
"height": 580,
"path": "foobar/0be420cbedde023f-500.jpg",
"width": 500,
},
{
"height": 595,
"path": "foobar/9e08ba9531804694-513.jpg",
"width": 513,
},
],
"src": "foobar/9e08ba9531804694-513.jpg",
"srcSet": "foobar/0be420cbedde023f-500.jpg 500w,foobar/9e08ba9531804694-513.jpg 513w",
"toString": [Function],
"width": 513,
}
`;
exports[`png to webp with transparent background 1`] = `
{
"height": 595,
"images": [
{
"height": 580,
"path": "foobar/548d85cfef6a6c88-500.webp",
"width": 500,
},
{
"height": 595,
"path": "foobar/924e13d10c98fb28-513.webp",
"width": 513,
},
],
"src": "foobar/924e13d10c98fb28-513.webp",
"srcSet": "foobar/548d85cfef6a6c88-500.webp 500w,foobar/924e13d10c98fb28-513.webp 513w",
"toString": [Function],
"width": 513,
}
`;
exports[`preserves rotation 1`] = `
{
"height": 554,
"images": [
{
"height": 554,
"path": "foobar/eb9b881857272a9d-499.jpg",
"width": 499,
},
],
"src": "foobar/eb9b881857272a9d-499.jpg",
"srcSet": "foobar/eb9b881857272a9d-499.jpg 499w",
"toString": [Function],
"width": 499,
}
`;
exports[`progressive image 1`] = `
{
"height": 864,
"images": [
{
"height": 684,
"path": "foobar/e971e26ec2eea1f1-760.jpg",
"width": 760,
},
{
"height": 864,
"path": "foobar/e5e8c73f5f931a48-960.jpg",
"width": 960,
},
],
"src": "foobar/e5e8c73f5f931a48-960.jpg",
"srcSet": "foobar/e971e26ec2eea1f1-760.jpg 760w,foobar/e5e8c73f5f931a48-960.jpg 960w",
"toString": [Function],
"width": 960,
}
`;
exports[`public path should replace global publicPath 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "public/282d046d550fa19c-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "public/b8e6012830c98919-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "public/3cf3c37514578847-1000.jpg",
"width": 1000,
},
],
"src": "public/3cf3c37514578847-1000.jpg",
"srcSet": "public/282d046d550fa19c-500.jpg 500w,public/b8e6012830c98919-750.jpg 750w,public/3cf3c37514578847-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`rotates 90 1`] = `
{
"height": 666,
"images": [
{
"height": 666,
"path": "foobar/aa309fc022524f4b-599.jpg",
"width": 599,
},
],
"src": "foobar/aa309fc022524f4b-599.jpg",
"srcSet": "foobar/aa309fc022524f4b-599.jpg 599w",
"toString": [Function],
"width": 599,
}
`;
exports[`single size 1`] = `
{
"height": 450,
"images": [
{
"height": 450,
"path": "foobar/282d046d550fa19c-500.jpg",
"width": 500,
},
],
"src": "foobar/282d046d550fa19c-500.jpg",
"srcSet": "foobar/282d046d550fa19c-500.jpg 500w",
"toString": [Function],
"width": 500,
}
`;
exports[`with min and max sizes 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/282d046d550fa19c-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/b8e6012830c98919-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/3cf3c37514578847-1000.jpg",
"width": 1000,
},
],
"src": "foobar/3cf3c37514578847-1000.jpg",
"srcSet": "foobar/282d046d550fa19c-500.jpg 500w,foobar/b8e6012830c98919-750.jpg 750w,foobar/3cf3c37514578847-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`with min and max sizes options 1`] = `
{
"height": 270,
"images": [
{
"height": 90,
"path": "foobar/661fbbbdbe7372a2-100.jpg",
"width": 100,
},
{
"height": 150,
"path": "foobar/87ddc3296017abd7-167.jpg",
"width": 167,
},
{
"height": 211,
"path": "foobar/c792e1e8f501c416-234.jpg",
"width": 234,
},
{
"height": 270,
"path": "foobar/5de63dd31e93ce82-300.jpg",
"width": 300,
},
],
"src": "foobar/5de63dd31e93ce82-300.jpg",
"srcSet": "foobar/661fbbbdbe7372a2-100.jpg 100w,foobar/87ddc3296017abd7-167.jpg 167w,foobar/c792e1e8f501c416-234.jpg 234w,foobar/5de63dd31e93ce82-300.jpg 300w",
"toString": [Function],
"width": 300,
}
`;
exports[`with min and max sizes, and default steps 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/282d046d550fa19c-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/b8e6012830c98919-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/3cf3c37514578847-1000.jpg",
"width": 1000,
},
],
"src": "foobar/3cf3c37514578847-1000.jpg",
"srcSet": "foobar/282d046d550fa19c-500.jpg 500w,foobar/b8e6012830c98919-750.jpg 750w,foobar/3cf3c37514578847-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`with placeholder image 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/282d046d550fa19c-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/b8e6012830c98919-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/3cf3c37514578847-1000.jpg",
"width": 1000,
},
],
"placeholder": "data:image/jpeg;base64,/9j/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAkACgDASIAAhEBAxEB/8QAHAAAAgEFAQAAAAAAAAAAAAAAAAcGAQIDBAgF/8QALBAAAQMDAwIGAgIDAAAAAAAAAQIDBAAFEQYSIQcxCBMiQVFhFDJxgSNSgv/EABgBAAMBAQAAAAAAAAAAAAAAAAABAwIE/8QAHBEAAwEBAAMBAAAAAAAAAAAAAAECEQMEEjEi/9oADAMBAAIRAxEAPwDx734dA/cGU2i4Q4EIq/yqKFlxCfhKckKP2SMVpK8M8tN1YWjVMd6EFEuhyGpLmPYABRB++RXSrzrEaO7JkONtMMpK3HFcJQkDJJPsBSk1N4g9FWuQti2MTL0pPHms4aaJ+lK5P8gVdzK+k02RK+eGW1yGiu2agfiyOf2jhTZ/oHI/o1F3/DprKzSok+0XK3XVxs5eaJLB/wCSrIPHzim1oTrvpfUl4ZtUuDKs8iSdrLjriXGlK9klQA25+SMU02pMN7HkyY7u47RscSrJ+OD3ocRSNTdS00JHRnR2S5KZm6tWx5SCFfgsK3biP919sfQ7/NFPEoBGO/0aKUcOcLEivXyevWtpiT1b0/6h3WxyxqzqGXbfHjrcW00A02vakn14CQRx3Oa5qMMuHc2pLiAAco9QI7c/Fd6zLdDmQnYsxhMhmQkodZc9SFg9wR2NL689JIz+sIOorSiBAMdSQ6x+MPKcSn9SEpwAocd+OB8Vmo34SVYIjpn01vd8XMlNlMF+3Ppa8uQClW7bnGPbgim30i6aTdNa1YvLjzCY6W1LlDzN298hQASMD0jcDn+am19tV6UFoQhvdL4clNN4XuA9JJByOBjJ7Vs9LLfqBnTezUbi3ZKZLnlvqSlKnW8+klI7e4rKj9D9nhLwUr5HA9xiiqeQU5IORRXRpMyoGVE5NWuZSeCaKKGBayoqCyrBrMrhIGe9FFCAByM+570UUUwP/9k=",
"src": "foobar/3cf3c37514578847-1000.jpg",
"srcSet": "foobar/282d046d550fa19c-500.jpg 500w,foobar/b8e6012830c98919-750.jpg 750w,foobar/3cf3c37514578847-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
exports[`with placeholder image on image with size 1`] = `
{
"height": 360,
"images": [
{
"height": 360,
"path": "foobar/c985fe28cb56f01d-400.jpg",
"width": 400,
},
],
"placeholder": "data:image/jpeg;base64,/9j/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAkACgDASIAAhEBAxEB/8QAHAAAAgEFAQAAAAAAAAAAAAAAAAcGAQIDBAgF/8QALBAAAQMDAwIGAgIDAAAAAAAAAQIDBAAFEQYSIQcxCBMiQVFhFDJxgSNSgv/EABgBAAMBAQAAAAAAAAAAAAAAAAABAwIE/8QAHBEAAwEBAAMBAAAAAAAAAAAAAAECEQMEEjEi/9oADAMBAAIRAxEAPwDx734dA/cGU2i4Q4EIq/yqKFlxCfhKckKP2SMVpK8M8tN1YWjVMd6EFEuhyGpLmPYABRB++RXSrzrEaO7JkONtMMpK3HFcJQkDJJPsBSk1N4g9FWuQti2MTL0pPHms4aaJ+lK5P8gVdzK+k02RK+eGW1yGiu2agfiyOf2jhTZ/oHI/o1F3/DprKzSok+0XK3XVxs5eaJLB/wCSrIPHzim1oTrvpfUl4ZtUuDKs8iSdrLjriXGlK9klQA25+SMU02pMN7HkyY7u47RscSrJ+OD3ocRSNTdS00JHRnR2S5KZm6tWx5SCFfgsK3biP919sfQ7/NFPEoBGO/0aKUcOcLEivXyevWtpiT1b0/6h3WxyxqzqGXbfHjrcW00A02vakn14CQRx3Oa5qMMuHc2pLiAAco9QI7c/Fd6zLdDmQnYsxhMhmQkodZc9SFg9wR2NL689JIz+sIOorSiBAMdSQ6x+MPKcSn9SEpwAocd+OB8Vmo34SVYIjpn01vd8XMlNlMF+3Ppa8uQClW7bnGPbgim30i6aTdNa1YvLjzCY6W1LlDzN298hQASMD0jcDn+am19tV6UFoQhvdL4clNN4XuA9JJByOBjJ7Vs9LLfqBnTezUbi3ZKZLnlvqSlKnW8+klI7e4rKj9D9nhLwUr5HA9xiiqeQU5IORRXRpMyoGVE5NWuZSeCaKKGBayoqCyrBrMrhIGe9FFCAByM+570UUUwP/9k=",
"src": "foobar/c985fe28cb56f01d-400.jpg",
"srcSet": "foobar/c985fe28cb56f01d-400.jpg 400w",
"toString": [Function],
"width": 400,
}
`;
exports[`with size defined in webpack.config.js 1`] = `
{
"height": 900,
"images": [
{
"height": 450,
"path": "foobar/282d046d550fa19c-500.jpg",
"width": 500,
},
{
"height": 675,
"path": "foobar/b8e6012830c98919-750.jpg",
"width": 750,
},
{
"height": 900,
"path": "foobar/3cf3c37514578847-1000.jpg",
"width": 1000,
},
],
"src": "foobar/3cf3c37514578847-1000.jpg",
"srcSet": "foobar/282d046d550fa19c-500.jpg 500w,foobar/b8e6012830c98919-750.jpg 750w,foobar/3cf3c37514578847-1000.jpg 1000w",
"toString": [Function],
"width": 1000,
}
`;
================================================
FILE: test/sharp/index.js
================================================
test('Works with the cache', () => {
const output = require('../cat-1000.jpg?size=501,cacheDirectory')
expect(output).toMatchSnapshot()
})
test('creates jpeg extension', () => {
const output = require('../cat-1000copy.jpeg?size=439')
expect(output).toMatchSnapshot()
})
test('png to avif', () => {
const output = require('../cat-transparent.png?format=avif&size=777')
expect(output).toMatchSnapshot()
})
test('png to avif', () => {
const output = require('../cat-1000.jpg?format=avif')
expect(output).toMatchSnapshot()
})
test('preserves rotation', () => {
const single = require('../cat-rotated-1000.jpg?size=499')
expect(single).toMatchSnapshot()
})
test('rotates 90', () => {
const single = require('../cat-1000.jpg?size=599&rotate=90')
expect(single).toMatchSnapshot()
})
test('progressive image', () => {
const multi = require('../cat-1000.jpg?sizes[]=760&sizes[]=960&progressive=true')
expect(multi).toMatchSnapshot()
})
test('multiple sizes', () => {
const multi = require('../cat-1000.jpg?sizes[]=500&sizes[]=2000')
expect(multi).toMatchSnapshot()
expect(multi.toString()).toBe(multi.src)
})
test('parses json notation', () => {
const multi = require('../cat-1000.jpg?{sizes:[50,100,200], format: "webp"}')
expect(multi).toMatchSnapshot()
})
test('single size', () => {
const single = require('../cat-1000.jpg?size=500')
expect(single).toMatchSnapshot()
})
test('with size defined in webpack.config.js', () => {
const multi = require('../cat-1000.jpg')
expect(multi).toMatchSnapshot()
})
test('disable', () => {
const multi = require('../cat-1000.jpg?disable')
expect(multi).toMatchSnapshot()
})
test('output should be relative to context', () => {
const multi = require('../cat-1000.jpg?name=[path][hash]-[width]x[height].[ext]&context=./')
expect(multi).toMatchSnapshot()
})
test('hash lenght output should be relative to context', () => {
const multi = require('../cat-1000.jpg?name=[path][contenthash:8]-[width].[ext]&context=./')
expect(multi).toMatchSnapshot()
})
test('output should be in outputPath dir', () => {
const multi = require('../cat-1000.jpg?outputPath=img/')
expect(multi).toMatchSnapshot()
})
test('public path should replace global publicPath', () => {
const multi = require('../cat-1000.jpg?outputPath=img/&publicPath=public/')
expect(multi).toMatchSnapshot()
})
test('with placeholder image', () => {
const output = require('../cat-1000.jpg?placeholder=true')
expect(output).toMatchSnapshot()
})
test('with placeholder image on image with size', () => {
const output = require('../cat-1000.jpg?size=400&placeholder=true')
expect(output).toMatchSnapshot()
})
test('output first resized image height & width', () => {
const output = require('../cat-1000.jpg?size=500')
expect(output).toMatchSnapshot()
})
test('png', () => {
const output = require('../cat-transparent.png')
expect(output).toMatchSnapshot()
})
test('png to jpeg with background color', () => {
const output = require('../cat-transparent.png?background=%23FF0000&format=jpg')
expect(output).toMatchSnapshot()
})
test('with min and max sizes', () => {
const output = require('../cat-1000.jpg?min=600&max=800&steps=3')
expect(output).toMatchSnapshot()
})
test('with min and max sizes, and default steps', () => {
const output = require('../cat-1000.jpg?min=500&max=1000')
expect(output).toMatchSnapshot()
})
test('with min and max sizes options', () => {
const output = require('../cat-1000.jpg?minmax')
expect(output).toMatchSnapshot()
})
test('override min and max with sizes', () => {
const output = require('../cat-1000.jpg?minmax&sizes[]=100&sizes[]=200')
expect(output).toMatchSnapshot()
})
test('override min and max with size', () => {
const output = require('../cat-1000.jpg?minmax&size=100')
expect(output).toMatchSnapshot()
})
test('jpg to webp', () => {
const output = require('../cat-1000.jpg?format=webp')
expect(output).toMatchSnapshot()
})
test('png to webp with transparent background', () => {
const output = require('../cat-transparent.png?format=webp')
expect(output).toMatchSnapshot()
})
test("doesn't emit file", () => {
const multi = require('../cat-1000.jpg?emitFile=false&sizes[]=250')
expect(multi).toMatchSnapshot()
})
================================================
FILE: test/sharp/webpack.config.js
================================================
const path = require('path')
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, 'index'),
module: {
rules: [
// This rule will be matched when the resourceQuery contains `minmax`, e.g. `cat-1000.jpg?minmax`
{
test: /\.(png|jpe?g)$/,
resourceQuery: /minmax/,
use: [
{
loader: require.resolve('../../lib/index'),
options: {
min: 100,
max: 300,
},
},
],
type: 'javascript/auto',
},
{
test: /\.(png|jpe?g)$/,
use: [
{
loader: require.resolve('../../lib/index'),
options: {
sizes: [500, 750, 1000],
},
},
],
type: 'javascript/auto',
},
],
},
output: {
path: path.resolve(__dirname, 'build'),
publicPath: 'foobar/',
filename: 'test.js',
},
target: 'node',
}
================================================
FILE: test/utils.test.js
================================================
import { getOutputAndPublicPath } from '../lib/utils'
describe('Utils package', () => {
it('should create both paths respecting absolutes', () => {
const { outputPath, publicPath } = getOutputAndPublicPath('file.png', {
outputPath: '/dist/img/',
publicPath: '/public',
})
expect(outputPath).toBe('/dist/img/file.png')
expect(publicPath).toBe('"/public/file.png"')
})
it('should create both paths ', () => {
const { outputPath, publicPath } = getOutputAndPublicPath('file.png', {
outputPath: 'dist/img/',
publicPath: 'public/',
})
expect(outputPath).toBe('dist/img/file.png')
expect(publicPath).toBe('"public/file.png"')
})
it('https:// slashes are kept on public path', () => {
const { outputPath, publicPath } = getOutputAndPublicPath('file.png', {
outputPath: 'dist/img/',
publicPath: 'https://example.com/public/',
})
expect(publicPath).toBe('"https://example.com/public/file.png"')
})
})
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
// node 10 https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping
"target": "es2018" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"lib": ["es2018"],
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"outDir": "lib",
"allowJs": true,
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
"moduleResolution": "node",
"noUnusedLocals": true,
"noUnusedParameters": true,
/* Additional Checks */
"resolveJsonModule": true
},
"include": ["src/**/*"],
}
gitextract_hh3w3uug/ ├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .github/ │ └── workflows/ │ └── codeql-analysis.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jimp.js ├── package.json ├── sharp.js ├── src/ │ ├── adapters/ │ │ ├── jimp.ts │ │ └── sharp.ts │ ├── cache.ts │ ├── cjs.js │ ├── index.ts │ ├── loader-utils.d.ts │ ├── parseQuery.ts │ ├── schema.json │ ├── types.d.ts │ └── utils.ts ├── test/ │ ├── cjs.test.js │ ├── jimp/ │ │ ├── build/ │ │ │ └── __snapshots__/ │ │ │ └── test.js.snap │ │ ├── index.js │ │ └── webpack.config.js │ ├── sharp/ │ │ ├── build/ │ │ │ └── __snapshots__/ │ │ │ └── test.js.snap │ │ ├── index.js │ │ └── webpack.config.js │ └── utils.test.js └── tsconfig.json
SYMBOL INDEX (35 symbols across 7 files)
FILE: src/adapters/jimp.ts
type ResizeProps (line 3) | type ResizeProps = {
class JimpAdapter (line 14) | class JimpAdapter {
method constructor (line 16) | constructor(imagePath: string) {
method metadata (line 19) | metadata(): Promise<{ height: number; width: number }> {
method resize (line 25) | resize({
FILE: src/adapters/sharp.ts
type ResizeProps (line 3) | type ResizeProps = {
class SharpAdapter (line 14) | class SharpAdapter {
method constructor (line 16) | constructor(imagePath: string) {
method metadata (line 19) | metadata(): Promise<sharp.Metadata> {
method resize (line 22) | resize({ width, mime, options }: ResizeProps): Promise<{ data: Buffer;...
FILE: src/cache.ts
function cache (line 138) | async function cache(cacheOptions: CacheOptions, transformParams: Transf...
FILE: src/index.ts
constant DEFAULTS (line 22) | const DEFAULTS = {
function loader (line 46) | function loader(this: LoaderContext<Options>, content: string): void {
type OrchestrateParams (line 130) | interface OrchestrateParams {
function orchestrate (line 134) | async function orchestrate(params: OrchestrateParams) {
function transform (line 149) | async function transform({
type TransformationParams (line 189) | interface TransformationParams {
function transformations (line 203) | async function transformations({
FILE: src/parseQuery.ts
type LooseObject (line 3) | interface LooseObject {
function parseQuery (line 13) | function parseQuery(query: string): LooseObject {
FILE: src/types.d.ts
type Options (line 1) | type Options = {
type Format (line 26) | type Format = 'png' | 'jpg' | 'jpeg' | 'webp' | 'avif'
type FileExt (line 27) | type FileExt = 'jpg' | 'png' | 'webp' | 'avif'
type MimeType (line 28) | type MimeType = 'image/jpeg' | 'image/png' | 'image/webp' | 'image/avif'
type CacheOptions (line 30) | interface CacheOptions {
type Adapter (line 36) | type Adapter = (imagePath: string) => AdapterImplementation
type ImageOptions (line 37) | interface ImageOptions {
type AdapterImplementation (line 43) | interface AdapterImplementation {
type AdapterResizeResponse (line 47) | type AdapterResizeResponse = { data: string | Buffer; width: number; hei...
type TransformParams (line 49) | interface TransformParams {
FILE: src/utils.ts
type MIMES (line 6) | enum MIMES {
type EXTS (line 14) | enum EXTS {
type ParsedOptions (line 21) | type ParsedOptions = {
function parseOptions (line 32) | function parseOptions(resourcePath: string, options: Options): ParsedOpt...
type GetOutputAndPublicPath (line 112) | interface GetOutputAndPublicPath {
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (100K chars).
[
{
"path": ".babelrc",
"chars": 137,
"preview": "{\n \"presets\": [\n [\n \"@babel/preset-env\",\n {\n \"targets\": {\n \"node\": \"12.22.1\"\n }\n "
},
{
"path": ".eslintignore",
"chars": 139,
"preview": "# don't ever lint node_modules\nnode_modules\n# don't lint build output\nlib\n# don't lint test\ntest\n\nsharp.js\njimp.js\nsrc/c"
},
{
"path": ".eslintrc.js",
"chars": 283,
"preview": "module.exports = {\n root: true,\n parser: \"@typescript-eslint/parser\",\n plugins: [\"@typescript-eslint\"],\n extends: [\""
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 2330,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".gitignore",
"chars": 214,
"preview": "node_modules\n*.log\ntest/build\ntest/**/build/*.js\ntest/**/build/**/*.png\ntest/**/build/**/*.jpg\ntest/**/build/**/*.avif\nt"
},
{
"path": ".travis.yml",
"chars": 44,
"preview": "os: osx\nlanguage: node_js\nnode_js:\n - '12'\n"
},
{
"path": "CHANGELOG.md",
"chars": 3857,
"preview": "# Change Log\n\n## v1.1.0\n\n - Added `min` and `max` options to automatically generate a number of images, and `steps` opti"
},
{
"path": "LICENSE",
"chars": 1489,
"preview": "Copyright (c) 2016, Jeremy Stucki\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or witho"
},
{
"path": "README.md",
"chars": 17327,
"preview": "# responsive-loader\n\n[![build][travis]][travis-url]\n[![node][node]][node-url]\n\nA webpack loader for responsive images. C"
},
{
"path": "jimp.js",
"chars": 49,
"preview": "module.exports = require('./lib/adapters/jimp');\n"
},
{
"path": "package.json",
"chars": 2236,
"preview": "{\n \"name\": \"responsive-loader\",\n \"version\": \"3.1.2\",\n \"description\": \"A webpack loader for responsive images\",\n \"mai"
},
{
"path": "sharp.js",
"chars": 50,
"preview": "module.exports = require('./lib/adapters/sharp');\n"
},
{
"path": "src/adapters/jimp.ts",
"chars": 1391,
"preview": "import * as jimp from \"jimp\"\n\ntype ResizeProps = {\n width: number\n mime: \"image/jpeg\" | \"image/png\" | \"image/webp\" | \""
},
{
"path": "src/adapters/sharp.ts",
"chars": 2155,
"preview": "import * as sharp from 'sharp'\n\ntype ResizeProps = {\n width: number\n mime: 'image/jpeg' | 'image/png' | 'image/webp' |"
},
{
"path": "src/cache.ts",
"chars": 3948,
"preview": "/**\n * Filesystem Cache\n *\n * Given a file and a transform function, cache the result into files\n * or retrieve the prev"
},
{
"path": "src/cjs.js",
"chars": 99,
"preview": "const loader = require('./index')\n\nmodule.exports = loader.default\nmodule.exports.raw = loader.raw\n"
},
{
"path": "src/index.ts",
"chars": 6539,
"preview": "import * as schema from './schema.json'\nimport { validate } from 'schema-utils'\nimport { JSONSchema7 } from 'schema-util"
},
{
"path": "src/loader-utils.d.ts",
"chars": 121,
"preview": "declare module 'loader-utils' {\n export function interpolateName(LoaderContext: any, name: string, options: any): any\n}"
},
{
"path": "src/parseQuery.ts",
"chars": 1628,
"preview": "import * as JSON5 from 'json5'\n\ninterface LooseObject {\n [key: string]: any\n}\n\nconst specialValues: LooseObject = {\n n"
},
{
"path": "src/schema.json",
"chars": 2009,
"preview": "{\n \"title\": \"Responsive Loader options\",\n \"type\": \"object\",\n \"properties\": {\n \"size\": {\n \"anyOf\": [{ \"type\": "
},
{
"path": "src/types.d.ts",
"chars": 1863,
"preview": "export type Options = {\n size?: string | number\n sizes?: [string | number]\n min?: string | number\n max?: string | nu"
},
{
"path": "src/utils.ts",
"chars": 4660,
"preview": "import * as path from 'path'\nimport type { Options, MimeType, ImageOptions, CacheOptions } from './types'\n\nconst version"
},
{
"path": "test/cjs.test.js",
"chars": 234,
"preview": "import src from '../lib'\nimport cjs from '../lib/cjs'\n\ndescribe('CJS', () => {\n it('should export loader', () => {\n "
},
{
"path": "test/jimp/build/__snapshots__/test.js.snap",
"chars": 13209,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`disable 1`] = `\n{\n \"default\": {\n \"images\": [\n {\n \"h"
},
{
"path": "test/jimp/index.js",
"chars": 3038,
"preview": "test('multiple sizes', () => {\n const multi = require('../cat-1000.jpg?sizes[]=500&sizes[]=2000')\n expect(multi).toMat"
},
{
"path": "test/jimp/webpack.config.js",
"chars": 957,
"preview": "const path = require('path')\n\nmodule.exports = {\n mode: 'development',\n entry: path.resolve(__dirname, 'index'),\n mod"
},
{
"path": "test/sharp/build/__snapshots__/test.js.snap",
"chars": 16372,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Works with the cache 1`] = `\n{\n \"height\": 451,\n \"images\": [\n {"
},
{
"path": "test/sharp/index.js",
"chars": 4283,
"preview": "test('Works with the cache', () => {\n const output = require('../cat-1000.jpg?size=501,cacheDirectory')\n expect(output"
},
{
"path": "test/sharp/webpack.config.js",
"chars": 963,
"preview": "const path = require('path')\n\nmodule.exports = {\n mode: 'development',\n entry: path.resolve(__dirname, 'index'),\n mod"
},
{
"path": "test/utils.test.js",
"chars": 987,
"preview": "import { getOutputAndPublicPath } from '../lib/utils'\n\ndescribe('Utils package', () => {\n it('should create both paths "
},
{
"path": "tsconfig.json",
"chars": 837,
"preview": "{\n \"compilerOptions\": {\n /* Visit https://aka.ms/tsconfig.json to read more about this file */\n // node 10 https:"
}
]
About this extraction
This page contains the full source code of the dazuaz/responsive-loader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (91.3 KB), approximately 28.5k tokens, and a symbol index with 35 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.