🦿

Deno2.0でSvelte5の開発環境を構築したメモ

2025/03/11に公開
1


Deno上でSvelteを開発してみたくなったので、試しに最小限で構築してみた備忘メモです。私はSSGしか使用していないためSSGが前提の構成になっています。もともとnpx sv createの中の選択肢にdenoが出てくるようになったので、対応したのかなぁと思っていました。最初にdeno run -A npm:sv createを試してみたところ普通に構築できました。それはそれで互換性があるということで非常に良いことなのですが、構成があまりにもNode.jsなので気持ち的に何か違いました。せっかくDenoを使用するので、せめて見た目だけでもDenoプロジェクトのようにしたいと思い色々変更した結果です。VSCodeのDevContainerを使用しています。

(2025-03-17追記)
いちいちプロジェクトを構成するのが面倒なので、自分で使う用にjsr:@scirexs/svパッケージを作成しました。最低限のセットアップではないですが、不要なものを削除すれば誰でも使用できる気がします。

結論

DevContainerの設定

./.devcontainer/Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/base:debian

ENV DENO_INSTALL=/deno
RUN mkdir -p /deno \
    && curl -fsSL https://deno.land/x/install/install.sh | sh \
    && chown -R vscode /deno
ENV PATH=${DENO_INSTALL}/bin:${PATH} \
    DENO_DIR=${DENO_INSTALL}/.cache
./.devcontainer/devcontainer.json
{
  "name": "deno_${localWorkspaceFolderBasename}",
  "build": {
    "dockerfile": "Dockerfile",
  },
  "runArgs": ["--name", "vsc-${localWorkspaceFolderBasename}"],
  "customizations": {
    "vscode": {
      "extensions": [
        "denoland.vscode-deno",
        "svelte.svelte-vscode",
      ],
      "settings": {
        "deno.enable": true,
        "deno.disablePaths": [
          "./.svelte-kit",
          "./.vite",
          "./build",
          "./node_modules"
        ],
      },
    },
  },
}

プロジェクト構成

結果のプロジェクト構成は以下(node_modulesと隠しディレクトリは省略)。スクリプトで生成しているファイルの大半はsvで生成されるファイルと同じ内容になっている。別途切り出して都度コピー等した方が管理しやすくなると思われる。

$ tree .
.
├── deno.json
├── src
│   ├── app.html
│   ├── lib
│   └── routes
│       ├── +layout.ts
│       └── +page.svelte
├── static
│   └── favicon.png
├── svelte.config.mjs
├── tsconfig.json
└── vite.config.ts
setup script
setup.sh
cat << EOF > ./deno.json
{
  "nodeModulesDir": "auto",
  "tasks": {
    "dev": "deno run -A npm:vite --open",
    "build": "deno run -A npm:vite build",
    "preview": "deno run -A npm:vite preview --open",
    "prepare": "deno run -A npm:@sveltejs/kit/svelte-kit sync || echo ''",
    "check": "deno run -A npm:@sveltejs/kit/svelte-kit sync && deno run -A npm:svelte-check --tsconfig ./tsconfig.json",
    "check:watch": "deno run -A npm:@sveltejs/kit/svelte-kit sync && deno run -A npm:svelte-check --tsconfig ./tsconfig.json --watch"
  }
}
EOF

deno add npm:vite
deno add npm:@deno/vite-plugin
deno add npm:@sveltejs/vite-plugin-svelte
deno add npm:@sveltejs/kit
deno add npm:svelte
deno add npm:svelte-check
deno add npm:@sveltejs/adapter-static

mkdir -p ./src/routes
mkdir -p ./src/lib
mkdir ./static

cat << EOF > ./vite.config.ts
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import deno from "@deno/vite-plugin";

export default defineConfig({
  plugins: [
    deno(),
    sveltekit()
  ]
});
EOF
cat << EOF > ./svelte.config.mjs
import adapter from "@sveltejs/adapter-static";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";

/** @type {import("@sveltejs/kit").Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      pages: "build",
      assets: "build",
      fallback: undefined,
      precompress: false,
      strict: true
    })
  }
};

export default config;
EOF
cat << EOF > ./tsconfig.json
{
  "extends": "./.svelte-kit/tsconfig.json",
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "strict": true
  }
}
EOF
echo "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAQMAAABJtOi3AAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAAtJREFUCNdjGAUkCQABLAABwXDTkAAAAABJRU5ErkJggg==" | base64 -d > ./static/favicon.png
cat << EOF > ./src/app.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%sveltekit.assets%/favicon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    %sveltekit.head%
  </head>
  <body data-sveltekit-preload-data="hover">
    <div style="display: contents">%sveltekit.body%</div>
  </body>
</html>
EOF
cat << EOF > ./src/routes/+page.svelte
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
EOF
echo "export const prerender = true;" > ./src/routes/+layout.ts
contents by file
./deno.json
{
  "nodeModulesDir": "auto",
  "tasks": {
    "dev": "deno run -A npm:vite --open",
    "build": "deno run -A npm:vite build",
    "preview": "deno run -A npm:vite preview --open",
    "prepare": "deno run -A npm:@sveltejs/kit/svelte-kit sync || echo ''",
    "check": "deno run -A npm:@sveltejs/kit/svelte-kit sync && deno run -A npm:svelte-check --tsconfig ./tsconfig.json",
    "check:watch": "deno run -A npm:@sveltejs/kit/svelte-kit sync && deno run -A npm:svelte-check --tsconfig ./tsconfig.json --watch"
  }
}
./vite.config.ts
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import deno from "@deno/vite-plugin";

export default defineConfig({
  plugins: [
    deno(),
    sveltekit()
  ]
});
./svelte.config.mjs
import adapter from "@sveltejs/adapter-static";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";

/** @type {import("@sveltejs/kit").Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      pages: "build",
      assets: "build",
      fallback: undefined,
      precompress: false,
      strict: true
    })
  }
};

export default config;
./tsconfig.json
{
  "extends": "./.svelte-kit/tsconfig.json",
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "strict": true
  }
}
./src/app.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%sveltekit.assets%/favicon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    %sveltekit.head%
  </head>
  <body data-sveltekit-preload-data="hover">
    <div style="display: contents">%sveltekit.body%</div>
  </body>
</html>
./src/routes/+page.svelte
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
./src/routes/+layout.ts
export const prerender = true;

使い方

コマンド実行

npm run xxxではなくdeno task xxxを実行する。

  • npm run dev -> deno task dev

バージョンアップ

# confirm latest version
deno outdated
# update based on version range
deno outdated --update
# force update to latest
deno outdated --update --latest

キャッシュ削除

deno clean

その他

  • svを使用しない理由
    • package.json等のDenoでは不要なファイルが生成されるため
    • 色々試しているうちに直接設定ファイルを作成した方が早い&クリーンになると判断した
  • tsconfig.jsonが残っている理由
    • deno.jsonに統合したかったが、extendsに対応していないため断念した
    • deno.jsonで設定できないオプションは削除
  • svelte-kit syncnpm:@sveltejs/kit/svelte-kit syncの理由
    • svelte-kitパッケージは内容が何もなかったため
    • 切り離す予定があるのか、統合されたのかは不明
      • そのため今後変更する必要があるかもしれない
  • svelte.config.jsmjsにする理由
    • jsのままでpackage.jsonがない場合、svelte-language-serverがうまく動作しない
    • package.json{ "type": "module" }を指定すれば解決する
      • が、そのためだけのpackage.jsonは置きたくない
    • これと同等の指定となるようにsvelte.config.jssvelte.config.mjsに変更する
      • 拡張子をmjsとした場合、svelte-kit syncが動作しない
        • svelte-kit sync内で対象ファイル名がsvelte.config.js固定のため
        • svelte-kit syncを実行する場合は実行前に以下のような処置が必要
          • svelte-kit sync内の固定値を2か所変更する
          • 一時的にmjsjsに置換する
          • jsのハードリンクを作成する 等

雑記

起動中のviteを終わるためにq + Enterしてもプロンプトが戻ってこない場合があるのは何なんでしょうか。
あとdeno.jsonの中で"lock": falseを記載していないとパッケージをバージョンアップするとTypeErrorが出たりするので、この方法だと完全互換とまではいかないようです。全パッケージをremoveしてnode_modulesディレクトリを完全に削除した後に再度addすれば直るようです。面倒なのでjsr:@scirexs/reinstallを作成しました。
それでも@tailwindcss/viteはViteが競合してエラーが出る場合があるようなので、./node_modules/.deno/@tailwindcss+vite@x.y.z/node_modules/viteのsymlinkを修正する必要があるようです。

参考文献

1

Discussion