Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does not work with hooks and preact/debug #48

Open
patdx opened this issue Sep 21, 2022 · 1 comment
Open

Does not work with hooks and preact/debug #48

patdx opened this issue Sep 21, 2022 · 1 comment

Comments

@patdx
Copy link

patdx commented Sep 21, 2022

I was trying to use react-router, preact and preact-ssr-prepass together and ran into some issues. After eliminating an unrelated ESM/CJS error, I discovered that I was still running into issues with preact-ssr-prepass. It turns out it can be reproduced with a pretty short example:

import 'preact/debug'; // if you comment this line out, it will work
import { h } from 'preact';
import renderToString from 'preact-render-to-string';
import prepass from 'preact-ssr-prepass';

import { useState } from 'preact/hooks';

const App = () => {
  const [x, setX] = useState(10);
  console.log(`Use state inside of App`, x);
  return h('div', undefined, x);
};

const vnode = h(App);

console.log('begin prepass');
await prepass(vnode);
console.log('end prepass');

const out = renderToString(vnode);

console.log(out);

Output:

❯ node index.js
begin prepass
Error: Hook can only be invoked from render methods.
    at async ESMLoader.import (https://node-qpdrhr-230n5kih.w.staticblitz.com/blitz.f2b7d4e326e0543f39833cc6d890b02bb01d7899.js:6:1209283)
    at async i.loadESM (https://node-qpdrhr-230n5kih.w.staticblitz.com/blitz.f2b7d4e326e0543f39833cc6d890b02bb01d7899.js:6:246622)
    at async handleMainPromise (https://node-qpdrhr-230n5kih.w.staticblitz.com/blitz.f2b7d4e326e0543f39833cc6d890b02bb01d7899.js:6:989292)

Here is a stackblitz: https://stackblitz.com/edit/node-qpdrhr?file=index.js


Basically as far as I can tell preact/debug is throwing an error because when the useState() hook is called, hooksAllowed is false. I guess this is related to the timing of the component lifecycle. I think the error from preact/debug seems mistaken because when I comment out that preact/debug import any code I write seems to work fine.

I wonder if this will be fixed by #47? Because it seems that options._diff function will set hooksAllowed = true.

The obvious solution may be to "not use preact/debug on the server side", which I think makes sense. But, in my case I was trying to set up an SSR project with @preact/preset-vite, which has the preact/debug hardcoded in so I never had a choice or knew it was being imported. I'm going to see if I can override it and skip the preact/debug import for the server side render.

@rhengles
Copy link

@patdx Bizarrely, I think the problem is in the minification. I already got a similar problem with using react-redux with preact, vite and ssr. A bundle of react-redux minified with Terser, with all default options, simply does not work. Then I tried a very basic online minifier and it worked.

My solution to this problem was the following: I patched the preact/debug package to load the file ./debug/src/index.mjs instead of the default ./debug/dist/debug.mjs and I had to change all the source extensions from .js to .mjs. Maybe it would have been simpler to just add type: "module" to the package since I don't need cjs, lol.

This is my patch script:

import { resolve as pathResolve, join as pathJoin } from 'node:path'
import { fileURLToPath } from 'node:url'
import { rename } from 'node:fs'
import { readFile, writeFile } from 'node:fs/promises'
import dirFiles from 'dir-files'
import { extendDeepModify } from '@arijs/frontend/isomorphic/utils/extend'

const dirname = fileURLToPath(new URL('../', import.meta.url)).replace(/\/+$/,'')

renameExts(() => modifyPreactPkg(() => console.log(`preact/debug patched`)))

function renameExts(cb) {
	var dfp = dirFiles.plugins;
	var pluginOpt = {};

	const srcDir = 'node_modules/preact/debug/src'
	const reExtFrom = /\.js$/i
	const extFrom = '.js'
	const extTo = '.mjs'
	console.log(`renaming files in ${srcDir} from ${extFrom} to ${extTo}`)

	dirFiles({
		result: 0,
		path: pathResolve(dirname, srcDir),
		plugins: [
			dfp.skip(function skipSpecial({ name }) {
				return name.startsWith('.')
					|| name.startsWith('$')
					|| name.startsWith('node_modules');
			}),
			dfp.stat(pluginOpt),
			dfp.queueDir(pluginOpt),
			dfp.readDir(pluginOpt),
			dfp.queueDirFiles(pluginOpt),
			dfp.skip(function skipEmptyNameOrDir({ name, stat }) {
				return !name || stat.isDirectory() || !name.endsWith(extFrom);
			}),
			function renameFile({ dir, name: nameFrom }, cb) {
				const nameTo = nameFrom.replace(reExtFrom, extTo)
				const from = pathJoin(dir.root, dir.sub, nameFrom)
				const to = pathJoin(dir.root, dir.sub, nameTo)
				console.log(`~ ${pathJoin(dir.sub, nameFrom)} -> ${nameTo}`)
				this.result++
				rename(from, to, cb)
			},
		],
		onError: function(err, { dir, name }) {
			console.log('! '+pathJoin(dir.sub, name));
			console.error(err);
		},
		callback: function(err) {
			if (err) {
				throw err;
			}
			console.log(`${this.result} files renamed`)
			cb()
		},
	});
}

function modifyPreactPkg(cb) {
	const prom = runPackage('preact', () => ({
		'exports': {
			'./debug': {
				'import': "./debug/src/index.mjs"
			}
		}
	}))
	prom.catch(error => { throw error })
	prom.then(cb)
}

async function runPackage(name, fnAdditions) {
	const fOpt = {
		encoding: 'utf8',
		highWaterMark: 1024,
	}
	const pkgDir = `node_modules/${name}/package.json`
	const pkgPath = pathResolve(dirname, pkgDir)
	const pkgSource = await readFile(pkgPath, fOpt)
	const pkg = JSON.parse(pkgSource)
	const additions = fnAdditions(pkg)
	extendDeepModify(pkg, additions)
	const pkgTarget = JSON.stringify(pkg, null, '\t')
	const sizeFrom = pkgSource.length
	const sizeTo = pkgTarget.length
	const sizeDiff = sizeTo - sizeFrom
	await writeFile(pkgPath, pkgTarget, fOpt)
	console.log(`Package ${pkgDir}`)
	console.log(` overwrite ${JSON.stringify(additions)}`)
	console.log(`Diff: ${sizeDiff} from ${sizeFrom} to ${sizeTo} bytes`)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants