diff --git a/next.config.js b/next.config.js index 767719f..91ef62f 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,6 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + reactStrictMode: true, +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index 0cfd498..39447c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "next": "13.4.5", "react": "18.2.0", "react-dom": "18.2.0", - "typescript": "5.1.3" + "typescript": "5.1.3", + "utfdump_wasm": "file:pkg" } }, "node_modules/@babel/runtime": { @@ -3541,6 +3542,10 @@ "punycode": "^2.1.0" } }, + "node_modules/utfdump_wasm": { + "resolved": "pkg", + "link": true + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -3651,6 +3656,9 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "pkg": { + "version": "0.1.0" } }, "dependencies": { @@ -6031,6 +6039,9 @@ "punycode": "^2.1.0" } }, + "utfdump_wasm": { + "version": "file:pkg" + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 3564fbc..16e4cfc 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "next": "13.4.5", "react": "18.2.0", "react-dom": "18.2.0", - "typescript": "5.1.3" + "typescript": "5.1.3", + "utfdump_wasm": "file:pkg" } } diff --git a/src/app/inspector.tsx b/src/app/inspector.tsx deleted file mode 100644 index 1ac1089..0000000 --- a/src/app/inspector.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import styles from './page.module.css' -import { TextField } from './textfield'; - -type InspectorProps = { - sourceUrl: string, -}; - -export function Inspector(props: InspectorProps) { - const [currentString, setCurrentString] = useState(''); - - return ( -
-
- - -
-

- Source code is available - here. - Text entered above is not sent over the network. -

-
-
- -
-

{currentString}

-
-
- ) -} diff --git a/src/app/page.tsx b/src/app/page.tsx index 2ceeb90..366e26c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ import Image from 'next/image' import styles from './page.module.css' -import { Inspector } from './inspector'; +import { Inspector } from '@/components/inspector'; const sourceUrl = 'https://example.com'; diff --git a/src/components/inspector.tsx b/src/components/inspector.tsx new file mode 100644 index 0000000..3932853 --- /dev/null +++ b/src/components/inspector.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { useContext, useState } from 'react'; +import styles from '@/app/page.module.css' +import { TextField } from './textfield'; +import { WASMContext, WASMContextProvider } from '@/context/utfdump'; + +type InspectorProps = { + sourceUrl: string, +}; + +export function Inspector(props: InspectorProps) { + const [currentString, setCurrentString] = useState(''); + + return ( + +
+
+ + +
+

+ Source code is available here. + Text entered above is not sent over the network. +

+
+
+ + +
+
+ ); +} + +function Out(props: { currentString: string }) { + const ctx = useContext(WASMContext); + + if (!ctx.wasm) { + return ( +

Loading WASM...

+ ); + } + + return ( +
+ {/*

{spongebob_case(currentString)}

*/} +

{ctx.wasm.spongebob_case(props.currentString)}

+
+ ); +} diff --git a/src/app/textfield.tsx b/src/components/textfield.tsx similarity index 90% rename from src/app/textfield.tsx rename to src/components/textfield.tsx index d3b6f30..119de65 100644 --- a/src/app/textfield.tsx +++ b/src/components/textfield.tsx @@ -1,6 +1,6 @@ 'use client'; -import styles from './page.module.css' +import styles from '@/app/page.module.css' type TextFieldProps = { onChange: (value: string) => void, diff --git a/src/context/utfdump.tsx b/src/context/utfdump.tsx new file mode 100644 index 0000000..4ec8230 --- /dev/null +++ b/src/context/utfdump.tsx @@ -0,0 +1,49 @@ +// using solution from https://github.com/satelllte/nextjs-wasm for now + +import { useState, createContext, useEffect, useRef } from 'react' +import type { ReactNode } from 'react' + +const initial: IWASMContext = {} + +const useMountEffectOnce = (fn: () => void) => { + const wasExecutedRef = useRef(false) + useEffect(() => { + if (!wasExecutedRef.current) { + fn() + } + wasExecutedRef.current = true + }, [fn]) +} + +export const WASMContext = createContext(initial) + +export const WASMContextProvider: React.FC = ({ + children +}) => { + const [state, setState] = useState(initial) + + // This has to run only once: https://github.com/rustwasm/wasm-bindgen/issues/3153 + // Though, in development React renders twice when Strict Mode is enabled: https://reactjs.org/docs/strict-mode.html + // That's why it must be limited to a single mount run + useMountEffectOnce(() => { + (async() => { + const wasm = await import("utfdump_wasm"); + await wasm.default(); + setState({ wasm }); + })() + }) + + return ( + + {children} + + ) +} + +interface IWASMContext { + wasm?: typeof import('utfdump_wasm') +} + +interface WASMContextProviderProps { + children: ReactNode +}