bun1.3+svelte5 编译打包初探

版本信息

bun-1.3.0、svelte-5.41.1、macos-15.6.1。

% sw_vers
ProductName:            macOS
ProductVersion:         15.6.1
BuildVersion:           24G90
% bun --version
1.3.0

概述

利用 bun 的插件机制,自行编写 sveltePlugin 插件,在打包期使用 svelte/compiler 编译 .svelte 文件为 js 模块(实测在运行期无法做到)。

代码

package.json
build.ts
svelte_plugin.ts
server.ts
src/index.html
src/index.ts
src/App.svelte

package.json:

{
  "name": "bun_svelte",
  "type": "module",
  "version": "0.0.1",
  "scripts": {
    "build": "bun build.ts",
  },
  "devDependencies": {
    "@types/bun": "^1.3.0",
    "svelte": "^5.41.1"
  },
  "peerDependencies": {
    "typescript": "^5.9.3"
  }
}

build.ts:

import sveltePlugin from "./svelte_plugin.ts";

// 编译 server.ts 并打包成单一可执行文件
const result = await Bun.build({
  entrypoints: ["server.ts"],
  outdir: './dist',
  minify: false,
  format: "esm",
  target: "bun",
  plugins: [sveltePlugin()], // 必须的,否则无法编译 svelte 文件
  sourcemap: true,
  compile: true
});
if (!result.success) throw new Error('build "./dist/server" failed');
else console.log(`Create a standalone executable file "./dist/server". Can run with "./dist/server"`)

svelte_plugin.ts:

import type { BunPlugin } from "bun";
import { compile } from "svelte/compiler";

export default function sveltePlugin(): BunPlugin {
  return {
    name: "bun-plugin-svelte",
    async setup(build) {
      build.onLoad({ filter: /\.svelte$/ }, async ({ path }) => {
        const source = await Bun.file(path).text();
        const { js } = compile(source, { filename: path, generate: "client" });
        console.log(`sveltePlugin: compiled svelte source code to js code`)
        return { contents: js.code, loader: "js" };
      });
    },
  };
}

server.ts:

import { serve } from "bun";
import index from "./src/index.html";
const port = 3000
serve({
  development: true, // 可以避免 minify 方便观看源代码
  port,
  // `routes` requires Bun v1.2.3+
  routes: {
    // SPA
    "/": index,

    // Static route - content is buffered in memory at startup
    "/favicon.ico": new Response(await Bun.file("./src/favicon.ico").bytes()),
    "/api/status": new Response("OK"),

    // File route - content is read from filesystem on each request
    // "/download.zip": new Response(Bun.file("./download.zip")),

    // Dynamic routes
    "/users/:id": req => new Response(`Hello User ${req.params.id}!`),
  },

  // (optional) fallback for unmatched routes:
  // Required if Bun's version < 1.2.3
  fetch(req) {
    return new Response("Not Found", { status: 404 });
  },
});

console.log(`Listening http://localhost:${port}`);

src/index.html:

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="./styles.css" />
    <script src="./index.ts" type="module"></script>
  </head>
  <body></body>
</html>

src/index.ts:

import { mount } from 'svelte';
import App from "./App.svelte";

// mount App to body
const app = mount(App, {
  target: document.body,
  props: { name: "RJ" }
});

export default app;

src/App.svelte:

<script>
  let { name } = $props();
  let count = $state(0);
  function onclick() {
    count += 1;
  }
</script>

<main>
  <h1>Hello {name}!</h1>
  <button {onclick}>Clicked {count} {count > 1 ? 'times' : 'time'}</button>
</main>

编译打包

% bun build
Create a standalone executable file "./dist/server". Can run with "./dist/server"
or
% bun ./build.ts

% ls -alh ./dist 
total 130760
drwxr-xr-x   3 rj  staff    96B Oct 22 11:18 .
drwxr-xr-x  20 rj  staff   640B Oct 22 11:18 ..
-rwxr-xr-x   1 rj  staff    64M Oct 22 11:18 server

打包成功后将生成单一可执行文件 ./dist/server,直接执行这个文件就可以启动服务:

% ./dist/server
Listening http://localhost:3000

运行效果

image.png

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容