◆ JSX を使うなら vue ファイルの必要無い気がする
◆ style タグが使えない問題があったけど CSS Modules や @scope でなんとかなりそう
◆ JSX の情報は思ったより少なくてそこまで使われてないのかも

個人的に Vue のテンプレートが好みでないので テンプレートを使わない方法で使っています
ほぼ JSX と同じですが 一応ビルド不要で使える htm です
Vue3 を htm で使う

1 つの HTML ファイルに全部詰め込むようなシンプルなものだと特に問題もなかったのですが 少し大きめのものになったので ちゃんとビルドしようかと思いました
そこで問題になったのが vue ファイルです

vue ファイル

テンプレートを書くならこれでいいのですが JSX を使う場合に vue ファイルっているのでしょうか?
vue ファイルの中身ってこんな感じです (公式サイト Playground の初期状態)

<script setup>
import { ref } from "vue"

const msg = ref("Hello World!")
</script>

<template>
<h1>{{ msg }}</h1>
<input v-model="msg">
</template>

JSX を使うなら template タグはなくなります
script タグだけなら jsx ファイルでもいいと思います

jsx ファイル

import { ref } from "vue"

export default {
setup() {
const msg = ref("Hello World!")

return () => {
return (
<>
<h1>{msg.value}</h1>
<input
value={msg.value}
onInput={(event) => msg.value = event.target.value}
/>
</>
)
}
}
}

JSX を使って書くのなら このほうが不要な script タグがなくスッキリしていて好きです
ですが あまりこの形式は多くないみたいです

jsx 形式の問題

jsx 形式でなにか問題あるかなと探すと style タグが書けないようでした
たしかに style タグは特殊な扱いになって vue ファイルのビルド時にスコープをつけるために変換されたりします
vue ファイルを使わず jsx ファイルから単純に CSS を読み込むだけでは スコープが使えません
ということもあって jsx ファイルを使ってるサンプルは基本グローバルなスタイルを 1 つ用意してる感じでした

CSS Modules

でもスコープなら CSS Modules を使えばうまく動きそうです

[App.jsx]
import styles from "./App.module.css"

export default {
setup() {
return () => {
return (
<div class={styles.root}>
<h1 class={styles.header}>title</h1>
</div>
)
}
}
}

[App.module.css]
.root {
padding: 10px;
}
.header {
font-size: 2rem;
}

Vite なら .module.css という拡張子にすると自動で CSS Modules として扱ってくれるので簡単です

ただ CSS Modules ってクラス名を一意になるよう変換してくれるだけなので 制限も多いです
例えば p タグ全部といったことができません
「.foo p」 としても p はそのコンポーネントに限らず 子コンポーネント内もすべて対象になります
タグ名全部はあまりないかもですが 属性セレクタを使いたいときなんかは不便でした
そういう問題があるせいで ここ数年は CSS Modules をやめて CSS-in-JS してるくらいです

@scope を使う

最近は @scope という CSS 機能が標準で使えます
これがあると有効な範囲をセレクタで指定できます

これを使うとタグ名をセレクタで使っても スコープ内でしか効果がないので CSS Modules のような制限を受けません

[App.jsx]
import "./App.css"

export default {
setup() {
return () => {
return (
<div class="component-root app">
<h1>title</h1>
</div>
)
}
}
}

[App.css]
@scope(.app) to (.component-root) {
h1 {
color: red;
}
}

みたいにして 各コンポーネントのトップレベルに component-root というクラス名をつけておけば App コンポーネント内で Component1 などのコンポーネントを使ってもそこはスタイルの範囲外にできます
ただマルチルートの場合はすべてのルートに component-root をつけないといけないので少し面倒です

一見良さそうなのですが スロットを使う場合に問題があります
CSS Modules を使う場合は スロットとして渡す要素にも親側でクラスをつけているので親側のスタイルが有効になります
これは CustomElements と ShadowDOM を使う場合と同じ動きです
それに対して @scope の場合は 実際のツリー上の範囲がスコープになるので スロット機能で渡した部分は子コンポーネントのスタイルが適用されます

スロットとして渡す要素にも component-root とコンポーネント名のクラスをつければ対処はできますが コンポーネントというわけではないのに これらを設定するのはなにか違う感があります
それにスロットを受け取る側で追加のプロパティを渡す仕組みもあります
そういうことまで考えると複雑になります

スロットのことは使うときにどうしたいか次第なところもあるのであまり気にしないことにします

ビルド無し

@scope を使う方法で実際に動く例を作ってみたのですが ビルド無しでそのまま動かしたかったので JSX を htm にして CSS のインポートを CSS Modules (ウェブ標準機能の方) にしました

export const addCSS = cssss => {
document.adoptedStyleSheets.push(cssss)
}

という関数を作っておいて

import css from "./index.css" assert { type: "css" }
addCSS(css)

という感じで使います
CSS のインポートが古い assert 構文ですが まだ新しい with が動かないので仕方ないです

以下がコンポーネントのコードです

[App.js]
import { html, addCSS } from "./utils.js"
import Component1 from "./Component1.js"
import Component2 from "./Component2.js"
import css from "./App.css" assert { type: "css" }
addCSS(css)

const App = {
components: {
Component1,
Component2,
},
setup() {
return () => html`
<div class="component-root c-app">
App:
<p>ここは赤色</p>
<${Component1}></>
<${Component2}></>
</div>
`
},
}

export default App

[App.css]

scope (.c-app) to (.component-root) {

p {
color: red;
}
}

[Component1.js]
import { html, addCSS } from "./utils.js"
import css from "./Component1.css" assert { type: "css" }
addCSS(css)

const Component1 = {
setup(props, { slots }) {
return () => html`
<div class="component-root c-component1">
Component1:
<p class="blue">ここは青色</p>
<p>ここは黒色 (App の p タグのスタイルの影響は受けない)</p>
</div>
`
},
}
export default Component1

[Component1.css]

scope (.c-component1) to (.component-root) {

.blue {
color: blue;
}
}

[Component2.js]
import { html, addCSS } from "./utils.js"

const Component2 = {
setup(props, { slots }) {
return () => html`
<div class="component-root c-component2">
Component2:
<p class="blue">
このコンポーネントにスタイルは指定していないので
blue クラスだけど黒色
</p>
</div>
`
},
}
export default Component2

実際に動くページです
https://nexpr.gitlab.io/public-pages/vue3-no-sfc-scoped-style/

JSX はあまり使われてない?

調べていて思ったのが 思ったより情報が出てこないです
公式サイトでもシンプルな説明だけです
Vue のテンプレート構文は初心者やライトユーザーが主で 上級者になるほど自由度が高い JSX にいくものなのかなと思ってましたが そうでもないようですね

簡単なところで マルチルートを JSX で実現するだけでも困りました
結果としては React と同じように <></> や Fragment コンポーネントを使えばいいです

しかしドキュメント中には記載がないのですよね
h 関数を使う方法ではトップレベルを配列にすればいいとありますが JSX の説明では記載されていません
Vue3 と Fragment でググって出てくるページはマイグレーションガイドで template タグを使った方法しか書いていません
JSX を使うなら React と同じで <></> や Fragment だろうと察することはできるのですが API リファレンスで Fragment を探しても出てこないです
それなら <></> かなと思って Stackblitz とか Codepen で試してみると なぜか React が見つからないとエラーが出たりしました

最終的に Vite を使った環境では <></> で問題なく動いて 変換されたコードを見ると Fragment というコンポーネントが使われていました
Fragment は API リファレンスには記載がないだけで vue がエクスポートするものです
これを直接使っても動作しました

隠し機能があったり こういう部分がドキュメントになかったり JSX を使うのは思ったより大変なのかもしれないです