JavaScript로 Excel 파일 만들기 (XLSX/XLS)

2026-09-30 01:34:21 Allen Yang
AI Summarize:
ChatGPT
ChatGPT ✓
Claude ✓
Grok ✓
Perplexity ✓
Quick
Quick
Concise overview
Highlights
Key takeaways
Detailed
Structured explanation
Brief
One sentence summary
Summarize |

Create Excel files in JavaScript with Spire.XLS — browser to XLSX and XLS

JavaScript는 브라우저에서 직접 Excel 통합 문서를 생성할 수 있습니다. JavaScript용 Spire.XLS를 사용하면 통합 문서를 만들고, 워크시트를 추가하고, 값과 수식을 작성하고, 서식을 적용하고, 결과를 XLSX 또는 XLS 파일로 저장할 수 있습니다. 이 모든 작업이 클라이언트 측에서 이루어지며 Microsoft Excel을 설치할 필요가 없습니다.

이 튜토리얼은 기본 XLSX 통합 문서로 시작한 다음 형식이 지정된 셀 값, 수식, 서식, 여러 워크시트, 브라우저 다운로드 지원, CSV 변환, 레거시 호환성을 위한 XLS 출력을 추가합니다.

JavaScript용 Spire.XLS 설치 및 초기화

JavaScript용 Spire.XLS는 Spire.PDF, Spire.Doc, Spire.Presentation과 함께 spire.office 패키지 내에 제공됩니다:

npm i spire.office

런타임을 부팅하려면 두 번의 import가 필요합니다. 첫 번째는 공유 .NET WebAssembly 호스트를 시작하고, 두 번째는 스프레드시트 API를 등록합니다:

// 1. Boot the shared runtime once per page.
const common = await import('/node_modules/spire.office/spire.common.js');
await common.initializeWasm();

// 2. Load the spreadsheet engine — this is what creates window.spirexls.
await import('/node_modules/spire.office/spire.xls.js');

Spire.*.Wasm.zip 아카이브와 _framework 폴더는 사이트 루트에서 접근 가능해야 합니다. 런타임은 import를 수행한 모듈이 아니라 문서 URL을 기준으로 이들을 확인하므로, Vite 또는 Create React App 프로젝트에서는 public/에 있어야 합니다. React 기반 설정에서 표시되는 process.env.PUBLIC_URL 접두사는 Create React App 규칙이지 브라우저 표준이 아닙니다. CRA를 사용하지 않는 경우 빌드 도구에 맞게 기본 경로를 조정하세요. 아카이브가 없으면 브라우저는 WebAssembly.compile(): expected magic word를 기록합니다. 개발 서버가 아카이브 요청에 index.html로 응답한 것입니다.

그러면 모든 것이 하나의 전역 객체에 의존합니다:

const xls = window.spirexls;

이 튜토리얼에서 사용하는 현재 패키지 설정에서는 스프레드시트 API가 window.spirexls를 통해 노출됩니다. 런타임이 초기화된 후 해당 전역 객체를 사용하세요. 이전 릴리스에서는 이를 window.wasmModule.spirexls로 노출했습니다. 다른 패키지 버전을 사용하는 경우 어떤 전역 객체를 사용할 수 있는지 확인하세요.

이제 Excel 파일을 생성할 준비가 되었습니다.

JavaScript로 기본 Excel 파일 만들기

판매 보고서를 만들어 보겠습니다. 통합 문서를 만들고, 워크시트를 추가하고, 제품 데이터를 셀에 쓰고, 결과를 자동으로 다운로드되는 XLSX 파일로 저장합니다.

async function createExcelFile() {
    const xls = window.spirexls;
    if (!xls) {
        console.error('Spire.XLS is not initialized.');
        return;
    }

    // A fresh Workbook() already contains three blank worksheets, so clear them
    // and add the single sheet this report needs.
    const workbook = new xls.Workbook();
    workbook.Worksheets.Clear();
    const sheet = workbook.Worksheets.Add("Sales Report");

    // Sample data: product sales
    const data = [
        ["Product", "Quantity", "Price"],
        ["Laptop", 10, 999.99],
        ["Mouse", 50, 24.99],
        ["Keyboard", 30, 59.99],
        ["Monitor", 15, 329.99]
    ];

    // Write data to cells
    for (let row = 0; row < data.length; row++) {
        for (let col = 0; col < data[row].length; col++) {
            const cell = sheet.Range.get({ row: row + 1, column: col + 1 });
            if (typeof data[row][col] === "string") {
                cell.Text = data[row][col];
            } else {
                cell.NumberValue = data[row][col];
            }
        }
    }

    // Save to the virtual file system, read the bytes, download, then dispose
    const fileName = "SalesReport.xlsx";
    workbook.SaveToFile({
        fileName: fileName,
        version: xls.ExcelVersion.Version2016
    });

    const fileData = window.dotnetRuntime.Module.FS.readFile(fileName);
    const blob = new Blob([fileData], {
        type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = fileName;
    a.click();
    URL.revokeObjectURL(url);

    workbook.Dispose();
}

createExcelFile()를 호출하면 4개의 제품 행과 헤더가 있는 SalesReport.xlsx가 생성됩니다. 워크플로는 간단합니다: 워크북 생성 → 데이터 쓰기 → 저장 → 다운로드.

다음 섹션에서는 이 예제를 확장합니다. 각 코드 조각은 통합 문서와 워크시트가 생성된 후 createExcelFile() 내부에 추가된다고 가정합니다.

SalesReport.xlsx generated by the basic createExcelFile() function

Excel 셀에 다양한 데이터 유형 쓰기

Excel은 텍스트, 숫자, 날짜, 부울을 구분합니다. 이를 잘못 처리하면 정렬이 깨지고, 수식이 오류를 반환하며, 숫자가 텍스트로 표시되는 파일이 만들어집니다.

const sheet = workbook.Worksheets.get(0);

// Text — for labels, names, descriptions
sheet.Range.get("A1").Text = "Product Name";

// Number — for anything you'll calculate, sort, or filter
sheet.Range.get("B1").NumberValue = 999.99;

// Date — a real DateTimeValue plus a display format
const dateCell = sheet.Range.get("C1");
dateCell.DateTimeValue = new Date(Date.UTC(2025, 2, 15));
dateCell.NumberFormat = "yyyy-mm-dd";

// Boolean — use BooleanValue, not text
sheet.Range.get("D1").BooleanValue = true;

가장 흔한 실수는 무엇일까요? cell.NumberValue = 999.99 대신 cell.Text = "999.99"를 쓰는 것입니다. 파일을 열면 값이 동일해 보이지만 Excel은 이를 텍스트로 처리하므로 합계, 평균, 숫자 정렬을 할 수 없습니다. 숫자에는 항상 NumberValue를, 날짜에는 DateTimeValue를, 부울에는 BooleanValue를 사용하세요.

날짜에는 추가 주의가 필요합니다. DateTimeValue는 JavaScript Date가 나타내는 UTC 시점을 유지합니다. 따라서 현지 자정으로 날짜를 만들면 일부 시간대에서 표시되는 날짜가 이동할 수 있습니다. 특정 달력 날짜를 유지하려면 Date.UTC()를 사용하세요.

Excel 워크시트에 수식 추가

수식을 사용하면 생성된 파일이 단순한 데이터 덤프가 아니라 실제 스프레드시트가 됩니다. 각 행의 Quantity × Price를 계산하는 Total 열과 맨 아래에 총합계를 추가해 보겠습니다:

// Add "Total" header
sheet.Range.get({ row: 1, column: 4 }).Text = "Total";

// Per-row formula: Total = Quantity × Price
for (let i = 2; i <= 5; i++) {
    sheet.Range.get({ row: i, column: 4 }).Formula = `=B${i}*C${i}`;
}

// Grand total row
sheet.Range.get({ row: 6, column: 1 }).Text = "Total";
sheet.Range.get({ row: 6, column: 2 }).Formula = "=SUM(B2:B5)";
sheet.Range.get({ row: 6, column: 4 }).Formula = "=SUM(D2:D5)";

// Evaluate the formulas once, so their results are written into the file.
workbook.CalculateAllValue();

생성된 통합 문서에 계산된 수식 결과가 포함되어야 하는 경우 저장하기 전에 workbook.CalculateAllValue()를 호출하세요. 이는 열 때 수식을 다시 계산하는 대신 캐시된 값에 의존하는 뷰어나 애플리케이션에 유용합니다.

Excel 함수 및 수식 작업에 대한 종합 가이드는 React에서 JavaScript로 Excel 워크시트의 함수 및 수식 삽입 또는 읽기를 참조하세요.

생성된 Excel 파일 서식 지정

원시 데이터가 있는 스프레드시트도 작동하지만, 서식이 지정된 스프레드시트는 의사소통을 합니다. 판매 보고서를 이해관계자에게 실제로 보낼 수 있는 형태로 바꿔 보겠습니다:

// Bold, colored header row
const header = sheet.Range.get("A1:D1");
header.Style.Font.IsBold = true;
header.Style.Font.Size = 12;
header.Style.Color = xls.Color.get_LightSkyBlue();

// Currency format for Price and Total columns
for (let i = 2; i <= 5; i++) {
    sheet.Range.get({ row: i, column: 3 }).NumberFormat = "$#,##0.00";
    sheet.Range.get({ row: i, column: 4 }).NumberFormat = "$#,##0.00";
}

// Set column widths explicitly
[26, 10, 12, 12].forEach((width, i) => {
    sheet.Columns.get(i).ColumnWidth = width;
});

// Clean borders
const usedRange = sheet.Range.get("A1:D6");
usedRange.Borders.LineStyle = xls.LineStyleType.Thin;
usedRange.Borders.Color = xls.Color.get_LightSteelBlue();

여기서 알아 둘 만한 두 가지 세부 사항이 있습니다. sheet.Columns.get(i)와 sheet.Rows.get(i)는 0부터 시작하며 다른 컬렉션이 노출하는 get_Item과 달리 get을 사용합니다. 그리고 테스트한 브라우저 설정에서는 AutoFitColumn에 WebAssembly 샌드박스에서 사용할 수 없는 글꼴이 필요하므로 ColumnWidth를 명시적으로 설정하는 것이 더 안정적입니다.

결과: 굵은 파란색 헤더, 통화 형식의 가격, 적절한 크기의 열, 깔끔한 테두리입니다.

행과 열 크기에 대한 자세한 지침은 React에서 JavaScript로 Excel의 행 높이 및 열 너비 설정을 참조하세요.

Excel 통합 문서에서 여러 워크시트 만들기

실제 보고서는 하나의 시트에 담기 어렵습니다. 판매 보고서에는 첫 번째 탭에 요약, 두 번째 탭에 제품 세부 정보, 세 번째 탭에 월별 분석이 있을 수 있습니다.

workbook.Worksheets.Clear();
const summarySheet = workbook.Worksheets.Add("Summary");
const productsSheet = workbook.Worksheets.Add("Products");
const monthlySheet = workbook.Worksheets.Add("Monthly Data");

// Products sheet — write actual data so cross-sheet formulas work
productsSheet.Range.get("A1").Text = "Product";
productsSheet.Range.get("B1").Text = "Quantity";
productsSheet.Range.get("C1").Text = "Price";
productsSheet.Range.get("D1").Text = "Total";

const products = [
    ["Laptop", 10, 999.99],
    ["Mouse", 50, 24.99],
    ["Keyboard", 30, 59.99],
    ["Monitor", 15, 329.99]
];

for (let i = 0; i < products.length; i++) {
    const row = i + 2;
    productsSheet.Range.get({ row: row, column: 1 }).Text = products[i][0];
    productsSheet.Range.get({ row: row, column: 2 }).NumberValue = products[i][1];
    productsSheet.Range.get({ row: row, column: 3 }).NumberValue = products[i][2];
    productsSheet.Range.get({ row: row, column: 4 }).Formula = `=B${row}*C${row}`;
}

// Summary sheet with cross-sheet reference
summarySheet.Range.get("A1").Text = "Sales Summary";
summarySheet.Range.get("A1").Style.Font.IsBold = true;
summarySheet.Range.get("A2").Text = "Total Products";
summarySheet.Range.get("B2").NumberValue = 4;
summarySheet.Range.get("A3").Text = "Total Revenue";
summarySheet.Range.get("B3").Formula = "=SUM(Products!D2:D5)";

각 Worksheets.Add(name)는 호출된 순서대로 방금 생성한 시트를 반환하므로 탭 순서가 코드 순서와 일치합니다. 시트 간 수식에 주목하세요. Summary 시트의 =SUM(Products!D2:D5)는 Products 시트의 Total 열을 참조합니다. Excel은 파일을 열 때 이를 자동으로 처리하므로 추가 코드가 필요하지 않습니다.

Multi-sheet workbook with Summary and Products tabs

워크시트 관리(시트 추가, 제거, 순서 변경)에 대한 자세한 내용은 React에서 JavaScript로 Excel 워크시트 추가, 제거 및 이동을 참조하세요.

브라우저에서 XLSX 파일 저장 및 다운로드

통합 문서가 준비되면 Spire.XLS는 이를 WebAssembly 가상 파일 시스템에 저장합니다. 그런 다음 파일 데이터를 읽고 Blob으로 변환한 뒤 다운로드를 트리거합니다:

workbook.SaveToFile({
    fileName: "Report.xlsx",
    version: xls.ExcelVersion.Version2016
});

const fileData = window.dotnetRuntime.Module.FS.readFile("Report.xlsx");
const blob = new Blob([fileData], {
    type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "Report.xlsx";
a.click();
URL.revokeObjectURL(url);

workbook.Dispose();

다운로드 후에는 항상 workbook.Dispose()를 호출하여 메모리를 해제하세요. 특히 사용자가 한 세션에서 여러 파일을 생성하는 앱에서는 더욱 중요합니다. 그렇게 할 때까지 통합 문서 자체는 WebAssembly 힙에 남아 있으며, 해당 힙은 브라우저의 가비지 컬렉터가 회수하지 않습니다.

React 통합 또는 HTML 표 내보내기를 찾고 계신가요? JavaScript 및 React에서 Excel 파일 다운로드 및 내보내기 가이드를 확인하세요.

종합하기: 서식이 지정된 판매 보고서

위의 코드 조각은 한 번에 하나의 관심사씩 통합 문서를 구성합니다. 여기에서는 이를 하나의 함수로 모았습니다. 줄무늬 음영과 통화 형식이 있는 스타일 지정된 워크시트, 수식 기반 매출 및 점유율 열, 세로 막대형 차트, 숫자를 집계하는 두 번째 워크시트가 포함됩니다.

/**
 * Build a formatted sales report workbook in the browser and download it as
 * SalesReport.xlsx.
 *
 * The layout is driven by `products` below — swap it for form input, an API
 * response or component state and nothing else needs to change.
 */
async function createExcelReport() {
  // spire.office 11.7.0 exposes Spire.XLS as `window.spirexls`. Older builds
  // hung it off `window.wasmModule.spirexls`; that global no longer exists.
  const xls = window.spirexls;
  if (!xls) throw new Error('Spire.XLS is not ready yet');

  // [product, units sold, unit price] — revenue and share are derived by formula
  const products = [
    ['Atlas 14 Ultrabook', 42, 1249.0],
    ['Orbit Wireless Mouse', 380, 24.99],
    ['Vertex Mechanical Keyboard', 165, 89.5],
    ['Lumen 27 4K Monitor', 74, 429.0],
    ['Halo USB-C Dock', 210, 139.0],
    ['Pulse ANC Headset', 128, 199.0],
  ];
  const FIRST = 5; // first data row
  const TOTAL = FIRST + products.length; // total row

  const wb = new xls.Workbook();
  wb.Worksheets.Clear();
  const sheet = wb.Worksheets.Add('Sales Report');

  const at = (a) => sheet.Range.get(a);
  const cell = (row, col) => sheet.Range.get({ row: row, column: col });
  const paint = (a, colour) => {
    at(a).Style.Color = colour;
  };

  // ── Sizing ── explicit widths (AutoFitColumn is unreliable in the WASM sandbox)
  [34, 9, 13, 14, 9].forEach((w, i) => {
    sheet.Columns.get(i).ColumnWidth = w;
  });
  sheet.Rows.get(0).RowHeight = 34; // title
  sheet.Rows.get(1).RowHeight = 20; // subtitle
  sheet.Rows.get(2).RowHeight = 8; // spacer
  sheet.Rows.get(3).RowHeight = 24; // header

  // ── Title band ── merge first, then style the whole merged area
  at('A1:E1').Merge();
  cell(1, 1).Text = 'Sales Report — Q3 2026';
  paint('A1:E1', xls.Color.get_DarkBlue());
  at('A1:E1').Style.Font.Color = xls.Color.get_White();
  at('A1:E1').Style.Font.IsBold = true;
  at('A1:E1').Style.Font.Size = 15;
  at('A1:E1').Style.VerticalAlignment = xls.VerticalAlignType.Center;

  at('A2:E2').Merge();
  cell(2, 1).Text = 'Region: West · Period: 1 Jul – 30 Sep 2026 · Amounts in USD';
  paint('A2:E2', xls.Color.get_DarkBlue());
  at('A2:E2').Style.Font.Color = xls.Color.get_LightSteelBlue();
  at('A2:E2').Style.Font.Size = 9.5;
  at('A2:E2').Style.VerticalAlignment = xls.VerticalAlignType.Center;

  // ── Header row ───────────────────────────────────────────────────────────
  ['Product', 'Units', 'Unit Price', 'Revenue', 'Share'].forEach((label, i) => {
    cell(4, i + 1).Text = label;
  });
  paint('A4:E4', xls.Color.get_LightSteelBlue());
  at('A4:E4').Style.Font.Color = xls.Color.get_DarkBlue();
  at('A4:E4').Style.Font.IsBold = true;
  at('A4:E4').Style.Font.Size = 10.5;
  at('A4:E4').Style.HorizontalAlignment = xls.HorizontalAlignType.Center;
  at('A4:E4').Style.VerticalAlignment = xls.VerticalAlignType.Center;

  // ── Data rows ── numbers go in as NumberValue, never as text
  products.forEach(([name, units, price], i) => {
    const r = FIRST + i;
    cell(r, 1).Text = name;
    cell(r, 2).NumberValue = units;
    cell(r, 3).NumberValue = price;
    cell(r, 4).Formula = `=B${r}*C${r}`;
    cell(r, 5).Formula = `=D${r}/$D${TOTAL}`; // share of the grand total
    sheet.Rows.get(r - 1).RowHeight = 20;
    if (i % 2) paint(`A${r}:E${r}`, xls.Color.get_WhiteSmoke()); // zebra banding
  });

  // ── Total row ────────────────────────────────────────────────────────────
  cell(TOTAL, 1).Text = 'Total';
  [2, 4, 5].forEach((col) => {
    const letter = String.fromCharCode(64 + col);
    cell(TOTAL, col).Formula = `=SUM(${letter}${FIRST}:${letter}${TOTAL - 1})`;
  });
  paint(`A${TOTAL}:E${TOTAL}`, xls.Color.get_LightSkyBlue());
  at(`A${TOTAL}:E${TOTAL}`).Style.Font.IsBold = true;
  sheet.Rows.get(TOTAL - 1).RowHeight = 22;

  // ── Number formats and borders ───────────────────────────────────────────
  at(`B${FIRST}:B${TOTAL}`).NumberFormat = '#,##0';
  at(`C${FIRST}:D${TOTAL}`).NumberFormat = '$#,##0.00';
  at(`E${FIRST}:E${TOTAL}`).NumberFormat = '0.0%';

  const table = at(`A4:E${TOTAL}`);
  table.Borders.LineStyle = xls.LineStyleType.Thin;
  table.Borders.Color = xls.Color.get_LightSteelBlue();
  at(`A${TOTAL}:E${TOTAL}`).Borders.get_Item(xls.BordersLineType.EdgeTop).LineStyle =
    xls.LineStyleType.Medium;

  // ── Chart ── build the series by hand (DataRange would mix units)
  const chart = sheet.Charts.Add();
  chart.ChartType = xls.ExcelChartType.ColumnClustered;
  chart.LeftColumn = 6;
  chart.TopRow = 3;
  chart.RightColumn = 13;
  chart.BottomRow = 21;
  const serie = chart.Series.Add();
  serie.CategoryLabels = at(`A${FIRST}:A${TOTAL - 1}`);
  serie.Values = at(`D${FIRST}:D${TOTAL - 1}`);
  serie.Name = 'Revenue';
  chart.ChartTitleArea.Text = 'Revenue by product';
  chart.HasLegend = false;
  chart.PrimaryValueAxis.NumberFormat = '$#,##0';

  // ── Sheet chrome ──
  sheet.FreezePanes(FIRST, 1);
  sheet.GridLinesVisible = false;
  sheet.TabColor = xls.Color.get_DarkBlue();

  // ── Second worksheet: cross-sheet roll-up ──
  const summary = wb.Worksheets.Add('Summary');
  summary.Columns.get(0).ColumnWidth = 26;
  summary.Columns.get(1).ColumnWidth = 18;
  summary.Rows.get(0).RowHeight = 30;
  summary.Rows.get(1).RowHeight = 18;
  summary.Rows.get(3).RowHeight = 22;

  const band = (a, text, size) => {
    summary.Range.get(a).Merge();
    summary.Range.get(a.split(':')[0]).Text = text;
    summary.Range.get(a).Style.Color = xls.Color.get_DarkBlue();
    summary.Range.get(a).Style.Font.Color = xls.Color.get_White();
    summary.Range.get(a).Style.Font.IsBold = true;
    summary.Range.get(a).Style.Font.Size = size;
    summary.Range.get(a).Style.VerticalAlignment = xls.VerticalAlignType.Center;
  };
  band('A1:B1', 'Executive Summary', 14);
  band('A2:B2', 'Sales Report · Q3 2026', 9.5);

  ['Metric', 'Value'].forEach((label, i) => {
    summary.Range.get({ row: 4, column: i + 1 }).Text = label;
  });
  summary.Range.get('A4:B4').Style.Color = xls.Color.get_LightSteelBlue();
  summary.Range.get('A4:B4').Style.Font.Color = xls.Color.get_DarkBlue();
  summary.Range.get('A4:B4').Style.Font.IsBold = true;

  [
    ['Total revenue', "='Sales Report'!D" + TOTAL, '$#,##0.00'],
    ['Units shipped', "='Sales Report'!B" + TOTAL, '#,##0'],
    ['Average unit price', `='Sales Report'!D${TOTAL}/'Sales Report'!B${TOTAL}`, '$#,##0.00'],
  ].forEach(([label, formula, format], i) => {
    summary.Range.get({ row: 5 + i, column: 1 }).Text = label;
    const value = summary.Range.get({ row: 5 + i, column: 2 });
    value.Formula = formula;
    value.NumberFormat = format;
    value.Style.HorizontalAlignment = xls.HorizontalAlignType.Right;
    if (i % 2) summary.Range.get(`A${5 + i}:B${5 + i}`).Style.Color = xls.Color.get_WhiteSmoke();
  });

  // Date — use Date.UTC to avoid timezone shift
  summary.Range.get('A8').Text = 'Report date';
  const reportDate = summary.Range.get('B8');
  reportDate.DateTimeValue = new Date(Date.UTC(2026, 8, 28));
  reportDate.NumberFormat = 'yyyy-mm-dd';
  reportDate.Style.HorizontalAlignment = xls.HorizontalAlignType.Right;

  const kpis = summary.Range.get('A4:B8');
  kpis.Borders.LineStyle = xls.LineStyleType.Thin;
  kpis.Borders.Color = xls.Color.get_LightSteelBlue();
  summary.GridLinesVisible = false;
  summary.TabColor = xls.Color.get_LightSteelBlue();

  // ── Save, then release ──
  wb.CalculateAllValue();
  const fileName = 'SalesReport.xlsx';
  wb.SaveToFile({ fileName: fileName, version: xls.ExcelVersion.Version2016 });

  const fileData = window.dotnetRuntime.Module.FS.readFile(fileName);
  const blob = new Blob([fileData], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  });
  const url = URL.createObjectURL(blob);
  Object.assign(document.createElement('a'), { href: url, download: fileName }).click();
  URL.revokeObjectURL(url);

  wb.Dispose(); // free the WASM heap — one workbook per generation cycle
}

레이아웃은 데이터를 따릅니다. products 배열을 자체 API의 응답으로 바꾸면 나머지 모든 것(합계, 점유율 백분율, 차트 범위, 요약 집계)이 계속 작동합니다. 모두 하드 코딩된 값이 아니라 수식이나 파생 범위로 표현되기 때문입니다.

Complete formatted sales report with chart, zebra banding, and summary worksheet

더 많은 차트 유형과 구성 옵션은 React에서 JavaScript로 Excel 차트 만들기를 참조하세요.

CSV 데이터로 Excel 파일 만들기

CSV 파일의 표 형식 데이터를 워크시트에 로드하고 결과를 XLSX로 저장할 수 있습니다. 그런 다음 결과 통합 문서에 서식을 지정하거나 수식과 추가 워크시트로 확장할 수 있습니다:

// The library reads from its own virtual file system, so the CSV has to be
// there before you can load it — written from a string here, from the bytes of
// a File object in a real application.
const csv = "Product,Quantity,Price\nLaptop,10,999.99\nMouse,50,24.99\nKeyboard,30,59.99";
window.dotnetRuntime.Module.FS.writeFile("data.csv", new TextEncoder().encode(csv));

workbook.LoadFromFile("data.csv", ",");
workbook.SaveToFile({
    fileName: "ConvertedFromCSV.xlsx",
    version: xls.ExcelVersion.Version2016
});

가져온 시트는 파일 이름(data)을 따서 지정되므로 Worksheets.get(0)으로 선택할 수 있습니다.

구분 기호는 필수 두 번째 인수입니다. LoadFromFile("data.csv")만 사용하면 This is not a structured storage file과 함께 거부됩니다. 단일 인수 오버로드는 .xlsx 또는 .xls와 같은 구조화된 형식을 기대하며 CSV를 감지하지 않기 때문입니다.

두 번째 주의 사항이 있습니다. CSV 가져오기는 모든 필드를 텍스트로 씁니다. 수량 열은 10이 아니라 "10"으로 들어오므로 숫자로 합계하거나 정렬할 수 없습니다. 이는 이 튜토리얼 앞부분에서 설명한 바로 그 문제입니다. 저장하기 전에 필요한 열을 변환하세요:

const csvSheet = workbook.Worksheets.get(0);

// The importer leaves every field as text — coerce each numeric column.
// Columns 2 and 3 hold Quantity and Price; both arrive as "10" and "999.99".
for (let row = 2; row <= 4; row++) {
    [2, 3].forEach((column) => {
        const cell = csvSheet.Range.get({ row: row, column: column });
        if (cell.Text !== "") {
            cell.NumberValue = Number(cell.Text);
        }
    });
}

CSV와 Excel 형식 간 변환에 대한 전체 가이드는 CSV를 Excel로 변환하는 튜토리얼을 참조하세요.

XLSX 대신 XLS 파일 만들기

애플리케이션에서 .xlsx 대신 레거시 .xls 형식을 생성해야 하는 경우 버전 매개 변수를 변경하세요:

workbook.SaveToFile({
    fileName: "Report.xls",
    version: xls.ExcelVersion.Version97to2003
});

열거형 멤버는 Version97to2003입니다. Version97은 없습니다. 차트는 XLSX에 유지하세요. 테스트한 XLS 작성기는 차트 글꼴 메트릭이 필요할 때 실패합니다.

XLSX(Excel 2007 이상)는 새 애플리케이션에 권장되는 형식입니다. XLS(Excel 97–2003)는 이전 버전과의 호환성이 필요할 때만 필요합니다.

일반적인 문제

window.wasmModule이(가) 정의되지 않았습니다. 이전 샘플에서는 window.wasmModule.spirexls에서 스프레드시트 API를 읽었습니다. 현재 릴리스에서는 이를 window.spirexls에 직접 설치하고 wasmModule은 정의되지 않은 상태로 두므로 첫 번째 접근에서 바로 예외가 발생합니다. 대신 window.spirexls를 확인하세요.

WASM 모듈이 초기화되지 않았습니다. window.spirexls가 정의되지 않은 경우 런타임이 아직 로드를 완료하지 않은 것입니다. 로딩 표시기를 표시하고 초기화가 완료될 때까지 기다린 후 Excel 작업을 시도하세요.

자동 맞춤에서 글꼴 오류가 발생합니다. AutoFitColumn과 AutoFitRow는 텍스트를 측정하기 위해 시스템 글꼴이 필요하지만 WebAssembly 샌드박스에는 글꼴이 없습니다. 이들은 Cannot found font(Arial) installed on the system. 오류와 함께 실패합니다. sheet.Columns.get(i).ColumnWidth로 열 너비를 계산하거나 하드 코딩하세요.

숫자가 텍스트로 저장됩니다. cell.NumberValue = 100 대신 cell.Text = "100"을 사용하면 정렬과 계산이 깨집니다. 이는 개발자가 JavaScript에서 Excel 파일을 작성할 때 가장 흔히 겪는 문제이며 CSV 가져오기에서 자동으로 발생합니다. 숫자 데이터에는 항상 NumberValue를 사용하세요.

"평가판 경고" 시트가 나타납니다. 유효한 라이선스 없이 평가판 빌드를 사용하면 Spire.XLS가 저장된 통합 문서에 평가판 시트를 추가합니다. 이 시트는 저장할 때 다시 생성되므로 프로그래밍 방식으로 제거하는 것은 안정적인 해결 방법이 아닙니다. 라이선스 키를 적용하여 방지하세요.

장기 실행 앱에서 메모리 누수. 모든 생성 주기 후에 workbook.Dispose()를 호출하세요. 단일 페이지 애플리케이션에서는 해제되지 않은 통합 문서가 메모리를 축적하고 시간이 지남에 따라 성능을 저하시킵니다.

자주 묻는 질문

JavaScript로 Microsoft Excel 없이 Excel 파일을 만들 수 있나요?

예. JavaScript용 Spire.XLS는 WebAssembly를 통해 전적으로 브라우저에서 실행됩니다. XLSX 파일을 생성하는 데 Microsoft Excel이나 서버 측 Office 설치가 필요하지 않습니다.

JavaScript로 브라우저에서 직접 XLSX 파일을 만들 수 있나요?

예. 모든 스프레드시트 작업은 클라이언트 측에서 이루어집니다. 파일은 가상 파일 시스템에 저장된 다음 Blob으로 다운로드됩니다. 백엔드 서버가 필요하지 않습니다.

XLS와 XLSX의 차이점은 무엇인가요?

XLSX(Excel 2007 이상)는 최신 XML 기반 형식이며 새 애플리케이션에 권장됩니다. XLS(Excel 97–2003)는 레거시 이진 형식으로, 이전 시스템 호환성에 유용합니다.

결론

JavaScript로 Excel 파일을 만드는 데 백엔드 서버나 Microsoft Excel이 필요하지 않습니다. JavaScript용 Spire.XLS를 사용하면 브라우저에서 처음부터 통합 문서를 만들고, 형식이 지정된 데이터를 쓰고, 수식을 추가하고, 서식을 적용하고, 여러 시트에 걸쳐 데이터를 구성할 수 있습니다.

위의 기본 예제로 시작한 다음 필요에 따라 수식과 서식을 추가하세요. React 관련 통합 및 HTML 표 내보내기는 전용 내보내기 튜토리얼을 참조하세요.

참고 항목