Skip to content

Pour commencer, importez des fichiers HTML et passez-les à l'option routes dans Bun.serve().

ts
import { serve } from "bun";
import dashboard from "./dashboard.html";
import homepage from "./index.html";

const server = serve({
  routes: {
    // ** Importations HTML **
    // Bundle et route index.html vers "/". Cela utilise HTMLRewriter pour scanner
    // le HTML pour les balises `<script>` et `<link>`, exécute le bundler
    // JavaScript & CSS de Bun, transpile tout TypeScript, JSX et TSX,
    // abaisse le CSS avec l'analyseur CSS de Bun et sert le résultat.
    "/": homepage,
    // Bundle et route dashboard.html vers "/dashboard"
    "/dashboard": dashboard,

    // ** Points de terminaison API ** (Bun v1.2.3+ requis)
    "/api/users": {
      async GET(req) {
        const users = await sql`SELECT * FROM users`;
        return Response.json(users);
      },
      async POST(req) {
        const { name, email } = await req.json();
        const [user] = await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
        return Response.json(user);
      },
    },
    "/api/users/:id": async req => {
      const { id } = req.params;
      const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
      return Response.json(user);
    },
  },

  // Activer le mode développement pour :
  // - Messages d'erreur détaillés
  // - Rechargement à chaud (Bun v1.2.3+ requis)
  development: true,
});

console.log(`Écoute sur ${server.url}`);
bash
bun run app.ts

Routes HTML

Importations HTML comme routes

Le web commence avec HTML, et le serveur de développement fullstack de Bun aussi.

Pour spécifier les points d'entrée de votre frontend, importez des fichiers HTML dans vos fichiers JavaScript/TypeScript/TSX/JSX.

ts
import dashboard from "./dashboard.html";
import homepage from "./index.html";

Ces fichiers HTML sont utilisés comme routes dans le serveur de développement de Bun que vous pouvez passer à Bun.serve().

ts
Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  },

  fetch(req) {
    // ... requêtes api
  },
});

Lorsque vous faites une requête à /dashboard ou /, Bun bundle automatiquement les balises <script> et <link> dans les fichiers HTML, les expose comme routes statiques et sert le résultat.

Exemple de traitement HTML

Un fichier index.html comme ceci :

html
<!DOCTYPE html>
<html>
  <head>
    <title>Accueil</title>
    <link rel="stylesheet" href="./reset.css" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./sentry-and-preloads.ts"></script>
    <script type="module" src="./my-app.tsx"></script>
  </body>
</html>

Devient quelque chose comme ceci :

html
<!DOCTYPE html>
<html>
  <head>
    <title>Accueil</title>
    <link rel="stylesheet" href="/index-[hash].css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/index-[hash].js"></script>
  </body>
</html>

Intégration React

Pour utiliser React dans votre code côté client, importez react-dom/client et effectuez le rendu de votre application.

ts
import dashboard from "../public/dashboard.html";
import { serve } from "bun";

serve({
  routes: {
    "/": dashboard,
  },
  async fetch(req) {
    // ...requêtes api
    return new Response("bonjour le monde");
  },
});
tsx
import { createRoot } from 'react-dom/client';
import App from './app';

const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<App />);
html
<!DOCTYPE html>
<html>
  <head>
    <title>Tableau de bord</title>
    <link rel="stylesheet" href="../src/styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="../src/frontend.tsx"></script>
  </body>
</html>
tsx
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Tableau de bord</h1>
      <button onClick={() => setCount(count + 1)}>Compteur : {count}</button>
    </div>
  );
}

Mode développement

Lors de la construction en local, activez le mode développement en définissant development: true dans Bun.serve().

ts
import homepage from "./index.html";
import dashboard from "./dashboard.html";

Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  },

  development: true,

  fetch(req) {
    // ... requêtes api
  },
});

Fonctionnalités du mode développement

Lorsque development est true, Bun :

  • Inclut l'en-tête SourceMap dans la réponse pour que les devtools puissent afficher le code source original
  • Désactive la minification
  • Re-bundle les assets à chaque requête vers un fichier .html
  • Active le rechargement de module à chaud (sauf si hmr: false est défini)
  • Renvoie les logs console du navigateur vers le terminal

Configuration avancée du développement

Bun.serve() prend en charge le renvoi des logs console du navigateur vers le terminal.

Pour activer cela, passez console: true dans l'objet development dans Bun.serve().

ts
import homepage from "./index.html";

Bun.serve({
  // development peut aussi être un objet.
  development: {
    // Activer le rechargement de module à chaud
    hmr: true,

    // Renvoyer les logs console du navigateur vers le terminal
    console: true,
  },

  routes: {
    "/": homepage,
  },
});

Lorsque console: true est défini, Bun streamera les logs console du navigateur vers le terminal. Cela réutilise la connexion WebSocket existante de HMR pour envoyer les logs.

Développement vs Production

FonctionnalitéDéveloppementProduction
Source maps✅ Activées❌ Désactivées
Minification❌ Désactivée✅ Activée
Rechargement à chaud✅ Activé❌ Désactivé
Bundling d'assets🔄 À chaque requête💾 En cache
Logs console🖥️ Navigateur → Terminal❌ Désactivés
Détails d'erreur📝 Détaillés🔒 Minimaux

Mode production

Le rechargement à chaud et development: true vous aident à itérer rapidement, mais en production, votre serveur doit être aussi rapide que possible et avoir le moins de dépendances externes que possible.

Bundling Ahead of Time (Recommandé)

À partir de Bun v1.2.17, vous pouvez utiliser Bun.build ou bun build pour bundler votre application fullstack à l'avance.

bash
bun build --target=bun --production --outdir=dist ./src/index.ts

Lorsque le bundler de Bun voit une importation HTML depuis du code côté serveur, il bundle les fichiers JavaScript/TypeScript/TSX/JSX et CSS référencés dans un objet manifeste que Bun.serve() peut utiliser pour servir les assets.

ts
import { serve } from "bun";
import index from "./index.html";

serve({
  routes: { "/": index },
});

Bundling au moment de l'exécution

Lorsqu'ajouter une étape de construction est trop compliqué, vous pouvez définir development: false dans Bun.serve().

Cela :

  • Active la mise en cache en mémoire des assets bundle. Bun bundle les assets paresseusement lors de la première requête vers un fichier .html, et met en cache le résultat en mémoire jusqu'au redémarrage du serveur.
  • Active les en-têtes Cache-Control et ETag
  • Minifie les fichiers JavaScript/TypeScript/TSX/JSX
ts
import { serve } from "bun";
import homepage from "./index.html";

serve({
  routes: {
    "/": homepage,
  },

  // Mode production
  development: false,
});

Routes API

Gestionnaires de méthode HTTP

Définissez des points de terminaison API avec des gestionnaires de méthode HTTP :

ts
import { serve } from "bun";

serve({
  routes: {
    "/api/users": {
      async GET(req) {
        // Gérer les requêtes GET
        const users = await getUsers();
        return Response.json(users);
      },

      async POST(req) {
        // Gérer les requêtes POST
        const userData = await req.json();
        const user = await createUser(userData);
        return Response.json(user, { status: 201 });
      },

      async PUT(req) {
        // Gérer les requêtes PUT
        const userData = await req.json();
        const user = await updateUser(userData);
        return Response.json(user);
      },

      async DELETE(req) {
        // Gérer les requêtes DELETE
        await deleteUser(req.params.id);
        return new Response(null, { status: 204 });
      },
    },
  },
});

Routes dynamiques

Utilisez des paramètres d'URL dans vos routes :

ts
serve({
  routes: {
    // Paramètre unique
    "/api/users/:id": async req => {
      const { id } = req.params;
      const user = await getUserById(id);
      return Response.json(user);
    },

    // Paramètres multiples
    "/api/users/:userId/posts/:postId": async req => {
      const { userId, postId } = req.params;
      const post = await getPostByUser(userId, postId);
      return Response.json(post);
    },

    // Routes wildcard
    "/api/files/*": async req => {
      const filePath = req.params["*"];
      const file = await getFile(filePath);
      return new Response(file);
    },
  },
});

Gestion des requêtes

ts
serve({
  routes: {
    "/api/data": {
      async POST(req) {
        // Analyser le corps JSON
        const body = await req.json();

        // Accéder aux en-têtes
        const auth = req.headers.get("Authorization");

        // Accéder aux paramètres d'URL
        const { id } = req.params;

        // Accéder aux paramètres de requête
        const url = new URL(req.url);
        const page = url.searchParams.get("page") || "1";

        // Retourner la réponse
        return Response.json({
          message: "Données traitées",
          page: parseInt(page),
          authenticated: !!auth,
        });
      },
    },
  },
});

Plugins

Les plugins du bundler de Bun sont également pris en charge lors du bundling de routes statiques.

Pour configurer des plugins pour Bun.serve, ajoutez un tableau plugins dans la section [serve.static] de votre bunfig.toml.

Plugin TailwindCSS

Vous pouvez utiliser TailwindCSS en installant et en ajoutant le package tailwindcss et le plugin bun-plugin-tailwind.

bash
bun add tailwindcss bun-plugin-tailwind
toml
[serve.static]
plugins = ["bun-plugin-tailwind"]

Cela vous permettra d'utiliser les classes utilitaires TailwindCSS dans vos fichiers HTML et CSS. Tout ce que vous avez à faire est d'importer tailwindcss quelque part :

html
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="tailwindcss" />
  </head>
  <!-- le reste de votre HTML... -->
</html>

Alternativement, vous pouvez importer TailwindCSS dans votre fichier CSS :

css
@import "tailwindcss";

.custom-class {
  @apply bg-red-500 text-white;
}
html
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <!-- le reste de votre HTML... -->
</html>

Plugins personnalisés

Tout fichier ou module JS qui exporte un objet de plugin de bundler valide (essentiellement un objet avec un champ name et setup) peut être placé dans le tableau plugins :

toml
[serve.static]
plugins = ["./my-plugin-implementation.ts"]
ts
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "my-custom-plugin",
  setup(build) {
    // Implémentation du plugin
    build.onLoad({ filter: /\.custom$/ }, async args => {
      const text = await Bun.file(args.path).text();
      return {
        contents: `export default ${JSON.stringify(text)};`,
        loader: "js",
      };
    });
  },
};

export default myPlugin;

Bun résoudra et chargera paresseusement chaque plugin et les utilisera pour bundler vos routes.

NOTE

Ceci est actuellement dans `bunfig.toml` pour rendre possible de savoir statiquement quels plugins sont utilisés lorsque nous intégrerons éventuellement cela avec la CLI `bun build`. Ces plugins fonctionnent dans l'API JS de `Bun.build()`, mais ne sont pas encore pris en charge dans la CLI.

Variables d'environnement inline

Bun peut remplacer les références process.env.* dans votre JavaScript et TypeScript frontend par leurs valeurs réelles au moment de la construction. Configurez l'option env dans votre bunfig.toml :

toml
[serve.static]
env = "PUBLIC_*"  # uniquement les variables d'environnement commençant par PUBLIC_ (recommandé)
# env = "inline"  # inline toutes les variables d'environnement
# env = "disable" # désactiver le remplacement des variables d'environnement (par défaut)

Note

Cela fonctionne uniquement avec les références littérales process.env.FOO, pas import.meta.env ou l'accès indirect comme const env = process.env; env.FOO.

Si une variable d'environnement n'est pas définie, vous pouvez voir des erreurs d'exécution comme ReferenceError: process is not defined dans le navigateur.

Consultez la documentation HTML & sites statiques pour plus de détails sur la configuration au moment de la construction et des exemples.

Comment cela fonctionne

Bun utilise HTMLRewriter pour scanner les balises <script> et <link> dans les fichiers HTML, les utilise comme points d'entrée pour le bundler de Bun, génère un bundle optimisé pour les fichiers JavaScript/TypeScript/TSX/JSX et CSS, et sert le résultat.

Pipeline de traitement

1. Traitement des scripts

  • Transpile TypeScript, JSX et TSX dans les balises <script>
  • Bundle les dépendances importées
  • Génère des sourcemaps pour le débogage
  • Minifie lorsque development n'est pas true dans Bun.serve()
html
<script type="module" src="./counter.tsx"></script>

2. Traitement des

  • Traite les importations CSS et les balises <link>
  • Concatène les fichiers CSS
  • Réécrit les url et les chemins d'assets pour inclure des hachages adressables par contenu dans les URL
html
<link rel="stylesheet" href="./styles.css" />

3. Traitement des & assets

  • Les liens vers les assets sont réécrits pour inclure des hachages adressables par contenu dans les URL
  • Les petits assets dans les fichiers CSS sont inline dans des URL data:, réduisant le nombre total de requêtes HTTP envoyées

4. Réécriture HTML

  • Combine toutes les balises <script> en une seule balise <script> avec un hachage adressable par contenu dans l'URL
  • Combine toutes les balises <link> en une seule balise <link> avec un hachage adressable par contenu dans l'URL
  • Produit un nouveau fichier HTML

5. Service

  • Tous les fichiers de sortie du bundler sont exposés comme routes statiques, utilisant le même mécanisme en interne que lorsque vous passez un objet Response à static dans Bun.serve().
  • Cela fonctionne de manière similaire à la façon dont Bun.build traite les fichiers HTML.

Exemple complet

Voici un exemple complet d'application fullstack :

ts
import { serve } from "bun";
import { Database } from "bun:sqlite";
import homepage from "./public/index.html";
import dashboard from "./public/dashboard.html";

// Initialiser la base de données
const db = new Database("app.db");
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

const server = serve({
  routes: {
    // Routes frontend
    "/": homepage,
    "/dashboard": dashboard,

    // Routes API
    "/api/users": {
      async GET() {
        const users = db.query("SELECT * FROM users").all();
        return Response.json(users);
      },

      async POST(req) {
        const { name, email } = await req.json();

        try {
          const result = db.query("INSERT INTO users (name, email) VALUES (?, ?) RETURNING *").get(name, email);

          return Response.json(result, { status: 201 });
        } catch (error) {
          return Response.json({ error: "Email déjà existant" }, { status: 400 });
        }
      },
    },

    "/api/users/:id": {
      async GET(req) {
        const { id } = req.params;
        const user = db.query("SELECT * FROM users WHERE id = ?").get(id);

        if (!user) {
          return Response.json({ error: "Utilisateur non trouvé" }, { status: 404 });
        }

        return Response.json(user);
      },

      async DELETE(req) {
        const { id } = req.params;
        const result = db.query("DELETE FROM users WHERE id = ?").run(id);

        if (result.changes === 0) {
          return Response.json({ error: "Utilisateur non trouvé" }, { status: 404 });
        }

        return new Response(null, { status: 204 });
      },
    },

    // Point de terminaison de vérification de santé
    "/api/health": {
      GET() {
        return Response.json({
          status: "ok",
          timestamp: new Date().toISOString(),
        });
      },
    },
  },

  // Activer le mode développement
  development: {
    hmr: true,
    console: true,
  },

  // Fallback pour les routes non correspondantes
  fetch(req) {
    return new Response("Non trouvé", { status: 404 });
  },
});

console.log(`🚀 Serveur en cours d'exécution sur ${server.url}`);
html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Application Fullstack Bun</title>
    <link rel="stylesheet" href="../src/styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="../src/main.tsx"></script>
  </body>
</html>
tsx
import { createRoot } from "react-dom/client";
import { App } from "./App";

const container = document.getElementById("root")!;
const root = createRoot(container);
root.render(<App />);
tsx
import { useState, useEffect } from "react";

interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export function App() {
  const [users, setUsers] = useState<User[]>([]);
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [loading, setLoading] = useState(false);

  const fetchUsers = async () => {
    const response = await fetch("/api/users");
    const data = await response.json();
    setUsers(data);
  };

  const createUser = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      const response = await fetch("/api/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name, email }),
      });

      if (response.ok) {
        setName("");
        setEmail("");
        await fetchUsers();
      } else {
        const error = await response.json();
        alert(error.error);
      }
    } catch (error) {
      alert("Échec de la création de l'utilisateur");
    } finally {
      setLoading(false);
    }
  };

  const deleteUser = async (id: number) => {
    if (!confirm("Êtes-vous sûr ?")) return;

    try {
      const response = await fetch(`/api/users/${id}`, {
        method: "DELETE",
      });

      if (response.ok) {
        await fetchUsers();
      }
    } catch (error) {
      alert("Échec de la suppression de l'utilisateur");
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  return (
    <div className="container">
      <h1>Gestion des utilisateurs</h1>

      <form onSubmit={createUser} className="form">
        <input type="text" placeholder="Nom" value={name} onChange={e => setName(e.target.value)} required />
        <input type="email" placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} required />
        <button type="submit" disabled={loading}>
          {loading ? "Création..." : "Créer un utilisateur"}
        </button>
      </form>

      <div className="users">
        <h2>Utilisateurs ({users.length})</h2>
        {users.map(user => (
          <div key={user.id} className="user-card">
            <div>
              <strong>{user.name}</strong>
              <br />
              <span>{user.email}</span>
            </div>
            <button onClick={() => deleteUser(user.id)} className="delete-btn">
              Supprimer
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}
css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  background: #f5f5f5;
  color: #333;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}

h1 {
  color: #2563eb;
  margin-bottom: 2rem;
}

.form {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 2rem;
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.form input {
  flex: 1;
  min-width: 200px;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.form button {
  padding: 0.75rem 1.5rem;
  background: #2563eb;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.form button:hover {
  background: #1d4ed8;
}

.form button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.users {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.user-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-bottom: 1px solid #eee;
}

.user-card:last-child {
  border-bottom: none;
}

.delete-btn {
  padding: 0.5rem 1rem;
  background: #dc2626;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.delete-btn:hover {
  background: #b91c1c;
}

Bonnes pratiques

Structure du projet

my-app/
├── src/
│   ├── components/
│   │   ├── Header.tsx
│   │   └── UserList.tsx
│   ├── styles/
│   │   ├── globals.css
│   │   └── components.css
│   ├── utils/
│   │   └── api.ts
│   ├── App.tsx
│   └── main.tsx
├── public/
│   ├── index.html
│   ├── dashboard.html
│   └── favicon.ico
├── server/
│   ├── routes/
│   │   ├── users.ts
│   │   └── auth.ts
│   ├── db/
│   │   └── schema.sql
│   └── index.ts
├── bunfig.toml
└── package.json

Configuration basée sur l'environnement

ts
export const config = {
  development: process.env.NODE_ENV !== "production",
  port: process.env.PORT || 3000,
  database: {
    url: process.env.DATABASE_URL || "./dev.db",
  },
  cors: {
    origin: process.env.CORS_ORIGIN || "*",
  },
};

Gestion des erreurs

ts
export function errorHandler(error: Error, req: Request) {
  console.error("Erreur serveur :", error);

  if (process.env.NODE_ENV === "production") {
    return Response.json({ error: "Erreur interne du serveur" }, { status: 500 });
  }

  return Response.json(
    {
      error: error.message,
      stack: error.stack,
    },
    { status: 500 },
  );
}

Helpers de réponse API

ts
export function json(data: any, status = 200) {
  return Response.json(data, { status });
}

export function error(message: string, status = 400) {
  return Response.json({ error: message }, { status });
}

export function notFound(message = "Non trouvé") {
  return error(message, 404);
}

export function unauthorized(message = "Non autorisé") {
  return error(message, 401);
}

Sécurité de type

ts
export interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export interface CreateUserRequest {
  name: string;
  email: string;
}

export interface ApiResponse<T> {
  data?: T;
  error?: string;
}

Déploiement

Construction production

bash
# Construire pour la production
bun build --target=bun --production --outdir=dist ./server/index.ts

# Exécuter le serveur de production
NODE_ENV=production bun dist/index.js

Déploiement Docker

dockerfile
FROM oven/bun:1 as base
WORKDIR /usr/src/app

# Installer les dépendances
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

# Copier le code source
COPY . .

# Construire l'application
RUN bun build --target=bun --production --outdir=dist ./server/index.ts

# Étape de production
FROM oven/bun:1-slim
WORKDIR /usr/src/app
COPY --from=base /usr/src/app/dist ./
COPY --from=base /usr/src/app/public ./public

EXPOSE 3000
CMD ["bun", "index.js"]

Variables d'environnement

ini
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
CORS_ORIGIN=https://myapp.com

Migration depuis d'autres frameworks

Depuis Express + Webpack

ts
// Avant (Express + Webpack)
app.use(express.static("dist"));
app.get("/api/users", (req, res) => {
  res.json(users);
});

// Après (Bun fullstack)
serve({
  routes: {
    "/": homepage, // Remplace express.static
    "/api/users": {
      GET() {
        return Response.json(users);
      },
    },
  },
});

Depuis les routes API Next.js

ts
// Avant (Next.js)
export default function handler(req, res) {
  if (req.method === 'GET') {
    res.json(users);
  }
}

// Après (Bun)
"/api/users": {
  GET() { return Response.json(users); }
}

Limitations et plans futurs

Limitations actuelles

  • L'intégration de la CLI bun build n'est pas encore disponible pour les applications fullstack
  • La découverte automatique des routes API n'est pas implémentée
  • Le rendu côté serveur (SSR) n'est pas intégré

Fonctionnalités prévues

  • Intégration avec la CLI bun build
  • Routage basé sur les fichiers pour les points de terminaison API
  • Support SSR intégré
  • Écosystème de plugins amélioré

NOTE

Ceci est un travail en cours. Les fonctionnalités et les API peuvent changer au fur et à mesure que Bun continue d'évoluer.

Bun édité par www.bunjs.com.cn