preact-render-to-string icon indicating copy to clipboard operation
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'. "

Open jeffschwartz opened this issue 3 years ago • 5 comments

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.

jeffschwartz avatar Aug 16 '22 19:08 jeffschwartz

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 avatar Aug 16 '22 20:08 marvinhagemeister

@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 avatar Aug 16 '22 21:08 jeffschwartz

@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 avatar Aug 17 '22 09:08 marvinhagemeister

@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?

jeffschwartz avatar Aug 17 '22 15:08 jeffschwartz

Perhaps the issue with the diagnostic is that the code examples need to be updated to reflect the latest version of Preact.

jeffschwartz avatar Aug 17 '22 19:08 jeffschwartz

Closing this out, as anything else will likely require fixes upstream in Microbundle to support .d.cts, now that that's a thing.

rschristian avatar May 07 '24 18:05 rschristian