mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-11 16:17:25 +02:00
refactor: migrate lqip-loader to TS, fix typing for Webpack Loaders (#5779)
This commit is contained in:
parent
ca5d70d7fb
commit
68c970175a
18 changed files with 254 additions and 265 deletions
|
@ -10,6 +10,7 @@ website/
|
||||||
scripts
|
scripts
|
||||||
examples/
|
examples/
|
||||||
|
|
||||||
|
packages/lqip-loader/lib/
|
||||||
packages/docusaurus/lib/
|
packages/docusaurus/lib/
|
||||||
packages/docusaurus-*/lib/*
|
packages/docusaurus-*/lib/*
|
||||||
packages/docusaurus-*/lib-next/
|
packages/docusaurus-*/lib-next/
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -20,6 +20,7 @@ test-website
|
||||||
test-website-in-workspace
|
test-website-in-workspace
|
||||||
|
|
||||||
packages/create-docusaurus/lib/
|
packages/create-docusaurus/lib/
|
||||||
|
packages/lqip-loader/lib/
|
||||||
packages/docusaurus/lib/
|
packages/docusaurus/lib/
|
||||||
packages/docusaurus-*/lib/*
|
packages/docusaurus-*/lib/*
|
||||||
packages/docusaurus-*/lib-next/
|
packages/docusaurus-*/lib-next/
|
||||||
|
|
|
@ -4,6 +4,7 @@ node_modules
|
||||||
build
|
build
|
||||||
coverage
|
coverage
|
||||||
.docusaurus
|
.docusaurus
|
||||||
|
packages/lqip-loader/lib/
|
||||||
packages/docusaurus/lib/
|
packages/docusaurus/lib/
|
||||||
packages/docusaurus-*/lib/*
|
packages/docusaurus-*/lib/*
|
||||||
packages/docusaurus-*/lib-next/
|
packages/docusaurus-*/lib-next/
|
||||||
|
|
|
@ -22,12 +22,7 @@ import transformImage from './remark/transformImage';
|
||||||
import transformLinks from './remark/transformLinks';
|
import transformLinks from './remark/transformLinks';
|
||||||
import {getFileLoaderUtils} from '@docusaurus/core/lib/webpack/utils';
|
import {getFileLoaderUtils} from '@docusaurus/core/lib/webpack/utils';
|
||||||
import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';
|
import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';
|
||||||
|
import type {LoaderContext} from 'webpack';
|
||||||
// TODO temporary until Webpack5 export this type
|
|
||||||
// see https://github.com/webpack/webpack/issues/11630
|
|
||||||
interface Loader extends Function {
|
|
||||||
(this: any, source: string): Promise<string | Buffer | void | undefined>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loaders: {inlineMarkdownImageFileLoader},
|
loaders: {inlineMarkdownImageFileLoader},
|
||||||
|
@ -40,6 +35,19 @@ const DEFAULT_OPTIONS: RemarkAndRehypePluginOptions = {
|
||||||
beforeDefaultRehypePlugins: [],
|
beforeDefaultRehypePlugins: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Options = RemarkAndRehypePluginOptions & {
|
||||||
|
staticDir?: string;
|
||||||
|
isMDXPartial?: (filePath: string) => boolean;
|
||||||
|
isMDXPartialFrontMatterWarningDisabled?: boolean;
|
||||||
|
removeContentTitle?: boolean;
|
||||||
|
metadataPath?: string | ((filePath: string) => string);
|
||||||
|
createAssets?: (metadata: {
|
||||||
|
frontMatter: Record<string, unknown>;
|
||||||
|
metadata: Record<string, unknown>;
|
||||||
|
}) => Record<string, unknown>;
|
||||||
|
filepath: string;
|
||||||
|
};
|
||||||
|
|
||||||
// When this throws, it generally means that there's no metadata file associated with this MDX document
|
// When this throws, it generally means that there's no metadata file associated with this MDX document
|
||||||
// It can happen when using MDX partials (usually starting with _)
|
// It can happen when using MDX partials (usually starting with _)
|
||||||
// That's why it's important to provide the "isMDXPartial" function in config
|
// That's why it's important to provide the "isMDXPartial" function in config
|
||||||
|
@ -94,7 +102,10 @@ function createAssetsExportCode(assets: Record<string, unknown>) {
|
||||||
return `{\n${codeLines.join('\n')}\n}`;
|
return `{\n${codeLines.join('\n')}\n}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const docusaurusMdxLoader: Loader = async function (fileString) {
|
export default async function mdxLoader(
|
||||||
|
this: LoaderContext<Options>,
|
||||||
|
fileString: string,
|
||||||
|
): Promise<void> {
|
||||||
const callback = this.async();
|
const callback = this.async();
|
||||||
const filePath = this.resourcePath;
|
const filePath = this.resourcePath;
|
||||||
const reqOptions = this.getOptions() || {};
|
const reqOptions = this.getOptions() || {};
|
||||||
|
@ -107,7 +118,7 @@ const docusaurusMdxLoader: Loader = async function (fileString) {
|
||||||
|
|
||||||
const hasFrontMatter = Object.keys(frontMatter).length > 0;
|
const hasFrontMatter = Object.keys(frontMatter).length > 0;
|
||||||
|
|
||||||
const options = {
|
const options: Options = {
|
||||||
...reqOptions,
|
...reqOptions,
|
||||||
remarkPlugins: [
|
remarkPlugins: [
|
||||||
...(reqOptions.beforeDefaultRemarkPlugins || []),
|
...(reqOptions.beforeDefaultRemarkPlugins || []),
|
||||||
|
@ -119,7 +130,6 @@ const docusaurusMdxLoader: Loader = async function (fileString) {
|
||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
...(reqOptions.beforeDefaultRehypePlugins || []),
|
...(reqOptions.beforeDefaultRehypePlugins || []),
|
||||||
...DEFAULT_OPTIONS.rehypePlugins,
|
...DEFAULT_OPTIONS.rehypePlugins,
|
||||||
|
|
||||||
...(reqOptions.rehypePlugins || []),
|
...(reqOptions.rehypePlugins || []),
|
||||||
],
|
],
|
||||||
filepath: filePath,
|
filepath: filePath,
|
||||||
|
@ -129,7 +139,7 @@ const docusaurusMdxLoader: Loader = async function (fileString) {
|
||||||
try {
|
try {
|
||||||
result = await mdx(content, options);
|
result = await mdx(content, options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return callback(err);
|
return callback(err as Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MDX partials are MDX files starting with _ or in a folder starting with _
|
// MDX partials are MDX files starting with _ or in a folder starting with _
|
||||||
|
@ -195,6 +205,4 @@ ${result}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return callback(null, code);
|
return callback(null, code);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default docusaurusMdxLoader;
|
|
||||||
|
|
15
packages/docusaurus-mdx-loader/src/types.d.ts
vendored
15
packages/docusaurus-mdx-loader/src/types.d.ts
vendored
|
@ -6,9 +6,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare module '@docusaurus/mdx-loader' {
|
declare module '@docusaurus/mdx-loader' {
|
||||||
type RemarkOrRehypePlugin =
|
import type {Plugin} from 'unified';
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
[Function, Record<string, unknown>] | Function;
|
export type RemarkOrRehypePlugin =
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
[Plugin<any[]>, Record<string, unknown>] | Plugin<any[]>;
|
||||||
export type RemarkAndRehypePluginOptions = {
|
export type RemarkAndRehypePluginOptions = {
|
||||||
remarkPlugins: RemarkOrRehypePlugin[];
|
remarkPlugins: RemarkOrRehypePlugin[];
|
||||||
rehypePlugins: RemarkOrRehypePlugin[];
|
rehypePlugins: RemarkOrRehypePlugin[];
|
||||||
|
@ -19,15 +21,16 @@ declare module '@docusaurus/mdx-loader' {
|
||||||
|
|
||||||
// TODO Types provided by MDX 2.0 https://github.com/mdx-js/mdx/blob/main/packages/mdx/types/index.d.ts
|
// TODO Types provided by MDX 2.0 https://github.com/mdx-js/mdx/blob/main/packages/mdx/types/index.d.ts
|
||||||
declare module '@mdx-js/mdx' {
|
declare module '@mdx-js/mdx' {
|
||||||
import type {Plugin, Processor} from 'unified';
|
import type {Processor} from 'unified';
|
||||||
|
import type {RemarkOrRehypePlugin} from '@docusaurus/mdx-loader';
|
||||||
|
|
||||||
namespace mdx {
|
namespace mdx {
|
||||||
interface Options {
|
interface Options {
|
||||||
filepath?: string;
|
filepath?: string;
|
||||||
skipExport?: boolean;
|
skipExport?: boolean;
|
||||||
wrapExport?: string;
|
wrapExport?: string;
|
||||||
remarkPlugins?: Plugin[];
|
remarkPlugins?: RemarkOrRehypePlugin[];
|
||||||
rehypePlugins?: Plugin[];
|
rehypePlugins?: RemarkOrRehypePlugin[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function sync(content: string, options?: Options): string;
|
function sync(content: string, options?: Options): string;
|
||||||
|
|
|
@ -8,18 +8,16 @@
|
||||||
import {truncate, linkify} from './blogUtils';
|
import {truncate, linkify} from './blogUtils';
|
||||||
import {parseQuery} from 'loader-utils';
|
import {parseQuery} from 'loader-utils';
|
||||||
import {BlogMarkdownLoaderOptions} from './types';
|
import {BlogMarkdownLoaderOptions} from './types';
|
||||||
|
import type {LoaderContext} from 'webpack';
|
||||||
|
|
||||||
// TODO temporary until Webpack5 export this type
|
export default function markdownLoader(
|
||||||
// see https://github.com/webpack/webpack/issues/11630
|
this: LoaderContext<BlogMarkdownLoaderOptions>,
|
||||||
interface Loader extends Function {
|
source: string,
|
||||||
(this: any, source: string): string | Buffer | void | undefined;
|
): void {
|
||||||
}
|
|
||||||
|
|
||||||
const markdownLoader: Loader = function (source) {
|
|
||||||
const filePath = this.resourcePath;
|
const filePath = this.resourcePath;
|
||||||
const fileString = source as string;
|
const fileString = source;
|
||||||
const callback = this.async();
|
const callback = this.async();
|
||||||
const markdownLoaderOptions = this.getOptions() as BlogMarkdownLoaderOptions;
|
const markdownLoaderOptions = this.getOptions();
|
||||||
|
|
||||||
// Linkify blog posts
|
// Linkify blog posts
|
||||||
let finalContent = linkify({
|
let finalContent = linkify({
|
||||||
|
@ -38,6 +36,4 @@ const markdownLoader: Loader = function (source) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback && callback(null, finalContent);
|
return callback && callback(null, finalContent);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default markdownLoader;
|
|
||||||
|
|
|
@ -7,20 +7,16 @@
|
||||||
|
|
||||||
import {linkify} from './linkify';
|
import {linkify} from './linkify';
|
||||||
import {DocsMarkdownOption} from '../types';
|
import {DocsMarkdownOption} from '../types';
|
||||||
|
import type {LoaderContext} from 'webpack';
|
||||||
|
|
||||||
// TODO temporary until Webpack5 export this type
|
export default function markdownLoader(
|
||||||
// see https://github.com/webpack/webpack/issues/11630
|
this: LoaderContext<DocsMarkdownOption>,
|
||||||
interface Loader extends Function {
|
source: string,
|
||||||
(this: any, source: string): string | Buffer | void | undefined;
|
): void {
|
||||||
}
|
const fileString = source;
|
||||||
|
|
||||||
const markdownLoader: Loader = function (source) {
|
|
||||||
const fileString = source as string;
|
|
||||||
const callback = this.async();
|
const callback = this.async();
|
||||||
const options = this.getOptions() as DocsMarkdownOption;
|
const options = this.getOptions();
|
||||||
return (
|
return (
|
||||||
callback && callback(null, linkify(fileString, this.resourcePath, options))
|
callback && callback(null, linkify(fileString, this.resourcePath, options))
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default markdownLoader;
|
|
||||||
|
|
|
@ -5,13 +5,12 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO temporary until Webpack5 export this type
|
import type {LoaderContext} from 'webpack';
|
||||||
// see https://github.com/webpack/webpack/issues/11630
|
|
||||||
interface Loader extends Function {
|
|
||||||
(this: any, source: string): string | Buffer | void | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const markdownLoader: Loader = function (fileString) {
|
export default function markdownLoader(
|
||||||
|
this: LoaderContext<undefined>,
|
||||||
|
fileString: string,
|
||||||
|
): void {
|
||||||
const callback = this.async();
|
const callback = this.async();
|
||||||
|
|
||||||
// const options = this.getOptions();
|
// const options = this.getOptions();
|
||||||
|
@ -20,6 +19,4 @@ const markdownLoader: Loader = function (fileString) {
|
||||||
// fileString = linkify(fileString)
|
// fileString = linkify(fileString)
|
||||||
|
|
||||||
return callback && callback(null, fileString);
|
return callback && callback(null, fileString);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default markdownLoader;
|
|
||||||
|
|
|
@ -2,10 +2,14 @@
|
||||||
"name": "@docusaurus/lqip-loader",
|
"name": "@docusaurus/lqip-loader",
|
||||||
"version": "2.0.0-beta.8",
|
"version": "2.0.0-beta.8",
|
||||||
"description": "Low Quality Image Placeholders (LQIP) loader for webpack.",
|
"description": "Low Quality Image Placeholders (LQIP) loader for webpack.",
|
||||||
"main": "src/index.js",
|
"main": "lib/index.js",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "tsc --watch"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/facebook/docusaurus.git",
|
"url": "https://github.com/facebook/docusaurus.git",
|
||||||
|
@ -20,5 +24,8 @@
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.13.0"
|
"node": ">=12.13.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/sharp": "^0.29.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,14 @@ import Vibrant from 'node-vibrant';
|
||||||
import {Palette} from 'node-vibrant/lib/color';
|
import {Palette} from 'node-vibrant/lib/color';
|
||||||
|
|
||||||
import {toPalette, toBase64} from '../utils';
|
import {toPalette, toBase64} from '../utils';
|
||||||
import lqip from '../lqip';
|
import * as lqip from '../lqip';
|
||||||
|
|
||||||
describe('lqip-loader', () => {
|
describe('lqip-loader', () => {
|
||||||
describe('toBase64', () => {
|
describe('toBase64', () => {
|
||||||
test('should return a properly formatted Base64 image string', () => {
|
test('should return a properly formatted Base64 image string', () => {
|
||||||
const expected = 'data:image/jpeg;base64,hello world';
|
const expected = 'data:image/jpeg;base64,aGVsbG8gd29ybGQ=';
|
||||||
const mockedMimeType = 'image/jpeg';
|
const mockedMimeType = 'image/jpeg';
|
||||||
const mockedBase64Data = 'hello world';
|
const mockedBase64Data = Buffer.from('hello world');
|
||||||
expect(toBase64(mockedMimeType, mockedBase64Data)).toEqual(expected);
|
expect(toBase64(mockedMimeType, mockedBase64Data)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 lqip = require('./lqip');
|
|
||||||
|
|
||||||
module.exports = function (contentBuffer) {
|
|
||||||
if (this.cacheable) {
|
|
||||||
this.cacheable();
|
|
||||||
}
|
|
||||||
const callback = this.async();
|
|
||||||
const imgPath = this.resourcePath;
|
|
||||||
|
|
||||||
const config = this.getOptions() || {};
|
|
||||||
config.base64 = 'base64' in config ? config.base64 : true;
|
|
||||||
config.palette = 'palette' in config ? config.palette : false;
|
|
||||||
|
|
||||||
let content = contentBuffer.toString('utf8');
|
|
||||||
const contentIsUrlExport =
|
|
||||||
/^(?:export default|module.exports =) "data:(.*)base64,(.*)/.test(content);
|
|
||||||
const contentIsFileExport = /^(?:export default|module.exports =) (.*)/.test(
|
|
||||||
content,
|
|
||||||
);
|
|
||||||
|
|
||||||
let source = '';
|
|
||||||
const SOURCE_CHUNK = 1;
|
|
||||||
|
|
||||||
if (contentIsUrlExport) {
|
|
||||||
source = content.match(/^(?:export default|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(/^(?:export default|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 finalObject = JSON.stringify({src: 'STUB', preSrc, palette});
|
|
||||||
const result = `module.exports = ${finalObject.replace(
|
|
||||||
'"STUB"',
|
|
||||||
source,
|
|
||||||
)};`;
|
|
||||||
callback(null, result);
|
|
||||||
} else {
|
|
||||||
callback('ERROR', null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
callback(error, null);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.raw = true;
|
|
83
packages/lqip-loader/src/index.ts
Normal file
83
packages/lqip-loader/src/index.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as lqip from './lqip';
|
||||||
|
import type {LoaderContext} from 'webpack';
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
base64: boolean;
|
||||||
|
palette: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function lqipLoader(
|
||||||
|
this: LoaderContext<Options>,
|
||||||
|
contentBuffer: Buffer,
|
||||||
|
): Promise<void> {
|
||||||
|
if (this.cacheable) {
|
||||||
|
this.cacheable();
|
||||||
|
}
|
||||||
|
const callback = this.async();
|
||||||
|
const imgPath = this.resourcePath;
|
||||||
|
|
||||||
|
const config = this.getOptions() || {};
|
||||||
|
config.base64 = 'base64' in config ? config.base64 : true;
|
||||||
|
config.palette = 'palette' in config ? config.palette : false;
|
||||||
|
|
||||||
|
let content = contentBuffer.toString('utf8');
|
||||||
|
const contentIsUrlExport =
|
||||||
|
/^(?:export default|module.exports =) "data:(.*)base64,(.*)/.test(content);
|
||||||
|
const contentIsFileExport = /^(?:export default|module.exports =) (.*)/.test(
|
||||||
|
content,
|
||||||
|
);
|
||||||
|
|
||||||
|
let source = '';
|
||||||
|
const SOURCE_CHUNK = 1;
|
||||||
|
|
||||||
|
if (contentIsUrlExport) {
|
||||||
|
source = content.match(/^(?:export default|module.exports =) (.*)/)![
|
||||||
|
SOURCE_CHUNK
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
if (!contentIsFileExport) {
|
||||||
|
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
||||||
|
const fileLoader = require('file-loader');
|
||||||
|
content = fileLoader.call(this, contentBuffer);
|
||||||
|
}
|
||||||
|
source = content.match(/^(?:export default|module.exports =) (.*);/)![
|
||||||
|
SOURCE_CHUNK
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputPromises: [Promise<string> | null, Promise<string[]> | null] = [
|
||||||
|
config.base64 === true ? lqip.base64(imgPath) : null,
|
||||||
|
// color palette generation is set to false by default
|
||||||
|
// since it is little bit slower than base64 generation
|
||||||
|
config.palette === true ? lqip.palette(imgPath) : null,
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await Promise.all(outputPromises);
|
||||||
|
if (data) {
|
||||||
|
const [preSrc, palette] = data;
|
||||||
|
const finalObject = JSON.stringify({src: 'STUB', preSrc, palette});
|
||||||
|
const result = `module.exports = ${finalObject.replace(
|
||||||
|
'"STUB"',
|
||||||
|
source,
|
||||||
|
)};`;
|
||||||
|
callback(null, result);
|
||||||
|
} else {
|
||||||
|
callback(new Error('ERROR'), undefined);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
callback(new Error('ERROR'), undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lqipLoader.raw = true;
|
||||||
|
|
||||||
|
export default lqipLoader;
|
|
@ -1,75 +0,0 @@
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
};
|
|
52
packages/lqip-loader/src/lqip.ts
Normal file
52
packages/lqip-loader/src/lqip.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Vibrant from 'node-vibrant';
|
||||||
|
import path from 'path';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import {toPalette, toBase64} from './utils';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const {version} = require('../package.json');
|
||||||
|
|
||||||
|
const ERROR_EXT = `Error: Input file is missing or uses unsupported image format, lqip v${version}`;
|
||||||
|
|
||||||
|
const SUPPORTED_MIMES: Record<string, string> = {
|
||||||
|
jpeg: 'image/jpeg',
|
||||||
|
jpg: 'image/jpeg',
|
||||||
|
png: 'image/png',
|
||||||
|
};
|
||||||
|
|
||||||
|
async function base64(file: string): Promise<string> {
|
||||||
|
let extension = path.extname(file) || '';
|
||||||
|
extension = extension.split('.').pop()!;
|
||||||
|
|
||||||
|
if (!SUPPORTED_MIMES[extension]) {
|
||||||
|
throw new Error(ERROR_EXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await sharp(file).resize(10).toBuffer();
|
||||||
|
if (data) {
|
||||||
|
return toBase64(SUPPORTED_MIMES[extension], data);
|
||||||
|
}
|
||||||
|
throw new Error('Unhandled promise rejection in base64 promise');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function palette(file: string): Promise<string[]> {
|
||||||
|
const vibrant = new Vibrant(file, {});
|
||||||
|
const pal = await vibrant.getPalette();
|
||||||
|
if (pal) {
|
||||||
|
return toPalette(pal);
|
||||||
|
}
|
||||||
|
throw new Error(`Unhandled promise rejection in colorPalette ${pal}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (up) => {
|
||||||
|
throw up;
|
||||||
|
});
|
||||||
|
|
||||||
|
export {base64, palette};
|
|
@ -1,51 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
const {sortBy} = require('lodash');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* toBase64
|
|
||||||
* @description it returns a Base64 image string with required formatting
|
|
||||||
* to work on the web (<img src=".." /> or in CSS url('..'))
|
|
||||||
*
|
|
||||||
* @param {string} extMimeType: image mime type string
|
|
||||||
* @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 {import("node-vibrant/lib/color").Palette} swatch
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
const toPalette = (swatch) => {
|
|
||||||
/** @type {Array<{popularity: number, hex: string}>} */
|
|
||||||
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']);
|
|
||||||
return palette.map((color) => color.hex).reverse();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
toBase64,
|
|
||||||
toPalette,
|
|
||||||
};
|
|
37
packages/lqip-loader/src/utils.ts
Normal file
37
packages/lqip-loader/src/utils.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {sortBy} from 'lodash';
|
||||||
|
import type {Palette} from 'node-vibrant/lib/color';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* it returns a Base64 image string with required formatting
|
||||||
|
* to work on the web (<img src=".." /> or in CSS url('..'))
|
||||||
|
*/
|
||||||
|
const toBase64 = (extMimeType: string, data: Buffer): string => {
|
||||||
|
return `data:${extMimeType};base64,${data.toString('base64')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* takes a color swatch object, converts it to an array & returns
|
||||||
|
* only hex color
|
||||||
|
*/
|
||||||
|
const toPalette = (swatch: Palette): string[] => {
|
||||||
|
let palette = Object.keys(swatch).reduce((result, key) => {
|
||||||
|
if (swatch[key] !== null) {
|
||||||
|
result.push({
|
||||||
|
popularity: swatch[key]!.getPopulation(),
|
||||||
|
hex: swatch[key]!.getHex(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [] as {popularity: number; hex: string}[]);
|
||||||
|
palette = sortBy(palette, ['popularity']);
|
||||||
|
return palette.map((color) => color.hex).reverse();
|
||||||
|
};
|
||||||
|
|
||||||
|
export {toBase64, toPalette};
|
9
packages/lqip-loader/tsconfig.json
Normal file
9
packages/lqip-loader/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"incremental": true,
|
||||||
|
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "lib"
|
||||||
|
}
|
||||||
|
}
|
|
@ -4549,6 +4549,13 @@
|
||||||
"@types/mime" "^1"
|
"@types/mime" "^1"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/sharp@^0.29.2":
|
||||||
|
version "0.29.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.2.tgz#b4e932e982e258d1013236c8b4bcc14f9883c9a3"
|
||||||
|
integrity sha512-tIbMvtPa8kMyFMKNhpsPT1HO3CgXLuiCAA8bxHAGAZLyALpYvYc4hUu3pu0+3oExQA5LwvHrWp+OilgXCYVQgg==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/shelljs@^0.8.6":
|
"@types/shelljs@^0.8.6":
|
||||||
version "0.8.9"
|
version "0.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.9.tgz#45dd8501aa9882976ca3610517dac3831c2fbbf4"
|
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.9.tgz#45dd8501aa9882976ca3610517dac3831c2fbbf4"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue