Solid 2.0 릴리스 후보의 새로운 비동기 반응성 모델, Rust 기반 컴파일러, 시작 모드, 서버 함수, SolidStart 마이그레이션을 소개합니다.
UI 프레임워크에서 가장 어려운 문제는 렌더링이 아니었습니다. 우리는 언제나 효율적으로 변경할 수 있는 DOM을 가지고 있었습니다. 초기 과제는 동기화였습니다. 어떤 일이 일어나든 일관된 인터페이스를 보여 주면서도 이를 효율적으로 처리하는 일이었죠. 세분화된 반응성은 10년 전에 이 문제를 해결했습니다. 변경된 부분만 정확히 업데이트하고, 나머지는 건너뜁니다.
사라지지 않았던 문제는 비동기였습니다. 우리를 포함한 모든 프레임워크는 이를 프레임워크에 일어나는 조건으로 취급했습니다. 동기식 코어가 견뎌 내야 하는 어떤 것이었습니다.
오늘 Solid 2.0이 릴리스 후보에 도달했으며, 다른 길을 택합니다. 비동기는 반응형 시스템 자체의 속성입니다. 그래프의 일부입니다. 이 하나의 결정은 이번 릴리스의 모든 것을 관통합니다. 모델이 더 많은 일을 하므로 프레임워크는 더 적은 일을 합니다.
계산은 Promise(또는 비동기 이터레이터)를 반환할 수 있으며, 그 아래의 모든 것은 그냥 이를 이해합니다. 이를 받아들이기 위한 특별한 프리미티브도 필요 없습니다. 수동 로딩 상태도, 널 검사도 필요 없습니다.
import { createMemo, isPending, Loading } from "solid-js";
function Profile(props) {
const user = createMemo(() => fetchUser(props.id));
return (
<Loading fallback={<Skeleton />}>
<h1 class={{ stale: isPending(user) }}>{user().name}</h1>
</Loading>
);
}
이것이 데이터 가져오기에 관한 전체 이야기입니다. user는 우연히 비동기인 메모입니다. <Loading>은 준비될 때까지 이를 감쌉니다. props.id가 바뀌면 새 응답이 진행 중인 동안 이전 콘텐츠는 계속 보이고, isPending은 변경이 오고 있음을 알려 줍니다. 즉, “어딘가에서 무엇이든 가져오고 있는가”가 아니라 “이 질문에 대한 새 응답이 오는 중인가”를 알려 줍니다.
파생 상태, 오류 처리, 전환, 낙관적 업데이트는 모두 이 하나의 아이디어에서 나옵니다. 비동기가 그래프 안에 있으므로, 동일한 컴포넌트가 클라이언트 가져오기, 서버 렌더, 서버 함수 중 어디에서 데이터를 받든 작동합니다. 서버 관련 기능은 앱을 대체하는 대신 앱 위에 쌓입니다.
비동기 이야기는 한 섹션으로 다룰 수 있는 것보다 더 많습니다. 아래의 다음 계획을 참고하세요.
Solid 2.0이 제거한 모든 것은 여러분이 배워야 했던 것이었습니다. 추가한 모든 것은 여러분이 사용할 수 있는 것입니다. 이는 우리가 잘라 낸 기능이 아닙니다. 여러분이 배워야 했던 우회책이었습니다. 이제 이것이 Solid가 작동하는 방식입니다.
createResource — 제거됨. 비동기는 일반 메모를 통해 흐릅니다.batch — 제거됨. 모든 것이 배치됩니다. 쓰기는 마이크로태스크에서 적용되며, 지금 바로 필요하면 flush()를 사용하세요.startTransition / useTransition — 제거됨. 그래프가 스스로 일관된 상태를 유지하며, isPending과 latest가 이를 읽습니다.on 및 createComputed — 제거됨. 분할 이펙트인 createEffect(compute, apply)가 추적과 부수 효과를 분리합니다.produce 및 createMutable — 제거됨. 스토어 세터는 변경할 드래프트를 전달합니다. 이제 스토어는 원래 이렇게 작동합니다.1.x 마이그레이션 가이드는 제거된 모든 항목을 대체 방법에 연결합니다.
새 API는 다른 이야기입니다. 첫 앱을 만들기 위해 낙관적 스토어, 프로젝션, 액션, 공개 순서를 알 필요는 없습니다. 열 번째 앱을 만들 때도 필요 없을 수 있습니다. 하지만 필요할 때는 그 기능들이 있으며, 다른 모든 것과 함께 작동합니다. 적게 배우고, 더 많이 하세요. 그리고 런타임은 그 절반에 불과합니다.
도구 체인에도 같은 절충을 적용했습니다.
Solid 2.0은 Oxc 위에 Rust로 작성한 새 컴파일러 도구 체인을 제공하며, @solidjs/vite-plugin은 기본적으로 이를 사용합니다. 플러그인을 업그레이드하면 네이티브 도구로 Solid를 컴파일하게 됩니다. 설정은 전혀 필요 없습니다. 마이그레이션할 것도 없습니다. Babel 프리셋도 계속 사용할 수 있습니다.
| 작업 부하 | babel-plugin-jsx-dom-expressions | Oxc 컴파일러 | 속도 향상 |
|---|---|---|---|
| 픽스처 모음(88개 파일, 175 KB, 전체 10개 모드) | 440 ms | 19 ms | 23배 |
| 129 KB 단일 모듈 | 545 ms | 9.4 ms | 58배 |
| 1 MB 단일 모듈 | 24,975 ms | 70 ms | 355배 |
또한 플러그인은 이제 시작 모드를 제공합니다. 플러그인에 직접 내장된 즉시 사용 가능한 서빙 계층입니다.
// vite.config.ts
import { defineConfig } from "vite";
import solid from "@solidjs/vite-plugin";
export default defineConfig({
plugins: [solid({ start: true })]
});
이는 순수 Vite에서 완성된 앱 설정입니다. 엔트리 파일도, index.html도, 개발 서버 스크립트도 없습니다. 플러그인이 엔트리, 개발 서빙, 빌드를 담당합니다. src/App.tsx를 작성하고 시작하면 됩니다.
그 위에 모든 것이 쌓입니다.
start: true만으로 클라이언트 모드가 됩니다. 개발 환경에서는 앱을 스트리밍된 문서 셸에 클라이언트 렌더링하여 제공하고, vite build는 어떤 정적 호스트에도 배포할 수 있는 순수 정적 dist/client를 생성합니다.GET/POST API 라우트, Solid Router용 타입 지정 라우트 생성을 제공합니다.solid({ start: true, ssr: true })를 통한 SSR. 플러그인은 render를 hydrate로 바꾸고, 하이드레이션 가능 변환을 켜며, 서버 번들을 제공합니다. 프로덕션 계약은 함수 하나, 즉 handleRequest(request)이며, 이것이 어떤 호스트 플랫폼과도 조합되는 이유입니다. 가져오기 스타일 미들웨어(start.middleware), 라우터를 위한 요청별 설정 지점(start.setup), 그리고 Standard Schema로 검증되는 타입 지정 환경 변수까지 제공합니다. 서버 비밀 정보가 클라이언트 청크로 새어 나가면 컴파일을 차단하는 빌드 시점 검사도 포함됩니다."use server"는 @solidjs/web/server-functions가 뒷받침하는 핵심 기능입니다. 어떤 Vite 앱에서도 타입 지정 RPC, 스트리밍 반환, 점진적 향상, 사용자 지정 직렬화를 제공합니다. 서버 함수의 서버 측은 함수 본문 자체입니다. 검증, 인증, 로깅은 프레임워크 훅이 아니라 코드 몇 줄입니다. 본문 내부에서만 참조되는 것은 클라이언트에 절대 도달하지 않으며, 지시문 경계 자체가 개인정보 보호 메커니즘입니다.import { reload } from "@solidjs/web";
export async function addTodo(title: string) {
"use server";
await db.insert(title);
return reload({ revalidate: "todos" });
}
dist/client와 웹 표준 Fetchable 핸들러를 기본 내보내기하는 서버 모듈을 함께 생성합니다. 이는 Cloudflare Workers, Netlify Functions, Nitro, Bun, deno serve가 이미 사용하는 규약입니다. Cloudflare, Netlify, Nitro의 플랫폼 Vite 플러그인은 Solid의 서버 환경을 직접 채택하므로, 여러분이 설정하거나 우리가 유지할 Solid 어댑터 계층은 없습니다. 배포는 웹 표준과 플랫폼 자체 도구만으로 이루어집니다. 전체 배포 가이드.메타프레임워크는 프레임워크의 빈틈을 채우기 위해 존재합니다. SolidStart의 역할은 코어가 제공하지 못한 것을 제공하는 것이었습니다. 2.0 주기 동안 이러한 기능은 각각 제자리로 옮겨졌습니다. 서버 함수는 코어로, 서빙 계층은 시작 모드로, 파일 시스템 라우팅은 라우터 중립 패키지로 이동했습니다. 이 과정의 끝에 남은 것은 더 이상 감쌀 필요가 없는 것들을 감싸는 래퍼였습니다.
그래서 비어 있는 3.0을 출시하는 대신, 이를 종료합니다. 시작 모드가 SolidStart를 대체합니다.
오늘 프로덕션에서 SolidStart를 실행 중이라면 아무것도 깨지지 않습니다. SolidStart는 계속 유지 관리 릴리스를 받을 것이며, 마이그레이션 가이드도 지금 이용할 수 있습니다. 대부분의 앱에서 이전 작업은 기계적으로 수행할 수 있고, 아래의 마이그레이션 도우미가 나머지를 표시합니다. 이것은 끝이 아닙니다. 프레임워크가 메타프레임워크가 시작한 일을 마무리하는 것입니다.
베타 발표를 따라왔다면 기반을 알고 있을 것입니다. 그렇지 않은 분들을 위해 각 항목은 문서로 연결됩니다.
<Loading>, <Errored>, <Reveal>. 새로운 비동기 모델을 위해 재고안한 Suspense, ErrorBoundary, SuspenseList입니다. 재검증 중에도 오래된 콘텐츠는 계속 보이고, 경계는 복구되며, 공개 순서가 조정됩니다.action, createOptimistic, createOptimisticStore는 진행 중인 변경을 즉시 렌더링하고 서버 응답이 오면 조정합니다.createStore(fn), createProjection)가 1.x의 되돌려 쓰기 패턴을 대체합니다.<For>가 <For>/<Index>를 대체하고, <Repeat>는 차이 비교 없이 개수로 렌더링합니다.class 객체와 배열, ref 지시문 팩토리를 제공합니다.@solidjs/signals이고, 웹 런타임은 @solidjs/web이며, 스토어는 solid-js 자체에 있습니다.이 모든 설계 근거는 2.0 RFC에 있습니다.
이 글에는 의도적으로 넣지 않은 두 가지가 있습니다.
비동기 모델은 제대로 된 심층 분석이 필요합니다. 다음 주에 시리즈를 시작합니다. 읽기, 쓰기, 그리고 와이어를 다루며, 데이터가 메모, 낙관적 변경, 스트리밍 서버 중 어디에서 오든 컴포넌트가 어떻게 지연 시간에 구애받지 않는지 설명합니다.
그리고 설정 타입에서 serverFunctions: { components: true }를 발견한 분들을 위해 말씀드리면, 맞습니다. 서버 함수는 컴포넌트를 반환할 수 있습니다. 반응형 서버 컴포넌트는 해당 플래그 뒤에서 실험적 미리 보기로 제공되고 있으며, 2.0 정식 버전 이후에 전체 발표가 있을 예정입니다. 기다릴 가치가 있습니다.
Solid 2.0 미리 보기 문서는 v2.solidjs.com에서 공개되어 있습니다.
새 프로젝트에서는 다음으로 Solid 2.0 템플릿을 선택하세요.
npm create solid@latest
기존 프로젝트를 마이그레이션하려면 여기의 가이드를 따르세요.
또한 프로젝트를 검사하고 감지한 모든 1.x 마이그레이션 지점에 대한 구체적인 안내를 출력하는 마이그레이션 도우미를 개발 중입니다. 레거시 가져오기, 인자 하나짜리 createEffect, onMount, Suspense/Index/classList, 이전 스토어 도우미 등을 검사합니다.
npx solid-migration-assistant
생태계는 기다리지 않았습니다. Solid Router 2.0은 완전히 타입 지정된 라우트, 매개변수, 내비게이션과 함께 RC와 동시에 출시됩니다. Solid Meta 1.0은 이제 2.0에 내장된 헤드 레지스트리 위의 얇은 계층입니다. TanStack을 선호하시나요? fullstack-tanstack 템플릿은 TanStack Router와 TanStack Query를 시작 모드와 즉시 사용할 수 있도록 함께 제공하며, TanStack Start는 이미 Solid 2.0 베타(@tanstack/solid-start@beta)를 제공합니다. 그리고 실제로 앱을 만들 때 사용하는 라이브러리, 즉 Solid Primitives, Kobalte, Solid Testing Library, Storybook, AG Grid는 베타 기간 내내 2.0 지원을 위해 열심히 작업했으며 오늘 RC와 함께 사용할 준비가 되었습니다. 유틸리티, 컴포넌트, 메타프레임워크, 테스트, 라우팅, 헤드 관리까지, 스택은 릴리스 전에 준비되었습니다.
지난 5개월 동안 이 일을 가능하게 하기 위해 투입된 엄청난 노력을 설명하기는 어렵습니다. 이렇게 짧은 시간에 এত এত 많은 것을 해낼 수 있으리라고는 생각하지 못했습니다. 대체로 소홀히 하기 쉬운 것은 삶의 질을 높이는 부분이고, 그것이 차이를 만들었습니다. Solid 2.0 베타는 출시하기도 전에 꽤 “Solid”했습니다. 선언적 반응성을 위한 최선의 패턴을 수년간 연구한 결과가 결실을 맺었습니다.
하지만 예상하지 못한 것은 Solid 1.0 출시를 준비할 때와 비교해 이것이 얼마나 달랐는지였습니다. 당시 커뮤니티는 훨씬 작았고, 끝없이 오래 걸리는 것처럼 느껴졌습니다. 이번에는 아들 Nico와 유대감을 쌓기 위해 6주간 육아휴직을 갔는데도, 조금도 흐트러지지 않았습니다. 베타 테스터와 AI 에이전트는 계속 앞으로 나아갔습니다. 코어 범위가 과도하게 늘어나지 않도록 결심한 동안, 다른 모든 것은 그에 따라 자연스럽게 따라왔습니다. 제가 Solid 3.0이나 심지어 4.0을 위해 계획해 둔 것들이었습니다. 실제로 6개월 전에 Solid 3.0 계획 문서를 개략적으로 작성했는데, 그 안의 모든 것을 해냈습니다.
AI 시대에는 이런 일이 원래 이렇게 진행되는 것일 수도 있지만, 이를 가능하게 하는 사람들을 인정하는 일은 중요합니다. 언급하기에는 너무 많은 분들이 계시지만, 짧게 감사 인사를 전하겠습니다.
먼저 제 작업을 직접 지원해 주는 자비로운 고용주 Sentry와, 불가능을 가능하게 만든 크레디트를 제공한 Cursor에 감사드립니다.
그리고 베타 테스트와 기여에 참여한 모든 분께 감사드립니다: @brenelz @yumemi-thomas @mizulu @titoBouzout @GabbeV @birkskyum @dangkyokhoang @tsushanth @maciek50322 @kanashimia @SnowingFox @atk @AFatNiBBa @snatvb @better-salmon @m-canton @arpitjain099 @DominicDolan @deluksic @danon @danielalanbates @beanscg @trusktr @sonukapoor @rtritto @ngotruonghuy @mudmaster556 @jpdutoit @gameroman @echab @danielrkling @katywings @clinuxrulz @ahzvenol @tonghuaxingdsb @thomasbuilds @thep0y @subotac @spokodev @samualtnorman @rvlzzr @rrshaban @rexblade58 @mitsuhiko @mesram @mariokresic @madaxen86 @lxsmnsyc @Tommypop2 @LadyBluenotes @le0-0 @jer3m01 @iamssen @gnomical @developerdizzle @devagrawal09 @milomg @mihar-22 @tannerlinsley @crassicus @alfi-dim @aekobear @WolffM @VXsz @PierBover @Jungzl @JLouisa @DakshSinghDhami @CxRes
릴리스 후보는 API가 동결되었다는 뜻이지 버그가 없다는 뜻은 아니므로, 프로젝트를 업데이트하고 문제를 보고해 주시는 모든 분께 진심으로 감사드립니다. 생태계 구축자이거나 Solid 1.0 프로젝트를 유지 관리하고 있다면, 지금 마이그레이션하고 문제를 보고해 주시기 바랍니다.
Solid 2.0을 정식 릴리스로 함께 이끌어 갑시다!