htmx에서 Twitter 클론 응용 프로그램 구축

39012 단어 twitterhtmxhtml
본 논문에서, 우리는 htmx 트위터 복제 프레젠테이션 프로그램을 구축할 것이다.본 강좌를 깊이 있게 배우기 전에 htmx를 사용하여 이 프로그램을 구축하기로 결정한 배경에 대한 이야기를 들려드리겠습니다.
이 모든 것은 마테 이 패터슨(Matt E.Patterson)이 A List Apart에 발표한 이 블로그 글The Future of Web Software Is HTML-over-WebSockets에서 비롯되었다.본고에서 Matt는 AJAX 요청이나 웹 플러그인을 통해 HTML을 온라인으로 보내는 장점과 그들이 제공하는 성능 장점을 토론하고Hotwire,StimulusReflex 등 도구와 라이브러리를 언급했다.
그는 인터넷을 통해 HTML을 보내는 장점을 강화할 수 있는 프로그램도 언급했다.이것은 나의 영감의 시작이다.하지만 Javascript 배경과 노드에서 왔습니다.js 개발자, 저는 정말 RAILS로 유사한 것을 구축할 동력이 없습니다.

그래서 나는 며칠 동안 각종 프레임워크와 언어에서 다른 유사한 해결 방안을 찾았다. 이것이 바로 내가 htmx를 찾았을 때이다.이것은 나의 주의를 끌었다. 완전히 내가 전방 개발자이기 때문이다. 자바스크립트가 아주 적거나 전혀 필요하지 않아도 HTML 속성 자체로 멋있고 상호작용적인 것을 구축할 수 있기 때문이다.

htmx란 무엇입니까?


htmx는 하이퍼텍스트의 간단함과 강력함으로 현대 사용자 인터페이스를 구축할 수 있습니다.AJAX, CSS 변환, WebSocket, 서버에서 보내는 이벤트에 HTML로 직접 접근할 수 있도록 합니다.
크기는 9KB(축소 및 압축)로 작고 의존성이 없으며 IE11과 호환됩니다.
그리고 나는 우리가 더욱 복잡하고 상호작용적인 것을 구축하고 htmx를 사용하여 웹 플러그인을 사용하도록 시도하고 싶다.이것은 그 실험의 결과로, 나는 그것에 관한 블로그를 써서 나의 경험을 공유하기로 결정했다.

프로젝트 작성


프로젝트 템플릿을 작성하기 시작합니다.우리는 서버를 위해 Express.js 응용 프로그램을 구축할 것이다.
우선 프로젝트 폴더와 파일을 만듭니다.터미널을 열고 다음 명령을 보내서 폴더 구조를 만듭니다.
mkdir htmx-twitter
cd htmx-twitter
mkdir views
touch views/index.pug index.js
프로젝트 의존항을 설치합니다.우리는express,pug,bodyparsernpm 패키지와nodemon을 개발하여 파일을 쉽게 보고 서버를 자동으로 다시 시작할 수 있도록 해야 한다.
npm i --save express pug body-parser
npm i --save-dev nodemon
pug는 우리express 프로그램의 템플릿 엔진이며, body-parser는 우리의 요청 본문에서 폼 제출 값을 가져오는 데 사용됩니다.
가방에 스크립트를 추가합니다.json에서 응용 프로그램을 시작하고 실행하는 개발 서버입니다.
...
scripts: {
  "start": "node index.js",
  "dev": "nodemon"
}
...

서버


앞에서 만든 index.js 파일에서 서버 사이드 코드를 구축하기 시작합니다.다음 코드 목록은 가장 간단한express 프로그램을 보여 줍니다.
const express = require('express');
const bodyParser = require('body-parser');
const pug = require('pug');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine','pug');

app.use(express.static(__dirname + '/assets'));

app.get('/', (req, res) => {
  res.render('index');
});

app.listen(PORT);
console.log('root app listening on port: 3000');
메인 보기 파일을 보십시오. index.pug 폴더 아래에서 /views 라고 부릅니다.

색인파그


doctype html
html(lang="en")
  head
    title Twitter clone in htmx
    link(href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css", rel="stylesheet", integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl", crossorigin="anonymous")

  body
    header.d-flex.flex-column.flex-md-row.align-items-center.p-3.px-md-4.mb-3.bg-body.border-bottom.shadow-sm
      p.h5.my-0.me-md-auto.fw-normal HTMX - Twitter
      nav.my-2.my-md-0.me-md-3
        a.p-2.text-dark(href='#') #{name}
    .container
      .row.justify-content-center
        .col-10
          p.text-center A Twitter clone in <a href="https://htmx.org">htmx</a> and Node
          div(hx-ws="connect:/tweet")
            form(hx-ws="send:submit")
              input.form-control(type="hidden", name="username", value=name, readonly)
              .mb-3.row
                textarea.form-control(rows="3", name="message", required="true")
              .d-grid.gap-2.col-3.mx-auto.mb-3
                  button.btn.btn-primary.text-center(type="submit") Tweet
          #timeline
    script(src="https://unpkg.com/[email protected]")
    script(src="https://unpkg.com/[email protected]")

웹 소켓


현재, 우리는 /tweet 이라는 웹소켓을 사용하여 브라우저의 플러그인으로 메시지를 서버에 보내는 새로운 루트를 만듭니다.그런 다음 서버에서 소켓 메시지를 처리하고 메시지 및 사용자 이름 속성에 따라 트윗을 생성합니다.
htmxexperimental support는 웹소켓과 서버가 이벤트를 보내는 성명적인 사용을 지원합니다.이 예에서는 HTML 연결 /tweet 채널에서 hx-ws 속성을 사용합니다.
<div hx-ws="connect:/tweet">
  <form hx-ws="send:submit">
...
  </form>
</div>
원본 성명에 연결이 되어 있으며, 성명서를 보내면 제출할 때 플러그인에 값을 제출할 수 있습니다.
우리express 백엔드에 대해 npm 패키지express-ws를 사용하여express 응용 프로그램의 웹소켓 단점을 감청할 수 있습니다.WebSocket 노드를 다른 종류의 루트와 같이 정의하고 일반적인 Express 중간부품을 적용할 수 있습니다.
패키지를 설치하고 색인에서 사용하십시오.js 파일은 다음과 같습니다.
npm install --save express-ws
색인에서 사용합니다.js 파일은 다음과 같습니다.
const expressWs = require('express-ws')(app);
그리고express app 대상에서 사용할 수 있는 \tweet 방법을 사용하여 urlapp.ws로 단점을 정의할 수 있습니다.
app.ws('/tweet', function(ws, req) {
  ws.on('message', function(msg) {
    const { message, username } = JSON.parse(msg);

    const _tweet = {
        id: v4(),
        message,
        username,
        retweets: 0,
        likes: 0,
      time: new Date().toString(),
      avatar : 'https://ui-avatars.com/api/?background=random&rounded=true&name=' + username
    };

    tweets.push(_tweet);

    const posts  = pug.compileFile('views/components/post.pug', { globals: ['global'] });

    // Format time 
     _tweet.time = dayjs().to(dayjs(_tweet.time));
    const markup = posts({ t: _tweet });

    tweetChannel.clients.forEach(function (client) {
      client.send(markup);
    });
  });
});
그리고 posts 템플릿에서 서버에서 생성된 추문 표시를 구성하고 생성된 표시를 모든 연결된 플러그인 클라이언트에게 응답으로 보내서 모든 클라이언트가 업데이트된 추문을 받을 수 있도록 합니다.이 작업은 다음과 같이 생성된 브로드캐스트 채널을 통해 수행됩니다.
const tweetChannel  = expressWs.getWss('/tweet');

취향 업데이트


이제 애플리케이션에서 Like 버튼을 클릭할 때마다 특정 트윗의 Like 계수를 업데이트해야 합니다.그래서 우리는 템플릿, 우리가 좋아하는 단추가 필요하다.

좋아하다파그


button.btn.btn-link(id='like-' + id,type="button", hx-post="/like/" + id) Like (#{likes})
이것은 POST 요청을 백엔드/like/<tweet-id>로 보내는 간단한 단추입니다. 서버에서 이 요청을 처리할 것입니다. 아래와 같습니다.
app.post('/like/:id', (req, res) => {
const { id } = req.params;
    const tweet = tweets.find(t => t.id === id);
    tweet.likes += 1;

    const likes  = pug.compileFile('views/components/likes.pug');
    const markup = likes({ id, likes: tweet.likes });
    tweetChannel.clients.forEach(function (client) {
      client.send(markup);
    });

  res.send(markup);
});
여기서 주의해야 할 중요한 것은 우리가 이전에 만든 tweetChannel 에서 연결된 모든 클라이언트에 대한 방송 업데이트 횟수를 웹 플러그인으로 실시간으로 모든 사용자의 횟수를 업데이트할 수 있도록 하는 것이다.

실시간 업데이트 게시물 전달


마찬가지로 전송에 대해 우리는 우리가 좋아하는 계수에서 사용하는 논리와 같은 논리가 필요하다.이것은 우리의 같은 전송 템플릿이다.

전달파그


button.btn.btn-link(id='retweet-' + id, type="button", hx-post="/retweet/" + id) Retweet (#{retweets})
이것은 우리가 전송 횟수를 업데이트하는 POST 요청의 종점입니다.
app.post('/retweet/:id', (req, res) => {
    const { id } = req.params;
    const tweet = tweets.find(t => t.id === id);
    tweet.retweets += 1;

    const retweets  = pug.compileFile('views/components/retweets.pug');
    const markup = retweets({ id, retweets: tweet.retweets });
    tweetChannel.clients.forEach(function (client) {
      client.send(markup);
    });
  res.send(markup);
});
이것은 우리의 트위터 게시물 템플릿입니다. like와retweet 단추 템플릿을 포함합니다.이것은 우리가 서버에서 새 트윗을 만든 후에 보낼 태그입니다. 여기서 주의해야 할 중요한 것은 htmx가 자동으로 DOM#timeline 요소의 맨 위에 추가됩니다. 왜냐하면 우리는 out-of-band 교환을 사용하기 때문입니다.
id 속성을 사용하여 응답 중의 내용을 DOM으로 직접 교환하려면 응답 html에서 hx-swap-oob 속성을 사용할 수 있습니다.
<div id="message" hx-swap-oob="true">Swap me directly!</div>
  Additional Content
htmx는 DOM으로 되돌아오는 HTML을 교환하기 위해 몇 가지 different ways 를 제공합니다.기본적으로 컨텐트는 대상 요소의 innerHTML을 대체합니다.hx-swap 속성을 사용하여 수정할 수 있습니다.

글을 올리다.파그


div(hx-swap-oob="afterbegin:#timeline")
  .card.mb-2(id='tweet-' + t.id)
    .card-body
      .d-flex
        img.me-4(src=t.avatar)
        div
          h5.card-title.text-muted
            | #{t.username}
            small : #{t.time}
          .card-text.lead.mb-2
            | #{t.message}
          include retweets
          include likes
이렇게htmx를 사용하여 트위터 클론 프레젠테이션 프로그램을 만들었습니다.터미널npm start에서 서버를 시작할 수 있습니다. 이 프로그램은 로컬 컴퓨터http://localhost:3000에서 사용할 수 있습니다.두 브라우저 창을 동시에 열고 두 창에서 트윗 작성을 시작합니다.RetweetLike 단추를 눌러 두 브라우저 창의 수가 실시간으로 증가하는 것을 볼 수도 있다.

이것은 우리가 응용 프로그램의 완전함과 최종 서버 코드를 보여 주는 것이다.코드 트랜잭션은 에서 실시간으로 보여 줍니다.

서버.js


const express = require('express');
const bodyParser = require('body-parser');
const pug = require('pug');
const { v4 } = require('uuid');
const dayjs = require('dayjs');
const relativeTime = require('dayjs/plugin/relativeTime');
const Chance = require('chance');

const app = express();
const expressWs = require('express-ws')(app);
const PORT = process.env.PORT || 3000;

const tweetChannel  = expressWs.getWss('/tweet');

const tweets = [];

const chance = new Chance();
let username = '';

dayjs.extend(relativeTime);

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine','pug');

app.use(express.static(__dirname + '/assets'));

app.get('/', (req, res) => {
  username = chance.name();
  res.render('index', { name: username });
});


app.ws('/tweet', function(ws, req) {
  ws.on('message', function(msg) {
    const { message, username } = JSON.parse(msg);

    const _tweet = {
        id: v4(),
        message,
        username,
        retweets: 0,
        likes: 0,
      time: new Date().toString(),
      avatar : 'https://ui-avatars.com/api/?background=random&rounded=true&name=' + username
    };

    tweets.push(_tweet);

    const posts  = pug.compileFile('views/components/post.pug', { globals: ['global'] });

    // Format time 
     _tweet.time = dayjs().to(dayjs(_tweet.time));
    const markup = posts({ t: _tweet });

    tweetChannel.clients.forEach(function (client) {
      client.send(markup);
    });
  });
});

app.post('/like/:id', (req, res) => {
const { id } = req.params;
    const tweet = tweets.find(t => t.id === id);
    tweet.likes += 1;

    const likes  = pug.compileFile('views/components/likes.pug');
    const markup = likes({ id, likes: tweet.likes });
    tweetChannel.clients.forEach(function (client) {
      client.send(markup);
    });

  res.send(markup);
});

app.post('/retweet/:id', (req, res) => {
    const { id } = req.params;
    const tweet = tweets.find(t => t.id === id);
    tweet.retweets += 1;

    const retweets  = pug.compileFile('views/components/retweets.pug');
    const markup = retweets({ id, retweets: tweet.retweets });
    tweetChannel.clients.forEach(function (client) {
      client.send(markup);
    });
  res.send(markup);
});

app.listen(PORT);
console.log('root app listening on port: 3000');
강좌에 관한 평론 부분에서 당신의 생각과 피드백을 알려주시고 코드에서 개선할 수 있는 모든 것을 알려 주십시오.나는 네가 이 일에 대한 견해를 듣고 매우 기뻤다.

도구책

  • htmx
  • The Future of Web Software Is HTML-over-WebSockets
  • StimulusReflex

  • 좋은 웹페이지 즐겨찾기