Skip to content

Commit

Permalink
refactor(v2): import lqip-loader, fix build on Node 13
Browse files Browse the repository at this point in the history
  • Loading branch information
Simek committed Apr 5, 2020
1 parent 1f00d15 commit 3f309af
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 41 deletions.
9 changes: 3 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,13 @@
"lint-staged": {
"*.{js,jsx}": [
"yarn lint --fix",
"yarn prettier",
"git add"
"yarn prettier"
],
"*.{ts,tsx}": [
"yarn prettier",
"git add"
"yarn prettier"
],
"*.md": [
"yarn prettier-docs",
"git add"
"yarn prettier-docs"
]
},
"husky": {
Expand Down
4 changes: 2 additions & 2 deletions packages/docusaurus-plugin-ideal-image/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
"fs-extra": "^9.0.0"
},
"dependencies": {
"@endiliey/lqip-loader": "^3.0.2",
"@docusaurus/lqip-loader": "^2.0.0-alpha.50",
"@endiliey/react-ideal-image": "^0.0.11",
"@endiliey/responsive-loader": "^1.3.2",
"react-waypoint": "^9.0.2",
"sharp": "^0.22.1"
"sharp": "^0.25.2"
},
"peerDependencies": {
"@docusaurus/core": "^2.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus-plugin-ideal-image/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function (_context: LoadContext, options: PluginOptions) {
{
test: /\.(png|jpe?g|gif)$/i,
use: [
'@endiliey/lqip-loader',
'@docusaurus/lqip-loader',
{
loader: '@endiliey/responsive-loader',
options: {
Expand Down
89 changes: 89 additions & 0 deletions packages/lqip-loader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
## lqip-loader: low quality images placeholders for webpack

### Installation

```
npm install --save-dev @docusaurus/lqip-loader
```

### Example

Generating Base64 & dominant colours palette for a jpeg image imported in your JS bundle:

> The large image file will be emitted & only 400byte of Base64 (if set to true in the loader options) will be bundled.
`webpack.config.js`

```json
{
/**
* OPTION A:
* default file-loader fallback
**/
test: /\.jpe?g$/,
loaders: [
{
loader: '@docusaurus/lqip-loader',
options: {
path: '/media', // your image going to be in media folder in the output dir
name: '[name].[ext]', // you can use [hash].[ext] too if you wish,
base64: true, // default: true, gives the base64 encoded image
palette: true // default: false, gives the dominant colours palette
}
}
]

/**
* OPTION B:
* Chained with your own url-loader or file-loader
**/
test: /\.(png|jpe?g)$/,
loaders: [
{
loader: '@docusaurus/lqip-loader',
options: {
base64: true,
palette: false
}
},
{
loader: 'url-loader',
options: {
limit: 8000
}
}
]
}
```

`your-app-module.js`

```js
import banner from './images/banner.jpg';

console.log(banner.preSrc);
// outputs: "data:image/jpeg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhY....

// the object will have palette property, array will be sorted from most dominant colour to the least
console.log(banner.palette); // [ '#628792', '#bed4d5', '#5d4340', '#ba454d', '#c5dce4', '#551f24' ]

console.log(banner.src); // that's the original image URL to load later!
```

### Important note

To save memory and improve GPU performance, browsers (including Chrome started from 61.0.3163.38) will now render a slightly more crisp or pixelated Base64 encoded images. If you want the blur to be very intense (smooth), here's a fix!

```css
img {
filter: blur(25px);
}
```

More history about the issue can be [found here](https://bugs.chromium.org/p/chromium/issues/detail?id=771110#c3) and [here](https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/6L_3ZZeuA0M).

Alternatively, you can fill the container with a really cheap colour or gradient from the amazing palette we provide.

### Credits

This package has been imported from [`@endiliey/lqip-loader`](https://github.com/endiliey/lqip-loader) which was a fork of original [`lqip-loader`](https://github.com/zouhir/lqip-loader) created exclusively for Docusaurus.
22 changes: 22 additions & 0 deletions packages/lqip-loader/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@docusaurus/lqip-loader",
"version": "2.0.0-alpha.50",
"description": "Low Quality Image Placeholders (LQIP) loader for webpack",
"main": "src/index.js",
"publishConfig": {
"access": "public"
},
"license": "MIT",
"dependencies": {
"loader-utils": "^1.2.3",
"lodash.sortby": "^4.7.0",
"node-vibrant": "^3.1.5"
},
"peerDependencies": {
"file-loader": "*",
"sharp": "*"
},
"engines": {
"node": ">=10.9.0"
}
}
77 changes: 77 additions & 0 deletions packages/lqip-loader/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const loaderUtils = require('loader-utils');
const lqip = require('./lqip');

module.exports = function (contentBuffer) {
if (this.cacheable) {
this.cacheable();
}
const callback = this.async();
const imgPath = this.resourcePath;

const config = loaderUtils.getOptions(this) || {};
config.base64 = 'base64' in config ? config.base64 : true;
config.palette = 'palette' in config ? config.palette : false;

let content = contentBuffer.toString('utf8');
const contentIsUrlExport = /^module.exports = "data:(.*)base64,(.*)/.test(
content,
);
const contentIsFileExport = /^module.exports = (.*)/.test(content);

let source = '';
const SOURCE_CHUNK = 1;

if (contentIsUrlExport) {
source = content.match(/^module.exports = (.*)/)[SOURCE_CHUNK];
} else {
if (!contentIsFileExport) {
// eslint-disable-next-line global-require
const fileLoader = require('file-loader');
content = fileLoader.call(this, contentBuffer);
}
source = content.match(/^module.exports = (.*);/)[SOURCE_CHUNK];
}

const outputPromises = [];

if (config.base64 === true) {
outputPromises.push(lqip.base64(imgPath));
} else {
outputPromises.push(null);
}

// color palette generation is set to false by default
// since it is little bit slower than base64 generation

if (config.palette === true) {
outputPromises.push(lqip.palette(imgPath));
} else {
outputPromises.push(null);
}

Promise.all(outputPromises)
.then((data) => {
if (data) {
const [preSrc, palette] = data;
const param1 = preSrc ? `, "preSrc": ${JSON.stringify(preSrc)}` : '';
const param2 = palette ? `, "palette": ${JSON.stringify(palette)}` : '';
const result = `module.exports = {"src":${source}${param1}${param2}};`;
callback(null, result);
} else {
callback('ERROR', null);
}
})
.catch((error) => {
console.error(error);
callback(error, null);
});
};

module.exports.raw = true;
75 changes: 75 additions & 0 deletions packages/lqip-loader/src/lqip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const Vibrant = require('node-vibrant');
const path = require('path');
const sharp = require('sharp');

const {version} = require('../package.json');
const {toPalette, toBase64} = require('./utils');

const ERROR_EXT = `Error: Input file is missing or uses unsupported image format, lqip v${version}`;

const SUPPORTED_MIMES = {
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
png: 'image/png',
};

const base64 = (file) => {
return new Promise((resolve, reject) => {
let extension = path.extname(file) || '';
extension = extension.split('.').pop();

if (!SUPPORTED_MIMES[extension]) {
return reject(ERROR_EXT);
}

return sharp(file)
.resize(10)
.toBuffer()
.then((data) => {
if (data) {
return resolve(toBase64(SUPPORTED_MIMES[extension], data));
}
return reject(
new Error('Unhandled promise rejection in base64 promise'),
);
})
.catch((err) => {
return reject(err);
});
});
};

const palette = (file) => {
return new Promise((resolve, reject) => {
const vibrant = new Vibrant(file, {});
vibrant
.getPalette()
.then((pal) => {
if (pal) {
return resolve(toPalette(pal));
}
return reject(
new Error('Unhandled promise rejection in colorPalette', pal),
);
})
.catch((err) => {
return reject(err);
});
});
};

process.on('unhandledRejection', (up) => {
throw up;
});

module.exports = {
base64,
palette,
};
49 changes: 49 additions & 0 deletions packages/lqip-loader/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const sortBy = require('lodash.sortby');

/**
* toBase64
* @description it returns a Base64 image string with required formatting
* to work on the web (<img src=".." /> or in CSS url('..'))
*
* @param extension: image file extension
* @param data: base64 string
* @returns {string}
*/
const toBase64 = (extMimeType, data) => {
return `data:${extMimeType};base64,${data.toString('base64')}`;
};

/**
* toPalette
* @description takes a color swatch object, converts it to an array & returns
* only hex color
*
* @param swatch
* @returns {{palette: Array}}
*/
const toPalette = (swatch) => {
let palette = Object.keys(swatch).reduce((result, key) => {
if (swatch[key] !== null) {
result.push({
popularity: swatch[key].getPopulation(),
hex: swatch[key].getHex(),
});
}
return result;
}, []);
palette = sortBy(palette, ['popularity']);
palette = palette.map((color) => color.hex).reverse();
return palette;
};

module.exports = {
toBase64,
toPalette,
};
Loading

0 comments on commit 3f309af

Please sign in to comment.