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 ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "arrowParens": "avoid"
3
+ }
@@ -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
+ }
@@ -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 retextSentenceSpacing from "../plugin.js";
12
+ import glob from "glob";
13
+ import colors from "colors";
9
14
 
10
- import { fileURLToPath } from "url";
11
- const __filename = fileURLToPath(import.meta.url);
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 __dirname = dirname(__filename);
14
- const buffer = fs.readFileSync(path.join(__dirname, "example.md"));
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
- remark()
18
- .use(retextSentenceSpacing)
19
- .process(buffer)
20
- .then((file) => {
21
- fs.writeFileSync("output.md", file.toString());
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", "url"];
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.imgEl && this.canvasEl) {
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.checkReady()) {
37
- this.mo = new MutationObserver(this.checkReady);
38
- this.mo.observe(this, { subtree: true, childList: true, attributes: false });
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
- ready() {
43
- this.isReady = true;
44
- this.updatePreview();
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
- if (name === "preview") {
57
- this.updatePreview();
58
- } else if (name === "url") {
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.canvasEl, previewObj);
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("uikit-image")) {
85
- customElements.define("uikit-image", ImageWithPreview);
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
- <uikit-image ref={wcRef} {...props}>
22
+ <blurhash-image ref={wcRef} {...props}>
15
23
  <img style={{ display: "none" }} />
16
24
  <canvas width={w} height={h}></canvas>
17
- </uikit-image>
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!
@@ -0,0 +1,10 @@
1
+ ![A](./img1.png)
2
+
3
+ <some-component>
4
+ <p>Hello</p>
5
+ </some-component>
6
+
7
+ Yo
8
+
9
+ ![external image](https://d193qjyckdxivp.cloudfront.net/medium-covers/573d1b97120426ef0078aa92/fcb820e4-36e3-4741-a3de-6994c46a66cc.jpg)
10
+
@@ -0,0 +1,10 @@
1
+ ![A](./img1.png)
2
+
3
+ <some-component>
4
+ <p>Hello</p>
5
+ </some-component>
6
+
7
+ Yo
8
+
9
+ ![external image](https://d193qjyckdxivp.cloudfront.net/medium-covers/573d1b97120426ef0078aa92/fcb820e4-36e3-4741-a3de-6994c46a66cc.jpg)
10
+
@@ -0,0 +1,10 @@
1
+ ![A](./img1.png)
2
+
3
+ <some-component>
4
+ <p>Hello</p>
5
+ </some-component>
6
+
7
+ Yo
8
+
9
+ ![external image](https://d193qjyckdxivp.cloudfront.net/medium-covers/573d1b97120426ef0078aa92/fcb820e4-36e3-4741-a3de-6994c46a66cc.jpg)
10
+
@@ -21,10 +21,17 @@
21
21
  }
22
22
  },
23
23
  "..": {
24
- "version": "0.0.4",
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-static-markdown-previews": "index.js"
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-static-image-previews": {
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-static-image-previews": {
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-static-image-previews";
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((x) => x + 1)}>Refresh</button>
14
+ <button onClick={() => setVal(x => x + 1)}>Refresh</button>
15
15
  {val}
16
- <uikit-image key={val} url="/dynamo-introduction/img1.png" preview='{ "w": 364, "h": 196, "blurhash": "L8S6SsD*%gt7IVM|tRRj~qWBM{NG" }'>
17
- <img slot="image" src="/dynamo-introduction/img1.png" />
18
- <canvas slot="preview" width="364" height="196"></canvas>
19
- </uikit-image>
20
- <uikit-image key={val + 9} url="/dynamo-introduction/img5a.png" preview='{ "w": 2244, "h": 622, "blurhash": "LnPGmj_zIA:KS}j[s:ay-VWVRjay" }'>
21
- <img slot="image" src="/dynamo-introduction/img5a.png" />
22
- <canvas slot="preview" width="2244" height="622"></canvas>
23
- </uikit-image>
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
@@ -0,0 +1,3 @@
1
+ .container {
2
+ margin: 15px;
3
+ }
@@ -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-static-image-previews@file:/Users/arackis/Documents/git/next-static-image-previews":
1125
+ "next-blurhash-previews@file:/Users/arackis/Documents/git/next-blurhash-previews":
1126
1126
  "resolved" "file:.."
1127
- "version" "0.0.4"
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.1",
3
+ "version": "0.0.3-beta10",
4
4
  "description": "",
5
5
  "main": "index.js",
6
+ "module": "index.js",
6
7
  "bin": {
7
- "next-static-markdown-previews": "./bin/markdown-sync.js"
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-html-parser": "^5.3.3",
32
- "prettier": "^2.7.1",
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",
@@ -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("./components/imagePreviewBootstrap.tsx", "utf8");
8
- const bootstrapScript = fs.readFileSync("./build/ImageWithPreview.js", "utf8");
9
- BootstrapModule = BootstrapModule.replace("/*HERE*/", bootstrapScript.replace(/[\r\n]\s*$/, "").replace(/`/g, "\\`")).replace(/\${/g, "\\${");
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/ImageWithPreview.tsx",
10
+ entry: "components/imageWithPreview.tsx",
11
11
  formats: ["cjs"],
12
- fileName: () => "ImageWithPreview.js",
12
+ fileName: () => "imageWithPreview.js",
13
13
  name: "imageWithPreview",
14
14
  },
15
15
  //minify: false,
package/example.md DELETED
@@ -1,9 +0,0 @@
1
- ![A](./img1.png)
2
-
3
- <some-component>
4
- <p>Hello</p>
5
- </some-component>
6
-
7
- Yo
8
-
9
- ![Query execution plan](/img5.png)
package/output.md DELETED
@@ -1,15 +0,0 @@
1
- <div>
2
- <img />
3
- <span>Hey there 2</span>
4
- </div>
5
-
6
- <some-component>
7
- <p>Hello</p>
8
- </some-component>
9
-
10
- Yo
11
-
12
- <div>
13
- <img />
14
- <span>Hey there 2</span>
15
- </div>
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
- }