next-blurhash-previews 0.0.1 → 0.0.3-beta10
Sign up to get free protection for your applications and to get access to all the features.
- package/.prettierrc +3 -0
- package/bin/generateBlurhash.js +55 -0
- package/bin/markdown-sync.js +50 -12
- package/bin/remark-plugin.js +66 -0
- package/components/ImageWithPreview.tsx +31 -48
- package/components/NextWrapper.tsx +12 -4
- package/index.js +1 -3
- package/next-runner/blog-posts/post1.md +303 -0
- package/next-runner/blog-posts/post2.md +10 -0
- package/next-runner/blog-posts/post3.md +10 -0
- package/next-runner/blog-posts/post4.md +10 -0
- package/next-runner/package-lock.json +22 -6
- package/next-runner/pages/_document.js +1 -3
- package/next-runner/pages/index.js +30 -9
- package/next-runner/public/css-modules/loadingError.png +0 -0
- package/next-runner/public/css-modules/styledComponent.png +0 -0
- package/next-runner/public/css-modules/styledSass.png +0 -0
- package/next-runner/public/css-modules/unstyledComp.png +0 -0
- package/next-runner/public/img1.png +0 -0
- package/next-runner/public/img5.png +0 -0
- package/next-runner/styles/Home.module.css +3 -0
- package/next-runner/yarn.lock +7 -2
- package/package.json +11 -5
- package/util/setBootstrap.js +12 -3
- package/vite.config.ts +2 -2
- package/example.md +0 -9
- package/output.md +0 -15
- package/plugin.js +0 -78
package/.prettierrc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
import sharp from "sharp";
|
2
|
+
import fetch from "node-fetch";
|
3
|
+
import { encode, isBlurhashValid } from "blurhash";
|
4
|
+
|
5
|
+
import path from "path";
|
6
|
+
const __dirname = process.cwd();
|
7
|
+
|
8
|
+
export async function getSharpImage(imgPath) {
|
9
|
+
if (/^http/.test(imgPath)) {
|
10
|
+
const buffer = await fetch(imgPath)
|
11
|
+
.then(fetchResponse => fetchResponse.arrayBuffer())
|
12
|
+
.then(ab => Buffer.from(ab));
|
13
|
+
|
14
|
+
return sharp(buffer);
|
15
|
+
} else {
|
16
|
+
return sharp(imgPath);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
export async function getBlurhash(path) {
|
21
|
+
const blurhashImage = await getSharpImage(path);
|
22
|
+
const dimensions = await blurhashImage.metadata();
|
23
|
+
|
24
|
+
const { width, height } = dimensions;
|
25
|
+
|
26
|
+
return new Promise((res, rej) => {
|
27
|
+
blurhashImage
|
28
|
+
.raw()
|
29
|
+
.ensureAlpha()
|
30
|
+
.toBuffer((err, buffer) => {
|
31
|
+
try {
|
32
|
+
if (err) {
|
33
|
+
console.log("Error getting buffer", err);
|
34
|
+
} else {
|
35
|
+
const blurhash = encode(
|
36
|
+
new Uint8ClampedArray(buffer),
|
37
|
+
width,
|
38
|
+
height,
|
39
|
+
4,
|
40
|
+
4
|
41
|
+
);
|
42
|
+
if (isBlurhashValid(blurhash)) {
|
43
|
+
return res({ blurhash, w: width, h: height });
|
44
|
+
} else {
|
45
|
+
console.log("FAIL");
|
46
|
+
return rej("FAIL");
|
47
|
+
}
|
48
|
+
}
|
49
|
+
} catch (err) {
|
50
|
+
console.log("FAIL", err);
|
51
|
+
return rej("FAIL", err);
|
52
|
+
}
|
53
|
+
});
|
54
|
+
});
|
55
|
+
}
|
package/bin/markdown-sync.js
CHANGED
@@ -2,24 +2,62 @@
|
|
2
2
|
|
3
3
|
import fs from "fs";
|
4
4
|
import path, { dirname } from "path";
|
5
|
+
import { fileURLToPath } from "url";
|
6
|
+
|
5
7
|
import { reporter } from "vfile-reporter";
|
6
8
|
import { remark } from "remark";
|
9
|
+
import remarkFrontmatter from "remark-frontmatter";
|
10
|
+
import remarkStringify from "remark-stringify";
|
7
11
|
|
8
|
-
import
|
12
|
+
import glob from "glob";
|
13
|
+
import colors from "colors";
|
9
14
|
|
10
|
-
import {
|
11
|
-
|
15
|
+
import { blurhashPlugin } from "./remark-plugin.js";
|
16
|
+
|
17
|
+
const directoryProcess = process.cwd();
|
18
|
+
|
19
|
+
const allArgs = process.argv;
|
20
|
+
const inputGlob = allArgs.at(-1);
|
21
|
+
|
22
|
+
const files = glob.sync(inputGlob, { root: directoryProcess });
|
12
23
|
|
13
|
-
const
|
14
|
-
|
24
|
+
const publicPath = path.resolve(directoryProcess, "public");
|
25
|
+
|
26
|
+
if (fs.existsSync(publicPath)) {
|
27
|
+
console.log(colors.blue(`Found path to public folder at: ${publicPath}\n\n`));
|
28
|
+
run();
|
29
|
+
} else {
|
30
|
+
console.log(
|
31
|
+
colors.red(
|
32
|
+
`\nERROR: Could not find Next's public directory to resolve images from. Looking for:\n\n${publicPath}\n\nEnsure you run this command from your Next application.\n`
|
33
|
+
)
|
34
|
+
);
|
35
|
+
}
|
36
|
+
|
37
|
+
async function runFile(file) {
|
38
|
+
const buffer = fs.readFileSync(file);
|
39
|
+
|
40
|
+
return new Promise(res => {
|
41
|
+
remark()
|
42
|
+
.use(blurhashPlugin(publicPath))
|
43
|
+
.use(remarkFrontmatter)
|
44
|
+
.use(remarkStringify, {
|
45
|
+
fences: true,
|
46
|
+
})
|
47
|
+
.process(buffer)
|
48
|
+
.then(outputFile => {
|
49
|
+
fs.writeFileSync(file, outputFile.toString());
|
50
|
+
res();
|
51
|
+
});
|
52
|
+
});
|
53
|
+
}
|
15
54
|
|
16
55
|
async function run() {
|
17
|
-
|
18
|
-
.
|
19
|
-
|
20
|
-
.
|
21
|
-
|
22
|
-
});
|
56
|
+
for (const file of files) {
|
57
|
+
console.log(colors.blue(`Processing file: ${file}\n`));
|
58
|
+
await runFile(file);
|
59
|
+
console.log(colors.blue(`${file} finished\n`));
|
60
|
+
}
|
23
61
|
}
|
24
62
|
|
25
|
-
run();
|
63
|
+
//run();
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import path from "path";
|
2
|
+
import { visitParents } from "unist-util-visit-parents";
|
3
|
+
|
4
|
+
import colors from "colors";
|
5
|
+
|
6
|
+
import { getBlurhash } from "./generateBlurhash.js";
|
7
|
+
|
8
|
+
export const blurhashPlugin = publicPath => () => {
|
9
|
+
return (tree, file, done) => {
|
10
|
+
let outstanding = 0;
|
11
|
+
|
12
|
+
visitParents(tree, "image", async (node, ancestors) => {
|
13
|
+
let { url: imagePath, alt } = node;
|
14
|
+
|
15
|
+
const originalImg = imagePath;
|
16
|
+
if (!/http/.test(imagePath)) {
|
17
|
+
if (imagePath.startsWith("/")) {
|
18
|
+
imagePath = imagePath.substr(1);
|
19
|
+
}
|
20
|
+
|
21
|
+
imagePath = path.resolve(publicPath, imagePath);
|
22
|
+
}
|
23
|
+
|
24
|
+
console.log(colors.blue(`Attempting ${imagePath}...`));
|
25
|
+
|
26
|
+
try {
|
27
|
+
outstanding++;
|
28
|
+
|
29
|
+
const blurHash = await getBlurhash(imagePath);
|
30
|
+
|
31
|
+
console.log(
|
32
|
+
colors.green(`Finished processing ${imagePath}\n\t`, blurHash)
|
33
|
+
);
|
34
|
+
|
35
|
+
const parent = ancestors[ancestors.length - 1];
|
36
|
+
const index = parent.children.indexOf(node);
|
37
|
+
|
38
|
+
const newNode = `
|
39
|
+
<blurhash-image url="${originalImg}" preview='${JSON.stringify(blurHash)}'>
|
40
|
+
<img alt="${alt || ""}" src="${originalImg}" slot="image" />
|
41
|
+
<canvas width="${blurHash.w}" height="${blurHash.h}" slot="preview"></canvas>
|
42
|
+
</blurhash-image>`.trim();
|
43
|
+
|
44
|
+
parent.children[index] = {
|
45
|
+
type: "html",
|
46
|
+
value: newNode,
|
47
|
+
};
|
48
|
+
} catch (er) {
|
49
|
+
console.log(colors.red(`Error processing ${imagePath}\n\t`, er));
|
50
|
+
} finally {
|
51
|
+
outstanding--;
|
52
|
+
setTimeout(() => {
|
53
|
+
if (outstanding === 0) {
|
54
|
+
done();
|
55
|
+
}
|
56
|
+
}, 1);
|
57
|
+
}
|
58
|
+
});
|
59
|
+
|
60
|
+
setTimeout(() => {
|
61
|
+
if (outstanding === 0) {
|
62
|
+
done();
|
63
|
+
}
|
64
|
+
}, 1);
|
65
|
+
};
|
66
|
+
};
|
@@ -3,17 +3,15 @@ import { decode } from "../node_modules/blurhash/dist/esm/index";
|
|
3
3
|
type blurhash = { w: number; h: number; blurhash: string };
|
4
4
|
|
5
5
|
class ImageWithPreview extends HTMLElement {
|
6
|
-
isReady: boolean = false;
|
7
|
-
loaded: boolean = false;
|
8
6
|
sd: ShadowRoot;
|
9
7
|
mo?: MutationObserver;
|
10
8
|
|
11
|
-
static observedAttributes = ["preview"
|
9
|
+
static observedAttributes = ["preview"];
|
12
10
|
|
13
|
-
get imgEl(): any {
|
11
|
+
get #imgEl(): any {
|
14
12
|
return this.querySelector("img");
|
15
13
|
}
|
16
|
-
get canvasEl(): any {
|
14
|
+
get #canvasEl(): any {
|
17
15
|
return this.querySelector("canvas");
|
18
16
|
}
|
19
17
|
|
@@ -24,65 +22,50 @@ class ImageWithPreview extends HTMLElement {
|
|
24
22
|
this.sd.innerHTML = `<slot name="preview"></slot>`;
|
25
23
|
}
|
26
24
|
|
27
|
-
checkReady = () => {
|
28
|
-
if (this
|
29
|
-
this.ready();
|
25
|
+
#checkReady = () => {
|
26
|
+
if (this.#imgEl && this.#canvasEl) {
|
30
27
|
this.mo?.disconnect();
|
28
|
+
|
29
|
+
if (this.#imgEl.complete) {
|
30
|
+
this.#imgLoad();
|
31
|
+
} else {
|
32
|
+
this.#updatePreview();
|
33
|
+
this.#imgEl.addEventListener("load", this.#imgLoad);
|
34
|
+
}
|
35
|
+
|
36
|
+
return true;
|
31
37
|
}
|
32
|
-
return this.isReady;
|
33
38
|
};
|
34
39
|
|
35
40
|
connectedCallback() {
|
36
|
-
if (!this
|
37
|
-
this.mo = new MutationObserver(this
|
38
|
-
this.mo.observe(this, {
|
41
|
+
if (!this.#checkReady()) {
|
42
|
+
this.mo = new MutationObserver(this.#checkReady);
|
43
|
+
this.mo.observe(this, {
|
44
|
+
subtree: true,
|
45
|
+
childList: true,
|
46
|
+
attributes: false,
|
47
|
+
});
|
39
48
|
}
|
40
49
|
}
|
41
50
|
|
42
|
-
|
43
|
-
this.
|
44
|
-
|
45
|
-
if (this.imgEl.complete) {
|
46
|
-
this.onImageLoad();
|
47
|
-
}
|
48
|
-
this.imgEl.addEventListener("load", this.onImageLoad);
|
49
|
-
}
|
50
|
-
|
51
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
52
|
-
if (!this.isReady) {
|
53
|
-
return;
|
54
|
-
}
|
51
|
+
#imgLoad = () => {
|
52
|
+
this.sd.innerHTML = `<slot name="image"></slot>`;
|
53
|
+
};
|
55
54
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
this.loaded = false;
|
55
|
+
attributeChangedCallback(name) {
|
56
|
+
if (this.#canvasEl && name === "preview") {
|
57
|
+
this.#updatePreview();
|
60
58
|
}
|
61
|
-
|
62
|
-
this.render();
|
63
59
|
}
|
64
60
|
|
65
|
-
updatePreview() {
|
61
|
+
#updatePreview() {
|
66
62
|
const previewObj = JSON.parse(this.getAttribute("preview")!);
|
67
|
-
updateBlurHashPreview(this
|
68
|
-
}
|
69
|
-
|
70
|
-
onImageLoad = () => {
|
71
|
-
if (this.getAttribute("url") !== this.imgEl.src) {
|
72
|
-
setTimeout(() => {
|
73
|
-
this.loaded = true;
|
74
|
-
this.render();
|
75
|
-
}, 1500);
|
76
|
-
}
|
77
|
-
};
|
78
|
-
|
79
|
-
render() {
|
80
|
-
this.sd.innerHTML = `<slot name="${this.loaded ? "image" : "preview"}"></slot>`;
|
63
|
+
updateBlurHashPreview(this.#canvasEl, previewObj);
|
81
64
|
}
|
82
65
|
}
|
83
66
|
|
84
|
-
if (!customElements.get("
|
85
|
-
customElements.define("
|
67
|
+
if (!customElements.get("blurhash-image")) {
|
68
|
+
customElements.define("blurhash-image", ImageWithPreview);
|
86
69
|
}
|
87
70
|
|
88
71
|
function updateBlurHashPreview(canvasEl: HTMLCanvasElement, preview: blurhash) {
|
@@ -1,7 +1,15 @@
|
|
1
|
-
import { useEffect, useRef } from "react";
|
1
|
+
import React, { useEffect, useRef } from "react";
|
2
|
+
|
3
|
+
declare global {
|
4
|
+
namespace JSX {
|
5
|
+
interface IntrinsicElements {
|
6
|
+
["blurhash-image"]: any;
|
7
|
+
}
|
8
|
+
}
|
9
|
+
}
|
2
10
|
|
3
11
|
export const ImageWithPreview = (props: any) => {
|
4
|
-
const wcRef = useRef(null);
|
12
|
+
const wcRef = useRef<any>(null);
|
5
13
|
|
6
14
|
const { preview } = props;
|
7
15
|
const { w, h } = JSON.parse(preview);
|
@@ -11,9 +19,9 @@ export const ImageWithPreview = (props: any) => {
|
|
11
19
|
}, []);
|
12
20
|
|
13
21
|
return (
|
14
|
-
<
|
22
|
+
<blurhash-image ref={wcRef} {...props}>
|
15
23
|
<img style={{ display: "none" }} />
|
16
24
|
<canvas width={w} height={h}></canvas>
|
17
|
-
</
|
25
|
+
</blurhash-image>
|
18
26
|
);
|
19
27
|
};
|
package/index.js
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
import { imagePreviewBootstrap } from "./imagePreviewBootstrap";
|
1
|
+
import { imagePreviewBootstrap } from "./imagePreviewBootstrap.js";
|
2
2
|
|
3
3
|
export { imagePreviewBootstrap };
|
4
|
-
|
5
|
-
export const src = `(() => { "use strict";var A=Object.defineProperty;var R=(t,e,s)=>e in t?A(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var u=(t,e,s)=>(R(t,typeof e!="symbol"?e+"":e,s),s);const G=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","#","$","%","*","+",",","-",".",":",";","=","?","@","[","]","^","_","{","|","}","~"],h=t=>{let e=0;for(let s=0;s<t.length;s++){const n=t[s],o=G.indexOf(n);e=e*83+o}return e},p=t=>{let e=t/255;return e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4)},b=t=>{let e=Math.max(0,Math.min(1,t));return e<=.0031308?Math.round(e*12.92*255+.5):Math.round((1.055*Math.pow(e,.4166666666666667)-.055)*255+.5)},P=t=>t<0?-1:1,E=(t,e)=>P(t)*Math.pow(Math.abs(t),e);class v extends Error{constructor(e){super(e),this.name="ValidationError",this.message=e}}const k=t=>{if(!t||t.length<6)throw new v("The blurhash string must be at least 6 characters");const e=h(t[0]),s=Math.floor(e/9)+1,n=e%9+1;if(t.length!==4+2*n*s)throw new v(\`blurhash length mismatch: length is \${t.length} but it should be \${4+2*n*s}\`)},D=t=>{const e=t>>16,s=t>>8&255,n=t&255;return[p(e),p(s),p(n)]},H=(t,e)=>{const s=Math.floor(t/361),n=Math.floor(t/19)%19,o=t%19;return[E((s-9)/9,2)*e,E((n-9)/9,2)*e,E((o-9)/9,2)*e]},S=(t,e,s,n)=>{k(t),n=n|1;const o=h(t[0]),a=Math.floor(o/9)+1,c=o%9+1,q=(h(t[1])+1)/166,g=new Array(c*a);for(let r=0;r<g.length;r++)if(r===0){const i=h(t.substring(2,6));g[r]=D(i)}else{const i=h(t.substring(4+r*2,6+r*2));g[r]=H(i,q*n)}const l=e*4,d=new Uint8ClampedArray(l*s);for(let r=0;r<s;r++)for(let i=0;i<e;i++){let I=0,x=0,C=0;for(let m=0;m<a;m++)for(let f=0;f<c;f++){const M=Math.cos(Math.PI*i*f/e)*Math.cos(Math.PI*r*m/s);let w=g[f+m*c];I+=w[0]*M,x+=w[1]*M,C+=w[2]*M}let B=b(I),T=b(x),y=b(C);d[4*i+0+r*l]=B,d[4*i+1+r*l]=T,d[4*i+2+r*l]=y,d[4*i+3+r*l]=255}return d};class L extends HTMLElement{constructor(){super();u(this,"loaded",!1);u(this,"sd");u(this,"onImageLoad",()=>{this.getAttribute("url")!==this.currentImageEl.src&&setTimeout(()=>{this.loaded=!0,this.render()},1500)});this.sd=this.attachShadow({mode:"open"}),this.sd.innerHTML='<slot name="preview"></slot>'}get currentImageEl(){return this.querySelector("img")}get currentCanvasEl(){return this.querySelector("canvas")}connectedCallback(){this.currentImageEl.complete&&this.onImageLoad(),this.currentImageEl.addEventListener("load",this.onImageLoad)}attributeChangedCallback(s,n,o){if(s==="preview"){const a=JSON.parse(o);O(this.currentCanvasEl,a)}else s==="url"&&o!==this.currentImageEl.getAttribute("src")&&(this.loaded=!1,this.currentImageEl.src=o||"");this.render()}render(){this.sd.innerHTML=\`<slot name="\${this.loaded?"image":"preview"}"></slot>\`}}u(L,"observedAttributes",["preview","url"]);customElements.get("uikit-image")||customElements.define("uikit-image",L);function O(t,e){const{w:s,h:n}=e;t.width=s,t.height=n;const o=S(e.blurhash,s,n),a=t.getContext("2d"),c=a.createImageData(s,n);c.data.set(o),a.putImageData(c,0,0)} })();`;
|
@@ -0,0 +1,303 @@
|
|
1
|
+
---
|
2
|
+
title: Loading css, css-modules, and Sass with webpack
|
3
|
+
date: "2019-05-13T10:00:00.000Z"
|
4
|
+
description: An introduction to loading css with webpack, and enabling css-modules, and SASS in the process
|
5
|
+
---
|
6
|
+
|
7
|
+
The css ecosystem is immense and, at times, intimidating. This post will start at the beginning. We'll go over loading basic css with webpack, then move on to css modules, and wrap up with Sass. If you have some experience loading css in webpack-based web applications, some of this may be old news for you.
|
8
|
+
|
9
|
+
Note that while the code samples in this post use React, none of the concepts are specific to it in the least. Also, this post does _not_ cover css-in-js, for the simple reason that I haven't yet gotten around to diving into that ecosystem; I'm hoping by the time I do, it'll be a bit less crowded :)
|
10
|
+
|
11
|
+
## Starting at the beginning: basic css loading
|
12
|
+
|
13
|
+
Let's say we're rendering this component.
|
14
|
+
|
15
|
+
```jsx
|
16
|
+
const Component = () => (
|
17
|
+
<div className="pane">
|
18
|
+
<span>Pane Content</span>
|
19
|
+
<ul className="list">
|
20
|
+
<li className="list-item">Item 1</li>
|
21
|
+
<li className="list-item">Item 2</li>
|
22
|
+
<li className="list-item">Item 3</li>
|
23
|
+
</ul>
|
24
|
+
</div>
|
25
|
+
);
|
26
|
+
```
|
27
|
+
|
28
|
+
Without accompanying styles, it'll look something like this.
|
29
|
+
|
30
|
+
![Unstyled Component](/css-modules/unstyledComp.png)
|
31
|
+
|
32
|
+
Let's add some basic styling. Let's start simple, and have the JS module this component sits in import a css file, with standard, global styling rules. The import will look like this
|
33
|
+
|
34
|
+
```javascript
|
35
|
+
import "./styles.css";
|
36
|
+
```
|
37
|
+
|
38
|
+
Let's create that file, and add some purposefully ugly styles
|
39
|
+
|
40
|
+
```css
|
41
|
+
.pane {
|
42
|
+
background-color: green;
|
43
|
+
max-width: 300px;
|
44
|
+
}
|
45
|
+
|
46
|
+
.pane span {
|
47
|
+
color: purple;
|
48
|
+
}
|
49
|
+
|
50
|
+
.list {
|
51
|
+
margin-left: 20px;
|
52
|
+
}
|
53
|
+
|
54
|
+
.list-item {
|
55
|
+
list-style-type: lower-greek;
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
As we have it, this code leads to the following webpack error
|
60
|
+
|
61
|
+
![Loading error](/css-modules/loadingError.png)
|
62
|
+
|
63
|
+
webpack only knows how to load standard JavaScript by default. To add other content, like css, we need to tell webpack how to handle it. Let's do that now. First, install the `mini-css-extract-plugin` and `css-loader` plugins, using your favorite package manager, in your favorite cli.
|
64
|
+
|
65
|
+
Now load the mini css extract plugin in your webpack.config.js file.
|
66
|
+
|
67
|
+
```javascript
|
68
|
+
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
69
|
+
```
|
70
|
+
|
71
|
+
Now, in the same config file, there should be a `module` object at the top of the config object, and somewhere under that, there should be a `rules` array. If either are missing, add them. Now, under `rules`, add this entry
|
72
|
+
|
73
|
+
```javascript
|
74
|
+
{
|
75
|
+
test: /\.css$/,
|
76
|
+
use: [MiniCssExtractPlugin.loader, "css-loader"]
|
77
|
+
},
|
78
|
+
```
|
79
|
+
|
80
|
+
Finally, under the plugins array, also at the top level of your webpack.config object (add it if necessary), add this
|
81
|
+
|
82
|
+
```javascript
|
83
|
+
new MiniCssExtractPlugin({
|
84
|
+
filename: isProd ? "[name]-[contenthash].css" : "[name].css"
|
85
|
+
});
|
86
|
+
```
|
87
|
+
|
88
|
+
If you're new to webpack, and that went a little too fast for you, check out the [webpack docs](https://webpack.js.org/plugins/mini-css-extract-plugin/#root) for a slower treatment of this.
|
89
|
+
|
90
|
+
Now, if we restart webpack, and reload our page, we should see this disgusting, but technically correct result
|
91
|
+
|
92
|
+
![Unstyled Component](/css-modules/styledComponent.png)
|
93
|
+
|
94
|
+
"Success" - hooray.
|
95
|
+
|
96
|
+
## Adding CSS Modules
|
97
|
+
|
98
|
+
Right now we have code-split css. We can load css within any JavaScript module which uses it, and the CSS will only load if, and when that JS module is loaded. However, the css is global; if we add style rules for `list-item` in any other .css file, they'll conflict with the styles in this one. Wouldn't it be nice if we could have these styles be scoped only to the JS module which loads them? We can, with css-modules.
|
99
|
+
|
100
|
+
css-modules are a pre-processor step on your css file. It runs through all of your class names, and makes them unique. Moreover, it creates an exported object from the css file, on which these unique class names are exposed.
|
101
|
+
|
102
|
+
To enable this behavior, we'll first tweak the webpack loader rule, like so
|
103
|
+
|
104
|
+
```javascript
|
105
|
+
{
|
106
|
+
test: /\.css$/,
|
107
|
+
use: [
|
108
|
+
MiniCssExtractPlugin.loader,
|
109
|
+
{
|
110
|
+
loader: "css-loader",
|
111
|
+
options: { modules: true, exportOnlyLocals: false }
|
112
|
+
}
|
113
|
+
]
|
114
|
+
};
|
115
|
+
```
|
116
|
+
|
117
|
+
Note that the `exportOnlyLocals` may not be needed, as it should be the default; however, I've seen weird errors without it.
|
118
|
+
|
119
|
+
As we have it, our styles will still be loaded, but exposed behind dynamically generated class names. To apply them to our component at development time, we need to grab them off of the css module. Let's do that now
|
120
|
+
|
121
|
+
```jsx
|
122
|
+
import styles from "./styles.css";
|
123
|
+
const { pane, list, ["list-item"]: listItem } = styles;
|
124
|
+
|
125
|
+
const Component = () => (
|
126
|
+
<div className={pane}>
|
127
|
+
<span>Pane Content</span>
|
128
|
+
<ul className={list}>
|
129
|
+
<li className={listItem}>Item 1</li>
|
130
|
+
<li className={listItem}>Item 2</li>
|
131
|
+
<li className={listItem}>Item 3</li>
|
132
|
+
</ul>
|
133
|
+
</div>
|
134
|
+
);
|
135
|
+
```
|
136
|
+
|
137
|
+
We now import an object from the css file. The keys of this object are the class names we wrote originally in the css file, and the property values are the dynamically generated class names. Note the weird syntax around the `list-item` class. JavaScript identifiers cannot be hyphenated, so you'll either need to alias it, or just use valid JS names in your css modules.
|
138
|
+
|
139
|
+
_Edit_ - after publishing this, Marc Bernstein pointed out on Twitter that css-loader has a `camelCase` option that will convert hyphenated class names to camel-cased equivalents. You can read the docs on it [here](https://github.com/webpack-contrib/css-loader#camelcase)
|
140
|
+
|
141
|
+
Applying everything like so should reveal the same ugly output as before
|
142
|
+
|
143
|
+
![Unstyled Component](/css-modules/styledComponent.png)
|
144
|
+
|
145
|
+
## Best of Both Worlds?
|
146
|
+
|
147
|
+
So far so good, but what if, like me, you think global styles aren't so bad, _sometimes_. What if you have some styles that you plan to be universal in your app, used almost everywhere, and manually importing them as dynamic values just isn't worth the effort? Examples might include a `.btn`, `.table`, or even a `.pane` class. What if the `.pane` class is intended to be used far and wide, with exactly one meaning. Can we make that class (and others) be global, while using css-modules for module-specific stylings, like our list classes, above.
|
148
|
+
|
149
|
+
You can, and you have two options: you can define each and every global css class with `:global()` (see the [css-modules docs](https://github.com/css-modules/css-modules) for more info), or, my preferred approach, you can use a naming scheme to differentiate global css files from css-modules.
|
150
|
+
|
151
|
+
Specifically, what if we decide that files ending with `.module.css` are css modules, and any other `.css` file is an old-school, global css file. webpack makes this possible with the `oneOf` construct. Basically, turn your entry in the `rules` section, from before, into this
|
152
|
+
|
153
|
+
```javascript
|
154
|
+
{
|
155
|
+
test: /\.css$/,
|
156
|
+
oneOf: [
|
157
|
+
{
|
158
|
+
test: /\.module\.css$/,
|
159
|
+
use: [
|
160
|
+
MiniCssExtractPlugin.loader,
|
161
|
+
{
|
162
|
+
loader: "css-loader",
|
163
|
+
options: { modules: true, exportOnlyLocals: false }
|
164
|
+
}
|
165
|
+
]
|
166
|
+
},
|
167
|
+
{
|
168
|
+
use: [MiniCssExtractPlugin.loader, "css-loader"]
|
169
|
+
}
|
170
|
+
]
|
171
|
+
};
|
172
|
+
```
|
173
|
+
|
174
|
+
This tells webpack to match `.css` files against the first rule that's valid. If the `.css` file ends in `.module.css`, use css modules. Else, use global styles. Let's try this out.
|
175
|
+
|
176
|
+
Let's rename our original `styles.css` to be `styles.module.css`, and remove the `.pane` styles. It'll look like this now
|
177
|
+
|
178
|
+
```css
|
179
|
+
.list {
|
180
|
+
margin-left: 20px;
|
181
|
+
}
|
182
|
+
|
183
|
+
.list-item {
|
184
|
+
list-style-type: lower-greek;
|
185
|
+
}
|
186
|
+
```
|
187
|
+
|
188
|
+
Now, let's add a new `styles.css` file, and put our `pane` styles from before, into it
|
189
|
+
|
190
|
+
```css
|
191
|
+
.pane {
|
192
|
+
background-color: green;
|
193
|
+
max-width: 300px;
|
194
|
+
}
|
195
|
+
|
196
|
+
.pane span {
|
197
|
+
color: purple;
|
198
|
+
}
|
199
|
+
```
|
200
|
+
|
201
|
+
Now, we'll import the global css styles (probably in one place, at the root of our application) like we did originally
|
202
|
+
|
203
|
+
```javascript
|
204
|
+
import "./styles.css";
|
205
|
+
```
|
206
|
+
|
207
|
+
and we'll grab the dynamic class names for the things we left in the css module, as we did above
|
208
|
+
|
209
|
+
```jsx
|
210
|
+
import styles from "./styles.module.css";
|
211
|
+
const { list, ["list-item"]: listItem } = styles;
|
212
|
+
|
213
|
+
const Component = () => (
|
214
|
+
<div className="pane">
|
215
|
+
<span>Pane Content</span>
|
216
|
+
<ul className={list}>
|
217
|
+
<li className={listItem}>Item 1</li>
|
218
|
+
<li className={listItem}>Item 2</li>
|
219
|
+
<li className={listItem}>Item 3</li>
|
220
|
+
</ul>
|
221
|
+
</div>
|
222
|
+
);
|
223
|
+
```
|
224
|
+
|
225
|
+
If all went well, everything should look identical to before.
|
226
|
+
|
227
|
+
## Getting Sassy
|
228
|
+
|
229
|
+
Lastly, let's say you want to add Sass. Being subject to normal developer constraints, you certainly can't convert each and every css file to be scss, so you want to support both, side-by-side. Fortunately this is the easiest part of the post. Since scss is a superset of css, we can just run all `.css` and `.scss` files through the `sass-loader` as a first step, and leave all the rest of the css processing the same, as before. Let's see how.
|
230
|
+
|
231
|
+
First, we'll install some new dependencies
|
232
|
+
|
233
|
+
```
|
234
|
+
npm i node-sass sass-loader --save
|
235
|
+
```
|
236
|
+
|
237
|
+
Now, we'll add a slight tweak to our webpack rules
|
238
|
+
|
239
|
+
```javascript
|
240
|
+
{
|
241
|
+
test: /\.s?css$/,
|
242
|
+
oneOf: [
|
243
|
+
{
|
244
|
+
test: /\.module\.s?css$/,
|
245
|
+
use: [
|
246
|
+
MiniCssExtractPlugin.loader,
|
247
|
+
{
|
248
|
+
loader: "css-loader",
|
249
|
+
options: { modules: true, exportOnlyLocals: false }
|
250
|
+
},
|
251
|
+
"sass-loader"
|
252
|
+
]
|
253
|
+
},
|
254
|
+
{
|
255
|
+
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
|
256
|
+
}
|
257
|
+
]
|
258
|
+
};
|
259
|
+
```
|
260
|
+
|
261
|
+
We added `sass-loader` as a new, first loader (loaders are processed from right to left). Did you catch the other change? It's the two `?`'s in the `test` properties. `?` means optional in regular expressions, so all this means is, our rules now apply to both `.css` and `.scss` files. Plain `.css` files are processed by the sass-loader, but again, css is a subset of `scss`, so this is effectively a no-op.
|
262
|
+
|
263
|
+
To make sure things still work, let's convert our css files to scss, add some Sass, and maybe even tweak the styles to be even cooler, and make sure everything still works.
|
264
|
+
|
265
|
+
First, for `styles.css`, we'll rename it to `styles.scss`, and add a few upgrades.
|
266
|
+
|
267
|
+
```scss
|
268
|
+
$paneColor: pink;
|
269
|
+
$paneSpanColor: purple;
|
270
|
+
|
271
|
+
.pane {
|
272
|
+
background-color: $paneColor;
|
273
|
+
max-width: 300px;
|
274
|
+
}
|
275
|
+
|
276
|
+
.pane span {
|
277
|
+
color: $paneSpanColor;
|
278
|
+
}
|
279
|
+
```
|
280
|
+
|
281
|
+
Now, we'll rename `styles.module.css` to be `styles.modules.scss` and make it look something like this
|
282
|
+
|
283
|
+
```scss
|
284
|
+
$listStyleType: armenian;
|
285
|
+
|
286
|
+
.list {
|
287
|
+
margin-left: 20px;
|
288
|
+
}
|
289
|
+
|
290
|
+
.list-item {
|
291
|
+
list-style-type: $listStyleType;
|
292
|
+
}
|
293
|
+
```
|
294
|
+
|
295
|
+
after re-starting our webpack process, our cool component should look like this
|
296
|
+
|
297
|
+
![Unstyled Component](/css-modules/styledSass.png)
|
298
|
+
|
299
|
+
## Concluding thoughts
|
300
|
+
|
301
|
+
In the end, a few lines of webpack config allowed us to easily load global, or scoped css, with optional sass processing in either case. Of course this is only scratching the surface of what's possible. There's no shortage of PostCSS, or other plugins you could toss into the loader list.
|
302
|
+
|
303
|
+
Happy Coding!
|
@@ -21,10 +21,17 @@
|
|
21
21
|
}
|
22
22
|
},
|
23
23
|
"..": {
|
24
|
-
"version": "0.0.
|
24
|
+
"version": "0.0.1",
|
25
25
|
"license": "ISC",
|
26
26
|
"dependencies": {
|
27
|
+
"@types/react": "^18.0.15",
|
28
|
+
"@types/react-dom": "^18.0.6",
|
29
|
+
"@vitejs/plugin-react": "^1.3.2",
|
27
30
|
"blurhash": "^1.1.5",
|
31
|
+
"colors": "^1.4.0",
|
32
|
+
"glob": "^8.0.3",
|
33
|
+
"next": "^12.2.0",
|
34
|
+
"node-fetch": "^3.2.6",
|
28
35
|
"node-html-parser": "^5.3.3",
|
29
36
|
"prettier": "^2.7.1",
|
30
37
|
"remark": "^14.0.2",
|
@@ -33,10 +40,11 @@
|
|
33
40
|
"to-vfile": "^7.2.3",
|
34
41
|
"unist-util-visit": "^4.1.0",
|
35
42
|
"unist-util-visit-parents": "^5.1.0",
|
36
|
-
"vfile-reporter": "^7.0.4"
|
43
|
+
"vfile-reporter": "^7.0.4",
|
44
|
+
"vite": "^2.9.13"
|
37
45
|
},
|
38
46
|
"bin": {
|
39
|
-
"next-
|
47
|
+
"next-blurhash-markdown": "bin/markdown-sync.js"
|
40
48
|
}
|
41
49
|
},
|
42
50
|
"node_modules/@babel/runtime": {
|
@@ -2167,7 +2175,7 @@
|
|
2167
2175
|
}
|
2168
2176
|
}
|
2169
2177
|
},
|
2170
|
-
"node_modules/next-
|
2178
|
+
"node_modules/next-blurhash-previews": {
|
2171
2179
|
"resolved": "..",
|
2172
2180
|
"link": true
|
2173
2181
|
},
|
@@ -4481,10 +4489,17 @@
|
|
4481
4489
|
"use-sync-external-store": "1.1.0"
|
4482
4490
|
}
|
4483
4491
|
},
|
4484
|
-
"next-
|
4492
|
+
"next-blurhash-previews": {
|
4485
4493
|
"version": "file:..",
|
4486
4494
|
"requires": {
|
4495
|
+
"@types/react": "^18.0.15",
|
4496
|
+
"@types/react-dom": "^18.0.6",
|
4497
|
+
"@vitejs/plugin-react": "^1.3.2",
|
4487
4498
|
"blurhash": "^1.1.5",
|
4499
|
+
"colors": "^1.4.0",
|
4500
|
+
"glob": "^8.0.3",
|
4501
|
+
"next": "^12.2.0",
|
4502
|
+
"node-fetch": "^3.2.6",
|
4488
4503
|
"node-html-parser": "^5.3.3",
|
4489
4504
|
"prettier": "^2.7.1",
|
4490
4505
|
"remark": "^14.0.2",
|
@@ -4493,7 +4508,8 @@
|
|
4493
4508
|
"to-vfile": "^7.2.3",
|
4494
4509
|
"unist-util-visit": "^4.1.0",
|
4495
4510
|
"unist-util-visit-parents": "^5.1.0",
|
4496
|
-
"vfile-reporter": "^7.0.4"
|
4511
|
+
"vfile-reporter": "^7.0.4",
|
4512
|
+
"vite": "^2.9.13"
|
4497
4513
|
}
|
4498
4514
|
},
|
4499
4515
|
"object-assign": {
|
@@ -1,7 +1,5 @@
|
|
1
1
|
import Document, { Html, Head, Main, NextScript } from "next/document";
|
2
|
-
import { imagePreviewBootstrap, src } from "next-
|
3
|
-
|
4
|
-
import Script from "next/script";
|
2
|
+
import { imagePreviewBootstrap, src } from "next-blurhash-previews";
|
5
3
|
|
6
4
|
export default class MyDocument extends Document {
|
7
5
|
render() {
|
@@ -11,16 +11,37 @@ export default function Home() {
|
|
11
11
|
<div className={styles.container}>
|
12
12
|
Root
|
13
13
|
<br />
|
14
|
-
<button onClick={() => setVal(
|
14
|
+
<button onClick={() => setVal(x => x + 1)}>Refresh</button>
|
15
15
|
{val}
|
16
|
-
<
|
17
|
-
<
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
<div style={{ display: "flex" }}>
|
17
|
+
<blurhash-image
|
18
|
+
key={val}
|
19
|
+
url="/dynamo-introduction/img1.png"
|
20
|
+
preview='{ "w": 364, "h": 196, "blurhash": "L8S6SsD*%gt7IVM|tRRj~qWBM{NG" }'
|
21
|
+
>
|
22
|
+
<img slot="image" src="/dynamo-introduction/img1.png" />
|
23
|
+
<canvas slot="preview" width="364" height="196"></canvas>
|
24
|
+
</blurhash-image>
|
25
|
+
<blurhash-image
|
26
|
+
key={val + 2}
|
27
|
+
url="/dynamo-introduction/img5a.png"
|
28
|
+
preview='{ "w": 500, "h": 139, "blurhash": "LnPGmj_zIA:KS}j[s:ay-VWVRjay" }'
|
29
|
+
>
|
30
|
+
<img slot="image" src="/dynamo-introduction/img5a.png" />
|
31
|
+
<canvas slot="preview" width="500" height="139"></canvas>
|
32
|
+
</blurhash-image>
|
33
|
+
<blurhash-image
|
34
|
+
key={val + 5}
|
35
|
+
url="https://d193qjyckdxivp.cloudfront.net/medium-covers/573d1b97120426ef0078aa92/fcb820e4-36e3-4741-a3de-6994c46a66cc.jpg"
|
36
|
+
preview='{ "w": 106, "h": 160, "blurhash": "U38|kkTJ01}A;{Or57;N01NG+?IW01rX^NAW" }'
|
37
|
+
>
|
38
|
+
<img
|
39
|
+
slot="image"
|
40
|
+
src="https://d193qjyckdxivp.cloudfront.net/medium-covers/573d1b97120426ef0078aa92/fcb820e4-36e3-4741-a3de-6994c46a66cc.jpg"
|
41
|
+
/>
|
42
|
+
<canvas slot="preview" width="106" height="160"></canvas>
|
43
|
+
</blurhash-image>
|
44
|
+
</div>
|
24
45
|
</div>
|
25
46
|
);
|
26
47
|
}
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
package/next-runner/yarn.lock
CHANGED
@@ -1122,11 +1122,15 @@
|
|
1122
1122
|
"resolved" "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
1123
1123
|
"version" "1.4.0"
|
1124
1124
|
|
1125
|
-
"next-
|
1125
|
+
"next-blurhash-previews@file:/Users/arackis/Documents/git/next-blurhash-previews":
|
1126
1126
|
"resolved" "file:.."
|
1127
|
-
"version" "0.0.
|
1127
|
+
"version" "0.0.1"
|
1128
1128
|
dependencies:
|
1129
|
+
"@types/react" "^18.0.15"
|
1130
|
+
"@types/react-dom" "^18.0.6"
|
1131
|
+
"@vitejs/plugin-react" "^1.3.2"
|
1129
1132
|
"blurhash" "^1.1.5"
|
1133
|
+
"next" "^12.2.0"
|
1130
1134
|
"node-html-parser" "^5.3.3"
|
1131
1135
|
"prettier" "^2.7.1"
|
1132
1136
|
"remark" "^14.0.2"
|
@@ -1136,6 +1140,7 @@
|
|
1136
1140
|
"unist-util-visit" "^4.1.0"
|
1137
1141
|
"unist-util-visit-parents" "^5.1.0"
|
1138
1142
|
"vfile-reporter" "^7.0.4"
|
1143
|
+
"vite" "^2.9.13"
|
1139
1144
|
|
1140
1145
|
"next@12.2.0":
|
1141
1146
|
"integrity" "sha512-B4j7D3SHYopLYx6/Ark0fenwIar9tEaZZFAaxmKjgcMMexhVJzB3jt7X+6wcdXPPMeUD6r09weUtnDpjox/vIA=="
|
package/package.json
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
{
|
2
2
|
"name": "next-blurhash-previews",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.3-beta10",
|
4
4
|
"description": "",
|
5
5
|
"main": "index.js",
|
6
|
+
"module": "index.js",
|
6
7
|
"bin": {
|
7
|
-
"
|
8
|
+
"blurhash-markdown": "./bin/markdown-sync.js"
|
8
9
|
},
|
9
10
|
"scripts": {
|
10
11
|
"test": "echo \"Error: no test specified\" && exit 1",
|
11
12
|
"build": "vite build",
|
12
|
-
"build-watch": "vite build -w"
|
13
|
+
"build-watch": "vite build -w",
|
14
|
+
"prepare": "npm run build"
|
13
15
|
},
|
14
16
|
"type": "module",
|
15
17
|
"repository": {
|
@@ -27,10 +29,14 @@
|
|
27
29
|
"@types/react-dom": "^18.0.6",
|
28
30
|
"@vitejs/plugin-react": "^1.3.2",
|
29
31
|
"blurhash": "^1.1.5",
|
32
|
+
"colors": "^1.4.0",
|
33
|
+
"glob": "^8.0.3",
|
34
|
+
"install": "^0.13.0",
|
30
35
|
"next": "^12.2.0",
|
31
|
-
"node-
|
32
|
-
"
|
36
|
+
"node-fetch": "^3.2.6",
|
37
|
+
"npm": "^8.15.1",
|
33
38
|
"remark": "^14.0.2",
|
39
|
+
"remark-frontmatter": "^4.0.1",
|
34
40
|
"retext": "^8.1.0",
|
35
41
|
"sharp": "^0.30.7",
|
36
42
|
"to-vfile": "^7.2.3",
|
package/util/setBootstrap.js
CHANGED
@@ -4,9 +4,18 @@ export default function writeBootstrapScript() {
|
|
4
4
|
return {
|
5
5
|
name: "write-bootstrap-script",
|
6
6
|
closeBundle() {
|
7
|
-
let BootstrapModule = fs.readFileSync(
|
8
|
-
|
9
|
-
|
7
|
+
let BootstrapModule = fs.readFileSync(
|
8
|
+
"./components/imagePreviewBootstrap.tsx",
|
9
|
+
"utf8"
|
10
|
+
);
|
11
|
+
const bootstrapScript = fs.readFileSync(
|
12
|
+
"./build/imageWithPreview.js",
|
13
|
+
"utf8"
|
14
|
+
);
|
15
|
+
BootstrapModule = BootstrapModule.replace(
|
16
|
+
"/*HERE*/",
|
17
|
+
bootstrapScript.replace(/[\r\n]\s*$/, "").replace(/`/g, "\\`")
|
18
|
+
).replace(/\${/g, "\\${");
|
10
19
|
fs.writeFileSync("./imagePreviewBootstrap.js", BootstrapModule);
|
11
20
|
},
|
12
21
|
};
|
package/vite.config.ts
CHANGED
@@ -7,9 +7,9 @@ export default defineConfig({
|
|
7
7
|
target: "es2022",
|
8
8
|
outDir: "./build",
|
9
9
|
lib: {
|
10
|
-
entry: "components/
|
10
|
+
entry: "components/imageWithPreview.tsx",
|
11
11
|
formats: ["cjs"],
|
12
|
-
fileName: () => "
|
12
|
+
fileName: () => "imageWithPreview.js",
|
13
13
|
name: "imageWithPreview",
|
14
14
|
},
|
15
15
|
//minify: false,
|
package/example.md
DELETED
package/output.md
DELETED
package/plugin.js
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
import fs from "fs";
|
2
|
-
import path, { dirname } from "path";
|
3
|
-
|
4
|
-
import sharp from "sharp";
|
5
|
-
import { encode, isBlurhashValid } from "blurhash";
|
6
|
-
|
7
|
-
import { visit } from "unist-util-visit";
|
8
|
-
import { visitParents } from "unist-util-visit-parents";
|
9
|
-
|
10
|
-
import HTMLParser from "node-html-parser";
|
11
|
-
import prettier from "prettier";
|
12
|
-
|
13
|
-
import { fileURLToPath } from "url";
|
14
|
-
const __filename = fileURLToPath(import.meta.url);
|
15
|
-
const __dirname = dirname(__filename);
|
16
|
-
|
17
|
-
export default function retextSentenceSpacing() {
|
18
|
-
return (tree, file, done) => {
|
19
|
-
visitParents(tree, "image", async (node, ancestors) => {
|
20
|
-
const { url } = node;
|
21
|
-
|
22
|
-
//const resolvedPath = path.join(process.cwd(), url);
|
23
|
-
const resolvedPath = path.join(__dirname, url);
|
24
|
-
console.log(resolvedPath);
|
25
|
-
|
26
|
-
const image = sharp(resolvedPath);
|
27
|
-
const dimensions = await image.metadata();
|
28
|
-
|
29
|
-
const { width, height } = dimensions;
|
30
|
-
console.log(height, width);
|
31
|
-
|
32
|
-
image
|
33
|
-
.raw()
|
34
|
-
.ensureAlpha()
|
35
|
-
.toBuffer((err, buffer) => {
|
36
|
-
console.log("got buffer");
|
37
|
-
try {
|
38
|
-
if (err) {
|
39
|
-
console.log("Error getting buffer", err);
|
40
|
-
} else {
|
41
|
-
const blurhash = encode(new Uint8ClampedArray(buffer), width, height, 4, 4);
|
42
|
-
if (isBlurhashValid(blurhash)) {
|
43
|
-
console.log({ blurhash, w: width, h: height });
|
44
|
-
} else {
|
45
|
-
return console.log("FAIL");
|
46
|
-
}
|
47
|
-
}
|
48
|
-
} catch (err) {
|
49
|
-
return console.log("FAIL", err);
|
50
|
-
}
|
51
|
-
});
|
52
|
-
|
53
|
-
const parent = ancestors[ancestors.length - 1];
|
54
|
-
const index = parent.children.indexOf(node);
|
55
|
-
|
56
|
-
const newNode = HTMLParser.parse(`<div>
|
57
|
-
<img />
|
58
|
-
<span>Hey there 2</span>
|
59
|
-
</div>`);
|
60
|
-
|
61
|
-
parent.children[index] = {
|
62
|
-
type: "html",
|
63
|
-
value: prettier.format(newNode.outerHTML, { parser: "html" }).trimEnd(),
|
64
|
-
};
|
65
|
-
|
66
|
-
setTimeout(() => {
|
67
|
-
done();
|
68
|
-
}, 3000);
|
69
|
-
//parent.children[index] = replacement;
|
70
|
-
|
71
|
-
// parent.children.push({
|
72
|
-
// type: "html",
|
73
|
-
// value: `<div><img /><span>Hey there</span></div>`,
|
74
|
-
// position: node.position,
|
75
|
-
// });
|
76
|
-
});
|
77
|
-
};
|
78
|
-
}
|