Skip to content

Commit

Permalink
fix: bundler-less ESM imports
Browse files Browse the repository at this point in the history
Node requires that ESM imports must always have an extension.
Fixes #5
  • Loading branch information
Desdaemon committed Apr 20, 2024
1 parent 8ee00fd commit d64c693
Show file tree
Hide file tree
Showing 16 changed files with 84 additions and 39 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pnpm-lock.yaml
docs
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

## 0.2.0

- *(Breaking)* `jsxConfig.jsonAttributes` changed to be a Set
- _(Breaking)_ `jsxConfig.jsonAttributes` changed to be a Set
- New template function `html` for compatibility with swc-plugin-static-jsx

## 0.1.4
Expand Down
8 changes: 4 additions & 4 deletions example/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// typed-htmx declares mostly ambient types so this is all you need.
import 'typed-htmx';
import "typed-htmx";

// A demo of how to augment foreign types with htmx attributes.
// In this case, Hono sources its types from its own namespace, so we do the same
// and directly extend its namespace.
declare global {
namespace Hono {
interface HTMLAttributes extends HtmxAttributes {}
}
namespace Hono {
interface HTMLAttributes extends HtmxAttributes {}
}
}
10 changes: 5 additions & 5 deletions example/tsconfig.hono.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
// This is (usually) the only setting to change when swapping renderers.
"jsxImportSource": "hono/jsx"
}
"extends": "./tsconfig.json",
"compilerOptions": {
// This is (usually) the only setting to change when swapping renderers.
"jsxImportSource": "hono/jsx"
}
}
8 changes: 3 additions & 5 deletions example/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"compilerOptions": {
"target": "es6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "jsx": "react",
"jsx": "react-jsx", /* Specify what JSX code is generated. */
// "jsx": "react",
"jsx": "react-jsx" /* Specify what JSX code is generated. */,
// If your frontend framework prescribes a JSX source, change it here.
// In this example, we will use typed-htmx's own text renderer based on typed-html.
"jsxImportSource": "typed-htmx/typed-html",
Expand All @@ -19,7 +19,5 @@
"skipDefaultLibCheck": true /* Skip type checking .d.ts files that are included with TypeScript. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src/**/*",
]
"include": ["src/**/*"]
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"version": "0.2.1",
"description": "Definitions for htmx attributes in JSX",
"scripts": {
"dist": "tsc -b && tsc -p tsconfig.esm.json",
"lint": "prettier --write src .",
"dist": "bash scripts/dist.sh",
"lint": "sg scan -U && prettier --write src .",
"doc": "typedoc && poetry run mkdocs"
},
"keywords": [
Expand Down Expand Up @@ -57,4 +57,4 @@
"cross-env": "^7.0.3",
"prettier": "^3.0.0"
}
}
}
28 changes: 28 additions & 0 deletions rules/no-extensionless-relative-import.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
id: no-extensionless-relative-import
message: Relative imports must end in .js
severity: error
language: ts
rule:
pattern: "$IMPORT"
regex: "/([^.]+)[^/]$"
kind: string_fragment
any:
- inside:
stopBy: end
kind: import_statement
- inside:
stopBy: end
kind: export_statement
- inside:
stopBy: end
kind: call_expression
has:
field: function
regex: "^import$"
transform:
OUTPUT:
replace:
replace: '.*'
by: '$0.js'
source: $IMPORT
fix: "$OUTPUT"
7 changes: 7 additions & 0 deletions scripts/dist.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

set -euxo pipefail
rm -rf ./dist
tsc -b
tsc -p tsconfig.esm.json
echo '{"type":"module"}' > ./dist/esm/package.json
1 change: 1 addition & 0 deletions sgconfig.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruleDirs: [rules]
9 changes: 5 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ function isRenderable(value: unknown): value is Renderable {
return value === 0 || !!value;
}
function attrSanitizer(raw: Renderable): string {
return String(raw).replaceAll(attrPattern, (sub) => attrReplacements[sub] || sub);
return String(raw).replace(attrPattern, (sub) => attrReplacements[sub] || sub);
}
function attrSanitizerWithoutDQ(raw: Renderable): string {
return String(raw).replaceAll(attrPatternWithoutDQ, (sub) => attrReplacements[sub] || sub);
return String(raw).replace(attrPatternWithoutDQ, (sub) => attrReplacements[sub] || sub);
}
function htmlSanitizer(raw: Renderable): string {
const out = String(raw);
Expand All @@ -84,7 +84,8 @@ function htmlTransformChildren(value: InterpValue): string {
else if (isObject(value)) obj = value;
else return "";
const out: string[] = [];
for (const [key, attr] of Object.entries(obj)) {
for (const key in Object.keys(obj)) {
const attr = (obj as Record<string, unknown>)[key];
if (!isRenderable(attr) && attr !== "") continue;
if (jsxConfig.jsonAttributes.has(key)) {
out.push(`${key}='${attrSanitizerWithoutDQ(JSON.stringify(attr))}'`);
Expand All @@ -98,7 +99,7 @@ function htmlTransformChildren(value: InterpValue): string {
/**
* A [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates)
* that interprets different kinds of {@link InterpValue values} into escaped HTML.
*
*
* ```ts twoslash
* import { html } from 'typed-htmx';
* function assertEqual(left: any, right: any) {}
Expand Down
8 changes: 4 additions & 4 deletions src/typed-html/jsx-dev-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference path="../jsx.d.ts" />

export { Fragment } from "./jsx-runtime";
import { jsx, jsxs, type Node } from "./jsx-runtime";
export { Fragment } from "./jsx-runtime.js";
import { jsx, jsxs, type Node } from "./jsx-runtime.js";

export function jsxDEV(
tag: any,
Expand All @@ -14,8 +14,8 @@ export function jsxDEV(
try {
return Array.isArray(props.children) ? jsxs(tag, props) : jsx(tag, props);
} catch (error) {
const cause = error instanceof Error && error.cause;
console.error(`Error encountered while rendering ${tag}`, { error, cause, source });
const stack = error instanceof Error ? error.stack : "";
console.error(`Error encountered while rendering ${tag}`, { error, source, stack });
throw error;
}
}
2 changes: 1 addition & 1 deletion src/typed-html/jsx-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/// <reference types="typed-html" />

import { createElement } from "typed-html";
import { jsxConfig } from "../index";
import { jsxConfig } from "../index.js";

type Element = JSX.Element | Node;

Expand Down
14 changes: 11 additions & 3 deletions tests/static-jsx/main.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
describe("html templator", () => {
it("works", () => {
expect(<div data-foo={123} />).toBe('<div data-foo="123" />');
expect(<div data-foo={123} />).toBe('<div data-foo="123"></div>');
});
it("processes json attributes", () => {
expect(<div hx-vals={{ foo: "It's joever" }} />).toBe(`<div hx-vals='{"foo":"It&#39;s joever"}'/>`);
expect(<div hx-vals={{ foo: "It's joever" }} />).toBe(`<div hx-vals='{"foo":"It&#39;s joever"}'></div>`);
});
it("skips falsy attributes", () => {
expect(<div data-foo={false} {...{ foobar: false }} />).toBe("<div />");
expect(<div data-foo={false} {...{ foobar: false }} />).toBe("<div></div>");
});
it("correctly handles void elements", () => {
expect(
<>
<img />
<br />
</>,
).toBe("<img><br>");
});
});
2 changes: 1 addition & 1 deletion tests/static-jsx/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */

/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
"module": "Node16" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node16" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
Expand Down
5 changes: 4 additions & 1 deletion tsconfig.esm.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "esnext",
"module": "ES2020",
// Bundler doesn't require relative imports to have extensions, so we will
// use ast-grep to enforce it.
"moduleResolution": "Bundler",
"outDir": "./dist/esm",
"declaration": false,
"sourceMap": false
Expand Down
12 changes: 5 additions & 7 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
{
"include": [
"src/**/*"
],
"include": ["src/**/*"],
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"moduleResolution": "node",
"target": "ES2018",
"module": "Node16",
"moduleResolution": "Node16",
"declaration": true,
"sourceMap": true,
"outDir": "./dist",
Expand All @@ -15,4 +13,4 @@
"skipDefaultLibCheck": true,
"skipLibCheck": true
}
}
}

0 comments on commit d64c693

Please sign in to comment.