또 시작이네! 이것들 중 또 하나, 그리고 약속해, 이번 사건 이후로 내 제정신인지 의심하게 될 거야.
막 장을 보고 있었어요. 그게 날 덮쳤을 때 동네 가게로 가는 길을 걸었어요. 계단식... 서버 시트!
오늘은 서버측 언어로 CSS를 사용할 것입니다. 맞아요. CSS를 사용하여 라우팅을 선언하거나, 수학을 하거나, 헉, 심지어 CSS를 사용하여 템플릿 작성을 수행합니다! 그리고 우리는 SASS나 LESS (pff, 우리는 냄새나는 루프가 필요하지 않아!) 같은 것을 사용하지 않지만, 플레인 ol` CSS를 사용합니다.
뭐라고요? 왜요?
SMBC는 양자컴퓨터에 관한 코믹의 일부이긴 하지만, 최근 이 표현을 꽤 잘 쓰고 있다.
허블 망원경으로 타이어를 바꾼다고 상상해 보세요. 잘 안 풀리죠, 그렇죠? 그래도 네가 해낸다면 얼마나 멋질까? 그게 내가 쫓는 거야 이봐, 어쩌면 내가 여기서 새로운 트렌드를 시작하고 있는 것 같아, 누가 알겠어! 유행이 내 바보 같은 생각들을 비웃고 다시는 나를 진지하게 받아들이지 않을지라도 말이야.
여러분은 "사람들은 습기에 너무 사로잡혀서 물어보는 것을 잊었다"는 속담을 알지도 모릅니다. 그러면 안 된다는 사실을 잘 알고 있지만, 문제는 그럴 수 있느냐는 것입니다.
이 도구는 제가 프로덕션에서 절대 사용하지 않을 도구일 것이며, 독자 여러분 역시 그렇게 해서는 안 됩니다. 부탁합니다. 자, 경고받았네
좋아, 캐스케이딩 가... Server Sheets입니다.
먼저, 이게 어떻게 작동하는지 정의해보자. 익스프레스 인터페이스를 생각하고 있었어요. 기본적으로 익스프레스에서 캐치 올 루트를 정의하고, CSS 파일을 로드하고, 스타일을 구문 분석 및 해석하며(이 부분은 재미있을 것 같다), 와이어를 통해 나타나는 DOM은 무엇이든 촬영한다.
그렇게 하려면 먼저 Express를 설치하겠습니다. 여기서 nvm을 사용하여 노드 버전을 전환하고 있습니다.
echo "14" > .nvmrc
nvm use
npm init # Hit enter a few times
npm i express
대단해! 이제 작은 앱을 만들고 패키지에 시작 스크립트를 추가하겠습니다.json:
{
"name": "css-server",
"version": "1.0.0",
"description": "A bad idea.",
"main": "index.js",
"scripts": {
"start": "node ./css-server.js"
},
"author": "Pascal Thormeier",
"license": "donttrythisathome",
"dependencies": {
"express": "^4.17.2"
}
}
익스프레스 앱에서는 주어진 경로가 CSS 파일에 해당하는지 여부를 파악하려는 캐치 올 경로를 정의한다. 파일이 존재하면 이 파일의 내용을 반환하고, 없으면 404가 던져집니다.
const express = require('express')
const bodyParser = require('body-parser')
const path = require('path')
const fs = require('fs')
const app = express()
// Allows to get POST bodies as JSON
app.use(bodyParser.urlencoded({ extended: true }))
// Catch-all route
app.use((req, res) => {
let cssFile = req.path
// So `index.css` works.
if (cssFile.endsWith('/')) {
cssFile += 'index'
}
const cssFilePath = path.resolve('./app' + cssFile + '.css')
try {
const css = fs.readFileSync(cssFilePath, 'utf8')
res.send(css)
} catch (e) {
// Any error of the file system will
// be caught and treated as "not found"
res.sendStatus(404)
}
})
app.listen(3000)
빠른 테스트를 통해 작은 Index.css 파일을 제외한 모든 파일이 404를 생성하고 CSS 파일이 표시됩니다.
CSS 평가 - 큰 소리로 생각
좋아, 재밌는 부분이야. 우리는 어떻게든 CSS 서버 측을 실행하고 앱 응답으로 출력되는 모든 것을 가져갈 방법을 알아내야 합니다.
렌더링을 할 때 가장 먼저 떠오르는 것은 CSS 콘텐츠 룰을 사용해서 콘텐츠를 잘 렌더링하는 것입니다. 그것은 CSS 변수와 카운터를 사용할 수 있기 때문에 기술적으로 수학도 할 수 있습니다. 딱 한 가지 문제가 있다: 브라우저는 카운터와 변수를 즉시 평가하므로 CSS만 평가할 수는 없습니다. 컨텐츠에 있는 모든 것을 가져다가 출력합니다. 그래서 "계산된 스타일"은 통하지 않는다.
기본적으로 개발 도구의 "CSS" 탭에 표시되는 정보를 확인할 수 있습니다.
이 CSS 조각을 상상해 보세요.
body {
--num1: 12;
--num2: 13;
counter-set: sum 15;
}
body::before {
content: '<h1>The sum is ' counter(sum) '</h1>';
}
다음과 같은 결과를 얻을 수 있습니다.
음. 그럼 브라우저를 이용해서 바로 하면 어떨까요? 브라우저가 이런 걸 어떻게 평가하냐면요, 그렇지? 문제는 우리가 문제를 여기로 옮긴다는 거야 CSS의 노드 구현체가 있습니다. 컴퓨터 스타일도 제공되고 우리가 사용할 브라우저도 같은 기능만 제공하겠죠? 컴퓨터가 화면에 있는 것을 "읽게" 할 수 있는 방법이 있다면 좋을 것이다.
브라우저가 CSS 파일을 로드하고 인라인 작업을 수행하지 않는 것이 이상적입니다. 그렇지 않으면 @import와 같은 작업을 실제로 사용할 수 없습니다. 그래서 우리는 CSS 파일을 로드하는 다른 컨트롤러가 필요합니다.
어쨌든, "미래의 나" 문제처럼 들리네요. 일단 인형사부터 소개해서 CSS를 실행하도록 합시다.
Puppetier 추가 중
직진:
npm i -s puppeteer
CSS를 로드하기 위해서는 약간의 HTML이 필요하다. 즉석에서 만들고, 로드된 CSS를 <링크>로 주입하고, base64는 전체 blob을 인코딩하고 브라우저가 다음과 같이 구문 분석하게 할 수 있다.
const escapeVarValue = value => {
if (!isNaN(value)){
return value
}
return `'${value}'`
}
const createDOM = (cssFilePath, method, args) => {
const varifiedArgs = Object.entries(args).map(([key, value]) => `--${key}: ${escapeVarValue(value)};\n`).join("\n")
const dataifiedArgs = Object.entries(args).map(([key, value]) => `data-${key}="${value}"`).join(' ')
return `
<!DOCTYPE html>
<html data-http-method="${method.toUpperCase()}">
<head>
<style>
:root {
${varifiedArgs}
}
</style>
<!-- Load the actual CSS -->
<link rel="stylesheet" href="${cssFilePath}">
</head>
<body ${dataifiedArgs}>
</body>
</html>
`
}
HTTP 메소드를 데이터 속성으로, arg를 CSS 변수 및 데이터 속성으로 이미 추가했습니다.
다음으로, 요청된 CSS 파일을 서비스하는 express 앱에 _internal route를 추가합니다.
app.get('/_internal/*', (req, res) => {
const appPath = req.path.replace('_internal', 'app')
if (appPath.includes('..') || !appPath.endsWith('.css')) {
res.send('Invalid file')
return
}
const internalFilePath = path.resolve('.' + appPath)
res.sendFile(internalFilePath)
})
그런 다음 /_internal/index.css에 대한 요청이 app/index.css를 로드하고 서비스를 제공합니다. Puppeteer는 이제 우리의 앱 코드를 로드하고 실행할 수 있습니다. 여기서 더 많은 검증을 할 수 있지만, 저는 단순함을 위해 여기서 기본을 유지했습니다.
이제 인형사를 게임에 참여시키려면:
const getContent = async (cssPath, method, args) => {
const dom = createDOM(cssPath, method, args)
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
})
const page = await browser.newPage()
const base64Html = Buffer.from(dom).toString('base64')
await page.goto('data:text\/html;base64;charset=UTF-8,' + base64Html, {
waitUntil: 'load',
timeout: 300000,
waitFor: 30000,
})
// Magic!
}
기본적인 작은 인덱스로 해보자.css:
body::after {
content: '<h1>Hello, World!</h1>';
}
자 그리고 보라: 됐다! Puppeteer는 CSS를 실행하고 결과를 표시합니다.
깔끔한 부작용: 헤드리스 변경: true to false로 CSS를 디버깅할 수 있습니다. 새로운 디버거는 확실히 좋은 것이다.
내용 추출
"미래의 나" 문제 기억나? 네
특히 변수나 카운터가 포함된 경우에는 계산된 스타일을 사용하여 요소의 내용을 가져올 수 없습니다. 또한 크롬에서는 렌더링된 텍스트를 선택하여 복사/붙여넣을 수 없습니다. 그럼, 어떻게 렌더링되고 평가된 텍스트를 얻을 수 있을까요?
PDF로 웹사이트를 다운받아 본 적이 있나요? 평가된 텍스트를 선택할 수 있게 됩니다. 인형사는 웹사이트에서 PDF를 만들 수 있나요? 네, 그럴 수 있어요. PDF를 분석해서 텍스트를 얻을 수 있을까요? 당연히 할 수 있지!
npm i -s pdf-parse
이 라이브러리를 통해 주어진 PDF를 구문 분석하고 텍스트를 추출할 수 있습니다. 우린 이미지나 레이아웃 같은 건 안 해선 안 돼 우리는 HTML을 파싱되지 않은 문자열로만 렌더링합니다. 다음을 복사/붙여넣을 수 있습니다.
const pdf = require('pdf-parse')
const getContent = async (cssPath, method, args) => {
const dom = createDOM(cssPath, method, args)
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
})
const page = await browser.newPage()
const base64Html = Buffer.from(dom).toString('base64')
await page.goto('data:text\/html;base64;charset=UTF-8,' + base64Html,{
waitUntil: 'load',
timeout: 300000,
waitFor: 30000,
})
// Get a PDF buffer
const pdfBuffer = await page.pdf()
// Parse the PDF
const renderedData = await pdf(pdfBuffer)
// Get the PDFs text
return Promise.resolve(renderedData.text)
}
마지막 단계로 텍스트를 가져오도록 모든 탐지 경로를 조정하겠습니다.
// Catch-all route
app.use((req, res) => {
let cssFile = req.path
// So `index.css` works.
if (cssFile.endsWith('/')) {
cssFile += 'index'
}
cssFile += '.css'
// File doesn't exist, so we break here
if (!fs.existsSync(path.resolve('./app/' + cssFile))) {
res.sendStatus(404)
return
}
const cssFilePath = 'http://localhost:3000/_internal' + cssFile
getContent(cssFilePath, req.method, {
...req.query, // GET parameters
...req.body, // POST body
}).then(content => {
res.send(content)
})
})
그것이 효과가 있어야 해요.
데모타임!
시험해 봅시다.
양식을 사용한 계산기
기본적인 "헬로 월드"는 충분히 간단합니다. CSS 계산기를 구축해보자.
body {
--title: '<h1>Calculator:</h1>';
--form: '<form method="POST" action="/"><div><label for="num1">Number 1</label><input id="num1" name="num1"></div><div><label for="num2">Number 2</label><input id="num2" name="num2"></div><button type="submit">Add two numbers</button></form>';
}
[data-http-method="POST"] body {
counter-set: sum var(--num1, 0) val1 var(--num1, 0) val2 var(--num2, 0);
}
[data-http-method="GET"] body::before {
content: var(--title) var(--form);
}
[data-http-method="POST"] body::before {
--form: '<form method="POST" action="/"><div><label for="num1">Number 1</label><input id="num1" name="num1" value="' counter(val1) '"></div><div><label for="num2">Number 2</label><input id="num2" name="num2" value="' counter(val2) '"></div><button type="submit">Add two numbers</button></form>';
counter-increment: sum var(--num2, 0);
content: var(--title) var(--form) '<div>Result: ' counter(sum) '</div>';
}
이 계산기는 다음과 같은 여러 기능을 사용합니다.
- GET 대 POST 대응
- 수학하기
- 결과 표시
그래서, 이것이 실제로 무엇을 하는 것일까요?
우리는 num1과 num2라는 두 개의 입력 필드로 제목과 양식을 렌더링합니다. "앱"이 POST 요청을 만나면 결과가 표시되고, 결과는 CSS 카운터를 통해 계산됩니다. CSS 카운터는 처음에는 num1로 설정되었다가 나중에 num2로 증가하여 두 숫자의 합을 산출한다. 따라서: 기본적인 덧셈 계산기.
작동합니까? 실제로 그렇습니다.
내비게이션이 포함된 간단한 2페이지 앱
머리글과 바닥글을 globalals.css 파일로 추상화하겠습니다.
:root {
--navigation: '<ul><li><a href="/">Home</a></li><li><a href="/about">About</a></li></ul>';
--footer: '<footer>© 2022</footer>';
}
그런 다음 다음과 같이 index.css에서 사용할 수 있습니다.
@import "./globals.css";
body::after {
content: var(--navigation) '<h1>Hello, World!</h1>' var(--footer);
}
매력적으로 작동:
휴, 대단한 차네요.
내가 이 글을 쓰는 것만큼 너도 이 글을 읽는 것을 즐겼기를 바라! ️ 또는 를 남겨두십시오! 저는 자유 시간에 기술 기사를 작성하고 가끔 커피를 마시기를 좋아합니다.
내 노력을 지원하고 싶다면 커피를 제공하거나 Twitter 에서 나를 팔로우할 수 있습니다. 페이팔을 통해 직접 나를 지원할 수도 있습니다!
'css' 카테고리의 다른 글
jarallax.js로 아름다운 시차 랜딩 페이지를 만드는 방법 (0) | 2022.02.22 |
---|---|
무한 루프 React 구성 요소 (0) | 2022.02.22 |
다음 React 프로젝트를 위한 상위 3가지 UI 키트 (0) | 2022.02.22 |
슈퍼 사이얀 CSS 아트! (0) | 2022.02.22 |
React에서 CSS를 코딩하는 3가지 방법 (0) | 2022.02.22 |
댓글