하나의 HTML 파일에서 Svelte 실행

Svelte는 일반적으로 컴퓨터에서 컴파일러를 사용하여 구성 요소를 JavaScript로 변환합니다. 컴파일러도 JavaScript이므로 페이지가 로드되는 동안 하나의 HTML 파일로 구성 요소를 컴파일할 수 있습니다.

영문 원문 보기

Svelte는 사용자 인터페이스를 구축하기 위한 프레임워크입니다. 자체 문서에서는 이를 컴파일러라고 설명합니다. HTML, CSS 및 JavaScript로 구성 요소를 작성하면 이를 JavaScript로 변환합니다.

해당 컴파일러는 일반적으로 SvelteKit 또는 Vite를 통해 컴퓨터에서 실행됩니다. 그러나 페이지에서도 실행될 수 있으므로 단일 HTML 파일이 작동합니다.

먼저 시도해 보세요. 아래 버튼은 조금 전에 브라우저에서 컴파일된 Svelte 5 구성 요소입니다.

실행 예제직접 써 보고 코드를 복사하세요
링크로 공유하기
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Svelte in one HTML file</title>
<style>
  body { margin: 0; padding: 20px; font-family: system-ui, sans-serif; background: #f6f7f9; }
  #app { min-height: 150px; }
</style>

<!-- 1. The Svelte compiler, as one script (pinned version) -->
<script src="https://cdn.jsdelivr.net/npm/svelte@5.57.1/compiler/index.js"></script>

<!-- 2. Tell the browser where the Svelte runtime files live -->
<script type="importmap">
{
  "imports": {
    "svelte": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/index-client.js",
    "svelte/internal/client": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/internal/client/index.js",
    "#client/constants": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/internal/client/constants.js",
    "esm-env": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/index.js",
    "esm-env/browser": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/true.js",
    "esm-env/development": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/false.js",
    "esm-env/node": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/false.js",
    "clsx": "https://cdn.jsdelivr.net/npm/clsx@2.1.1/dist/clsx.mjs"
  }
}
</script>
</head>
<body>
<div id="app">Loading Svelte...</div>

<script type="module">
  import { mount } from 'svelte';

  // 3. The component, written as in a .svelte file
  const source = `
<script>
  let count = $state(0);
<\/script>

<button onclick={() => count++}>Clicked {count} times</button>
<p>{count === 0 ? 'Press the button.' : 'Only this text was updated.'}</p>

<style>
  button { font: inherit; padding: 10px 16px; border: 0; border-radius: 8px;
           background: #ff3e00; color: #fff; cursor: pointer; }
  p { color: #4b5563; }
</style>`;

  // 4. Compile it here in the browser, then mount it
  const { js } = svelte.compile(source, { runes: true, css: 'injected', discloseVersion: false });
  const url = URL.createObjectURL(new Blob([js.code], { type: 'text/javascript' }));
  const { default: Counter } = await import(url);

  const app = document.getElementById('app');
  app.textContent = '';
  mount(Counter, { target: app });
</script>
</body>
</html>
하나의 HTML 파일에 있는 Svelte 카운터. 컴파일러와 런타임은 고정된 버전의 CDN에서 제공됩니다.

아무것도 설치되지 않았고 빌드 폴더도 없습니다. 모든 것이 저장하고 열고 편집할 수 있는 하나의 .html 파일입니다.

파일 내용: 네 부분

페이지에 Svelte를 추가하는 단일 <script> 태그는 없습니다. 구성 요소를 먼저 컴파일해야 하며, 컴파일된 코드는 Svelte의 런타임을 가져옵니다. 따라서 파일은 항상 이 순서대로 네 부분으로 구성됩니다.

단일 파일 설정: 컴파일러, 맵 가져오기, 구성 요소 텍스트, 컴파일 및 마운트.
단일 파일 설정: 컴파일러, 맵 가져오기, 구성 요소 텍스트, 컴파일 및 마운트.
  1. 컴파일러. 컴파일러 파일의 클래식 스크립트 태그입니다. compile() 기능을 사용하여 svelte라는 전역을 추가합니다.
  2. 가져오기 맵. svelte 및 svelte/internal/client가 어디에 있는지 브라우저에 알려주고 런타임에서 가져오는 세 개의 작은 도우미도 있습니다.
  3. 구성요소. JavaScript 문자열로 유지되는 일반 .svelte 텍스트입니다.
  4. 컴파일 및 마운트. svelte.compile()는 JavaScript 모듈을 텍스트로 반환합니다. 페이지는 Blob URL에서 이를 가져오고 mount()를 호출합니다.

주소에 버전이 기록된 컴파일러 라인:

<script src="https://cdn.jsdelivr.net/npm/svelte@5.57.1/compiler/index.js"></script>

<script type="module"> 내부의 마지막 단계는 다음과 같습니다.

const { js } = svelte.compile(source, { runes: true, css: 'injected', discloseVersion: false });
const url = URL.createObjectURL(new Blob([js.code], { type: 'text/javascript' }));
const { default: Counter } = await import(url);
mount(Counter, { target: document.getElementById('app') });

css: 'injected'는 마운트 시 구성요소의 <style>를 페이지에 넣습니다. 이것이 없으면 CSS는 결과의 css 필드에만 반환됩니다. discloseVersion: false는 window에 Svelte 버전만 기록하는 추가 가져오기를 건너뜁니다.

가져오기 맵이 필요한 이유

컴파일된 코드를 열면 첫 번째 줄은 이름만 있는 가져오기입니다.

import * as $ from 'svelte/internal/client';

브라우저는 그런 이름을 로드할 수 없습니다. 가져오기 맵은 각 이름에 실제 주소를 제공하는 JSON 블록입니다. MDN에서는 해당 이름을 사용하는 모듈보다 먼저 선언해야 하므로 <head>에 들어갑니다.

런타임 파일은 Svelte 내부의 #client/constants, 패키지 esm-env 및 clsx 등 몇 가지 이름으로 서로를 가져옵니다. 각각은 지도에 한 줄씩 필요합니다. 예제의 전체 지도:

<script type="importmap">
{
  "imports": {
    "svelte": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/index-client.js",
    "svelte/internal/client": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/internal/client/index.js",
    "#client/constants": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/internal/client/constants.js",
    "esm-env": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/index.js",
    "esm-env/browser": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/true.js",
    "esm-env/development": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/false.js",
    "esm-env/node": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/false.js",
    "clsx": "https://cdn.jsdelivr.net/npm/clsx@2.1.1/dist/clsx.mjs"
  }
}
</script>

모든 Svelte 주소는 컴파일러와 동일한 버전을 사용합니다. 런타임은 별도의 소스 파일로 로드되므로 브라우저는 처음 로드할 때 많은 작은 파일(Chromium 테스트에서는 103개 요청)을 가져옵니다.

페이지 내부에 구성요소 작성

구성 요소는 템플릿 리터럴에 있습니다. 한 가지 세부 사항이 중요합니다. <script> 요소 내에서 HTML 파서는 문자열 내부에 있는 첫 번째 </script>에서 스크립트를 종료합니다. 따라서 구성 요소 자체의 닫는 태그는 백슬래시로 작성됩니다.

const source = `
<script>
  let count = $state(0);
<\/script>

<button onclick={() => count++}>Clicked {count} times</button>
`;

JavaScript에서 \/는 /이므로 컴파일러는 일반 </script>를 받습니다.

두 번째 데모는 작은 놀이터입니다. 왼쪽의 컴포넌트를 편집하고 버튼을 누릅니다. 오른쪽에는 결과와 컴파일러가 작성한 JavaScript가 표시됩니다.

실행 예제직접 써 보고 코드를 복사하세요
링크로 공유하기
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Svelte compiler in the page</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f6f7f9; }
  .wrap { display: grid; gap: 12px; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  h3 { margin: 0 0 6px; font-size: 13px; color: #374151; }
  textarea { box-sizing: border-box; width: 100%; height: 200px; font: 13px/1.45 ui-monospace, Consolas, monospace;
             padding: 8px; border: 1px solid #d1d5db; border-radius: 8px; }
  #run { margin-top: 6px; font: inherit; padding: 8px 14px; border: 0; border-radius: 8px; background: #111827; color: #fff; cursor: pointer; }
  #out { min-height: 90px; padding: 12px; background: #fff; border: 1px solid #e5e7eb; border-radius: 8px; }
  #err { color: #b42318; font: 12px/1.4 ui-monospace, Consolas, monospace; white-space: pre-wrap; margin: 6px 0 0; }
  pre#code { margin: 0; height: 130px; overflow: auto; padding: 8px; background: #111827; color: #d1fae5;
             font: 11px/1.4 ui-monospace, Consolas, monospace; border-radius: 8px; }
  @media (max-width: 600px) { .wrap { grid-template-columns: minmax(0, 1fr); } textarea { height: 170px; } }
</style>

<!-- 1. The Svelte compiler, as one script (pinned version) -->
<script src="https://cdn.jsdelivr.net/npm/svelte@5.57.1/compiler/index.js"></script>

<!-- 2. Tell the browser where the Svelte runtime files live -->
<script type="importmap">
{
  "imports": {
    "svelte": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/index-client.js",
    "svelte/internal/client": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/internal/client/index.js",
    "#client/constants": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/internal/client/constants.js",
    "esm-env": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/index.js",
    "esm-env/browser": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/true.js",
    "esm-env/development": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/false.js",
    "esm-env/node": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/false.js",
    "clsx": "https://cdn.jsdelivr.net/npm/clsx@2.1.1/dist/clsx.mjs"
  }
}
</script>
</head>
<body>
<div class="wrap">
  <div>
    <h3>Your component (edit me)</h3>
<textarea id="src" spellcheck="false"><script>
  let name = $state('world');
  let shout = $derived(name.toUpperCase());
</script>

<input bind:value={name}>
<p>Hello {name}! {shout}!</p></textarea>
    <button id="run">Compile and run</button>
    <p id="err"></p>
  </div>
  <div>
    <h3>Result</h3>
    <div id="out"></div>
    <h3 style="margin-top:10px">What the compiler wrote</h3>
    <pre id="code"></pre>
  </div>
</div>

<script type="module">
  import { mount, unmount } from 'svelte';

  const out = document.getElementById('out');
  const err = document.getElementById('err');
  let current = null;

  async function run() {
    err.textContent = '';
    let js, warnings;
    try {
      ({ js, warnings } = svelte.compile(document.getElementById('src').value, { runes: true, css: 'injected', discloseVersion: false }));
    } catch (e) {
      err.textContent = e.message;  // compile errors name the line and column
      return;
    }
    // warnings do not stop the build, but they usually explain a bug
    err.textContent = warnings.map((w) => 'Warning: ' + w.message).join('\n');
    document.getElementById('code').textContent = js.code;
    const url = URL.createObjectURL(new Blob([js.code], { type: 'text/javascript' }));
    const { default: Component } = await import(url);
    if (current) unmount(current);  // remove the previous version first
    current = mount(Component, { target: out });
  }

  document.getElementById('run').addEventListener('click', run);
  run();
</script>
</body>
</html>
구성요소를 편집하고 다시 컴파일하십시오. 버튼 아래에 오류와 경고가 나타납니다.

스크립트를 export let name;로 바꾸거나 $state를 제거해 보세요. 컴파일러는 무엇이 잘못되었는지 보고하고 Svelte 문서의 페이지로 연결합니다.

Svelte 5 파일의 Svelte 4 구문

Svelte 4로 작성된 코드는 형태가 다릅니다. Svelte 5에는 $state, $derived, $props처럼 $로 시작하는 함수인 **룬(runes)**이 도입되었습니다. runes: true를 사용하면 기존 방식은 오류나 경고를 발생시킵니다.

왼쪽에는 Svelte 4 습관, 중간에는 룬 버전, 컴파일러가 말하는 내용이 있습니다.
왼쪽에는 Svelte 4 습관, 중간에는 룬 버전, 컴파일러가 말하는 내용이 있습니다.
날씬한 4 날씬한 5 룬 포함: 사실
let count = 0 let count = $state(0) 경고 및 텍스트가 업데이트되지 않습니다.
export let title let { title } = $props() 컴파일 오류
$: double = count * 2 $derived(count * 2) 컴파일 오류
on:click={add} onclick={add} 지원 중단 경고
new App({ target }) mount(App, { target }) 이제 컴포넌트는 함수입니다

왜 runes: true를 통과해야 합니까? 룬이 없는 구성 요소는 이전의 비룬 모드에서 컴파일되며 해당 출력은 런타임 파일을 하나 더 가져옵니다. 우리는 정확히 다음과 같은 결과를 얻었습니다. 옵션이 추가될 때까지 일반 <h1>Hi</h1>가 실패했습니다.

완성된 예: 포장 목록

동일한 네 부분이 실제 구성 요소를 처리합니다. 이 목록은 mount()에서 제목을 전달하고, $state 배열에 항목을 유지하며, $derived에 남은 항목을 계산합니다.

실행 예제직접 써 보고 코드를 복사하세요
링크로 공유하기
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Svelte packing list</title>
<style>
  body { margin: 0; padding: 20px; font-family: system-ui, sans-serif; background: #f6f7f9; }
  #app { min-height: 150px; }
</style>

<!-- 1. The Svelte compiler, as one script (pinned version) -->
<script src="https://cdn.jsdelivr.net/npm/svelte@5.57.1/compiler/index.js"></script>

<!-- 2. Tell the browser where the Svelte runtime files live -->
<script type="importmap">
{
  "imports": {
    "svelte": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/index-client.js",
    "svelte/internal/client": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/internal/client/index.js",
    "#client/constants": "https://cdn.jsdelivr.net/npm/svelte@5.57.1/src/internal/client/constants.js",
    "esm-env": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/index.js",
    "esm-env/browser": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/true.js",
    "esm-env/development": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/false.js",
    "esm-env/node": "https://cdn.jsdelivr.net/npm/esm-env@1.2.2/false.js",
    "clsx": "https://cdn.jsdelivr.net/npm/clsx@2.1.1/dist/clsx.mjs"
  }
}
</script>
</head>
<body>
<div id="app">Loading Svelte...</div>

<script type="module">
  import { mount } from 'svelte';

  const source = `
<script>
  let { title } = $props();          // passed in from mount() below
  let items = $state([
    { text: 'Passport', done: true },
    { text: 'Charger', done: false }
  ]);
  let draft = $state('');
  let left = $derived(items.filter((i) => !i.done).length);

  function add(e) {
    e.preventDefault();               // stay on the page
    if (draft.trim()) items.push({ text: draft.trim(), done: false });
    draft = '';
  }
<\/script>

<h2>{title}</h2>
<form onsubmit={add}>
  <input bind:value={draft} placeholder="Add an item" aria-label="New item">
  <button>Add</button>
</form>

<ul>
  {#each items as item}
    <li class:done={item.done}>
      <label><input type="checkbox" bind:checked={item.done}> {item.text}</label>
    </li>
  {/each}
</ul>
<p>{left === 0 ? 'All packed.' : left + ' left to pack'}</p>

<style>
  h2 { margin: 0 0 10px; font-size: 18px; }
  form { display: flex; gap: 6px; }
  input:not([type]) { flex: 1; min-width: 0; font: inherit; padding: 8px; border: 1px solid #d1d5db; border-radius: 8px; }
  button { font: inherit; padding: 8px 14px; border: 0; border-radius: 8px; background: #ff3e00; color: #fff; }
  ul { list-style: none; padding: 0; margin: 12px 0; }
  li { padding: 8px 10px; margin-bottom: 6px; background: #fff; border-radius: 8px; }
  .done { color: #9ca3af; text-decoration: line-through; }
  p { color: #4b5563; margin: 0; }
</style>`;

  const { js } = svelte.compile(source, { runes: true, css: 'injected', discloseVersion: false });
  const url = URL.createObjectURL(new Blob([js.code], { type: 'text/javascript' }));
  const { default: List } = await import(url);

  const app = document.getElementById('app');
  app.textContent = '';
  mount(List, { target: app, props: { title: 'Trip packing list' } });
</script>
</body>
</html>
항목을 추가하고 선택하세요. 개수와 취소선이 자동으로 업데이트됩니다.
  • 페이지의 소품: props: { title }를 mount()에 전달하고 $props()를 사용하여 내부에서 읽습니다.
  • 목록: {#each items as item}는 <li>를 반복합니다. $state 배열이 반응적이므로 items.push()는 행을 추가합니다.
  • 양방향 입력: 텍스트 필드의 bind:value 및 각 확인란의 bind:checked.
  • 양식: onsubmit는 e.preventDefault()를 호출하므로 항목을 추가해도 페이지가 다시 로드되지 않습니다.

일반적인 Svelte 프로젝트가 더 적합한 경우

페이지에서 컴파일하는 것이 공유할 수 있는 하나의 파일에 대한 최단 경로입니다. 일반 프로젝트는 동일한 작업을 미리 한 번 수행합니다.

프로젝트는 업로드하기 전에 컴파일됩니다. 단일 파일 버전은 각 방문자의 브라우저에서 컴파일됩니다.
프로젝트는 업로드하기 전에 컴파일됩니다. 단일 파일 버전은 각 방문자의 브라우저에서 컴파일됩니다.

Svelte 문서에서는 새 프로젝트에 SvelteKit을 권장하거나 자체적으로 Vite를 권장합니다.

npx sv create myapp
cd myapp
npm install
npm run dev

Vite를 사용하면 npm run build는 HTML, JavaScript 및 CSS 파일을 dist 폴더에 씁니다. 해당 폴더는 정적 파일입니다.

빌드된 프런트 엔드 호스팅은 일반적인 라우팅 및 기본 경로 문제를 다룹니다. 서버 측 렌더링과 같은 SvelteKit 기능에는 단일 HTML 파일이 제공할 수 없는 서버가 필요합니다.

작동하지 않을 때

당신이 보는 것 원인 수정
"Svelte 로드 중..."이 그대로 유지됩니다. 모듈 내에서만 import 문을 사용하라는 오류가 발생했습니다. 마지막 스크립트는 고전적인 스크립트입니다. <script type="module"> 사용
모듈 지정자 "svelte"를 확인하지 못했습니다. 누락되거나 지연된 가져오기 지도 모듈 앞에 <head>에 맵을 넣습니다.
레거시 플래그 파일에 대한 지정자를 확인하지 못했습니다. 비룬 모드에서 컴파일된 구성 요소 runes: true 통과
코드의 절반이 페이지에 텍스트로 표시됩니다. 문자열 내부의 </script> <\/script>로 작성하세요.
구성요소가 작동하지만 스타일이 없습니다. CSS가 삽입되지 않고 반환되었습니다. css: 'injected' 통과
클릭해도 숫자는 변하지 않습니다 룬 모드의 일반 let $state() 사용

다른 이유로 실패한 스크립트의 경우 HTML JavaScript가 작동하지 않음에서 일반 검사를 거칩니다.

링크로 공유하세요

Svelte 데모는 설명하는 것보다 보여주기가 더 쉽습니다. 스크린샷을 클릭할 수 없으며 .html 첨부 파일이 전화기에서 일반 코드로 열릴 수 있습니다.

작업 버전을 보내려면 페이지를 NOS 문서에 붙여넣고 공유 링크 만들기를 선택하세요. 링크할 HTML이 이를 안내합니다.

페이지는 작성된 대로 렌더링되고, 해당 스크립트가 실행되며, CDN에서 컴파일러와 런타임이 로드되므로 페이지를 받는 사람이 구성 요소 자체를 클릭하고 입력할 수 있습니다. 나중에 코드를 변경하면 동일한 링크에 새 버전이 표시됩니다.

자주 묻는 질문

다른 라이브러리와 마찬가지로 Svelte CDN 스크립트가 있습니까?

단일 런타임 파일이 아닙니다. Svelte의 자체 가이드는 페이지가 제공되기 전에 구성 요소를 컴파일하는 SvelteKit 또는 Vite로 프로젝트를 시작합니다. 하나의 HTML 파일에서 여기 예제와 같이 컴파일러 스크립트를 로드하고, 가져오기 맵으로 런타임 파일을 매핑하고, 페이지에서 컴파일합니다.

브라우저에서 컴파일하는 것이 일반적인 Svelte 빌드보다 느립니까?

방문할 때마다 더 많은 작업을 수행합니다. 방문자는 컴파일러(5.57.1 ​​컴파일러 파일은 압축되지 않은 873,343바이트)를 다운로드하고 페이지는 구성 요소를 표시하기 전에 컴파일합니다. 빌드는 미리 한 번만 해당 작업을 수행합니다. 데모, 프로토타입 또는 소형 도구의 경우 파일 하나가 거래하기 더 쉬운 경우가 많습니다.

이 설정에서 import let 및 $:와 같은 Svelte 4 구문을 사용할 수 있습니까?

룬이 아닌 경우: true입니다. 이 예제는 컴파일러에 전달됩니다. let 및 $:를 내보내면 오류가 발생하여 컴파일이 중지됩니다. Svelte 5는 여전히 룬이 아닌 구성 요소의 이전 구문을 허용하지만 해당 모드는 여기 가져오기 맵에 나열되지 않은 다른 런타임 파일을 가져옵니다.

SvelteKit이 필요합니까?

페이지의 단일 구성 요소에는 해당되지 않습니다. SvelteKit은 Svelte의 애플리케이션 프레임워크로, 새 프로젝트에 대해 Svelte 문서에서 권장됩니다. 라우터, 서버 측 렌더링, 사전 렌더링 등을 추가합니다. 서버 측 렌더링은 하나의 HTML 파일에 없는 서버에서 실행됩니다.

Svelte는 무료로 사용할 수 있나요?

그렇습니다. Svelte는 MIT 라이선스에 따른 오픈 소스입니다.