preact-render-to-string
preact-render-to-string copied to clipboard
First time using preact and when I import preact's render-to-string I get "Could not find a declaration file for module 'preact-render-to-string'. "
I am configuring a node project to use typescript, tsx, preact and preact-render-to-string.
I pulled this code from one of the preact-render-to-string examples.
import render from 'preact-render-to-string';
import { h, Component } from 'preact';
/** @jsx h */
// Classical components work
class Fox extends Component {
render({ name }) {
return <span class="fox">{ name }</span>;
}
}
// ... and so do pure functional components:
const Box = ({ type, children }) => (
<div class={`box box-${type}`}>{ children }</div>
);
let html = render(
<Box type="open">
<Fox name="Finn" />
</Box>
);
console.log(html);
// <div class="box box-open"><span class="fox">Finn</span></div>
When linting and running tsc on the above file I get the following errors:
src/test-redertostring.tsx|1 col 20-45 error| Could not find a declaration file for module 'preact-render-to-string'. '/Users/jeffreyschwartz/Development/poc/jsx-preact/node_modules/preact-render-to-string/dist/index.mjs' implicitly has an 'any' type. Try `npm i --save-dev @types/preact-render-to-string` if it exists or add a new declaration (.d.ts) file containing `declare module 'preact-render-to-string';`
src/test-redertostring.tsx|2 col 10-11 warning| 'h' is defined but never used.
src/test-redertostring.tsx|2 col 10-11 error| 'h' is defined but never used.
src/test-redertostring.tsx|7 col 5-11 error| Property 'render' in type 'Fox' is not assignable to the same property in base type 'Component<{}, {}>'. Type '({ name }: { name: any; }) => Element' is not assignable to type '(props?: Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<any> | undefined; }> | undefined, state?: Readonly<{}> | undefined, context?: any) => ComponentChild'. Types of parameters '__0' and 'props' are incompatible. Type 'Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<any> | undefined; }> | undefined' is not assignable to type '{ name: any; }'. Type 'undefined' is not assignable to type '{ name: any; }'.
src/test-redertostring.tsx|7 col 14-18 error| Binding element 'name' implicitly has an 'any' type.
src/test-redertostring.tsx|13 col 22-30 error| Binding element 'children' implicitly has an 'any' type.
src/test-redertostring.tsx|13 col 16-20 error| Binding element 'type' implicitly has an 'any' type.
src/test-redertostring.tsx|17 col 5-9 error| 'html' is never reassigned. Use 'const' instead.
src/test-redertostring.tsx|19 col 14-18 error| Type '{ name: string; }' is not assignable to type 'IntrinsicAttributes & Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<any> | undefined; }>'. Property 'name' does not exist on type 'IntrinsicAttributes & Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<any> | undefined; }>'.
src/test-redertostring.tsx|20-21 col 11-1 error| Missing trailing comma.
My typescript configuration is as follows:
{
"compilerOptions": {
"module": "nodenext",
"outDir": "./dist",
"allowJs": false,
"target": "es2021",
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"include": ["./src/**/*"]
}
My package.json is as follows:
{
"name": "jsx-preact",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "./source/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "tsc -w -strict",
"start": "node dist/index.js"
},
"keywords": [],
"author": "jeffreyschwartz",
"license": "ISC",
"devDependencies": {
"@types/jest": "^28.1.7",
"@types/node": "^18.7.6",
"@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"eslint": "^8.22.0",
"eslint-config-recommended": "^4.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-standard": "^5.0.0",
"jest": "^28.1.3",
"preact": "^10.10.3",
"preact-render-to-string": "^5.2.1",
"typescript": "^4.7.4"
}
}
My eslint configuration:
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"@typescript-eslint/eslint-plugin"
],
"env": {
"browser": false,
"node": true,
"commonjs": false,
"es2021": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"quotes": [2, "double"],
"no-unused-expressions": 0,
"comma-dangle": [2, "always-multiline"],
"semi": [2, "always"],
"indent": [2, 4],
"arrow-parens": [2, "as-needed"],
"eol-last": 0,
"no-multiple-empty-lines": [2, { "max": 1, "maxBOF": 0, "maxEOF": 0 }],
"strict": [2, "global"],
"no-prototype-builtins": 0,
"no-unused-vars": 2
}
}
Obviously I am doing something wrong and I am hoping someone can point me in the right direction.
Thanks for filing an issue! I'm a bit surprised that we didn't notice that earlier. I guess our tooling just picked up the typings regardless. We traced it down to the types property missing in package.json. Just cut a new release 5.2.2 that addresses this issue.
"devDependencies": {
"preact": "^10.10.3",
- "preact-render-to-string": "^5.2.1",
+ "preact-render-to-string": "^5.2.2",
"typescript": "^4.7.4"
}
Note that TS apparently changed something in their node module resolution algorithm, so it doesn't type the default export correctly. This can be addressed by adding this line:
{
"compilerOptions": {
- "module": "nodenext",
+ "module": "NodeNext",
+ "moduleResolution": "node",
"outDir": "./dist",
"allowJs": false,
"target": "es2021",
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"include": ["./src/**/*"]
}
Let me know if this works.
@marvinhagemeister Wow! That's like the quickest response I have ever had here on Github. Yes, that fixed it for sure and thanks.
I hope you don't mind but I have a question about using the "type": property in package.json. If I want Node to use import and export in a .js file what should I set it to? From package.json's docs it seems it should be set to "module" but when it is I get an error and when I remove it it works even though my output .js file is using import.
Here is the file that tsc generated:
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
console.log("__import", __importDefault);
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("preact/jsx-runtime");
const preact_render_to_string_1 = __importDefault(require("preact-render-to-string"));
/** @jsx h */
const vdom = (0, jsx_runtime_1.jsx)("div", { class: "foo", children: "content" });
const html = (0, preact_render_to_string_1.default)(vdom);
console.log(html);
When I run it using node dist/test-redertostring.js node complains as follows:
' This file is being treated as an ES module because it has a '.js' file extension and '/Users/jeffreyschwartz/Development/poc/jsx-preact/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.`
But when I remove the "type" property from package.json and run the .js file everything works and the correct output is generated.
@jeffschwartz Ah so it looks like the NodeNext in combination with moduleResolution: node leads to TS to assume that one would want CommonJS output. If NodeNext would properly support default imports in the first place that wouldn't be necessary. So I guess the easiest workaround for those TS quirks is to just use a named import instead.
index.tsx:
- import render from "preact-render-to-string";
+ import { render } from "preact-render-to-string";
import { h, Component } from "preact";
tsconfig.json:
{
"compilerOptions": {
"module": "NodeNext",
- "moduleResolution": "node",
"outDir": "./dist",
"allowJs": false,
"target": "es2021",
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"include": ["./src/**/*"]
}
@marvinhagemeister The following tsconfig is working for me and the generated code is now using native ES module imports and exports:
{
"compilerOptions": {
- "module": "NodeNext",
+ "module": "ES2020",
+ "moduleResolution": "Node",
"outDir": "./dist",
"allowJs": false,
"target": "es2021",
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"include": ["./src/**/*"]
}
Apparently NodeNext should only be used with nightly builds as per https://www.typescriptlang.org/tsconfig#node16nodenext-nightly-builds.
I've run into another problem with another of preact-render-to-string examples. The following example code
import render from "preact-render-to-string";
import { Component } from "preact";
/** @jsx h */
// Classical components work
class Fox extends Component {
render({ name }: { name: string }) {
return <span class="fox">{name}</span>;
}
}
// ... and so do pure functional components:
const Box = ({ type, children }: {type: string, children: []}) => (
<div class={`box box-${type}`}>{children}</div>
);
let html = render(
<Box type="open">
<Fox name="Finn" />
</Box>,
);
console.log(html);
// <div class="box box-open"><span class="fox">Finn</span></div>
generates the following diagnostics:
src/test-rendertostring.tsx|7 col 5-11 error| Property 'render' in type 'Fox' is not assignable to the same property in base type 'Component<{}, {}>'. Type '({ name }: { name: string; }) => Element' is not assignable to type '(props?: Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<any> | undefined; }> | undefined, state?: Readonly<{}> | undefined, context?: any) => ComponentChild'. Types of parameters '__0' and 'props' are incompatible. Type 'Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<any> | undefined; }> | undefined' is not assignable to type '{ name: string; }'. Type 'undefined' is not assignable to type '{ name: string; }'.
src/test-rendertostring.tsx|17 col 5-9 error| 'html' is never reassigned. Use 'const' instead.
src/test-rendertostring.tsx|18 col 6-9 error| This JSX tag's 'children' prop expects type '[]' which requires multiple children, but only a single child was provided.
src/test-rendertostring.tsx|19 col 14-18 error| Type '{ name: string; }' is not assignable to type 'IntrinsicAttributes & Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<any> | undefined; }>'. Property 'name' does not exist on type 'IntrinsicAttributes & Readonly<Attributes & { children?: ComponentChildren; ref?: Ref<any> | undefined; }>'.
Is it perhaps a .dts issue or could it be something else?
Perhaps the issue with the diagnostic is that the code examples need to be updated to reflect the latest version of Preact.
Closing this out, as anything else will likely require fixes upstream in Microbundle to support .d.cts, now that that's a thing.