From 586e1d9e2c5eb931cdfa1706b02a9ca32e6872fc Mon Sep 17 00:00:00 2001 From: pantonshire Date: Tue, 13 Jun 2023 11:45:16 +0100 Subject: [PATCH] auto wasm compilation --- .gitignore | 3 + next.config.js | 20 ++++ package-lock.json | 180 ++++++++++++++++++++++++++++++++++- package.json | 5 +- src/components/inspector.tsx | 13 ++- src/context/utfdump.tsx | 71 ++++++-------- 6 files changed, 240 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 8f322f0..4d77ae7 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# webassembly +/wasm diff --git a/next.config.js b/next.config.js index 91ef62f..6550b4d 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,26 @@ +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); +const path = require('path'); + +let loadedWasm = false; + /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + webpack(nextConfig) { + if (!loadedWasm) { + loadedWasm = true; + nextConfig.plugins.push( + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '..', 'utfdump', 'wasm'), + outDir: path.resolve(__dirname, 'wasm', 'utfdump'), + forceMode: 'production', + extraArgs: '--target web', + }) + ); + } + + return nextConfig; + }, }; module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index 39447c5..cbab2ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,10 @@ "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.1.3", - "utfdump_wasm": "file:pkg" + "utfdump_wasm": "file:wasm/utfdump" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "^1.7.0" } }, "node_modules/@babel/runtime": { @@ -459,6 +462,89 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@wasm-tool/wasm-pack-plugin": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@wasm-tool/wasm-pack-plugin/-/wasm-pack-plugin-1.7.0.tgz", + "integrity": "sha512-WikzYsw7nTd5CZxH75h7NxM/FLJAgqfWt+/gk3EL3wYKxiIlpMIYPja+sHQl3ARiicIYy4BDfxkbAVjRYlouTA==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "command-exists": "^1.2.7", + "watchpack": "^2.1.1", + "which": "^2.0.2" + } + }, + "node_modules/@wasm-tool/wasm-pack-plugin/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@wasm-tool/wasm-pack-plugin/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@wasm-tool/wasm-pack-plugin/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@wasm-tool/wasm-pack-plugin/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@wasm-tool/wasm-pack-plugin/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@wasm-tool/wasm-pack-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@wasm-tool/wasm-pack-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -788,6 +874,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3543,7 +3635,7 @@ } }, "node_modules/utfdump_wasm": { - "resolved": "pkg", + "resolved": "wasm/utfdump", "link": true }, "node_modules/watchpack": { @@ -3658,6 +3750,12 @@ } }, "pkg": { + "name": "utfdump_wasm", + "version": "0.1.0", + "extraneous": true + }, + "wasm/utfdump": { + "name": "utfdump_wasm", "version": "0.1.0" } }, @@ -3926,6 +4024,76 @@ "eslint-visitor-keys": "^3.3.0" } }, + "@wasm-tool/wasm-pack-plugin": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@wasm-tool/wasm-pack-plugin/-/wasm-pack-plugin-1.7.0.tgz", + "integrity": "sha512-WikzYsw7nTd5CZxH75h7NxM/FLJAgqfWt+/gk3EL3wYKxiIlpMIYPja+sHQl3ARiicIYy4BDfxkbAVjRYlouTA==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "command-exists": "^1.2.7", + "watchpack": "^2.1.1", + "which": "^2.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -4154,6 +4322,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6040,7 +6214,7 @@ } }, "utfdump_wasm": { - "version": "file:pkg" + "version": "file:wasm/utfdump" }, "watchpack": { "version": "2.4.0", diff --git a/package.json b/package.json index 16e4cfc..9c8fbbb 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.1.3", - "utfdump_wasm": "file:pkg" + "utfdump_wasm": "file:wasm/utfdump" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "^1.7.0" } } diff --git a/src/components/inspector.tsx b/src/components/inspector.tsx index 3932853..2a31ae9 100644 --- a/src/components/inspector.tsx +++ b/src/components/inspector.tsx @@ -3,7 +3,7 @@ import { useContext, useState } from 'react'; import styles from '@/app/page.module.css' import { TextField } from './textfield'; -import { WASMContext, WASMContextProvider } from '@/context/utfdump'; +import { UtfdumpContext, UtfdumpContextProvider } from '@/context/utfdump'; type InspectorProps = { sourceUrl: string, @@ -13,7 +13,7 @@ export function Inspector(props: InspectorProps) { const [currentString, setCurrentString] = useState(''); return ( - +
- + ); } function Out(props: { currentString: string }) { - const ctx = useContext(WASMContext); + const ctx = useContext(UtfdumpContext); - if (!ctx.wasm) { + if (!ctx.utfdump) { return (

Loading WASM...

); @@ -46,8 +46,7 @@ function Out(props: { currentString: string }) { return (
- {/*

{spongebob_case(currentString)}

*/} -

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

+

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

); } diff --git a/src/context/utfdump.tsx b/src/context/utfdump.tsx index 4ec8230..d80bda0 100644 --- a/src/context/utfdump.tsx +++ b/src/context/utfdump.tsx @@ -1,49 +1,38 @@ -// using solution from https://github.com/satelllte/nextjs-wasm for now +import { useState, createContext, useEffect, useRef } from 'react'; +import type { ReactNode } from 'react'; -import { useState, createContext, useEffect, useRef } from 'react' -import type { ReactNode } from 'react' +type UtfdumpContextTy = { + utfdump?: typeof import('utfdump_wasm'), +}; -const initial: IWASMContext = {} +type UtfdumpContextProviderProps = { + children: ReactNode, +}; -const useMountEffectOnce = (fn: () => void) => { - const wasExecutedRef = useRef(false) +export const UtfdumpContext = createContext({}); + +export function UtfdumpContextProvider(props: UtfdumpContextProviderProps) { + const hasLoaded = useRef(false); + const [contextValue, setContextValue] = useState({}); + useEffect(() => { - if (!wasExecutedRef.current) { - fn() + // Ensure the WASM module is only run once, as this effect callback is called twice when React + // is in strict mode. + // FIXME: is atomic compare-and-swap necessary? Does React run this in a multithreaded context? + if (!hasLoaded.current) { + hasLoaded.current = true; + + (async() => { + const utfdumpWasmModule = await import('utfdump_wasm'); + await utfdumpWasmModule.default(); + setContextValue({ utfdump: utfdumpWasmModule }); + })(); } - 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 + + {props.children} + + ); }