Convert Word Documents to HTML in the Browser with JavaScript

Word documents are often the starting point for web content — articles, product specs, and compliance docs all need to live on a website eventually. Getting from .docx to clean HTML without a backend conversion service is the challenge. Spire.Doc for JavaScript makes this possible by running a full document-processing engine on WebAssembly, reading the Word file through a virtual file system (VFS), performing the conversion locally, and letting you download the resulting HTML — all client-side, with no server round-trip.
Two export strategies dominate the workflow, and choosing between them is the real decision:
- Embedded mode bundles CSS and images directly into the HTML file, producing a single self-contained document that opens anywhere.
- External mode writes CSS and images to separate files, giving you smaller HTML, reusable stylesheets, and individual image assets you can manage independently.
This article walks through both approaches in a React project and compares them side by side. For setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.
Basic Conversion: Embed Everything in One File
The simplest way to publish a Word document as a web page is to produce a single HTML file that contains everything — markup, styles, and images — in one self-contained package. This is ideal when you need a portable artifact that renders correctly no matter where it is opened, with no missing-file references or broken links.
The conversion follows three steps. First, load the font file and the source Word document into the WASM virtual file system using FetchFileToVFS. Second, create a Document instance, load the file, configure HtmlExportOptions to embed both CSS and images, and call SaveToFile to write the HTML. Third, read the generated file back from VFS, wrap it in a Blob, and trigger a browser download.
function App() {
const wordToHtml = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load fonts and the Word file into VFS
await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
const inputFileName = 'ToHtml.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);
// Load the Word document
const wordDocument = new docModule.Document();
wordDocument.LoadFromFile(inputFileName);
// Embed the CSS styles into the HTML and embed images as Base64
wordDocument.HtmlExportOptions.CssStyleSheetType = docModule.CssStyleSheetType.Internal;
wordDocument.HtmlExportOptions.ImageEmbedded = true;
// Convert the document to HTML
const outputFileName = 'ToHtml-result.html';
wordDocument.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Html });
// Read the converted file from VFS and trigger download
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = window.document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
// Release resources
wordDocument.Dispose();
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Convert Word To HTML</h1>
<button onClick={wordToHtml}>
Generate
</button>
</div>
);
}
export default App;
HTML page generated from a Word document via SaveToFile

Export Options: Separate CSS and Images
Embedding everything into one file is convenient, but it has trade-offs. A large document with many images produces a very big HTML file, and every page that shares the same styling carries its own duplicate copy of the CSS. When you want to maintain styles centrally, reuse image assets across pages, or keep the HTML payload small for faster initial rendering, you should export CSS and images as separate files instead.
HtmlExportOptions gives you fine-grained control over how each resource type is written. You can direct the CSS to a named stylesheet file, send images to a dedicated directory, and even control how form fields are serialized. The result is no longer a single file but a directory structure containing the HTML, the stylesheet, and the image files.
The workflow mirrors the embedded approach, with two additions. Before conversion, create an output directory in VFS and use CssStyleSheetFileName and ImagesPath to tell Spire.Doc where to write each resource type. After conversion, read the entire output directory recursively, package everything into a zip archive using JSZip, and download it in one operation.
import JSZip from 'jszip';
function App() {
const wordToHtmlWithOptions = async () => {
// Get the Spire.Doc WASM module
const docModule = window.wasmModule?.spiredoc;
// Check if the module is ready
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load fonts and the Word file into VFS
await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
const inputFileName = 'ToHtml.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);
// Create the output directory in VFS
const outputDirectoryName = 'ToHTMLFolder/';
window.dotnetRuntime.Module.FS.mkdirTree(outputDirectoryName);
// Load the Word document
const wordDocument = new docModule.Document();
wordDocument.LoadFromFile(inputFileName);
// Export the CSS styles to a separate file
wordDocument.HtmlExportOptions.CssStyleSheetFileName = outputDirectoryName + 'sample.css';
wordDocument.HtmlExportOptions.CssStyleSheetType = docModule.CssStyleSheetType.External;
// Export images to a separate directory
wordDocument.HtmlExportOptions.ImageEmbedded = false;
wordDocument.HtmlExportOptions.ImagesPath = outputDirectoryName + 'Demo/';
// Export form fields as plain text
wordDocument.HtmlExportOptions.IsTextInputFormFieldAsText = true;
// Convert the document to HTML
const outputFileName = 'ToHtmlExportOption-out.html';
wordDocument.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Html });
// Release resources
wordDocument.Dispose();
// Read the output directory recursively and write each level of files into the zip
const zip = new JSZip();
const addFilesToZip = async (folderPath, zipFolder) => {
let items = await window.dotnetRuntime.Module.FS.readdir(folderPath);
items = items.filter((item) => item !== '.' && item !== '..');
for (const item of items) {
const itemPath = `${folderPath}/${item}`;
try {
const fileData = await window.dotnetRuntime.Module.FS.readFile(itemPath);
zipFolder.file(item, fileData);
} catch (error) {
const zipSubFolder = zipFolder.folder(item);
await addFilesToZip(itemPath, zipSubFolder);
}
}
};
// Package the HTML file together with the resource directory
zip.file(outputFileName, window.dotnetRuntime.Module.FS.readFile(outputFileName));
await addFilesToZip(outputDirectoryName, zip);
const zipBlob = await zip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(zipBlob);
// Trigger download
const a = window.document.createElement('a');
a.href = url;
a.download = 'ToHTMLFolder.zip';
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Convert Word To HTML With Export Options</h1>
<button onClick={wordToHtmlWithOptions}>
Generate
</button>
</div>
);
}
export default App;
HTML, CSS, and image files generated after configuring the export options

One detail worth noting: Spire.Doc does not place images directly in the directory specified by ImagesPath. Instead, it creates an external_images subfolder inside that directory to hold the image files. The resulting structure looks like Demo/external_images/*.png, which is why addFilesToZip walks the directory tree recursively rather than reading a flat list of files.
Embedded vs. External: Choosing the Right Strategy
Both export modes produce valid HTML from the same Word document, but they serve different publishing needs. The table below summarizes the key differences to help you decide which approach fits your workflow.
| Aspect | Embedded (Single File) | External (Separate Files) |
|---|---|---|
| Output | One .html file with inline CSS and Base64 images |
HTML + .css + image files in a directory |
| File size | Larger — all assets are Base64-encoded into the HTML | Smaller HTML; total size is similar but assets are individual files |
| Portability | Fully self-contained; opens correctly anywhere with no dependencies | Requires all files to stay together; relative paths must be preserved |
| Download mechanism | Single file download via Blob | Zip archive download (e.g., with JSZip) |
| Style reuse | Each document carries its own copy of the CSS | Multiple pages can share one stylesheet file |
| Image management | Images are Base64 strings inside the HTML; cannot be referenced or cached separately | Images are individual files that can be cached, lazy-loaded, or reused |
| Initial render speed | Slower for large documents — the browser must parse one big file | Faster initial HTML parse; CSS and images load in parallel |
| Best for | Email attachments, one-off previews, archival snapshots, sharing a single document | CMS content migration, multi-page publishing, knowledge bases, sites with shared styling |
| Maintainability | Low — changing a style means regenerating the entire file | High — edit the CSS file once and all linked pages update |
Quick decision guide:
- Choose embedded when you need a single, portable artifact — for example, generating a preview that a user downloads and opens offline, or attaching a converted document to an email.
- Choose external when you are publishing to a web platform where multiple documents share the same design system, where you want to cache or lazy-load images, or where the HTML file size matters for performance.
FAQ
Fonts in the exported HTML do not match the original document
If the fonts in your converted HTML look different from the source Word file, the cause is almost always missing font data in the WASM virtual file system. Spire.Doc relies on fonts loaded into VFS to perform accurate layout calculations and font-name resolution during conversion. When a required font is not available, the engine substitutes a fallback font, and the font-family declarations in the output CSS will not match what the original document specifies. For documents that use symbol fonts such as Wingdings, the affected characters may also render as garbled text.
The fix is straightforward: preload the necessary font files into VFS via FetchFileToVFS before you run the conversion. For documents containing Chinese, Japanese, or Korean text, use a font with broad Unicode coverage such as ARIALUNI.TTF:
await window.spire.FetchFileToVFS(
'ARIALUNI.TTF', '/Library/Fonts/', '/'
);
Exported HTML loses its styles and images when opened
When you use external mode (CssStyleSheetType.External with ImageEmbedded = false), the CSS and image files are written to separate locations, and the HTML references them through relative paths. If you download only the HTML file without its accompanying resources, the browser cannot resolve those paths and the page falls back to unstyled plain text with broken images.
To avoid this, always package the HTML together with its resource directory — the addFilesToZip approach shown in the export-options section handles this by bundling everything into a single zip download. Alternatively, if you do not actually need separate resource files, switch to embedded mode so everything stays in one self-contained HTML file:
wordDocument.HtmlExportOptions.CssStyleSheetType = docModule.CssStyleSheetType.Internal;
wordDocument.HtmlExportOptions.ImageEmbedded = true;
See Also
Set Word Document Backgrounds with JavaScript

Every contract, official letter, and brand collateral piece carries an implicit visual identity. A plain white page gets the job done, but it says nothing about the organization behind it. The moment you add a soft tint, a subtle two-tone gradient, or a tiled background image, the entire document shifts from a generic file into a recognizable branded artifact — and your readers notice, even if they cannot articulate why.
Spire.Doc for JavaScript brings this visual styling directly into the browser through WebAssembly. There is no server round-trip, no Office automation dependency, and no desktop install requirement. You load a Word file into the WASM virtual file system (VFS), pick one of three background modes, and export the styled document — all client-side in a React application.
This guide walks through each of the three background options not as an API catalog, but as a set of design decisions. We start with a quick comparison so you can match the right technique to your use case, then dive into the implementation details for each one.
Three Background Approaches at a Glance
Before writing any code, it helps to understand what each background type brings to the table from a design perspective. The table below summarizes the visual outcome, the amount of configuration involved, and the scenarios where each approach shines.
| Approach | Visual Effect | Configuration Effort | Best Suited For |
|---|---|---|---|
| Solid Color | A single uniform color fills every page | Low — set BackgroundType.Color and assign one color |
Contracts, internal memos, official letters that need a clean, professional base tone |
| Gradient | A two-color directional blend across the page | Medium — define Color1, Color2, plus ShadingStyle and ShadingVariant |
Cover pages, certificates, marketing templates that benefit from subtle depth |
| Picture | A background image tiled across the entire page | Medium — load the image into VFS, then call SetPicture |
Branded stationery, letterhead with decorative elements, themed document templates |
All three share the same overall workflow: load the source document into the VFS, configure the Background property on a Document instance, save the result, and trigger a browser download. The differences lie entirely in how you configure that Background property — which is where the design choices come in.
For project setup and installation instructions, see Integrating Spire.Doc for JavaScript in a React Project. The code examples below assume the WASM module is already initialized and available on window.wasmModule.
Solid Color Background
A solid color is the most restrained background choice — and often the most effective. A warm cream or pale gray behind black text reduces eye strain without competing for attention. For formal documents like contracts and policy papers, a subtle tint signals "this document belongs to a specific organization" without crossing into decoration.
The implementation follows three clean steps. First, use FetchFileToVFS to load the target Word file (and font files) into the WASM virtual file system. Second, create a Document, load the file, set Background.Type to BackgroundType.Color, and assign a built-in color to Background.Color. Third, save the document back to the VFS with SaveToFile, read the resulting file as a byte array, wrap it in a Blob, and initiate a download.
function App() {
const SetSolidColorBackground = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the sample file into the virtual file system (VFS)
let inputFileName = "ScienceTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// Create Word document
let doc = new docModule.Document();
// Load the file
doc.LoadFromFile(inputFileName);
// Set the background type as Color
doc.Background.Type = docModule.BackgroundType.Color;
// Set the background color
doc.Background.Color = docModule.Color.get_LightYellow();
// Define the output file name
const outputFileName = "SetSolidColorBackground_out.docx";
// Save the document to the specified path
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set a Solid Color Background for a Word Document</h1>
<button onClick={SetSolidColorBackground}>Generate</button>
</div>
);
}
export default App;
Once Background.Color is applied, every page in the document is filled with the chosen built-in color — in this case, LightYellow.

Gradient Background
Gradients introduce a sense of dimension that flat colors cannot. A top-to-bottom transition from white to pale blue, for example, evokes sky and openness — useful for certificates, award letters, or any document where a touch of ceremony is appropriate. The key is restraint: pick two closely related colors and let the gradient do the work quietly.
The code mirrors the solid color workflow, but the middle step expands. After setting Background.Type to BackgroundType.Gradient, you retrieve the gradient object via Background.Gradient and configure four properties: Color1 (start color), Color2 (end color), ShadingVariant (transition direction), and ShadingStyle (axis of the gradient).
function App() {
const SetGradientBackground = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the sample file into the virtual file system (VFS)
let inputFileName = "ScienceTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// Create Word document
let doc = new docModule.Document();
// Load the file
doc.LoadFromFile(inputFileName);
// Set the background type as Gradient
doc.Background.Type = docModule.BackgroundType.Gradient;
let gradient = doc.Background.Gradient;
// Set the start color and the end color of the gradient
gradient.Color1 = docModule.Color.get_White();
gradient.Color2 = docModule.Color.get_LightBlue();
// Set the shading style and variant of the gradient
gradient.ShadingVariant = docModule.GradientShadingVariant.ShadingDown;
gradient.ShadingStyle = docModule.GradientShadingStyle.Horizontal;
// Define the output file name
const outputFileName = "SetGradientBackground_out.docx";
// Save the document to the specified path
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set a Gradient Background for a Word Document</h1>
<button onClick={SetGradientBackground}>Generate</button>
</div>
);
}
export default App;
After applying Background.Gradient, the page is filled with a smooth horizontal transition from white to light blue, flowing downward.

Picture Background
A picture background is the most expressive option. Whether it is a subtle watermark pattern, a corporate texture, or a decorative motif for event programs, a tiled image can carry branding elements that color and gradient simply cannot. The trade-off is file weight — the image must be loaded into the VFS alongside the document — so reserve this approach for templates where the visual payoff justifies the extra resource.
The setup differs from the previous two methods in one important way: the background image must also be loaded into the VFS using FetchFileToVFS before it can be referenced. Once both the document and the image are in the VFS, set Background.Type to BackgroundType.Picture and call Background.SetPicture with the image's VFS path. The image is then tiled across every page as the background.
function App() {
const SetImageBackground = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the sample file into the virtual file system (VFS)
let inputFileName1 = "ScienceTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName1, "", `${process.env.PUBLIC_URL}static/data/`);
// Load the background image into the virtual file system (VFS)
let inputFileName2 = "Background.png";
await window.spire.FetchFileToVFS(inputFileName2, "", `${process.env.PUBLIC_URL}static/data/`);
// Load a Word document
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName1);
// Set the background type as Picture
doc.Background.Type = docModule.BackgroundType.Picture;
// Set the background picture
doc.Background.SetPicture(inputFileName2);
// Define the output file name
const outputFileName = "SetImageBackground_out.docx";
// Save the document to the specified path
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set a Picture Background in a Word Document</h1>
<button onClick={SetImageBackground}>Generate</button>
</div>
);
}
export default App;
After calling Background.SetPicture, the specified image is tiled across the entire page surface as the document background.

Printing Considerations
There is one practical caveat that catches many developers off guard: Microsoft Word does not print page backgrounds by default. This is not a bug in your code or a limitation of Spire.Doc — the background is correctly stored in the document and displays normally on screen. Word simply omits it from printed output unless you explicitly tell it otherwise.
To ensure backgrounds appear in printed copies, the end user needs to enable a specific setting in their Word client:
- Open the document in Microsoft Word.
- Go to File > Options > Display.
- Check Print background colors and images.
- Print as usual.
If you need the background to render in every output environment regardless of the reader's Word settings, consider an alternative approach: place a full-page shape in the document header or use a watermark to simulate the background effect. These techniques are treated as content rather than page formatting, so they print reliably across all configurations.
FAQ
Why does the background not show up when I print the document?
This is expected behavior. Word suppresses page backgrounds in print output by default — the setting is stored correctly and renders on screen, but the Word client's print options filter it out. The background has not been lost; it is simply not included in the print stream.
To fix this, enable Print background colors and images under File > Options > Display in Word before printing. For environments where you cannot control the reader's print settings, use a full-page shape in the header or a watermark to replicate the visual effect, as those elements are treated as printable content.
Why does the picture background have no effect?
This typically happens for one of two reasons: either Background.Type was not set to BackgroundType.Picture before calling SetPicture, or the image file was never loaded into the VFS via FetchFileToVFS, so SetPicture cannot locate it.
Make sure you set the background type first and pass the exact filename of an image that has already been loaded into the virtual file system:
document.Background.Type = wasmModule.BackgroundType.Picture;
document.Background.SetPicture("Background.png");
See Also
Control Page Breaks in Word Documents with JavaScript

Anyone who has formatted a long Word document in the browser knows the pain: a section heading sits orphaned at the bottom of a page while its body text begins on the next, or content pasted from another file drags along a chain of unwanted blank pages. Both problems almost always trace back to page breaks — either absent where they should be, or left behind where they should not.
Spire.Doc for JavaScript runs entirely in the browser through WebAssembly, using a virtual file system (VFS) to handle file I/O. That means you can insert and remove page breaks without any backend round-trip. This guide covers both operations and tackles two edge cases that frequently surface with real-world documents:
- Insert a page break at a specific paragraph
- Remove all page breaks in one pass
- Fix the extra blank line that appears after inserting a break
- Fix the document that still paginates after breaks are removed
For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume the WASM module is already initialized.
Insert a Page Break at a Specific Paragraph
The workflow is three steps: load the target document into the WASM virtual file system via FetchFileToVFS, open it as a Document object, then locate the paragraph where you want the split to happen and call AppendBreak with BreakType.PageBreak. The break is appended as a child object at the end of that paragraph, so everything after it flows onto the next page. Finally, read the saved file from VFS, wrap it as a Blob, and trigger a browser download.
function App() {
const InsertPageBreak = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the target Word document into VFS
const inputFileName = "Template_Docx_1.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// Create a Document instance and load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Locate the fourth paragraph of the first section and append a page break to its end
doc.Sections.get_Item(0).Paragraphs.get_Item(3).AppendBreak(docModule.BreakType.PageBreak);
// Define the output file name
const outputFileName = "InsertPageBreak_out.docx";
// Save the document to VFS
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Insert a Page Break into a Word Document</h1>
<button onClick={InsertPageBreak}>Generate</button>
</div>
);
}
export default App;
The document after a page break is appended to the end of the fourth paragraph

Remove All Page Breaks in One Pass
When a document collects page breaks from repeated edits or copy-paste operations, you often need to strip them all out and let the content reflow naturally. The approach: iterate over every paragraph in the target section and inspect each child object. When a child is identified as a Break with BreakType.PageBreak, remove it via ChildObjects.Remove.
A critical detail in the inner loop — the child objects are traversed backwards, from the last index down to zero. Removing an element from a forward-traversed collection shifts the indices of every subsequent element, which can cause skipped items or out-of-bounds access. Reverse traversal sidesteps this problem entirely because each removal only affects indices you have already processed.
function App() {
const RemovePageBreaks = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the target Word document into VFS
const inputFileName = "Template_Docx_4.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// Create a Document instance and load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Get the first section
const section = doc.Sections.get_Item(0);
// Walk through every paragraph in the section
for (let j = 0; j < section.Paragraphs.Count; j++) {
const p = section.Paragraphs.get_Item(j);
// Walk the paragraph's child objects backwards, so that removing an element does not shift the indices still to come
for (let i = p.ChildObjects.Count - 1; i >= 0; i--) {
const obj = p.ChildObjects.get_Item(i);
// Test whether the object is a page break
if (obj.DocumentObjectType == docModule.DocumentObjectType.Break
&& obj.BreakType == docModule.BreakType.PageBreak) {
// Remove the page break from the paragraph
p.ChildObjects.Remove(obj);
}
}
}
// Define the output file name
const outputFileName = "RemovePageBreaks_out.docx";
// Save the document to VFS
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Remove Page Breaks from a Word Document</h1>
<button onClick={RemovePageBreaks}>Generate</button>
</div>
);
}
export default App;
The document after every page break has been removed

Extra Blank Line After Inserting a Page Break
After calling AppendBreak, you may spot an unwanted blank strip at the top of the new page. This occurs because AppendBreak attaches the break to the end of the target paragraph, and that paragraph's "space after" setting carries over to the beginning of the next page. When automatic spacing is enabled, the gap even scales with the font size — making it especially visible after large headings.
The fix is to neutralize the paragraph's trailing spacing before appending the break:
const para = document.Sections.get_Item(0).Paragraphs.get_Item(3);
// Turn off automatic spacing after the paragraph and set the space after it to 0
para.Format.AfterAutoSpacing = false;
para.Format.AfterSpacing = 0;
// Then insert the page break
para.AppendBreak(wasmModule.BreakType.PageBreak);
Setting AfterAutoSpacing to false and AfterSpacing to 0 ensures the paragraph contributes no vertical gap after the break, so the new page starts flush at the top margin.
Document Still Paginated After Removing Breaks
Even after successfully stripping every page break object, the document may still split at the same positions. The reason: pagination can also originate from a paragraph-level property called "page break before." When this flag is set on a paragraph, that paragraph always starts on a new page — regardless of whether a Break object exists. The removal loop above only targets break objects, so paragraphs carrying this flag remain untouched.
To fully eliminate forced pagination, reset the PageBreakBefore flag on every paragraph alongside the break-object cleanup:
const section = document.Sections.get_Item(0);
for (let j = 0; j < section.Paragraphs.Count; j++) {
const p = section.Paragraphs.get_Item(j);
// Clear the page-break-before property set on the paragraph
if (p.Format.PageBreakBefore) {
p.Format.PageBreakBefore = false;
}
}
You can run this loop in the same pass as the break-object removal or as a separate step afterward — the order does not matter because the two operations target independent properties.
FAQ
How do I get a free license for Spire.Doc for JavaScript?
Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. You can apply here to evaluate the product before purchasing.
See Also
Lock Down Editable Areas in Word Documents with JavaScript
Table of Contents

Picture a contract template that gets sent to dozens of clients. The legal team has carefully drafted every clause, and the only things each recipient should touch are the signature block, the project name, and the acceptance date. Hand them a fully editable Word file and someone will inevitably reword a penalty clause or delete a liability section. Lock the entire document and nobody can fill in the fields at all. What you really need is selective editing — a way to say "these specific paragraphs are fair game, everything else is frozen."
That is exactly what editable ranges give you. You protect the whole document as read-only, then drop a pair of permission markers around the paragraphs you want to keep open. Anyone opening the file in Word can type inside the marked region but cannot alter a single character outside it. Spire.Doc for JavaScript brings this capability to the browser through WebAssembly, so you can generate protected documents from a React app with no server round-trip — fonts and input files are managed through an in-memory virtual file system (VFS).
This guide walks through both halves of the workflow:
- Set an editable range — protect the document and mark the open area
- Remove an editable range — strip the markers and lift the restriction
If you have not yet wired Spire.Doc into your project, start with Integrating Spire.Doc for JavaScript in a React Project. The snippets below assume the WebAssembly module is loaded and ready.
Set an Editable Range
The process has three stages. First, pull the font files and the target Word document into the WASM virtual file system with FetchFileToVFS. Next, instantiate a Document, load the file, call Protect to lock the entire document as read-only, and then create a PermissionStart / PermissionEnd pair that shares the same id — these two markers bracket the paragraph you want to leave editable. Finally, save the file, read it back from VFS, wrap it in a Blob, and trigger a download.
function App() {
const SetEditableRange = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the input document into VFS
const inputFileName = "SetEditableRange.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// Create a document object and load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Protect the whole document: everything outside the editable range is read-only
doc.Protect({ type: docModule.ProtectionType.AllowOnlyReading, password: "password" });
// Create the permission markers: a start and an end with the same id form one editable range
const start = new docModule.PermissionStart(doc, "testID");
const end = new docModule.PermissionEnd(doc, "testID");
// Insert the markers into the first paragraph: the start at the beginning, the end appended at the end
doc.Sections.get_Item(0).Paragraphs.get_Item(0).ChildObjects.Insert(0, start);
doc.Sections.get_Item(0).Paragraphs.get_Item(0).ChildObjects.Add(end);
// Save the document
const outputFileName = "Set Editable Range.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Set Editable Range in a Word Document</h1>
<button onClick={SetEditableRange}>
Generate
</button>
</div>
);
}
export default App;
In the sample file, the fields a reviewer is allowed to fill in carry light shading — that is purely a visual cue for the reader and has no bearing on how the editable range is defined in code. Once the markers are in place, Word treats the shaded paragraph as editable and every other paragraph as locked.

Remove an Editable Range
Stripping the editable range is a single traversal: loop through every section and every paragraph, inspect each object in the paragraph's ChildObjects collection, and yank out anything that is a PermissionStart or PermissionEnd.
One subtlety catches people off guard: ChildObjects.Remove shrinks the collection on the spot, so every element after the removed one slides forward by one index. If you increment your loop counter while deleting, each removal causes the very next marker to be skipped — and the more markers you have, the more survivors you leave behind.
function App() {
const RemoveEditableRange = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the input document into VFS
const inputFileName = "RemoveEditableRange.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// Create a document object and load the document
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// Iterate over every section and paragraph and delete the permission markers
for (let i = 0; i < doc.Sections.Count; i++) {
const section = doc.Sections.get_Item(i);
for (let j = 0; j < section.Body.Paragraphs.Count; j++) {
const paragraph = section.Body.Paragraphs.get_Item(j);
// Remove on a match; the collection shrinks, so the index is not incremented
for (let k = 0; k < paragraph.ChildObjects.Count;) {
const obj = paragraph.ChildObjects.get_Item(k);
if (obj instanceof docModule.PermissionStart || obj instanceof docModule.PermissionEnd) {
paragraph.ChildObjects.Remove(obj);
} else {
k++;
}
}
}
}
// Save the document
const outputFileName = "Remove Editable Range.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// Release resources
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Remove Editable Ranges from a Word Document</h1>
<button onClick={RemoveEditableRange}>
Generate
</button>
</div>
);
}
export default App;
Deleting the markers only redraws the boundary of what is editable — the text itself and all formatting remain untouched.

The Complete Protection Lifecycle
In a real approval workflow you rarely do just one thing. A typical round-trip looks like this:
- Protect — Call
doc.ProtectwithAllowOnlyReading(orAllowOnlyFormFields) and a password. The entire document is now locked. - Mark — Wrap each reviewer-editable paragraph in a
PermissionStart/PermissionEndpair sharing one id. Those regions become the only places a reviewer can type. - Unmark — When the review round is over, walk the document and remove every permission marker. The regions rejoin the read-only body.
- Unprotect — Call
doc.Unprotect("password")to release the document entirely, returning it to a fully editable state for the next stage of processing.
The key insight is that protection and editable ranges are two independent layers. Protection decides whether the document is locked at all; the marker pair decides which slivers are exempt from that lock. You can add and remove markers as many times as you like without touching the protection state, and you can toggle protection on or off without disturbing the markers — but the markers only have teeth while protection is active.
FAQ
The editable range is set, but the content inside it still cannot be edited
Why it happens: Permission markers are inert on their own. They only carve out exceptions to a document-wide restriction, so if Protect was never called, there is no restriction to be exempt from and the markers do nothing. A second requirement is that the PermissionStart and PermissionEnd must carry the same id string — Word treats them as a pair only when the ids match.
Fix: Turn on the editing restriction first, then create both markers with an identical id:
// Enable protection first so that the markers mean something
document.Protect({ type: wasmModule.ProtectionType.AllowOnlyReading, password: "password" });
// The start and the end must use the same id
const start = new wasmModule.PermissionStart(document, "testID");
const end = new wasmModule.PermissionEnd(document, "testID");
Some markers are missed when removing editable ranges
Why it happens: Each call to ChildObjects.Remove collapses the collection by one, shifting every subsequent element's index down. If the loop counter advances on the same iteration as a removal, the element that slid into the current position is never examined — it gets bypassed, and the problem compounds with every additional marker.
Fix: Either hold the index steady while removing (advance it only when no removal occurred), or gather the target objects first and delete them in reverse order:
for (let k = 0; k < paragraph.ChildObjects.Count;) {
const obj = paragraph.ChildObjects.get_Item(k);
if (obj instanceof wasmModule.PermissionStart || obj instanceof wasmModule.PermissionEnd) {
paragraph.ChildObjects.Remove(obj);
// Do not increment k here: check the new object at the current index
} else {
k++;
}
}
The document is still read-only after the markers are removed
Why it happens: The markers only define which areas are exempt from the lock — they are not the lock itself. Removing them simply removes the exemptions; the underlying protection that Protect established is still in force, so the whole document stays read-only.
Fix: Once the markers are gone and you no longer need the restriction, call Unprotect with the original password:
document.Unprotect("password");
See Also
Como Criar Arquivos Excel em JavaScript (XLSX/XLS)
Índice

JavaScript pode gerar pastas de trabalho do Excel diretamente no navegador. Com o Spire.XLS para JavaScript, você pode criar uma pasta de trabalho, adicionar planilhas, escrever valores e fórmulas, aplicar formatação e salvar o resultado como um arquivo XLSX ou XLS — tudo no lado do cliente, sem necessidade de instalação do Microsoft Excel.
Este tutorial começa com uma pasta de trabalho XLSX básica e depois adiciona valores de célula tipados, fórmulas, formatação, várias planilhas, suporte a download no navegador, conversão de CSV e saída XLS para compatibilidade com sistemas legados.
Instalar e Inicializar o Spire.XLS para JavaScript
O Spire.XLS para JavaScript vem dentro do pacote spire.office, junto com Spire.PDF, Spire.Doc e Spire.Presentation:
npm i spire.office
Iniciar o runtime exige duas importações. A primeira inicia o host WebAssembly .NET compartilhado; a segunda registra a API de planilhas:
// 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');
Os arquivos Spire.*.Wasm.zip e a pasta _framework precisam estar acessíveis a partir da raiz do site. O runtime os resolve em relação à URL do documento, e não em relação ao módulo que fez a importação, então em um projeto Vite ou Create React App eles precisam ficar em public/. O prefixo process.env.PUBLIC_URL mostrado em configurações baseadas em React é uma convenção do Create React App, não um padrão do navegador — ajuste o caminho base para corresponder à sua ferramenta de build se você não estiver usando CRA. Se os arquivos estiverem ausentes, o navegador registra WebAssembly.compile(): expected magic word — o servidor de desenvolvimento respondeu à solicitação do arquivo com index.html.
Depois, tudo depende de um único global:
const xls = window.spirexls;
Na configuração atual do pacote usada por este tutorial, a API de planilhas é exposta por meio de window.spirexls. Use esse global após o runtime ter sido inicializado. Versões mais antigas a expunham como window.wasmModule.spirexls; se você estiver trabalhando com uma versão diferente do pacote, verifique qual global está disponível.
Agora você está pronto para gerar arquivos Excel.
Criar um Arquivo Excel Básico em JavaScript
Vamos criar um relatório de vendas. Criaremos uma pasta de trabalho, adicionaremos uma planilha, escreveremos dados de produtos nas células e salvaremos o resultado como um arquivo XLSX que é baixado automaticamente.
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();
}
Chame createExcelFile() e você obterá um SalesReport.xlsx com quatro linhas de produtos mais um cabeçalho. O fluxo de trabalho é simples: criar pasta de trabalho → escrever dados → salvar → baixar.
As seções a seguir ampliam este exemplo. Cada trecho de código pressupõe que seja adicionado dentro de createExcelFile() depois que a pasta de trabalho e a planilha tiverem sido criadas.

Escrever Diferentes Tipos de Dados em Células do Excel
O Excel distingue entre texto, números, datas e booleanos. Errar isso leva a arquivos nos quais a classificação quebra, as fórmulas retornam erros e os números são exibidos como texto.
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;
O erro mais comum? Escrever cell.Text = "999.99" em vez de cell.NumberValue = 999.99. O valor parece idêntico ao abrir o arquivo, mas o Excel o trata como texto — você não pode somá-lo, calcular a média ou classificá-lo numericamente. Sempre use NumberValue para números, DateTimeValue para datas e BooleanValue para booleanos.
Datas exigem uma precaução extra. DateTimeValue preserva o instante UTC representado pelo Date do JavaScript. Criar uma data com meia-noite local pode, portanto, deslocar o dia exibido em alguns fusos horários; use Date.UTC() quando quiser que uma data de calendário específica seja preservada.
Adicionar Fórmulas à Planilha do Excel
As fórmulas fazem do seu arquivo gerado uma planilha de verdade, e não apenas um despejo de dados. Vamos adicionar uma coluna Total que calcula Quantity × Price para cada linha, além de um total geral na parte inferior:
// 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();
Chame workbook.CalculateAllValue() antes de salvar quando precisar que a pasta de trabalho gerada contenha resultados de fórmulas calculados. Isso é útil para visualizadores ou aplicativos que dependem de valores em cache em vez de recalcular fórmulas ao abrir.
Para um guia abrangente sobre funções do Excel e operações com fórmulas, consulte Inserir ou Ler Funções e Fórmulas em Planilhas do Excel com JavaScript no React.
Formatar o Arquivo Excel Gerado
Uma planilha com dados brutos funciona, mas uma planilha formatada comunica. Vamos transformar nosso relatório de vendas em algo que você realmente enviaria a uma parte interessada:
// 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();
Dois detalhes vale a pena conhecer aqui. sheet.Columns.get(i) e sheet.Rows.get(i) são baseados em 0 e usam get, diferentemente do get_Item que outras coleções expõem. E na configuração de navegador testada, AutoFitColumn exige uma fonte que não está disponível no sandbox do WebAssembly, então definir ColumnWidth explicitamente é mais confiável.
O resultado: um cabeçalho azul em negrito, preços formatados como moeda, colunas com tamanho adequado e bordas limpas.
Para orientações detalhadas sobre dimensões de linhas e colunas, consulte Definir Altura da Linha e Largura da Coluna no Excel com JavaScript no React.
Criar Várias Planilhas em uma Pasta de Trabalho do Excel
Relatórios reais raramente cabem em uma única planilha. Um relatório de vendas pode ter um resumo na primeira aba, detalhes de produtos na segunda e detalhamentos mensais na terceira.
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)";
Cada Worksheets.Add(name) retorna a planilha que acabou de criar, na ordem em que as chamadas são feitas, então a ordem das abas corresponde à ordem do código. Observe a fórmula entre planilhas: =SUM(Products!D2:D5) na planilha Summary faz referência à coluna Total na planilha Products. O Excel lida com isso automaticamente quando o arquivo é aberto — nenhum código extra é necessário.

Para mais informações sobre gerenciamento de planilhas — adicionar, remover e reordenar planilhas — consulte Adicionar, Remover e Mover Planilhas do Excel com JavaScript no React.
Salvar e Baixar o Arquivo XLSX no Navegador
Depois que sua pasta de trabalho estiver pronta, o Spire.XLS a salva em um sistema de arquivos virtual do WebAssembly. Em seguida, você lê os dados do arquivo, converte-os em um Blob e dispara o download:
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();
Sempre chame workbook.Dispose() após o download para liberar memória — especialmente em aplicativos nos quais os usuários geram vários arquivos em uma sessão. A pasta de trabalho em si permanece no heap do WebAssembly até você fazer isso, e esse heap não é recuperado pelo coletor de lixo do navegador.
Procurando integração com React ou exportação de tabela HTML? Confira nosso guia para baixar e exportar arquivos Excel em JavaScript e React.
Juntando Tudo: Um Relatório de Vendas Formatado
Os trechos acima constroem uma pasta de trabalho um aspecto de cada vez. Aqui eles estão em uma única função: uma planilha estilizada com faixas zebradas e formatos de moeda, colunas de receita e participação orientadas por fórmulas, um gráfico de colunas e uma segunda planilha que consolida os números.
/**
* 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
}
O layout segue os dados. Substitua o array products por uma resposta da sua própria API e todo o resto — totais, percentuais de participação, o intervalo do gráfico, a consolidação do resumo — continua funcionando, porque tudo é expresso como fórmulas ou intervalos derivados, e não como valores codificados fixamente.

Para mais tipos de gráfico e opções de configuração, consulte Criar Gráficos do Excel com JavaScript no React.
Criar um Arquivo Excel a Partir de Dados CSV
Você pode carregar dados tabulares de um arquivo CSV em uma planilha e salvar o resultado como XLSX. A pasta de trabalho resultante pode então ser formatada ou ampliada com fórmulas e planilhas adicionais:
// 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
});
A planilha importada recebe o nome do arquivo (data), então Worksheets.get(0) a seleciona.
O separador é um segundo argumento obrigatório. LoadFromFile("data.csv") sozinho é rejeitado com This is not a structured storage file, porque a sobrecarga de argumento único espera um formato estruturado como .xlsx ou .xls e não detecta CSV.
Há um segundo problema: o importador de CSV grava todos os campos como texto. Uma coluna de quantidade chega como "10" em vez de 10, o que significa que não pode ser somada ou classificada numericamente — exatamente o problema descrito anteriormente neste tutorial. Converta as colunas necessárias antes de salvar:
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);
}
});
}
Para um guia completo sobre conversão entre os formatos CSV e Excel, consulte nosso tutorial de conversão de CSV para Excel.
Criar um Arquivo XLS em Vez de XLSX
Se seu aplicativo precisar gerar o formato legado .xls em vez de .xlsx, altere o parâmetro version:
workbook.SaveToFile({
fileName: "Report.xls",
version: xls.ExcelVersion.Version97to2003
});
O membro de enum é Version97to2003 — não existe Version97. Mantenha gráficos em XLSX; o gravador XLS testado falha quando métricas de fonte de gráfico são necessárias.
XLSX (Excel 2007+) é o formato recomendado para novos aplicativos. XLS (Excel 97–2003) só é necessário quando a compatibilidade com versões anteriores é exigida.
Problemas Comuns
window.wasmModule é indefinido. Exemplos mais antigos liam a API de planilhas de window.wasmModule.spirexls. Versões atuais a instalam diretamente em window.spirexls e deixam wasmModule indefinido, então o primeiro acesso já lança erro. Verifique window.spirexls em vez disso.
Módulo WASM não inicializado. Se window.spirexls estiver indefinido, o runtime ainda não terminou de carregar. Mostre um indicador de carregamento e espere a inicialização terminar antes de tentar qualquer operação do Excel.
Auto-fit lança um erro de fonte. AutoFitColumn e AutoFitRow precisam de uma fonte do sistema para medir o texto, e o sandbox do WebAssembly não tem nenhuma. Eles falham com Cannot found font(Arial) installed on the system. Calcule ou codifique fixamente as larguras das colunas com sheet.Columns.get(i).ColumnWidth.
Números armazenados como texto. Usar cell.Text = "100" em vez de cell.NumberValue = 100 quebra a classificação e os cálculos. Este é o problema mais comum que os desenvolvedores encontram ao escrever arquivos Excel em JavaScript, e a importação de CSV o aciona automaticamente — sempre use NumberValue para dados numéricos.
Uma planilha "Evaluation Warning" aparece. Ao usar a versão de avaliação sem uma licença válida, o Spire.XLS adiciona uma planilha de avaliação à pasta de trabalho salva. Essa planilha é recriada ao salvar, então removê-la programaticamente não é uma solução confiável. Aplique uma chave de licença para evitá-la.
Vazamentos de memória em aplicativos de longa duração. Chame workbook.Dispose() após cada ciclo de geração. Em aplicativos de página única, pastas de trabalho não descartadas acumulam memória e degradam o desempenho com o tempo.
Perguntas Frequentes
O JavaScript pode criar arquivos Excel sem o Microsoft Excel?
Sim. O Spire.XLS para JavaScript é executado inteiramente no navegador via WebAssembly. Nenhuma instalação do Microsoft Excel ou do Office no lado do servidor é necessária para gerar arquivos XLSX.
O JavaScript pode criar arquivos XLSX diretamente no navegador?
Sim. Todas as operações de planilha acontecem no lado do cliente. O arquivo é salvo em um sistema de arquivos virtual e depois baixado como um Blob — nenhum servidor de backend é necessário.
Qual é a diferença entre XLS e XLSX?
XLSX (Excel 2007 e posteriores) é o formato moderno baseado em XML, recomendado para novos aplicativos. XLS (Excel 97–2003) é o formato binário legado, útil para compatibilidade com sistemas mais antigos.
Conclusão
Criar arquivos Excel em JavaScript não exige um servidor de backend ou o Microsoft Excel. Com o Spire.XLS para JavaScript, você pode construir pastas de trabalho do zero, escrever dados tipados, adicionar fórmulas, aplicar formatação e organizar dados em várias planilhas — tudo no navegador.
Comece com o exemplo básico acima e depois adicione fórmulas e formatação conforme suas necessidades crescerem. Para integração específica com React e exportação de tabela HTML, consulte nosso tutorial dedicado de exportação.
Veja Também
JavaScript로 Excel 파일 만들기 (XLSX/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() 내부에 추가된다고 가정합니다.

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은 파일을 열 때 이를 자동으로 처리하므로 추가 코드가 필요하지 않습니다.

워크시트 관리(시트 추가, 제거, 순서 변경)에 대한 자세한 내용은 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의 응답으로 바꾸면 나머지 모든 것(합계, 점유율 백분율, 차트 범위, 요약 집계)이 계속 작동합니다. 모두 하드 코딩된 값이 아니라 수식이나 파생 범위로 표현되기 때문입니다.

더 많은 차트 유형과 구성 옵션은 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 표 내보내기는 전용 내보내기 튜토리얼을 참조하세요.
참고 항목
Come creare file Excel in JavaScript (XLSX/XLS)
Indice

JavaScript può generare cartelle di lavoro Excel direttamente nel browser. Con Spire.XLS for JavaScript, puoi creare una cartella di lavoro, aggiungere fogli di lavoro, scrivere valori e formule, applicare la formattazione e salvare il risultato come file XLSX o XLS—tutto lato client, senza richiedere l'installazione di Microsoft Excel.
Questo tutorial inizia con una cartella di lavoro XLSX di base e poi aggiunge valori di cella tipizzati, formule, formattazione, più fogli di lavoro, supporto al download dal browser, conversione CSV e output XLS per la compatibilità con i sistemi legacy.
Installare e inizializzare Spire.XLS for JavaScript
Spire.XLS for JavaScript è distribuito all'interno del pacchetto spire.office, insieme a Spire.PDF, Spire.Doc e Spire.Presentation:
npm i spire.office
L'avvio del runtime richiede due import. Il primo avvia l'host condiviso .NET WebAssembly; il secondo registra l'API del foglio di calcolo:
// 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');
Gli archivi Spire.*.Wasm.zip e la cartella _framework devono essere raggiungibili dalla radice del sito. Il runtime li risolve rispetto all'URL del documento anziché rispetto al modulo che ha eseguito l'import, quindi in un progetto Vite o Create React App devono trovarsi in public/. Il prefisso process.env.PUBLIC_URL mostrato nelle configurazioni basate su React è una convenzione di Create React App, non uno standard del browser — adatta il percorso di base per farlo corrispondere al tuo strumento di build se non usi CRA. Se gli archivi mancano, il browser registra WebAssembly.compile(): expected magic word — il server di sviluppo ha risposto alla richiesta dell'archivio con index.html.
Poi tutto dipende da un unico global:
const xls = window.spirexls;
Nella configurazione attuale del pacchetto utilizzata in questo tutorial, l'API del foglio di calcolo è esposta tramite window.spirexls. Usa quel global dopo che il runtime è stato inizializzato. Le versioni precedenti la esponevano come window.wasmModule.spirexls; se stai lavorando con una versione diversa del pacchetto, verifica quale global è disponibile.
Ora sei pronto a generare file Excel.
Creare un file Excel di base in JavaScript
Costruiamo un report delle vendite. Creeremo una cartella di lavoro, aggiungeremo un foglio di lavoro, scriveremo i dati dei prodotti nelle celle e salveremo il risultato come file XLSX che viene scaricato automaticamente.
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();
}
Chiama createExcelFile() e otterrai un SalesReport.xlsx con quattro righe di prodotto più un'intestazione. Il flusso di lavoro è semplice: creare la cartella di lavoro → scrivere i dati → salvare → scaricare.
Le sezioni seguenti estendono questo esempio. Ogni frammento di codice presuppone di essere aggiunto all'interno di createExcelFile() dopo che la cartella di lavoro e il foglio di lavoro sono stati creati.

Scrivere tipi di dati diversi nelle celle di Excel
Excel distingue tra testo, numeri, date e valori booleani. Sbagliare questo aspetto produce file in cui l'ordinamento si rompe, le formule restituiscono errori e i numeri vengono visualizzati come testo.
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;
L'errore più comune? Scrivere cell.Text = "999.99" invece di cell.NumberValue = 999.99. Il valore sembra identico quando apri il file, ma Excel lo tratta come testo—non puoi sommarlo, calcolarne la media o ordinarlo numericamente. Usa sempre NumberValue per i numeri, DateTimeValue per le date e BooleanValue per i valori booleani.
Le date richiedono una precauzione aggiuntiva. DateTimeValue conserva l'istante UTC rappresentato dall'oggetto Date di JavaScript. Creare una data con la mezzanotte locale può quindi spostare il giorno visualizzato in alcuni fusi orari; usa Date.UTC() quando vuoi che una data di calendario specifica venga conservata.
Aggiungere formule al foglio di lavoro Excel
Le formule trasformano il file generato in un vero foglio di calcolo, non solo in un mucchio di dati. Aggiungiamo una colonna Total che calcola Quantity × Price per ogni riga, più un totale generale in fondo:
// 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();
Chiama workbook.CalculateAllValue() prima di salvare quando hai bisogno che la cartella di lavoro generata contenga i risultati calcolati delle formule. Questo è utile per visualizzatori o applicazioni che si affidano ai valori memorizzati nella cache invece di ricalcolare le formule all'apertura.
Per una guida completa sulle funzioni di Excel e sulle operazioni con le formule, consulta Inserire o leggere funzioni e formule nei fogli di lavoro Excel con JavaScript in React.
Formattare il file Excel generato
Un foglio di calcolo con dati grezzi funziona, ma un foglio formattato comunica. Trasformiamo il nostro report delle vendite in qualcosa che invieresti davvero a uno stakeholder:
// 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();
Due dettagli meritano attenzione qui. sheet.Columns.get(i) e sheet.Rows.get(i) sono basati su 0 e usano get, a differenza del get_Item esposto da altre collezioni. E nella configurazione del browser testata, AutoFitColumn richiede un font che non è disponibile nella sandbox WebAssembly, quindi impostare ColumnWidth esplicitamente è più affidabile.
Il risultato: un'intestazione blu in grassetto, prezzi formattati come valuta, colonne di dimensioni corrette e bordi puliti.
Per una guida dettagliata sulle dimensioni di righe e colonne, consulta Impostare l'altezza delle righe e la larghezza delle colonne in Excel con JavaScript in React.
Creare più fogli di lavoro in una cartella di lavoro Excel
I report reali raramente stanno su un solo foglio. Un report delle vendite può avere un riepilogo nella prima scheda, i dettagli dei prodotti nella seconda e i dettagli mensili nella terza.
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)";
Ogni Worksheets.Add(name) restituisce il foglio appena creato, nell'ordine in cui vengono effettuate le chiamate, quindi l'ordine delle schede corrisponde all'ordine del codice. Nota la formula tra fogli: =SUM(Products!D2:D5) nel foglio Summary fa riferimento alla colonna Total del foglio Products. Excel gestisce questo automaticamente all'apertura del file—non serve codice aggiuntivo.

Per ulteriori informazioni sulla gestione dei fogli di lavoro — aggiunta, rimozione e riordino dei fogli — consulta Aggiungere, rimuovere e spostare fogli di lavoro Excel con JavaScript in React.
Salvare e scaricare il file XLSX nel browser
Una volta che la cartella di lavoro è pronta, Spire.XLS la salva in un file system virtuale WebAssembly. Poi leggi i dati del file, li converti in un Blob e avvii il download:
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();
Chiama sempre workbook.Dispose() dopo il download per liberare memoria—soprattutto nelle app in cui gli utenti generano più file in una sessione. La cartella di lavoro stessa risiede nell'heap WebAssembly finché non lo fai, e quell'heap non viene recuperato dal garbage collector del browser.
Cerchi l'integrazione con React o l'esportazione di tabelle HTML? Dai un'occhiata alla nostra guida al download e all'esportazione di file Excel in JavaScript e React.
Mettere tutto insieme: un report delle vendite formattato
I frammenti precedenti costruiscono una cartella di lavoro un aspetto alla volta. Eccoli in un'unica funzione: un foglio di lavoro formattato con righe alternate (zebra) e formati di valuta, colonne Ricavi e Quota guidate da formule, un grafico a colonne e un secondo foglio di lavoro che riepiloga i numeri.
/**
* 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
}
Il layout segue i dati. Sostituisci l'array products con una risposta della tua API e tutto il resto — totali, percentuali di quota, intervallo del grafico, riepilogo — continua a funzionare, perché è tutto espresso come formule o intervalli derivati anziché come valori hard-coded.

Per altri tipi di grafici e opzioni di configurazione, consulta Creare grafici Excel con JavaScript in React.
Creare un file Excel da dati CSV
Puoi caricare dati tabulari da un file CSV in un foglio di lavoro e salvare il risultato come XLSX. La cartella di lavoro risultante può poi essere formattata o estesa con formule e fogli di lavoro aggiuntivi:
// 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
});
Il foglio importato prende il nome dal file (data), quindi Worksheets.get(0) lo seleziona.
Il separatore è un secondo argomento obbligatorio. LoadFromFile("data.csv") da solo viene rifiutato con This is not a structured storage file, perché l'overload con un solo argomento si aspetta un formato strutturato come .xlsx o .xls e non riconosce il CSV.
C'è una seconda insidia: l'importatore CSV scrive ogni campo come testo. Una colonna di quantità arriva come "10" anziché 10, il che significa che non può essere sommata o ordinata numericamente — esattamente il problema descritto in precedenza in questo tutorial. Converti le colonne di cui hai bisogno prima di salvare:
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);
}
});
}
Per una guida completa sulla conversione tra formati CSV ed Excel, consulta il nostro tutorial sulla conversione da CSV a Excel.
Creare un file XLS invece di XLSX
Se la tua applicazione deve generare il formato legacy .xls invece di .xlsx, modifica il parametro version:
workbook.SaveToFile({
fileName: "Report.xls",
version: xls.ExcelVersion.Version97to2003
});
Il membro dell'enum è Version97to2003 — non esiste Version97. Mantieni i grafici in XLSX; il writer XLS testato fallisce quando sono richieste le metriche dei font dei grafici.
XLSX (Excel 2007+) è il formato consigliato per le nuove applicazioni. XLS (Excel 97–2003) è necessario solo quando è richiesta la compatibilità con le versioni precedenti.
Problemi comuni
window.wasmModule non è definito. I vecchi esempi leggono l'API del foglio di calcolo da window.wasmModule.spirexls. Le versioni attuali la installano direttamente su window.spirexls e lasciano wasmModule non definito, quindi il primo accesso genera un'eccezione. Controlla invece window.spirexls.
Modulo WASM non inizializzato. Se window.spirexls non è definito, il runtime non ha terminato il caricamento. Mostra un indicatore di caricamento e attendi il completamento dell'inizializzazione prima di tentare qualsiasi operazione su Excel.
L'adattamento automatico genera un errore sui font. AutoFitColumn e AutoFitRow richiedono un font di sistema per misurare il testo, e la sandbox WebAssembly non ne ha nessuno. Falliscono con Cannot found font(Arial) installed on the system. Calcola o codifica manualmente la larghezza delle colonne con sheet.Columns.get(i).ColumnWidth.
Numeri memorizzati come testo. Usare cell.Text = "100" invece di cell.NumberValue = 100 rompe l'ordinamento e i calcoli. Questo è il problema più comune che gli sviluppatori incontrano quando scrivono file Excel in JavaScript, e l'importazione CSV lo attiva automaticamente—usa sempre NumberValue per i dati numerici.
Compare un foglio "Evaluation Warning". Quando si utilizza la build di valutazione senza una licenza valida, Spire.XLS aggiunge un foglio di valutazione alla cartella di lavoro salvata. Questo foglio viene ricreato al salvataggio, quindi rimuoverlo a livello di codice non è una soluzione affidabile. Applica una chiave di licenza per evitarlo.
Perdite di memoria nelle app a esecuzione prolungata. Chiama workbook.Dispose() dopo ogni ciclo di generazione. Nelle applicazioni a pagina singola, le cartelle di lavoro non eliminate accumulano memoria e degradano le prestazioni nel tempo.
Domande frequenti
JavaScript può creare file Excel senza Microsoft Excel?
Sì. Spire.XLS for JavaScript viene eseguito interamente nel browser tramite WebAssembly. Non è necessaria alcuna installazione di Microsoft Excel o di Office lato server per generare file XLSX.
JavaScript può creare file XLSX direttamente nel browser?
Sì. Tutte le operazioni sul foglio di calcolo avvengono lato client. Il file viene salvato in un file system virtuale, quindi scaricato come Blob—non serve alcun server backend.
Qual è la differenza tra XLS e XLSX?
XLSX (Excel 2007 e versioni successive) è il formato moderno basato su XML, consigliato per le nuove applicazioni. XLS (Excel 97–2003) è il formato binario legacy, utile per la compatibilità con sistemi più vecchi.
Conclusione
Creare file Excel in JavaScript non richiede un server backend né Microsoft Excel. Con Spire.XLS for JavaScript, puoi creare cartelle di lavoro da zero, scrivere dati tipizzati, aggiungere formule, applicare la formattazione e organizzare i dati su più fogli—tutto nel browser.
Inizia con l'esempio di base sopra, poi aggiungi gradualmente formule e formattazione man mano che le tue esigenze crescono. Per l'integrazione specifica con React e l'esportazione di tabelle HTML, consulta il nostro tutorial dedicato all'esportazione.
Vedi anche
Comment créer des fichiers Excel en JavaScript (XLSX/XLS)
Table des matières

JavaScript peut générer des classeurs Excel directement dans le navigateur. Avec Spire.XLS pour JavaScript, vous pouvez créer un classeur, ajouter des feuilles de calcul, écrire des valeurs et des formules, appliquer une mise en forme et enregistrer le résultat au format XLSX ou XLS — entièrement côté client, sans nécessiter l'installation de Microsoft Excel.
Ce tutoriel commence par un classeur XLSX de base, puis ajoute des valeurs de cellules typées, des formules, une mise en forme, plusieurs feuilles de calcul, la prise en charge du téléchargement dans le navigateur, la conversion CSV et l'export XLS pour la compatibilité avec les formats hérités.
Installer et initialiser Spire.XLS pour JavaScript
Spire.XLS pour JavaScript est fourni dans le package spire.office, avec Spire.PDF, Spire.Doc et Spire.Presentation :
npm i spire.office
Le démarrage du runtime nécessite deux imports. Le premier lance l'hôte .NET WebAssembly partagé ; le second enregistre l'API de feuille de calcul :
// 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');
Les archives Spire.*.Wasm.zip et le dossier _framework doivent être accessibles depuis la racine du site. Le runtime les résout par rapport à l'URL du document plutôt qu'au module qui a effectué l'import ; dans un projet Vite ou Create React App, ils doivent donc se trouver dans public/. Le préfixe process.env.PUBLIC_URL présenté dans les configurations basées sur React est une convention de Create React App, et non un standard du navigateur — ajustez le chemin de base en fonction de votre outil de build si vous n'utilisez pas CRA. Si les archives sont manquantes, le navigateur consigne WebAssembly.compile(): expected magic word — le serveur de développement a répondu à la requête d'archive avec index.html.
Tout repose ensuite sur un seul global :
const xls = window.spirexls;
Dans la configuration de package actuelle utilisée par ce tutoriel, l'API de feuille de calcul est exposée via window.spirexls. Utilisez ce global après l'initialisation du runtime. Les versions plus anciennes l'exposaient sous window.wasmModule.spirexls ; si vous travaillez avec une version de package différente, vérifiez quel global est disponible.
Vous êtes maintenant prêt à générer des fichiers Excel.
Créer un fichier Excel de base en JavaScript
Construisons un rapport de ventes. Nous allons créer un classeur, ajouter une feuille de calcul, écrire les données produits dans les cellules et enregistrer le résultat sous la forme d'un fichier XLSX qui se télécharge automatiquement.
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();
}
Appelez createExcelFile() et vous obtiendrez un SalesReport.xlsx contenant quatre lignes de produits plus un en-tête. Le flux de travail est simple : créer le classeur → écrire les données → enregistrer → télécharger.
Les sections suivantes étendent cet exemple. Chaque extrait de code suppose qu'il est ajouté à l'intérieur de createExcelFile() après la création du classeur et de la feuille de calcul.

Écrire différents types de données dans les cellules Excel
Excel distingue le texte, les nombres, les dates et les booléens. Se tromper à ce sujet produit des fichiers où le tri ne fonctionne plus, où les formules renvoient des erreurs et où les nombres s'affichent sous forme de texte.
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;
L'erreur la plus courante ? Écrire cell.Text = "999.99" au lieu de cell.NumberValue = 999.99. La valeur semble identique à l'ouverture du fichier, mais Excel la traite comme du texte — vous ne pouvez ni l'additionner, ni en faire la moyenne, ni la trier numériquement. Utilisez toujours NumberValue pour les nombres, DateTimeValue pour les dates et BooleanValue pour les booléens.
Les dates nécessitent une précaution supplémentaire. DateTimeValue conserve l'instant UTC représenté par l'objet Date JavaScript. Créer une date avec minuit en heure locale peut donc décaler le jour affiché dans certains fuseaux horaires ; utilisez Date.UTC() lorsque vous souhaitez préserver une date calendaire précise.
Ajouter des formules à la feuille de calcul Excel
Les formules font de votre fichier généré une véritable feuille de calcul, et non un simple export de données. Ajoutons une colonne Total qui calcule Quantity × Price pour chaque ligne, ainsi qu'un total général en bas :
// 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();
Appelez workbook.CalculateAllValue() avant d'enregistrer lorsque vous avez besoin que le classeur généré contienne les résultats calculés des formules. Cela est utile pour les visionneuses ou les applications qui s'appuient sur des valeurs mises en cache au lieu de recalculer les formules à l'ouverture.
Pour un guide complet des fonctions Excel et des opérations sur les formules, consultez Insérer ou lire des fonctions et des formules dans les feuilles de calcul Excel avec JavaScript dans React.
Mettre en forme le fichier Excel généré
Une feuille de calcul contenant des données brutes fonctionne, mais une feuille de calcul mise en forme communique. Transformons notre rapport de ventes en un document que vous enverriez réellement à une partie prenante :
// 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();
Deux détails méritent d'être connus ici. sheet.Columns.get(i) et sheet.Rows.get(i) sont basés sur 0 et utilisent get, contrairement au get_Item exposé par d'autres collections. Et dans la configuration de navigateur testée, AutoFitColumn nécessite une police qui n'est pas disponible dans le bac à sable WebAssembly, donc définir ColumnWidth explicitement est plus fiable.
Le résultat : un en-tête bleu en gras, des prix au format monétaire, des colonnes correctement dimensionnées et des bordures nettes.
Pour des conseils détaillés sur les dimensions des lignes et des colonnes, consultez Définir la hauteur de ligne et la largeur de colonne dans Excel avec JavaScript dans React.
Créer plusieurs feuilles de calcul dans un classeur Excel
Les vrais rapports tiennent rarement sur une seule feuille. Un rapport de ventes peut comporter un récapitulatif sur le premier onglet, le détail des produits sur le second et des répartitions mensuelles sur le troisième.
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)";
Chaque Worksheets.Add(name) renvoie la feuille qu'il vient de créer, dans l'ordre des appels, de sorte que l'ordre des onglets correspond à l'ordre du code. Remarquez la formule inter-feuilles : =SUM(Products!D2:D5) dans la feuille Summary fait référence à la colonne Total de la feuille Products. Excel gère cela automatiquement à l'ouverture du fichier — aucun code supplémentaire n'est nécessaire.

Pour en savoir plus sur la gestion des feuilles de calcul — ajout, suppression et réorganisation des feuilles — consultez Ajouter, supprimer et déplacer des feuilles de calcul Excel avec JavaScript dans React.
Enregistrer et télécharger le fichier XLSX dans le navigateur
Une fois votre classeur prêt, Spire.XLS l'enregistre dans un système de fichiers virtuel WebAssembly. Vous lisez ensuite les données du fichier, les convertissez en Blob et déclenchez un téléchargement :
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();
Appelez toujours workbook.Dispose() après le téléchargement pour libérer la mémoire — en particulier dans les applications où les utilisateurs génèrent plusieurs fichiers au cours d'une même session. Le classeur lui-même réside dans le tas WebAssembly jusqu'à ce que vous le fassiez, et ce tas n'est pas récupéré par le ramasse-miettes du navigateur.
Vous cherchez une intégration React ou un export de tableau HTML ? Consultez notre guide pour télécharger et exporter des fichiers Excel en JavaScript et React.
Mise en pratique : un rapport de ventes mis en forme
Les extraits ci-dessus construisent un classeur un aspect à la fois. Les voici réunis dans une seule fonction : une feuille de calcul stylisée avec un fond alterné et des formats monétaires, des colonnes de chiffre d'affaires et de part calculées par formule, un graphique à colonnes et une deuxième feuille de calcul qui consolide les chiffres.
/**
* 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
}
La mise en page suit les données. Remplacez le tableau products par une réponse de votre propre API et tout le reste — totaux, pourcentages de part, plage du graphique, consolidation du récapitulatif — continue de fonctionner, car tout est exprimé sous forme de formules ou de plages dérivées plutôt que de valeurs codées en dur.

Pour plus de types de graphiques et d'options de configuration, consultez Créer des graphiques Excel avec JavaScript dans React.
Créer un fichier Excel à partir de données CSV
Vous pouvez charger des données tabulaires à partir d'un fichier CSV dans une feuille de calcul et enregistrer le résultat au format XLSX. Le classeur obtenu peut ensuite être mis en forme ou enrichi avec des formules et des feuilles de calcul supplémentaires :
// 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
});
La feuille importée porte le nom du fichier (data), donc Worksheets.get(0) la récupère.
Le séparateur est un deuxième argument obligatoire. LoadFromFile("data.csv") seul est rejeté avec This is not a structured storage file, car la surcharge à un seul argument attend un format structuré tel que .xlsx ou .xls et ne détecte pas le CSV.
Il y a un second piège : l'importateur CSV écrit chaque champ sous forme de texte. Une colonne de quantités arrive sous la forme "10" plutôt que 10, ce qui signifie qu'elle ne peut pas être additionnée ni triée numériquement — exactement le problème décrit plus haut dans ce tutoriel. Convertissez les colonnes dont vous avez besoin avant d'enregistrer :
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);
}
});
}
Pour un guide complet sur la conversion entre les formats CSV et Excel, consultez notre tutoriel de conversion CSV vers Excel.
Créer un fichier XLS au lieu de XLSX
Si votre application doit générer l'ancien format .xls au lieu de .xlsx, modifiez le paramètre de version :
workbook.SaveToFile({
fileName: "Report.xls",
version: xls.ExcelVersion.Version97to2003
});
Le membre d'énumération est Version97to2003 — il n'existe pas de Version97. Gardez les graphiques au format XLSX ; l'écrivain XLS testé échoue lorsque les métriques de police des graphiques sont requises.
XLSX (Excel 2007 et versions ultérieures) est le format recommandé pour les nouvelles applications. XLS (Excel 97–2003) n'est nécessaire que lorsqu'une compatibilité ascendante est requise.
Problèmes courants
window.wasmModule est undefined. Les anciens exemples lisaient l'API de feuille de calcul depuis window.wasmModule.spirexls. Les versions actuelles l'installent directement sur window.spirexls et laissent wasmModule undefined, de sorte que le tout premier accès lève une erreur. Vérifiez plutôt window.spirexls.
Module WASM non initialisé. Si window.spirexls est undefined, le runtime n'a pas terminé son chargement. Affichez un indicateur de chargement et attendez la fin de l'initialisation avant de tenter toute opération Excel.
L'ajustement automatique lève une erreur de police. AutoFitColumn et AutoFitRow ont besoin d'une police système pour mesurer le texte, et le bac à sable WebAssembly n'en a aucune. Ils échouent avec Cannot found font(Arial) installed on the system. Calculez ou codez en dur vos largeurs de colonne avec sheet.Columns.get(i).ColumnWidth.
Nombres stockés sous forme de texte. Utiliser cell.Text = "100" au lieu de cell.NumberValue = 100 casse le tri et les calculs. C'est le problème le plus courant rencontré par les développeurs lorsqu'ils écrivent des fichiers Excel en JavaScript, et l'import CSV le déclenche automatiquement — utilisez toujours NumberValue pour les données numériques.
Une feuille « Evaluation Warning » apparaît. Lorsque vous utilisez la version d'évaluation sans licence valide, Spire.XLS ajoute une feuille d'évaluation au classeur enregistré. Cette feuille est recréée à chaque enregistrement, donc la supprimer par programmation n'est pas une solution fiable. Appliquez une clé de licence pour l'éviter.
Fuites de mémoire dans les applications de longue durée. Appelez workbook.Dispose() après chaque cycle de génération. Dans les applications monopage, les classeurs non libérés accumulent de la mémoire et dégradent les performances au fil du temps.
FAQ
JavaScript peut-il créer des fichiers Excel sans Microsoft Excel ?
Oui. Spire.XLS pour JavaScript s'exécute entièrement dans le navigateur via WebAssembly. Aucune installation de Microsoft Excel ou d'Office côté serveur n'est nécessaire pour générer des fichiers XLSX.
JavaScript peut-il créer des fichiers XLSX directement dans le navigateur ?
Oui. Toutes les opérations sur la feuille de calcul se déroulent côté client. Le fichier est enregistré dans un système de fichiers virtuel, puis téléchargé en tant que Blob — aucun serveur backend n'est nécessaire.
Quelle est la différence entre XLS et XLSX ?
XLSX (Excel 2007 et versions ultérieures) est le format moderne basé sur XML, recommandé pour les nouvelles applications. XLS (Excel 97–2003) est l'ancien format binaire, utile pour la compatibilité avec les systèmes plus anciens.
Conclusion
Créer des fichiers Excel en JavaScript ne nécessite ni serveur backend ni Microsoft Excel. Avec Spire.XLS pour JavaScript, vous pouvez construire des classeurs à partir de zéro, écrire des données typées, ajouter des formules, appliquer une mise en forme et organiser les données sur plusieurs feuilles — le tout dans le navigateur.
Commencez par l'exemple de base ci-dessus, puis ajoutez des formules et de la mise en forme selon vos besoins. Pour une intégration spécifique à React et l'export de tableaux HTML, consultez notre tutoriel dédié à l'export.
Voir aussi
Cómo crear archivos Excel en JavaScript (XLSX/XLS)
Tabla de contenido

JavaScript puede generar libros de Excel directamente en el navegador. Con Spire.XLS for JavaScript, puede crear un libro, agregar hojas de cálculo, escribir valores y fórmulas, aplicar formato y guardar el resultado como un archivo XLSX o XLS, todo del lado del cliente, sin necesidad de instalar Microsoft Excel.
Este tutorial comienza con un libro XLSX básico y luego agrega valores de celda con tipo, fórmulas, formato, varias hojas de cálculo, compatibilidad de descarga en el navegador, conversión de CSV y salida XLS para compatibilidad heredada.
Instalar e inicializar Spire.XLS for JavaScript
Spire.XLS for JavaScript se distribuye dentro del paquete spire.office, junto con Spire.PDF, Spire.Doc y Spire.Presentation:
npm i spire.office
Iniciar el tiempo de ejecución requiere dos importaciones. La primera inicia el host compartido de .NET WebAssembly; la segunda registra la API de hojas de cálculo:
// 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');
Los archivos Spire.*.Wasm.zip y la carpeta _framework deben ser accesibles desde la raíz del sitio. El tiempo de ejecución los resuelve contra la URL del documento en lugar de contra el módulo que realizó la importación, por lo que en un proyecto de Vite o Create React App deben ubicarse en public/. El prefijo process.env.PUBLIC_URL que se muestra en configuraciones basadas en React es una convención de Create React App, no un estándar del navegador; ajuste la ruta base para que coincida con su herramienta de compilación si no está usando CRA. Si faltan los archivos, el navegador registra WebAssembly.compile(): expected magic word; el servidor de desarrollo ha respondido a la solicitud del archivo con index.html.
Luego, todo depende de una única variable global:
const xls = window.spirexls;
En la configuración actual del paquete utilizada por este tutorial, la API de hojas de cálculo se expone a través de window.spirexls. Use esa variable global después de que se haya inicializado el tiempo de ejecución. Las versiones anteriores la exponían como window.wasmModule.spirexls; si está trabajando con una versión diferente del paquete, verifique qué variable global está disponible.
Ya está listo para generar archivos de Excel.
Crear un archivo de Excel básico en JavaScript
Vamos a crear un informe de ventas. Crearemos un libro, agregaremos una hoja de cálculo, escribiremos datos de productos en las celdas y guardaremos el resultado como un archivo XLSX que se descarga automáticamente.
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();
}
Llame a createExcelFile() y obtendrá un SalesReport.xlsx con cuatro filas de productos más un encabezado. El flujo de trabajo es simple: crear libro → escribir datos → guardar → descargar.
Las siguientes secciones amplían este ejemplo. Cada fragmento de código supone que se agrega dentro de createExcelFile() después de que se hayan creado el libro y la hoja de cálculo.

Escribir diferentes tipos de datos en celdas de Excel
Excel distingue entre texto, números, fechas y valores booleanos. Equivocarse con esto genera archivos en los que la ordenación se rompe, las fórmulas devuelven errores y los números se muestran como texto.
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;
¿El error más común? Escribir cell.Text = "999.99" en lugar de cell.NumberValue = 999.99. El valor se ve idéntico al abrir el archivo, pero Excel lo trata como texto: no puede sumarlo, promediarlo ni ordenarlo numéricamente. Use siempre NumberValue para números, DateTimeValue para fechas y BooleanValue para booleanos.
Las fechas necesitan una precaución adicional. DateTimeValue conserva el instante UTC representado por el Date de JavaScript. Por lo tanto, crear una fecha con la medianoche local puede desplazar el día mostrado en algunas zonas horarias; use Date.UTC() cuando desee que se conserve una fecha de calendario específica.
Agregar fórmulas a la hoja de cálculo de Excel
Las fórmulas convierten su archivo generado en una hoja de cálculo real, no solo en un volcado de datos. Agreguemos una columna Total que calcule Quantity × Price para cada fila, más un total general en la parte inferior:
// 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();
Llame a workbook.CalculateAllValue() antes de guardar cuando necesite que el libro generado contenga resultados de fórmulas calculados. Esto es útil para visores o aplicaciones que dependen de valores en caché en lugar de recalcular las fórmulas al abrir.
Para obtener una guía completa sobre funciones de Excel y operaciones con fórmulas, consulte Insertar o leer funciones y fórmulas en hojas de cálculo de Excel con JavaScript en React.
Formatear el archivo de Excel generado
Una hoja de cálculo con datos sin procesar funciona, pero una hoja de cálculo con formato comunica. Convirtamos nuestro informe de ventas en algo que realmente enviaría a una parte interesada:
// 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();
Vale la pena conocer dos detalles aquí. sheet.Columns.get(i) y sheet.Rows.get(i) están basados en 0 y usan get, a diferencia del get_Item que exponen otras colecciones. Y en la configuración del navegador probada, AutoFitColumn requiere una fuente que no está disponible en el entorno aislado de WebAssembly, por lo que establecer ColumnWidth explícitamente es más confiable.
El resultado: un encabezado azul en negrita, precios con formato de moneda, columnas con el tamaño adecuado y bordes limpios.
Para obtener una guía detallada sobre las dimensiones de filas y columnas, consulte Establecer la altura de fila y el ancho de columna en Excel con JavaScript en React.
Crear varias hojas de cálculo en un libro de Excel
Los informes reales rara vez caben en una sola hoja. Un informe de ventas podría tener un resumen en la primera pestaña, detalles de productos en la segunda y desgloses mensuales en la tercera.
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)";
Cada Worksheets.Add(name) devuelve la hoja que acaba de crear, en el orden en que se realizan las llamadas, por lo que el orden de las pestañas coincide con el orden del código. Observe la fórmula entre hojas: =SUM(Products!D2:D5) en la hoja Summary hace referencia a la columna Total de la hoja Products. Excel lo maneja automáticamente cuando se abre el archivo; no se necesita código adicional.

Para obtener más información sobre la administración de hojas de cálculo (agregar, eliminar y reordenar hojas), consulte Agregar, eliminar y mover hojas de cálculo de Excel con JavaScript en React.
Guardar y descargar el archivo XLSX en el navegador
Una vez que su libro esté listo, Spire.XLS lo guarda en un sistema de archivos virtual de WebAssembly. Luego lee los datos del archivo, los convierte en un Blob y activa una descarga:
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();
Llame siempre a workbook.Dispose() después de la descarga para liberar memoria, especialmente en aplicaciones donde los usuarios generan varios archivos en una sesión. El libro en sí reside en el montón de WebAssembly hasta que lo haga, y el recolector de basura del navegador no reclama ese montón.
¿Busca integración con React o exportación de tablas HTML? Consulte nuestra guía para descargar y exportar archivos de Excel en JavaScript y React.
Integrarlo todo: un informe de ventas con formato
Los fragmentos anteriores construyen un libro una preocupación a la vez. Aquí están en una sola función: una hoja de cálculo con estilo, con bandas de cebra y formatos de moneda, columnas de ingresos y participación impulsadas por fórmulas, un gráfico de columnas y una segunda hoja de cálculo que consolida los números.
/**
* 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
}
El diseño sigue los datos. Reemplace el array products con una respuesta de su propia API y todo lo demás (totales, porcentajes de participación, el rango del gráfico, la consolidación del resumen) seguirá funcionando, porque todo está expresado como fórmulas o rangos derivados en lugar de valores codificados de forma fija.

Para obtener más tipos de gráficos y opciones de configuración, consulte Crear gráficos de Excel con JavaScript en React.
Crear un archivo de Excel a partir de datos CSV
Puede cargar datos tabulares desde un archivo CSV en una hoja de cálculo y guardar el resultado como XLSX. El libro resultante se puede formatear o ampliar con fórmulas y hojas de cálculo adicionales:
// 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
});
La hoja importada recibe el nombre del archivo (data), por lo que Worksheets.get(0) la toma.
El separador es un segundo argumento obligatorio. LoadFromFile("data.csv") por sí solo se rechaza con This is not a structured storage file, porque la sobrecarga de un solo argumento espera un formato estructurado como .xlsx o .xls y no detecta CSV.
Hay un segundo problema: el importador de CSV escribe todos los campos como texto. Una columna de cantidad llega como "10" en lugar de 10, lo que significa que no se puede sumar ni ordenar numéricamente, el problema exacto descrito anteriormente en este tutorial. Convierta las columnas que necesite antes de guardar:
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);
}
});
}
Para obtener una guía completa sobre la conversión entre formatos CSV y Excel, consulte nuestro tutorial de conversión de CSV a Excel.
Crear un archivo XLS en lugar de XLSX
Si su aplicación necesita generar el formato .xls heredado en lugar de .xlsx, cambie el parámetro de versión:
workbook.SaveToFile({
fileName: "Report.xls",
version: xls.ExcelVersion.Version97to2003
});
El miembro de enumeración es Version97to2003; no existe Version97. Mantenga los gráficos en XLSX; el escritor XLS probado falla cuando se requieren métricas de fuente de gráficos.
XLSX (Excel 2007+) es el formato recomendado para aplicaciones nuevas. XLS (Excel 97–2003) solo es necesario cuando se requiere compatibilidad con versiones anteriores.
Problemas comunes
window.wasmModule no está definido. Los ejemplos más antiguos leían la API de hojas de cálculo desde window.wasmModule.spirexls. Las versiones actuales la instalan directamente en window.spirexls y dejan wasmModule sin definir, por lo que el primer acceso genera un error. En su lugar, verifique window.spirexls.
Módulo WASM no inicializado. Si window.spirexls no está definido, el tiempo de ejecución no ha terminado de cargarse. Muestre un indicador de carga y espere a que se complete la inicialización antes de intentar cualquier operación de Excel.
El ajuste automático genera un error de fuente. AutoFitColumn y AutoFitRow necesitan una fuente del sistema para medir el texto, y el entorno aislado de WebAssembly no tiene ninguna. Fallan con Cannot found font(Arial) installed on the system. Calcule o codifique de forma fija los anchos de sus columnas con sheet.Columns.get(i).ColumnWidth.
Números almacenados como texto. Usar cell.Text = "100" en lugar de cell.NumberValue = 100 rompe la ordenación y los cálculos. Este es el problema más común que encuentran los desarrolladores al escribir archivos de Excel en JavaScript, y la importación de CSV lo provoca automáticamente; use siempre NumberValue para datos numéricos.
Aparece una hoja de "Advertencia de evaluación". Cuando se usa la compilación de evaluación sin una licencia válida, Spire.XLS agrega una hoja de evaluación al libro guardado. Esta hoja se vuelve a crear al guardar, por lo que eliminarla mediante programación no es una solución confiable. Aplique una clave de licencia para evitarlo.
Fugas de memoria en aplicaciones de larga duración. Llame a workbook.Dispose() después de cada ciclo de generación. En aplicaciones de una sola página, los libros no eliminados acumulan memoria y degradan el rendimiento con el tiempo.
Preguntas frecuentes
¿Puede JavaScript crear archivos de Excel sin Microsoft Excel?
Sí. Spire.XLS for JavaScript se ejecuta completamente en el navegador mediante WebAssembly. No se requiere Microsoft Excel ni una instalación de Office del lado del servidor para generar archivos XLSX.
¿Puede JavaScript crear archivos XLSX directamente en el navegador?
Sí. Todas las operaciones de hojas de cálculo ocurren del lado del cliente. El archivo se guarda en un sistema de archivos virtual y luego se descarga como un Blob; no se necesita un servidor backend.
¿Cuál es la diferencia entre XLS y XLSX?
XLSX (Excel 2007 y posteriores) es el formato moderno basado en XML, recomendado para aplicaciones nuevas. XLS (Excel 97–2003) es el formato binario heredado, útil para la compatibilidad con sistemas más antiguos.
Conclusión
Crear archivos de Excel en JavaScript no requiere un servidor backend ni Microsoft Excel. Con Spire.XLS for JavaScript, puede crear libros desde cero, escribir datos con tipo, agregar fórmulas, aplicar formato y organizar datos en varias hojas, todo en el navegador.
Comience con el ejemplo básico anterior y luego agregue fórmulas y formato a medida que crezcan sus necesidades. Para obtener integración específica de React y exportación de tablas HTML, consulte nuestro tutorial dedicado a la exportación.
Ver también
Wie man Excel-Dateien in JavaScript erstellt (XLSX/XLS)
Inhaltsverzeichnis

JavaScript kann Excel-Arbeitsmappen direkt im Browser generieren. Mit Spire.XLS für JavaScript können Sie eine Arbeitsmappe erstellen, Arbeitsblätter hinzufügen, Werte und Formeln schreiben, Formatierungen anwenden und das Ergebnis entweder als XLSX- oder XLS-Datei speichern – alles clientseitig, ohne dass Microsoft Excel installiert sein muss.
Dieses Tutorial beginnt mit einer einfachen XLSX-Arbeitsmappe und fügt dann typisierte Zellwerte, Formeln, Formatierungen, mehrere Arbeitsblätter, Browser-Download-Unterstützung, CSV-Konvertierung und XLS-Ausgabe für Legacy-Kompatibilität hinzu.
Spire.XLS für JavaScript installieren und initialisieren
Spire.XLS für JavaScript ist im Paket spire.office enthalten, zusammen mit Spire.PDF, Spire.Doc und Spire.Presentation:
npm i spire.office
Das Starten der Runtime erfordert zwei Imports. Der erste startet den gemeinsamen .NET-WebAssembly-Host; der zweite registriert die Tabellenkalkulations-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');
Die Archive Spire.*.Wasm.zip und der Ordner _framework müssen vom Stammverzeichnis der Website aus erreichbar sein. Die Runtime löst sie anhand der Dokument-URL auf und nicht anhand des Moduls, das den Import durchgeführt hat. In einem Vite- oder Create-React-App-Projekt müssen sie daher in public/ liegen. Das in React-basierten Setups gezeigte Präfix process.env.PUBLIC_URL ist eine Create-React-App-Konvention, kein Browserstandard – passen Sie den Basispfad an Ihr Build-Tool an, wenn Sie nicht CRA verwenden. Wenn die Archive fehlen, protokolliert der Browser WebAssembly.compile(): expected magic word – der Dev-Server hat die Archiv-Anfrage mit index.html beantwortet.
Alles hängt dann von einem einzigen globalen Objekt ab:
const xls = window.spirexls;
Im aktuellen Paket-Setup, das in diesem Tutorial verwendet wird, wird die Tabellenkalkulations-API über window.spirexls bereitgestellt. Verwenden Sie dieses globale Objekt, nachdem die Runtime initialisiert wurde. Ältere Releases haben sie als window.wasmModule.spirexls bereitgestellt; wenn Sie mit einer anderen Paketversion arbeiten, prüfen Sie, welches globale Objekt verfügbar ist.
Sie sind jetzt bereit, Excel-Dateien zu generieren.
Eine einfache Excel-Datei in JavaScript erstellen
Erstellen wir einen Verkaufsbericht. Wir erstellen eine Arbeitsmappe, fügen ein Arbeitsblatt hinzu, schreiben Produktdaten in Zellen und speichern das Ergebnis als XLSX-Datei, die automatisch heruntergeladen wird.
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();
}
Rufen Sie createExcelFile() auf, und Sie erhalten eine SalesReport.xlsx mit vier Produktzeilen plus einer Kopfzeile. Der Workflow ist einfach: Arbeitsmappe erstellen → Daten schreiben → speichern → herunterladen.
Die folgenden Abschnitte erweitern dieses Beispiel. Jedes Code-Snippet geht davon aus, dass es innerhalb von createExcelFile() hinzugefügt wird, nachdem die Arbeitsmappe und das Arbeitsblatt erstellt wurden.

Verschiedene Datentypen in Excel-Zellen schreiben
Excel unterscheidet zwischen Text, Zahlen, Datumsangaben und booleschen Werten. Wenn Sie dies falsch machen, entstehen Dateien, in denen die Sortierung fehlschlägt, Formeln Fehler zurückgeben und Zahlen als Text angezeigt werden.
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;
Der häufigste Fehler? Sie schreiben cell.Text = "999.99" statt cell.NumberValue = 999.99. Der Wert sieht beim Öffnen der Datei identisch aus, aber Excel behandelt ihn als Text – Sie können ihn nicht summieren, mitteln oder numerisch sortieren. Verwenden Sie immer NumberValue für Zahlen, DateTimeValue für Datumsangaben und BooleanValue für boolesche Werte.
Datumsangaben erfordern eine zusätzliche Vorsichtsmaßnahme. DateTimeValue bewahrt den UTC-Zeitpunkt, der durch das JavaScript-Date dargestellt wird. Das Erstellen eines Datums mit lokaler Mitternacht kann daher in einigen Zeitzonen den angezeigten Tag verschieben; verwenden Sie Date.UTC(), wenn ein bestimmtes Kalenderdatum beibehalten werden soll.
Formeln zum Excel-Arbeitsblatt hinzufügen
Formeln machen Ihre generierte Datei zu einer echten Tabellenkalkulation, nicht nur zu einem Daten-Dump. Fügen wir eine Spalte Total hinzu, die Quantity × Price für jede Zeile berechnet, plus eine Gesamtsumme am Ende:
// 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();
Rufen Sie workbook.CalculateAllValue() vor dem Speichern auf, wenn die generierte Arbeitsmappe berechnete Formelergebnisse enthalten soll. Dies ist nützlich für Viewer oder Anwendungen, die auf zwischengespeicherte Werte angewiesen sind, anstatt Formeln beim Öffnen neu zu berechnen.
Eine umfassende Anleitung zu Excel-Funktionen und Formeloperationen finden Sie unter Funktionen und Formeln in Excel-Arbeitsblättern mit JavaScript in React einfügen oder lesen.
Die generierte Excel-Datei formatieren
Eine Tabellenkalkulation mit Rohdaten funktioniert, aber eine formatierte Tabellenkalkulation kommuniziert. Machen wir aus unserem Verkaufsbericht etwas, das Sie tatsächlich an einen Stakeholder senden würden:
// 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();
Zwei Details sind hier erwähnenswert. sheet.Columns.get(i) und sheet.Rows.get(i) sind 0-basiert und verwenden get, anders als das get_Item, das andere Sammlungen bereitstellen. Und im getesteten Browser-Setup erfordert AutoFitColumn eine Schriftart, die in der WebAssembly-Sandbox nicht verfügbar ist, daher ist das explizite Festlegen von ColumnWidth zuverlässiger.
Das Ergebnis: eine fett gedruckte blaue Kopfzeile, währungsformatierte Preise, richtig dimensionierte Spalten und saubere Rahmen.
Eine ausführliche Anleitung zu Zeilen- und Spaltenabmessungen finden Sie unter Zeilenhöhe und Spaltenbreite in Excel mit JavaScript in React festlegen.
Mehrere Arbeitsblätter in einer Excel-Arbeitsmappe erstellen
Echte Berichte passen selten auf ein Blatt. Ein Verkaufsbericht könnte eine Zusammenfassung auf der ersten Registerkarte, Produktdetails auf der zweiten und monatliche Aufschlüsselungen auf der dritten haben.
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)";
Jedes Worksheets.Add(name) gibt das gerade erstellte Blatt zurück, in der Reihenfolge der Aufrufe, sodass die Registerkartenreihenfolge mit der Codereihenfolge übereinstimmt. Beachten Sie die blattübergreifende Formel: =SUM(Products!D2:D5) auf dem Blatt „Summary“ verweist auf die Spalte Total auf dem Blatt „Products“. Excel verarbeitet dies automatisch beim Öffnen der Datei – kein zusätzlicher Code erforderlich.

Weitere Informationen zur Arbeitsblattverwaltung – Hinzufügen, Entfernen und Neuanordnen von Blättern – finden Sie unter Excel-Arbeitsblätter mit JavaScript in React hinzufügen, entfernen und verschieben.
Die XLSX-Datei im Browser speichern und herunterladen
Sobald Ihre Arbeitsmappe fertig ist, speichert Spire.XLS sie in einem virtuellen WebAssembly-Dateisystem. Anschließend lesen Sie die Dateidaten, konvertieren sie in einen Blob und lösen einen Download aus:
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();
Rufen Sie nach dem Download immer workbook.Dispose() auf, um Speicher freizugeben – insbesondere in Apps, in denen Benutzer mehrere Dateien in einer Sitzung generieren. Die Arbeitsmappe selbst verbleibt bis dahin im WebAssembly-Heap, und dieser Heap wird nicht vom Garbage Collector des Browsers zurückgewonnen.
Suchen Sie React-Integration oder HTML-Tabellenexport? Sehen Sie sich unsere Anleitung zum Herunterladen und Exportieren von Excel-Dateien in JavaScript & React an.
Alles zusammenfügen: Ein formatierter Verkaufsbericht
Die obigen Snippets erstellen eine Arbeitsmappe Stück für Stück. Hier sind sie in einer einzigen Funktion: ein formatiertes Arbeitsblatt mit Zebra-Streifen und Währungsformaten, formelgesteuerten Umsatz- und Anteilsspalten, einem Säulendiagramm und einem zweiten Arbeitsblatt, das die Zahlen zusammenfasst.
/**
* 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
}
Das Layout folgt den Daten. Ersetzen Sie das Array products durch eine Antwort Ihrer eigenen API, und alles andere – Summen, Anteilsprozentsätze, der Diagrammbereich, die Zusammenfassung – funktioniert weiterhin, da sie alle als Formeln oder abgeleitete Bereiche und nicht als fest codierte Werte ausgedrückt sind.

Weitere Diagrammtypen und Konfigurationsoptionen finden Sie unter Excel-Diagramme mit JavaScript in React erstellen.
Eine Excel-Datei aus CSV-Daten erstellen
Sie können tabellarische Daten aus einer CSV-Datei in ein Arbeitsblatt laden und das Ergebnis als XLSX speichern. Die resultierende Arbeitsmappe kann dann formatiert oder um Formeln und zusätzliche Arbeitsblätter erweitert werden:
// 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
});
Das importierte Blatt wird nach der Datei benannt (data), sodass Worksheets.get(0) es aufgreift.
Das Trennzeichen ist ein erforderliches zweites Argument. LoadFromFile("data.csv") allein wird mit This is not a structured storage file abgelehnt, da die Überladung mit einem Argument ein strukturiertes Format wie .xlsx oder .xls erwartet und CSV nicht erkennt.
Es gibt einen zweiten Haken: Der CSV-Importer schreibt jedes Feld als Text. Eine Mengenspalte kommt als "10" statt als 10 an, was bedeutet, dass sie nicht numerisch summiert oder sortiert werden kann – genau das Problem, das zuvor in diesem Tutorial beschrieben wurde. Konvertieren Sie die benötigten Spalten vor dem Speichern:
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);
}
});
}
Eine vollständige Anleitung zur Konvertierung zwischen CSV- und Excel-Formaten finden Sie in unserem Tutorial zur CSV-zu-Excel-Konvertierung.
Eine XLS-Datei statt XLSX erstellen
Wenn Ihre Anwendung das Legacy-Format .xls statt .xlsx generieren muss, ändern Sie den Versionsparameter:
workbook.SaveToFile({
fileName: "Report.xls",
version: xls.ExcelVersion.Version97to2003
});
Das Enum-Element ist Version97to2003 – es gibt kein Version97. Behalten Sie Diagramme in XLSX; der getestete XLS-Writer schlägt fehl, wenn Diagramm-Schriftartmetriken erforderlich sind.
XLSX (Excel 2007+) ist das empfohlene Format für neue Anwendungen. XLS (Excel 97–2003) ist nur erforderlich, wenn Abwärtskompatibilität benötigt wird.
Häufige Probleme
window.wasmModule ist nicht definiert. Ältere Beispiele lesen die Tabellenkalkulations-API aus window.wasmModule.spirexls. Aktuelle Releases installieren sie direkt auf window.spirexls und lassen wasmModule nicht definiert, sodass bereits der erste Zugriff einen Fehler auslöst. Prüfen Sie stattdessen window.spirexls.
WASM-Modul nicht initialisiert. Wenn window.spirexls nicht definiert ist, hat die Runtime das Laden noch nicht abgeschlossen. Zeigen Sie einen Ladeindikator an und warten Sie, bis die Initialisierung abgeschlossen ist, bevor Sie Excel-Operationen versuchen.
Auto-Fit löst einen Schriftartfehler aus. AutoFitColumn und AutoFitRow benötigen eine Systemschriftart zum Messen von Text, und die WebAssembly-Sandbox hat keine. Sie schlagen mit Cannot found font(Arial) installed on the system. fehl. Berechnen oder hardcoden Sie Ihre Spaltenbreiten mit sheet.Columns.get(i).ColumnWidth.
Zahlen als Text gespeichert. Die Verwendung von cell.Text = "100" statt cell.NumberValue = 100 bricht Sortierung und Berechnungen. Dies ist das häufigste Problem, auf das Entwickler beim Schreiben von Excel-Dateien in JavaScript stoßen, und der CSV-Import löst es automatisch aus – verwenden Sie immer NumberValue für numerische Daten.
Ein Blatt "Evaluation Warning" erscheint. Wenn Sie die Evaluierungsversion ohne gültige Lizenz verwenden, fügt Spire.XLS der gespeicherten Arbeitsmappe ein Evaluierungsblatt hinzu. Dieses Blatt wird beim Speichern neu erstellt, sodass das programmgesteuerte Entfernen kein zuverlässiger Workaround ist. Wenden Sie einen Lizenzschlüssel an, um dies zu verhindern.
Speicherlecks in lang laufenden Apps. Rufen Sie workbook.Dispose() nach jedem Generierungszyklus auf. In Single-Page-Anwendungen sammeln nicht freigegebene Arbeitsmappen Speicher an und beeinträchtigen die Leistung im Laufe der Zeit.
FAQ
Kann JavaScript Excel-Dateien ohne Microsoft Excel erstellen?
Ja. Spire.XLS für JavaScript läuft vollständig im Browser über WebAssembly. Es ist keine Installation von Microsoft Excel oder server-seitigem Office erforderlich, um XLSX-Dateien zu generieren.
Kann JavaScript XLSX-Dateien direkt im Browser erstellen?
Ja. Alle Tabellenkalkulationsoperationen erfolgen clientseitig. Die Datei wird in einem virtuellen Dateisystem gespeichert und dann als Blob heruntergeladen – kein Backend-Server erforderlich.
Was ist der Unterschied zwischen XLS und XLSX?
XLSX (Excel 2007 und später) ist das moderne XML-basierte Format, empfohlen für neue Anwendungen. XLS (Excel 97–2003) ist das ältere binäre Format, nützlich für die Kompatibilität mit älteren Systemen.
Fazit
Das Erstellen von Excel-Dateien in JavaScript erfordert keinen Backend-Server oder Microsoft Excel. Mit Spire.XLS für JavaScript können Sie Arbeitsmappen von Grund auf erstellen, typisierte Daten schreiben, Formeln hinzufügen, Formatierungen anwenden und Daten über mehrere Blätter organisieren – alles im Browser.
Beginnen Sie mit dem obigen einfachen Beispiel und ergänzen Sie dann Formeln und Formatierungen, wenn Ihre Anforderungen wachsen. Für React-spezifische Integration und HTML-Tabellenexport lesen Sie unser dediziertes Export-Tutorial.