Closed1

Preact.js + vite + express, bookmark(ブックマーク) 作成

knaka Tech-Blogknaka Tech-Blog

概要

Preact.js + express SSRで、ブックマーク管理的なメモです

  • ほぼ、CSRになります。
  • テストは、vercelにデプロイする形です。
  • データは、 D1に保存

[ 公開: 2024/03/08 ]


環境

  • Preact.js
  • vite: 5
  • express
  • vercel
  • d1
  • workers

関連

  • 前のexpress 構成関連です。

https://zenn.dev/knaka0209/books/b1bd883fb8dd95


作成したコード

https://github.com/kuc-arc-f/express_39preact/tree/main/sample/bookmark


  • .env に、API接続先を設定します。
EXTERNAL_API_URL="http://localhost"

  • client/BookMark/app.tsx
app.tsx
export function App() {
  const [count, setCount] = useState(0);  
  const [updatetime, setUpdatetime] = useState("");
  //
  useEffect(() => {
    (async() => {
//      console.log('Count updated:', count);
      getList();
    })()
  }, []);
  //
  const addItem = async function(){
    try{
      await CrudIndex.addItem(); 
      location.reload();
    } catch (e) {
      console.error(e);
    } 
  }
  /**
   *
   * @param
   *
   * @return
   */
  const getList = async function() {
    try{
console.log("#getList");
      const item  = {
        "userId": 0,
      }      
      const json = await HttpCommon.serverPost(item, "/api/bookmark/get_list");
      pageItems = json;
      console.log(json);
      setUpdatetime(new Date().toString());
    } catch (e) {
      console.error(e);
    } 
  }
  //
  return (
    <div class="container mx-auto my-2 px-8 bg-white">
      <div>
        <Head />
      </div>
      <h1 class="text-4xl font-bold">BookMark </h1>
      <hr class="my-2" />
      <label class="text-2xl block text-gray-700 font-bold mb-2">Title</label>
      <input type="text" id="title" 
      class="border border-gray-400 rounded-md px-3 py-2 w-full focus:outline-none focus:border-blue-500"
      />
      <div class="mb-2">
        <label class="text-2xl block text-gray-700 font-bold mb-2">URL</label>
        <input type="text" id="url" 
        class="border border-gray-400 rounded-md px-3 py-2 w-full focus:outline-none focus:border-blue-500"
        placeholder="ex: https://url-input-123456789.com"
        />
      </div>
      <div class="card">
        <button class="btn-purple my-1" onClick={() => addItem()}>Save
        </button>
      </div>
      <hr />
      {pageItems.map((item: any) => {
        return (
        <div key={item.id}>
          <a href={`${item.url}`} target="_blank">
            <h3 class="text-3xl font-bold">{item.title}</h3>
          </a>
          <span>id: {item.id}</span>
          <a href={`/bookmark/show?id=${item.id}`}>
            <button class="btn-outline-purple ms-2">Show</button>
          </a>
          <hr />
        </div>
        );
      })}
    </div>
  )
}

  • show
  • client/BookMarkShow/app.tsx
app.tsx
export function App() {
  const [count, setCount] = useState(0);  
  const [updatetime, setUpdatetime] = useState("");
  //
  useEffect(() => {
    (async() => {
//      console.log('Count updated:', count);
      const searchParams = new URLSearchParams(window.location.search);
      const id = searchParams.get('id') || "";
      itemId = Number(id);
console.log(itemId);
      getItem(itemId);
    })()
  }, []);
  /**
   *
   * @param
   *
   * @return
   */
  const getItem = async function(id: number) {
    try{
        const item = await CrudShow.get(id);
        pageItem = item;
console.log(pageItem);
        setUpdatetime(new Date().toString());
    } catch (e) {
        console.error(e);
    } 
  }
  /**
   *
   * @param
   *
   * @return
   */
  const deleteItem = async function() {
    try{
        const result = await CrudShow.delete(itemId);
        if(result) {
          window.location.href = '/bookmark';
        }
    } catch (e) {
        console.error(e);
    } 
  } 
  //
  return (
    <div class="container mx-auto my-2 px-8 bg-white">
      <div>
        <Head />
      </div>
      <h1 class="text-4xl font-bold mt-2">BookMarkShow</h1>
      <hr class="my-2" />
      <h1 class="text-4xl font-bold">{pageItem.title}</h1>
      <p>ID: {pageItem.id}</p>
      <hr class="my-1" />
      <pre>{pageItem.url}</pre>
      <hr class="my-1" />
      <button onClick={()=>deleteItem()} class="btn-red"
      >delete</button>
      <hr class="my-1" />
    </div>
  )
}

  • package.json
{
  "type": "module",
  "scripts": {
    "dev": "NODE_ENV=develop nodemon",
    "dev:test": "NODE_ENV=develop npx nodemon ./dist/index.js",
    "build": "npm run clean && node build.js && npx vite build --mode client && npm run build:css",
    "build:css": "npx tailwindcss -i ./src/main.css -o ./public/static/main.css",
    "build:test": "vite build && vite build --mode client",
    "clean": "rimraf dist && rimraf public/static",
    "watch": "npx vite build --mode client --watch",
    "watch:css": "npx tailwindcss -i ./src/main.css -o ./public/static/main.css --watch",
    "test": "ts-node src/index.ts"
  },
  "dependencies": {
    "axios": "^1.6.5",
    "cookie-parser": "^1.4.6",
    "dotenv": "^16.4.4",
    "esm": "^3.2.25",
    "express": "^4.18.2",
    "express-basic-auth": "^1.2.1",
    "express-session": "^1.17.2",
    "log4js": "^6.4.4",
    "preact": "^10.19.5",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@preact/preset-vite": "^2.8.1",
    "@types/express": "^4.17.17",
    "@types/node": "^18.14.6",
    "@types/react": "^18.2.23",
    "@types/react-dom": "^18.2.8",
    "autoprefixer": "^10.4.17",
    "nodemon": "^3.0.3",
    "postcss": "^8.4.35",
    "rimraf": "^3.0.2",
    "tailwindcss": "^3.4.1",
    "typescript": "^5.3.3",
    "vite": "^5.1.4"
  }
}


このスクラップは19日前にクローズされました