sheetforce
@ -129,7 +129,7 @@ site.use(sheets({
 | 
			
		||||
 | 
			
		||||
:::note
 | 
			
		||||
 | 
			
		||||
This was tested against `lume v1.17.5` on 2023 June 25.
 | 
			
		||||
This demo was last tested against `lume v1.17.5` on 2023 June 25.
 | 
			
		||||
 | 
			
		||||
This example uses the Nunjucks template format. Lume plugins support additional
 | 
			
		||||
template formats, including Markdown and JSX.
 | 
			
		||||
 | 
			
		||||
@ -257,9 +257,9 @@ This demo was tested in the following environments:
 | 
			
		||||
 | 
			
		||||
| OS and Version | Arch | Tauri    | Date       |
 | 
			
		||||
|:---------------|:-----|:---------|:-----------|
 | 
			
		||||
| macOS 13.4.0   | x64  | `v1.4.0` | 2023-06-25 |
 | 
			
		||||
| macOS 13.5.1   | x64  | `v1.5.0` | 2023-09-30 |
 | 
			
		||||
| macOS 13.4.1   | ARM  | `v1.4.0` | 2023-06-29 |
 | 
			
		||||
| Windows 10     | x64  | `v1.4.1` | 2023-07-30 |
 | 
			
		||||
| Windows 10     | x64  | `v1.5.0` | 2023-10-01 |
 | 
			
		||||
| Windows 11     | ARM  | `v1.4.1` | 2023-09-26 |
 | 
			
		||||
| Linux (HoloOS) | x64  | `v1.4.1` | 2023-07-30 |
 | 
			
		||||
| Linux (Debian) | ARM  | `v1.4.1` | 2023-09-26 |
 | 
			
		||||
@ -287,14 +287,14 @@ If required dependencies are installed, the output will show a checkmark next to
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
[✔] Environment
 | 
			
		||||
    - OS: Mac OS 13.4.0 X64
 | 
			
		||||
    - OS: Mac OS 13.5.1 X64
 | 
			
		||||
    ✔ Xcode Command Line Tools: installed
 | 
			
		||||
    ✔ rustc: 1.70.0 (90c541806 2023-05-31)
 | 
			
		||||
    ✔ Cargo: 1.70.0 (ec8a8a0ca 2023-04-25)
 | 
			
		||||
    ✔ rustup: 1.26.0 (5af9b9484 2023-04-05)
 | 
			
		||||
    ✔ rustc: 1.72.1 (d5c2e9c34 2023-09-13)
 | 
			
		||||
    ✔ Cargo: 1.72.1 (103a7ff2e 2023-08-15)
 | 
			
		||||
    ✔ rustup: 1.26.0+1046 (d4c684485 2023-08-30)
 | 
			
		||||
    ✔ Rust toolchain: stable-x86_64-apple-darwin (default)
 | 
			
		||||
    - node: 18.16.1
 | 
			
		||||
    - npm: 9.5.1
 | 
			
		||||
    - node: 16.20.2
 | 
			
		||||
    - npm: 8.19.4
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::caution pass
 | 
			
		||||
@ -374,8 +374,7 @@ curl -o src/App.vue https://docs.sheetjs.com/tauri/App.vue
 | 
			
		||||
npm run tauri build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
At the end, it will print the path to the generated program. If the program path
 | 
			
		||||
is not listed, it is typically found in the `src-tauri/target/release` folder.
 | 
			
		||||
At the end, it will print the path to the generated installer.
 | 
			
		||||
 | 
			
		||||
:::info pass
 | 
			
		||||
 | 
			
		||||
@ -393,9 +392,28 @@ sudo pacman -S openssl
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
6) Run the program. The following features should be manually verified:
 | 
			
		||||
6) Run the program.
 | 
			
		||||
 | 
			
		||||
- When it is opened, the app will download <https://sheetjs.com/pres.numbers>
 | 
			
		||||
<Tabs groupId="os">
 | 
			
		||||
  <TabItem value="unix" label="Linux/MacOS">
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
./src-tauri/target/release/SheetJSTauri
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="win" label="Windows">
 | 
			
		||||
 | 
			
		||||
```powershell
 | 
			
		||||
.\src-tauri\target\release\SheetJSTauri.exe
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
</Tabs>
 | 
			
		||||
 | 
			
		||||
The following features should be manually verified:
 | 
			
		||||
 | 
			
		||||
- When it is loaded, the app will download <https://sheetjs.com/pres.numbers>
 | 
			
		||||
  and display the data in a table.
 | 
			
		||||
- Clicking "Save Data" will show a save dialog. After selecting a path and name,
 | 
			
		||||
  the app will write a file. That file can be opened in a spreadsheet editor.
 | 
			
		||||
 | 
			
		||||
@ -5,12 +5,20 @@ pagination_next: demos/extensions/index
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
import current from '/version.js';
 | 
			
		||||
import Tabs from '@theme/Tabs';
 | 
			
		||||
import TabItem from '@theme/TabItem';
 | 
			
		||||
import CodeBlock from '@theme/CodeBlock';
 | 
			
		||||
 | 
			
		||||
Salesforce apps can use third-party libraries in "Lightning Web Components".
 | 
			
		||||
[Salesforce](https://www.salesforce.com/) is a suite of cloud-based software
 | 
			
		||||
systems for Customer Relationship Management (CRM). "Lightning Web Components"
 | 
			
		||||
(LWC) is a robust JavaScript extension platform available to Salesforce apps[^1].
 | 
			
		||||
 | 
			
		||||
This demo assumes familiarity with Lightning Web Components.  Salesforce has a
 | 
			
		||||
[detailed introduction.](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_introduction)
 | 
			
		||||
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
 | 
			
		||||
data from spreadsheets.
 | 
			
		||||
 | 
			
		||||
This demo explores the LWC scripting features in Salesforce. We'll explore how
 | 
			
		||||
to install SheetJS scripts in Lightning Web Components and build a sample app
 | 
			
		||||
for exporting lists to XLSX workbooks.
 | 
			
		||||
 | 
			
		||||
:::caution pass
 | 
			
		||||
 | 
			
		||||
@ -21,191 +29,72 @@ may require some adjustments.  The official documentation should be consulted.
 | 
			
		||||
 | 
			
		||||
:::note
 | 
			
		||||
 | 
			
		||||
This demo was last tested on 2023 April 09 using Lightning API version `57.0`.
 | 
			
		||||
This demo was last tested on 2023 September 30 using Lightning API version `58.0`.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
## Getting Started
 | 
			
		||||
:::warning Telemetry
 | 
			
		||||
 | 
			
		||||
This demo was built on a "Developer Edition" account. At the time of writing, an
 | 
			
		||||
[account can be created for free.](https://developer.salesforce.com/signup)
 | 
			
		||||
 | 
			
		||||
### Create Sample Project and Component
 | 
			
		||||
 | 
			
		||||
<!-- spellchecker-disable -->
 | 
			
		||||
 | 
			
		||||
Following the steps in ["Develop in Non-Scratch Orgs"](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_sfdx_deploy):
 | 
			
		||||
 | 
			
		||||
<!-- spellchecker-enable -->
 | 
			
		||||
The Salesforce developer tools embed telemetry. It can be disabled by setting
 | 
			
		||||
the environment variable `SF_DISABLE_TELEMETRY` to `true` or by running
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
## Login
 | 
			
		||||
sfdx force:auth:web:login -d -a LWC-Hub
 | 
			
		||||
 | 
			
		||||
## Create Sample Project and Component
 | 
			
		||||
sfdx force:project:create --projectname SheetForce
 | 
			
		||||
cd SheetForce
 | 
			
		||||
sfdx force:lightning:component:create --type lwc -n sheetComponent -d force-app/main/default/lwc
 | 
			
		||||
npx @salesforce/cli config set disable-telemetry=true --global
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
By default, the component will not be available to app pages.  A few files must
 | 
			
		||||
be changed:
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
`force-app\main\default\lwc\sheetComponent\sheetComponent.html` add some HTML:
 | 
			
		||||
## Integration Details
 | 
			
		||||
 | 
			
		||||
```html title="force-app\main\default\lwc\sheetComponent\sheetComponent.html"
 | 
			
		||||
<template>
 | 
			
		||||
  <!-- highlight-next-line -->
 | 
			
		||||
  <b>SheetForce demo</b>
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
Lightning Web Components can load scripts stored in static resources.
 | 
			
		||||
 | 
			
		||||
`force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml` change
 | 
			
		||||
`isExposed` from `false` to `true` and add some metadata:
 | 
			
		||||
 | 
			
		||||
```xml title="force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml"
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
 | 
			
		||||
  <apiVersion>57.0</apiVersion>
 | 
			
		||||
  <!-- highlight-start -->
 | 
			
		||||
  <isExposed>true</isExposed>
 | 
			
		||||
  <masterLabel>SheetForce</masterLabel>
 | 
			
		||||
  <description>SheetJS Demo</description>
 | 
			
		||||
  <targets>
 | 
			
		||||
    <target>lightning__AppPage</target>
 | 
			
		||||
  </targets>
 | 
			
		||||
  <!-- highlight-end -->
 | 
			
		||||
</LightningComponentBundle>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Deploy Sample Project
 | 
			
		||||
 | 
			
		||||
Deploy the project:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
sfdx force:source:deploy -p force-app -u SALESFORCE@USER.NAME # replace with actual username
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The custom component can be found in Custom Code > Lightning Components.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### Initialize App Page
 | 
			
		||||
 | 
			
		||||
Create an "App Page" in the "Lightning App Builder".  Instructions are included
 | 
			
		||||
in [Hello World in a Scratch Org](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_sfdx_hello_world)
 | 
			
		||||
 | 
			
		||||
The following options should be set:
 | 
			
		||||
- The "App Page" option should be selected.
 | 
			
		||||
- The App Label should be set to "SheetJS Demo".
 | 
			
		||||
- The "One Region" layout should be selected.
 | 
			
		||||
 | 
			
		||||
Under Custom components, you should see "SheetForce".  Click and drag it into
 | 
			
		||||
the app builder main view to add it to the page.
 | 
			
		||||
 | 
			
		||||
Click "Save" then click "Activate".  The following options should be set:
 | 
			
		||||
- Click "Change..." next to "Icon" and pick a memorable icon
 | 
			
		||||
- Under "Lightning Experience" click "LightningBolt" then "Add page to app"
 | 
			
		||||
 | 
			
		||||
Click "Save" to activate the page, then click the left arrow to return to Setup.
 | 
			
		||||
 | 
			
		||||
Click the App Launcher and select "Bolt Solutions" then "SheetJS Demo".  You
 | 
			
		||||
should see a page like
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Adding the Standalone Script
 | 
			
		||||
### Installation
 | 
			
		||||
 | 
			
		||||
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
 | 
			
		||||
can be downloaded and added as a static resource.
 | 
			
		||||
 | 
			
		||||
Due to Salesforce name restrictions, the script must be renamed to `sheetjs.js`
 | 
			
		||||
:::info pass
 | 
			
		||||
 | 
			
		||||
<p>1) Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.full.min.js</a></p>
 | 
			
		||||
 | 
			
		||||
:::warning pass
 | 
			
		||||
 | 
			
		||||
**DO NOT "COPY AND PASTE"!**  The file should be explicitly downloaded.  Copying
 | 
			
		||||
and pasting corrupts the source code and the component will fail in subtle ways.
 | 
			
		||||
 | 
			
		||||
The easiest approach is to right-click the link and select "Save Link As..."
 | 
			
		||||
Due to Salesforce name restrictions, the script must be renamed to `sheetjs.js`.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
2) Move the file to the `force-app/main/default/staticresources/` folder and
 | 
			
		||||
   rename the file to `sheetjs.js`.
 | 
			
		||||
### Loading SheetJS
 | 
			
		||||
 | 
			
		||||
3) Create `force-app/main/default/staticresources/sheetjs.resource-meta.xml`:
 | 
			
		||||
Assuming the script was renamed to `sheetjs.js`, the name of the resource will
 | 
			
		||||
be `sheetjs`. `async` functions can use `loadScript` to fetch and load the
 | 
			
		||||
library. The script will define the variable `XLSX`[^2]
 | 
			
		||||
 | 
			
		||||
```xml title="force-app/main/default/staticresources/sheetjs.resource-meta.xml"
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
 | 
			
		||||
  <cacheControl>Private</cacheControl>
 | 
			
		||||
  <contentType>application/javascript</contentType>
 | 
			
		||||
</StaticResource>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
4) Deploy the project again:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
sfdx force:source:deploy -p force-app -u SALESFORCE@USER.NAME # replace with actual username
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
The official documentation recommends adding a static resource with a ZIP file.
 | 
			
		||||
That approach is not explored in this demo.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
Custom Code > Static Resources should now list `sheetjs`:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### Test the Static Resource
 | 
			
		||||
 | 
			
		||||
The script can be loaded from component code with:
 | 
			
		||||
It is recommended to load the library in a callback. For example, the following
 | 
			
		||||
`@api` method loads the library and exports sample data to a spreadsheet file:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import sheetjs from '@salesforce/resourceUrl/sheetjs';
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The library includes a version number that can be displayed:
 | 
			
		||||
 | 
			
		||||
1) Add a reference in `sheetComponent.js` and expose the `version` property:
 | 
			
		||||
 | 
			
		||||
```js title="force-app/main/default/lwc/sheetComponent/sheetComponent.js"
 | 
			
		||||
import { LightningElement } from 'lwc';
 | 
			
		||||
import { LightningElement, api } from 'lwc';
 | 
			
		||||
import { loadScript } from 'lightning/platformResourceLoader';
 | 
			
		||||
// highlight-next-line
 | 
			
		||||
import sheetjs from '@salesforce/resourceUrl/sheetjs';
 | 
			
		||||
 | 
			
		||||
export default class SheetComponent extends LightningElement {
 | 
			
		||||
  version = "???"; // start with ???
 | 
			
		||||
  async connectedCallback() {
 | 
			
		||||
    // highlight-next-line
 | 
			
		||||
  @api async download() {
 | 
			
		||||
    await loadScript(this, sheetjs); // load the library
 | 
			
		||||
    // At this point, the library is accessible with the `XLSX` variable
 | 
			
		||||
    this.version = XLSX.version;
 | 
			
		||||
 | 
			
		||||
    // Create worksheet
 | 
			
		||||
    var ws = XLSX.utils.aoa_to_sheet([
 | 
			
		||||
      [ "S", "h", "e", "e", "t", "J"," S" ],
 | 
			
		||||
      [  5 ,  4 ,  3 ,  3 ,  7 ,  9 ,  5  ]
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // Create workbook and add worksheet
 | 
			
		||||
    var wb = XLSX.utils.book_new();
 | 
			
		||||
    XLSX.utils.book_append_sheet(wb, ws, "Data");
 | 
			
		||||
 | 
			
		||||
    // Export Data
 | 
			
		||||
    XLSX.writeFile(wb, "SheetForceExport.xlsx");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2) Reference the variable in `sheetComponent.html`:
 | 
			
		||||
 | 
			
		||||
```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html"
 | 
			
		||||
<template>
 | 
			
		||||
  <!-- highlight-next-line -->
 | 
			
		||||
  <b>SheetForce {version}</b>
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3) Deploy the project again and re-load the Bolt Solutions "SheetJS Demo" page:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Exporting Data from SF Lists
 | 
			
		||||
### Exporting Data from SF List
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
@ -214,7 +103,9 @@ There are many different data types and APIs.  This demo uses the deprecated
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
### Steps
 | 
			
		||||
Using the LWC Wire Service, components receive data in separate events. Exports
 | 
			
		||||
are typically generated in a separate event handler. Component state is normally
 | 
			
		||||
used to handle the timing mismatch.
 | 
			
		||||
 | 
			
		||||
#### Getting Account Data
 | 
			
		||||
 | 
			
		||||
@ -222,6 +113,7 @@ The main method to obtain data is `getListUi` and the key for account data is
 | 
			
		||||
`ACCOUNT_OBJECT`:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import { LightningElement, wire } from 'lwc';
 | 
			
		||||
import { getListUi } from 'lightning/uiListApi';
 | 
			
		||||
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
 | 
			
		||||
 | 
			
		||||
@ -240,7 +132,7 @@ export default class SheetComponent extends LightningElement {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Generating an Array of Arrays
 | 
			
		||||
#### Array of Arrays
 | 
			
		||||
 | 
			
		||||
SheetJS most reliably translates "arrays of arrays", a nested array which
 | 
			
		||||
directly maps to individual cell addresses.  For example:
 | 
			
		||||
@ -301,29 +193,447 @@ A suitable SheetJS array of arrays can be constructed by mapping across records:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
      var headers = [ "Name", "Phone" ];
 | 
			
		||||
      this.aoa = [headers].concat(data.records.records.map(record => [
 | 
			
		||||
      var aoa = [headers].concat(data.records.records.map(record => [
 | 
			
		||||
        record.fields.Name.value,  // Name field
 | 
			
		||||
        record.fields.Phone.value, // Phone field
 | 
			
		||||
      ]));
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This is readily exported to a spreadsheet in a callback function:
 | 
			
		||||
#### State
 | 
			
		||||
 | 
			
		||||
This data is available in a wire service callback, but it is common to export
 | 
			
		||||
the data in a separate API event. This flow is handled with a state variable:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
export default class SheetComponent extends LightningElement {
 | 
			
		||||
  // highlight-next-line
 | 
			
		||||
  aoa; // will hold data for export
 | 
			
		||||
  @wire(getListUi, {
 | 
			
		||||
    objectApiName: ACCOUNT_OBJECT.objectApiName,
 | 
			
		||||
    listViewApiName: 'AllAccounts'
 | 
			
		||||
  }) listInfo({ error, data }) {
 | 
			
		||||
    if (data) {
 | 
			
		||||
      var headers = [ "Name", "Phone" ];
 | 
			
		||||
      // create AOA
 | 
			
		||||
      var _aoa = [headers].concat(data.records.records.map(record => [
 | 
			
		||||
        record.fields.Name.value,  // Name field
 | 
			
		||||
        record.fields.Phone.value, // Phone field
 | 
			
		||||
      ]));
 | 
			
		||||
 | 
			
		||||
      // assign to state
 | 
			
		||||
      // highlight-next-line
 | 
			
		||||
      this.aoa = _aoa;
 | 
			
		||||
    } else if (error) console.log(error);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Exporting Data
 | 
			
		||||
 | 
			
		||||
This is readily exported to a spreadsheet in a callback function. Starting from
 | 
			
		||||
the array of arrays, the SheetJS `aoa_to_sheet` method[^3] generates a SheetJS
 | 
			
		||||
sheet object[^4]. A workbook object[^5] is created with `book_new`[^6] and the
 | 
			
		||||
sheet is added with `book_append_sheet`[^7]. Finally, the SheetJS `writeFile`
 | 
			
		||||
method creates a XLSX file and initiates a download[^8].
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
  @api async download() {
 | 
			
		||||
    await loadScript(this, sheetjs); // load the library
 | 
			
		||||
    // get data from state
 | 
			
		||||
    // highlight-next-line
 | 
			
		||||
    var _aoa = this.aoa;
 | 
			
		||||
 | 
			
		||||
    // create workbook
 | 
			
		||||
    var wb = XLSX.utils.book_new();
 | 
			
		||||
    var ws = XLSX.utils.aoa_to_sheet(this.aoa);
 | 
			
		||||
    var ws = XLSX.utils.aoa_to_sheet(_aoa);
 | 
			
		||||
    XLSX.utils.book_append_sheet(wb, ws, "Data");
 | 
			
		||||
 | 
			
		||||
    // export
 | 
			
		||||
    XLSX.writeFile(wb, "SheetForceExport.xlsx");
 | 
			
		||||
  };
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Complete Example
 | 
			
		||||
## Complete Example
 | 
			
		||||
 | 
			
		||||
1) Add a button to `sheetComponent.html` that will call a `download` callback:
 | 
			
		||||
:::info pass
 | 
			
		||||
 | 
			
		||||
This demo was built on a "Developer Edition" account. At the time of writing, an
 | 
			
		||||
[account can be created for free.](https://developer.salesforce.com/signup)
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
0) Create a "Developer Edition" account. Take note of the unique Username
 | 
			
		||||
 | 
			
		||||
### Configure Tools
 | 
			
		||||
 | 
			
		||||
1) Install [NodeJS LTS](https://nodejs.org/en/download).
 | 
			
		||||
 | 
			
		||||
2) Disable telemetry:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli config set disable-telemetry=true --global
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3) Confirm the CLI tool works by checking version information:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli --version
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
When the demo was last tested, the command printed
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
@salesforce/cli/2.10.2 darwin-x64 node-v16.20.2
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
4) Log into the org from the CLI tool:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli org login web
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This will open a web browser. Sign in and authorize the application.
 | 
			
		||||
 | 
			
		||||
### Create Project
 | 
			
		||||
 | 
			
		||||
5) Create the "SheetForce" sample project with the `project generate` command:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli project generate -n SheetForce
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Enter the project directory:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
cd SheetForce
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
6) Create a LWC component with the `lightning generate component` command:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli lightning generate component --type lwc -n sheetComponent -d force-app/main/default/lwc
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::warning pass
 | 
			
		||||
 | 
			
		||||
At the time of testing, the CLI tool created components with app version 59.
 | 
			
		||||
This version number is not valid for scratch orgs.
 | 
			
		||||
 | 
			
		||||
**This is a bug in the Salesforce CLI**
 | 
			
		||||
 | 
			
		||||
The workaround is to manually pin version 58 in the JSON and `meta.xml` files.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
7) Replace `force-app\main\default\lwc\sheetComponent\sheetComponent.html` with
 | 
			
		||||
the following template code:
 | 
			
		||||
 | 
			
		||||
```html title="force-app\main\default\lwc\sheetComponent\sheetComponent.html"
 | 
			
		||||
<template>
 | 
			
		||||
  <!-- highlight-next-line -->
 | 
			
		||||
  <b>SheetForce demo</b>
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
8) Replace `force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml`
 | 
			
		||||
with the following XML:
 | 
			
		||||
 | 
			
		||||
```xml title="force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml"
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
 | 
			
		||||
  <!-- highlight-start -->
 | 
			
		||||
  <apiVersion>58.0</apiVersion>
 | 
			
		||||
  <isExposed>true</isExposed>
 | 
			
		||||
  <masterLabel>SheetForce</masterLabel>
 | 
			
		||||
  <description>SheetJS Demo</description>
 | 
			
		||||
  <targets>
 | 
			
		||||
    <target>lightning__AppPage</target>
 | 
			
		||||
  </targets>
 | 
			
		||||
  <!-- highlight-end -->
 | 
			
		||||
</LightningComponentBundle>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
9) Edit `sfdx-version.json` and set the `sourceApiVersion` to `58.0`:
 | 
			
		||||
 | 
			
		||||
```json title="sfdx-version.json"
 | 
			
		||||
  "name": "SheetForce",
 | 
			
		||||
  "namespace": "",
 | 
			
		||||
  "sfdcLoginUrl": "https://login.salesforce.com",
 | 
			
		||||
// highlight-next-line
 | 
			
		||||
  "sourceApiVersion": "58.0"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Deploy Sample Project
 | 
			
		||||
 | 
			
		||||
10) Deploy the project from the CLI. You will need the Salesforce unique
 | 
			
		||||
Username. For example, if the Username was `SF@USER.NAME`, the command is:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
11) Find the new component:
 | 
			
		||||
 | 
			
		||||
<Tabs groupId="sfview">
 | 
			
		||||
  <TabItem value="nuevo" label="Lightning Experience">
 | 
			
		||||
 | 
			
		||||
To find the new component in "Lightning Experience" view:
 | 
			
		||||
 | 
			
		||||
A) In the Salesforce site, click on the gear icon in the top-right corner of the
 | 
			
		||||
page and select "Setup" (Setup for current app).
 | 
			
		||||
 | 
			
		||||
B) Type "Custom Code" in the left sidebar search box. Expand "Custom Code",
 | 
			
		||||
expand "Lightning Components" and click "Lightning Components".
 | 
			
		||||
 | 
			
		||||
:::caution pass
 | 
			
		||||
 | 
			
		||||
With certain security settings, Salesforce will show an error:
 | 
			
		||||
 | 
			
		||||
> We can't display this page because your browser blocks cross-domain cookies, but you can view this page in Salesforce Classic.
 | 
			
		||||
 | 
			
		||||
Click the link to open the page in Salesforce Classic.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="classique" label="Classic">
 | 
			
		||||
 | 
			
		||||
A) Click the "Setup" link in the top-right corner of the page.
 | 
			
		||||
 | 
			
		||||
B) Type "Lightning" in the left sidebar search box. Expand "Develop", expand
 | 
			
		||||
"Lightning Components" and click "Lightning Components".
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
</Tabs>
 | 
			
		||||
 | 
			
		||||
The page in Salesforce Classic will look like the screenshot below:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### Initialize App Page
 | 
			
		||||
 | 
			
		||||
#### Create App Page
 | 
			
		||||
 | 
			
		||||
12) Create an "App Page" in the "Lightning App Builder":
 | 
			
		||||
 | 
			
		||||
<Tabs groupId="sfview">
 | 
			
		||||
  <TabItem value="nuevo" label="Lightning Experience">
 | 
			
		||||
 | 
			
		||||
A) In the Salesforce site, click on the gear icon in the top-right corner of the
 | 
			
		||||
page and select "Setup" (Setup for current app).
 | 
			
		||||
 | 
			
		||||
B) Type "App Build" in the left sidebar search box. Expand "User Interface" and
 | 
			
		||||
click "Lightning App Builder".
 | 
			
		||||
 | 
			
		||||
:::caution pass
 | 
			
		||||
 | 
			
		||||
With certain security settings, Salesforce will show an error:
 | 
			
		||||
 | 
			
		||||
> We can't display this page because your browser blocks cross-domain cookies, but you can view this page in Salesforce Classic.
 | 
			
		||||
 | 
			
		||||
Click the link to open the page in Salesforce Classic.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
C) Click the "New" button.
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="classique" label="Classic">
 | 
			
		||||
 | 
			
		||||
A) Click the "Setup" link in the top-right corner of the page.
 | 
			
		||||
 | 
			
		||||
B) Type "App Build" in the left search box and select "Lightning App Builder".
 | 
			
		||||
 | 
			
		||||
C) Click the "New" button.
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
</Tabs>
 | 
			
		||||
 | 
			
		||||
#### App Wizard
 | 
			
		||||
 | 
			
		||||
D) Select "App Page" in the left list and click "Next"
 | 
			
		||||
 | 
			
		||||
E) Type "SheetJS Demo" in the Label textbox and click "Next"
 | 
			
		||||
 | 
			
		||||
F) Select "One Region" in the left list and click "Done"
 | 
			
		||||
 | 
			
		||||
#### App Builder
 | 
			
		||||
 | 
			
		||||
13) Add the "SheetForce" component to the App Page.
 | 
			
		||||
 | 
			
		||||
In the left "Components" sidebar, under the "Custom" section, there should be a
 | 
			
		||||
"SheetForce" entry.
 | 
			
		||||
 | 
			
		||||
Click and drag "SheetForce" into the "Add Component(s) Here" frame in the app
 | 
			
		||||
builder main view to add it to the page.
 | 
			
		||||
 | 
			
		||||
Click "Save".
 | 
			
		||||
 | 
			
		||||
14) Activate the page.
 | 
			
		||||
 | 
			
		||||
When the "Page Saved" modal is displayed, click "Activate".
 | 
			
		||||
 | 
			
		||||
The following options should be set:
 | 
			
		||||
- Click "Change..." next to "Icon" and pick a memorable icon
 | 
			
		||||
- Under "Lightning Experience" click "LightningBolt" then "Add page to app"
 | 
			
		||||
 | 
			
		||||
Click "Save" to activate the page.
 | 
			
		||||
 | 
			
		||||
15) Open the demo page.
 | 
			
		||||
 | 
			
		||||
Click the left arrow in the top-left corner of the page to return to Setup.
 | 
			
		||||
 | 
			
		||||
If there is a "Switch to Lightning Experience" at the top, click the link.
 | 
			
		||||
 | 
			
		||||
Click the App Launcher (`᎒᎒᎒`) and search for "SheetJS". Under "Items", the new
 | 
			
		||||
"SheetJS Demo" will be listed, Click "SheetJS Demo".
 | 
			
		||||
 | 
			
		||||
The app will display the "SheetForce demo" text from the component template:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Add SheetJS
 | 
			
		||||
 | 
			
		||||
<p>16) Download <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.full.min.js</a></p>
 | 
			
		||||
 | 
			
		||||
:::warning pass
 | 
			
		||||
 | 
			
		||||
**DO NOT "COPY AND PASTE"!**  The file should be explicitly downloaded.  Copying
 | 
			
		||||
and pasting corrupts the source code and the component will fail in subtle ways.
 | 
			
		||||
 | 
			
		||||
The easiest approach is to right-click the link and select "Save Link As..."
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
The following command can be run in PowerShell or `bash`:
 | 
			
		||||
 | 
			
		||||
<CodeBlock language="bash">{`\
 | 
			
		||||
curl -o xlsx.full.min.js https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}
 | 
			
		||||
</CodeBlock>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
17) Move the file to the `force-app/main/default/staticresources/` folder and
 | 
			
		||||
rename the file to `sheetjs.js`.
 | 
			
		||||
 | 
			
		||||
If the file was downloaded from the previous command, `mv` can move and rename:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
mv xlsx.full.min.js force-app/main/default/staticresources/sheetjs.js
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
18) Create `force-app/main/default/staticresources/sheetjs.resource-meta.xml`
 | 
			
		||||
(`sheetjs.resource-meta.xml` in the folder from step 2) with the following XML:
 | 
			
		||||
 | 
			
		||||
```xml title="force-app/main/default/staticresources/sheetjs.resource-meta.xml"
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
 | 
			
		||||
  <cacheControl>Private</cacheControl>
 | 
			
		||||
  <contentType>application/javascript</contentType>
 | 
			
		||||
</StaticResource>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
19) Deploy the project again. Replace `SF@USER.NAME` with the unique Username:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
20) Look for the static resource:
 | 
			
		||||
 | 
			
		||||
<Tabs groupId="sfview">
 | 
			
		||||
  <TabItem value="nuevo" label="Lightning Experience">
 | 
			
		||||
 | 
			
		||||
A) In the Salesforce site, click on the gear icon in the top-right corner of the
 | 
			
		||||
page and select "Setup" (Setup for current app).
 | 
			
		||||
 | 
			
		||||
B) Type "Static" in the left sidebar search box. Click "Static Resources"
 | 
			
		||||
 | 
			
		||||
:::caution pass
 | 
			
		||||
 | 
			
		||||
With certain security settings, Salesforce will show an error:
 | 
			
		||||
 | 
			
		||||
> We can't display this page because your browser blocks cross-domain cookies, but you can view this page in Salesforce Classic.
 | 
			
		||||
 | 
			
		||||
Click the link to open the page in Salesforce Classic.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="classique" label="Classic">
 | 
			
		||||
 | 
			
		||||
A) Click the "Setup" link in the top-right corner of the page.
 | 
			
		||||
 | 
			
		||||
B) Type "Static" in the left sidebar search box. Click "Static Resources"
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
</Tabs>
 | 
			
		||||
 | 
			
		||||
The page in Salesforce Classic will look like the screenshot below:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### Test the Static Resource
 | 
			
		||||
 | 
			
		||||
21) Replace `force-app/main/default/lwc/sheetComponent/sheetComponent.js` with
 | 
			
		||||
the following script:
 | 
			
		||||
 | 
			
		||||
```js title="force-app/main/default/lwc/sheetComponent/sheetComponent.js"
 | 
			
		||||
import { LightningElement } from 'lwc';
 | 
			
		||||
import { loadScript } from 'lightning/platformResourceLoader';
 | 
			
		||||
import sheetjs from '@salesforce/resourceUrl/sheetjs';
 | 
			
		||||
 | 
			
		||||
export default class SheetComponent extends LightningElement {
 | 
			
		||||
  version = "???"; // start with ???
 | 
			
		||||
  async connectedCallback() {
 | 
			
		||||
    await loadScript(this, sheetjs); // load the library
 | 
			
		||||
    // At this point, the library is accessible with the `XLSX` variable
 | 
			
		||||
    // highlight-next-line
 | 
			
		||||
    this.version = XLSX.version;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This component exposes a `version` property based on the SheetJS version.
 | 
			
		||||
 | 
			
		||||
22) Replace `force-app/main/default/lwc/sheetComponent/sheetComponent.html` with
 | 
			
		||||
the following template:
 | 
			
		||||
 | 
			
		||||
```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html"
 | 
			
		||||
<template>
 | 
			
		||||
  <!-- highlight-next-line -->
 | 
			
		||||
  <b>SheetForce {version}</b>
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This template references the `version` property.
 | 
			
		||||
 | 
			
		||||
23) Deploy the project again. Replace `SF@USER.NAME` with the unique Username:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
24) Reload the "SheetJS Demo" page. The page should display the SheetJS version:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
:::info pass
 | 
			
		||||
 | 
			
		||||
It may take a few minutes for Salesforce to refresh. If the demo still shows the
 | 
			
		||||
original "SheetForce demo" text after refreshing, close and reopen the demo app.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
### Export Data from SF Lists
 | 
			
		||||
 | 
			
		||||
25) Add a button to `sheetComponent.html` that will call a `download` callback:
 | 
			
		||||
 | 
			
		||||
```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html"
 | 
			
		||||
<template>
 | 
			
		||||
@ -336,7 +646,7 @@ This is readily exported to a spreadsheet in a callback function:
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2) Replace `sheetComponent.js` with the following:
 | 
			
		||||
26) Replace `sheetComponent.js` with the following:
 | 
			
		||||
 | 
			
		||||
```js title="force-app/main/default/lwc/sheetComponent/sheetComponent.js"
 | 
			
		||||
import { LightningElement, wire, api } from 'lwc';
 | 
			
		||||
@ -373,13 +683,21 @@ export default class SheetComponent extends LightningElement {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3) Re-deploy and refresh the app page:
 | 
			
		||||
27) Deploy the project again. Replace `SF@USER.NAME` with the unique Username:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
```bash
 | 
			
		||||
npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The simple export has all of the data:
 | 
			
		||||
28) Reload the "SheetJS Demo" page. The page should include a button for export:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
29) Click the "Click to Export!" button. The app will attempt to download a file.
 | 
			
		||||
 | 
			
		||||
The simple export includes all of the data:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
:::tip pass
 | 
			
		||||
 | 
			
		||||
@ -387,3 +705,12 @@ The simple export has all of the data:
 | 
			
		||||
cell styling, automatic column width calculations, and frozen rows.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
[^1]: It is strongly recommended to review the [detailed introduction in the Salesforce documentation](https://developer.salesforce.com/docs/platform/lwc/guide/get-started-introduction.html)
 | 
			
		||||
[^2]: The `XLSX` variable is the main global for the SheetJS library. It exposes methods as described in ["API Reference"](/docs/api/)
 | 
			
		||||
[^3]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
 | 
			
		||||
[^4]: See ["Sheet Objects"](/docs/csf/sheet)
 | 
			
		||||
[^5]: See ["Workbook Object"](/docs/csf/book)
 | 
			
		||||
[^6]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
 | 
			
		||||
[^7]: See [`book_append_sheet` in "Utilities"](/docs/api/utilities/wb)
 | 
			
		||||
[^8]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
 | 
			
		||||
@ -7,8 +7,22 @@ pagination_next: demos/extensions/index
 | 
			
		||||
import current from '/version.js';
 | 
			
		||||
import CodeBlock from '@theme/CodeBlock';
 | 
			
		||||
 | 
			
		||||
AWS is a Cloud Services platform which includes traditional virtual machine
 | 
			
		||||
support, "Serverless Functions", cloud storage and much more.
 | 
			
		||||
[Amazon Web Services](https://aws.amazon.com/) (AWS) is a Cloud Services
 | 
			
		||||
platform which includes traditional virtual machine support, "Serverless
 | 
			
		||||
Functions" and cloud storage.
 | 
			
		||||
 | 
			
		||||
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
 | 
			
		||||
data from spreadsheets.
 | 
			
		||||
 | 
			
		||||
This demo explores two key AWS offerings:
 | 
			
		||||
 | 
			
		||||
- ["Lambda Functions"](#lambda-functions) ("Lambda") explores the serverless
 | 
			
		||||
  computing offering. The demo creates a JavaScript function that can process
 | 
			
		||||
  user-submitted files and generate spreadsheets.
 | 
			
		||||
 | 
			
		||||
- ["S3 Storage"](#s3-storage) explores the cloud storage ("S3") offering. The
 | 
			
		||||
  demo uses the NodeJS connection library to read spreadsheets from S3 and write
 | 
			
		||||
  spreadsheets to a S3 bucket.
 | 
			
		||||
 | 
			
		||||
:::caution pass
 | 
			
		||||
 | 
			
		||||
@ -17,30 +31,86 @@ will be available in the future.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
This demo focuses on two key offerings: cloud storage ("S3") and the
 | 
			
		||||
"Serverless Function" platform ("Lambda").
 | 
			
		||||
 | 
			
		||||
The [NodeJS Module](/docs/getting-started/installation/nodejs) can be shipped in
 | 
			
		||||
a bundled Lambda function.
 | 
			
		||||
 | 
			
		||||
:::note
 | 
			
		||||
 | 
			
		||||
This was tested on 2023 April 24.
 | 
			
		||||
This demo was last tested on 2023 October 01.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
## AWS Lambda Functions
 | 
			
		||||
## Lambda Functions
 | 
			
		||||
 | 
			
		||||
AWS offers the NodeJS runtime for JavaScript serverless function.[^1]
 | 
			
		||||
 | 
			
		||||
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
 | 
			
		||||
required in Lambda functions. When deploying, the entire `node_modules` folder
 | 
			
		||||
can be added to the ZIP package.
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
In this demo, the "Function URL" (automatic API Gateway management) features
 | 
			
		||||
are used.  Older deployments required special "Binary Media Types" to handle
 | 
			
		||||
formats like XLSX.  At the time of testing, the configuration was not required.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
:::info pass
 | 
			
		||||
 | 
			
		||||
Node.js runtime can use `x86_64` or `arm64` CPU architectures. SheetJS libraries
 | 
			
		||||
work on both platforms in Linux, Windows, and macOS operating systems.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
### Reading Data
 | 
			
		||||
 | 
			
		||||
In the Lambda handler method, the `event.body` attribute is a Base64-encoded
 | 
			
		||||
string.  The `busboy` body parser can accept a decoded body.
 | 
			
		||||
In the Lambda handler, the `event.body` attribute is a Base64-encoded string
 | 
			
		||||
representing the HTTP request form data. This body must be parsed.
 | 
			
		||||
 | 
			
		||||
<details open><summary><b>Code Sample</b> (click to hide)</summary>
 | 
			
		||||
#### Processing Form Bodies
 | 
			
		||||
 | 
			
		||||
The `busboy` body parser[^2] is battle-tested in NodeJS deployments.
 | 
			
		||||
 | 
			
		||||
`busboy` fires a `'file'` event for every file in the form body. The callback
 | 
			
		||||
receives a NodeJS stream that should be collected into a Buffer:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
/* accumulate the files manually */
 | 
			
		||||
var files = {};
 | 
			
		||||
bb.on('file', function(fieldname, file, filename) {
 | 
			
		||||
  /* concatenate the individual data buffers */
 | 
			
		||||
  var buffers = [];
 | 
			
		||||
  file.on('data', function(data) { buffers.push(data); });
 | 
			
		||||
  file.on('end', function() { files[fieldname] = Buffer.concat(buffers); });
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
`busboy` fires a `'finish'` event when the body parsing is finished. Callbacks
 | 
			
		||||
can assume every file in the form body has been stored in NodeJS Buffer objects.
 | 
			
		||||
 | 
			
		||||
#### Processing NodeJS Buffers
 | 
			
		||||
 | 
			
		||||
The SheetJS `read` method[^3] can read the Buffer objects and generate SheetJS
 | 
			
		||||
workbook objects[^4] which can be processed with other API functions.
 | 
			
		||||
 | 
			
		||||
For example, a handler can use `sheet_to_csv`[^5] to generate CSV text:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
/* on the finish event, all of the fields and files are ready */
 | 
			
		||||
bb.on('finish', function() {
 | 
			
		||||
  /* grab the first file */
 | 
			
		||||
  var f = files["upload"];
 | 
			
		||||
  if(!f) callback(new Error("Must submit a file for processing!"));
 | 
			
		||||
 | 
			
		||||
  /* f[0] is a buffer */
 | 
			
		||||
  // highlight-next-line
 | 
			
		||||
  var wb = XLSX.read(f[0]);
 | 
			
		||||
 | 
			
		||||
  /* grab first worksheet and convert to CSV */
 | 
			
		||||
  var ws = wb.Sheets[wb.SheetNames[0]];
 | 
			
		||||
  callback(null, { statusCode: 200, body: XLSX.utils.sheet_to_csv(ws) });
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<details><summary><b>Complete Code Sample</b> (click to show)</summary>
 | 
			
		||||
 | 
			
		||||
This example takes the first uploaded file submitted with the key `upload`,
 | 
			
		||||
parses the file and returns the CSV content of the first worksheet.
 | 
			
		||||
@ -92,12 +162,38 @@ exports.handler = function(event, context, callback) {
 | 
			
		||||
 | 
			
		||||
### Writing Data
 | 
			
		||||
 | 
			
		||||
For safely transmitting binary data, the `base64` type should be used.  Lambda
 | 
			
		||||
callback response `isBase64Encoded` property forces a binary download.
 | 
			
		||||
For safely transmitting binary data, Base64 strings should be used.
 | 
			
		||||
 | 
			
		||||
<details open><summary><b>Code Sample</b> (click to hide)</summary>
 | 
			
		||||
The SheetJS `write` method[^6] with the option `type: "base64"` will generate
 | 
			
		||||
Base64-encoded strings.
 | 
			
		||||
 | 
			
		||||
This example generates a sample workbook and writes to a XLSX workbook.
 | 
			
		||||
```js
 | 
			
		||||
/* sample SheetJS workbook object */
 | 
			
		||||
var wb = XLSX.read("S,h,e,e,t,J,S\n5,4,3,3,7,9,5", {type: "binary"});
 | 
			
		||||
/* write to XLSX file in Base64 encoding */
 | 
			
		||||
var b64 = XLSX.write(wb, { type: "base64", bookType: "xlsx" });
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The Lambda callback response function accepts options. Setting `isBase64Encoded`
 | 
			
		||||
to `true` will ensure the callback handler decodes the data. To ensure browsers
 | 
			
		||||
will try to download the response, the `Content-Disposition` header must be set:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
callback(null, {
 | 
			
		||||
  statusCode: 200,
 | 
			
		||||
  /* Base64-encoded file */
 | 
			
		||||
  isBase64Encoded: true,
 | 
			
		||||
  body: b64,
 | 
			
		||||
  headers: {
 | 
			
		||||
    /* Browsers will treat the response as the file SheetJSLambda.xlsx */
 | 
			
		||||
    "Content-Disposition": 'attachment; filename="SheetJSLambda.xlsx"'
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<details><summary><b>Complete Code Sample</b> (click to show)</summary>
 | 
			
		||||
 | 
			
		||||
This example creates a sample workbook object and sends the file in the response:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
var XLSX = require('xlsx');
 | 
			
		||||
@ -123,11 +219,18 @@ exports.handler = function(event, context, callback) {
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
### Demo
 | 
			
		||||
### Lambda Demo
 | 
			
		||||
 | 
			
		||||
<details open><summary><b>Complete Example</b> (click to hide)</summary>
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
0) Review the quick start for JavaScript on AWS
 | 
			
		||||
At the time of writing, the AWS Free Tier included an allowance of 1 million
 | 
			
		||||
free requests per month and 400 thousand GB-seconds of compute resources.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
0) If you do not have an account, create a new AWS free tier account[^7].
 | 
			
		||||
 | 
			
		||||
#### Create Project ZIP
 | 
			
		||||
 | 
			
		||||
1) Create a new folder and download [`index.js`](pathname:///aws/index.js):
 | 
			
		||||
 | 
			
		||||
@ -150,28 +253,67 @@ npm i https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz busboy`}
 | 
			
		||||
yes | zip -c ../SheetJSLambda.zip -r .
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
4) In the web interface for AWS Lambda, create a new Function with the options:
 | 
			
		||||
#### Lambda Setup
 | 
			
		||||
 | 
			
		||||
- Select "Author from scratch" (default choice when last verified)
 | 
			
		||||
- "Function Name": SheetJSLambda
 | 
			
		||||
- "Runtime": "Node.js" (select the version in the "Latest supported" block)
 | 
			
		||||
- Advanced Settings:
 | 
			
		||||
  + check "Enable function URL"
 | 
			
		||||
  + Auth type: NONE
 | 
			
		||||
4) Sign into the [AWS Management Console](https://aws.amazon.com/console/) with
 | 
			
		||||
a root user account.
 | 
			
		||||
 | 
			
		||||
5) Type "Lambda" in the top search box and click Lambda (under Services).
 | 
			
		||||
 | 
			
		||||
6) Open "Functions" in the left sidebar.
 | 
			
		||||
 | 
			
		||||
If the left sidebar is not open, click the `≡` icon in the left edge of the page.
 | 
			
		||||
 | 
			
		||||
7) Click the "Create function" button in the main panel.
 | 
			
		||||
 | 
			
		||||
8) Select the following options:
 | 
			
		||||
 | 
			
		||||
- In the top list, select "Author from scratch" (default choice)
 | 
			
		||||
 | 
			
		||||
- Type a memorable "Function Name" ("SheetJSLambda" when last tested)
 | 
			
		||||
 | 
			
		||||
- In the "Runtime" dropdown, look for the "Latest supported" section and select
 | 
			
		||||
  "Node.js" ("Node.js 18.x" when last tested)
 | 
			
		||||
 | 
			
		||||
- Expand "Advanced Settings" and check "Enable function URL". This will display
 | 
			
		||||
  a few sub-options:
 | 
			
		||||
  + "Auth type" select "NONE" (disable IAM authentication)
 | 
			
		||||
  + Check "Configure cross-origin resource sharing (CORS)"
 | 
			
		||||
 | 
			
		||||
5) In the Interface, click "Upload from" and select ".zip file".  Click the
 | 
			
		||||
"Upload" button in the modal, select SheetJSLambda.zip, and click "Save".
 | 
			
		||||
9) Click "Create function" to create the function.
 | 
			
		||||
 | 
			
		||||
#### Upload Code
 | 
			
		||||
 | 
			
		||||
10) In the Interface, scroll down and select the "Code" tab.
 | 
			
		||||
 | 
			
		||||
11) Click the "Upload from" dropdown and select ".zip file".
 | 
			
		||||
 | 
			
		||||
12) Click the "Upload" button in the modal. With the file picker, select the
 | 
			
		||||
`SheetJSLambda.zip` file created in step 3. Click "Save".
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
When the demo was last tested, the ZIP was small enough that the Lambda code
 | 
			
		||||
editor will load the package.
 | 
			
		||||
 | 
			
		||||
6) Enable external access to the function.
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
Under Configuration > Function URL, click "Edit" and ensure that Auth type is
 | 
			
		||||
set to NONE.  If it is not, select NONE and click Save.
 | 
			
		||||
13) In the code editor, double-click `index.js` and confirm the code editor
 | 
			
		||||
displays JavaScript code.
 | 
			
		||||
 | 
			
		||||
#### External Access
 | 
			
		||||
 | 
			
		||||
14) Click "Configuration" in the tab list.
 | 
			
		||||
 | 
			
		||||
15) In the sidebar below the tab list, select "Function URL" and click "Edit".
 | 
			
		||||
 | 
			
		||||
16) Set the "Auth type" to "NONE" and click Save. The page will redirect to the
 | 
			
		||||
Function properties.
 | 
			
		||||
 | 
			
		||||
17) Select the "Configuration" tab and select "Permissions" in the left sidebar.
 | 
			
		||||
 | 
			
		||||
18) Scroll down to "Resource-based policy statements" and ensure that 
 | 
			
		||||
`FunctionURLAllowPublicAccess` is listed.
 | 
			
		||||
 | 
			
		||||
Under Configuration > Permissions, scroll down to "Resource-based policy".
 | 
			
		||||
If no policy statements are defined, select "Add Permission" with the options:
 | 
			
		||||
 | 
			
		||||
- Select "Function URL" at the top
 | 
			
		||||
@ -182,110 +324,260 @@ If no policy statements are defined, select "Add Permission" with the options:
 | 
			
		||||
 | 
			
		||||
Click "Save" and a new Policy statement should be created.
 | 
			
		||||
 | 
			
		||||
7) Find the Function URL (It is in the "Function Overview" section).
 | 
			
		||||
#### Lambda Testing
 | 
			
		||||
 | 
			
		||||
Try to access that URL in a web browser and the site will try to download
 | 
			
		||||
`SheetJSLambda.xlsx`.  Save and open the file to confirm it is valid.
 | 
			
		||||
19) Find the Function URL (It is in the "Function Overview" section).
 | 
			
		||||
 | 
			
		||||
To test parsing, download <https://sheetjs.com/pres.numbers> and make a POST
 | 
			
		||||
request to the public function URL (change `FUNCTION_URL` in the command):
 | 
			
		||||
20) Try to access the function URL in a web browser.
 | 
			
		||||
 | 
			
		||||
The site will attempt to download `SheetJSLambda.xlsx`.  Save and open the file
 | 
			
		||||
to confirm it is valid.
 | 
			
		||||
 | 
			
		||||
21) Download <https://sheetjs.com/pres.numbers> and make a POST request to the
 | 
			
		||||
public function URL.
 | 
			
		||||
 | 
			
		||||
This can be tested on the command line. Change `FUNCTION_URL` in the commands:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
curl -LO https://sheetjs.com/pres.numbers
 | 
			
		||||
curl -X POST -F "upload=@pres.numbers" FUNCTION_URL
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The result should be a CSV output of the first sheet.
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
The terminal will display CSV output of the first sheet.
 | 
			
		||||
 | 
			
		||||
## S3 Storage
 | 
			
		||||
 | 
			
		||||
The main module for S3 and all AWS services is `aws-sdk`.
 | 
			
		||||
The main NodeJS module for S3 and all AWS services is `aws-sdk`[^8].
 | 
			
		||||
 | 
			
		||||
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
 | 
			
		||||
required in NodeJS scripts.
 | 
			
		||||
 | 
			
		||||
### Connecting to S3
 | 
			
		||||
 | 
			
		||||
The `AWS` module includes a function `S3` that performs the connection. Access
 | 
			
		||||
keys for an IAM user[^9] must be used:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
/* credentials */
 | 
			
		||||
var accessKeyId = "...", secretAccessKey = "..."";
 | 
			
		||||
 | 
			
		||||
/* file location */
 | 
			
		||||
var Bucket = "...", Key = "pres.numbers";
 | 
			
		||||
 | 
			
		||||
/* connect to s3 account */
 | 
			
		||||
var AWS = require('aws-sdk');
 | 
			
		||||
var s3 = new AWS.S3({
 | 
			
		||||
  apiVersion: '2006-03-01',
 | 
			
		||||
  credentials: { accessKeyId, secretAccessKey }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Reading Data
 | 
			
		||||
 | 
			
		||||
#### Fetching Files from S3
 | 
			
		||||
 | 
			
		||||
The `s3#getObject` method returns an object with a `createReadStream` method.
 | 
			
		||||
Buffers can be concatenated and passed to `XLSX.read`.
 | 
			
		||||
`createReadStream` returns a NodeJS stream:
 | 
			
		||||
 | 
			
		||||
<details open><summary><b>Demo</b> (click to hide)</summary>
 | 
			
		||||
```js
 | 
			
		||||
/* open stream to the file */
 | 
			
		||||
var stream = s3.getObject({ Bucket: Bucket, Key: Key }).createReadStream();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This sample fetches a buffer from S3 and parses the workbook.
 | 
			
		||||
#### Concatenating NodeJS Streams
 | 
			
		||||
 | 
			
		||||
1) Save the following script to `SheetJSReadFromS3.js`:
 | 
			
		||||
Buffers can be concatenated from the stream into one unified Buffer object:
 | 
			
		||||
 | 
			
		||||
```js title="SheetJSReadFromS3.js"
 | 
			
		||||
var XLSX = require("xlsx");
 | 
			
		||||
var AWS = require('aws-sdk');
 | 
			
		||||
 | 
			
		||||
/* replace these constants */
 | 
			
		||||
var accessKeyId = "<REPLACE WITH ACCESS KEY ID>";
 | 
			
		||||
var secretAccessKey = "<REPLACE WITH SECRET ACCESS KEY>";
 | 
			
		||||
var Bucket = "<REPLACE WITH BUCKET NAME>";
 | 
			
		||||
 | 
			
		||||
var Key = "pres.numbers";
 | 
			
		||||
 | 
			
		||||
/* Get stream */
 | 
			
		||||
var s3 = new AWS.S3({
 | 
			
		||||
  apiVersion: '2006-03-01',
 | 
			
		||||
  credentials: {
 | 
			
		||||
    accessKeyId: accessKeyId,
 | 
			
		||||
    secretAccessKey: secretAccessKey
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
var f = s3.getObject({ Bucket: Bucket, Key: Key }).createReadStream();
 | 
			
		||||
 | 
			
		||||
/* collect data */
 | 
			
		||||
```js
 | 
			
		||||
/* array of buffers */
 | 
			
		||||
var bufs = [];
 | 
			
		||||
f.on('data', function(data) { bufs.push(data); });
 | 
			
		||||
f.on('end', function() {
 | 
			
		||||
  /* concatenate and parse */
 | 
			
		||||
  var wb = XLSX.read(Buffer.concat(bufs));
 | 
			
		||||
  console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
 | 
			
		||||
/* add each data chunk to the array */
 | 
			
		||||
stream.on('data', function(data) { bufs.push(data); });
 | 
			
		||||
/* the callback will be called after all of the data is collected */
 | 
			
		||||
stream.on('end', function() {
 | 
			
		||||
  /* concatenate */
 | 
			
		||||
  var buf = Buffer.concat(bufs);
 | 
			
		||||
 | 
			
		||||
  /* AT THIS POINT, `buf` is a NodeJS Buffer */
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2) Create a new bucket (or get the name of an existing bucket).
 | 
			
		||||
#### Parsing NodeJS Buffers
 | 
			
		||||
 | 
			
		||||
3) Download <https://sheetjs.com/pres.numbers>
 | 
			
		||||
The SheetJS `read` method[^10] can read the final object and generate SheetJS
 | 
			
		||||
workbook objects[^11] which can be processed with other API functions.
 | 
			
		||||
 | 
			
		||||
In the S3 site, open the bucket and click "Upload". In the Upload page, click
 | 
			
		||||
and drag the `pres.numbers` file into the browser window and click "Upload".
 | 
			
		||||
For example, a callback can use `sheet_to_csv`[^12] to generate CSV text:
 | 
			
		||||
 | 
			
		||||
4) Edit `SheetJSReadFromS3.js` and replace the marked constants:
 | 
			
		||||
```js
 | 
			
		||||
stream.on('end', function() {
 | 
			
		||||
  /* concatenate */
 | 
			
		||||
  var buf = Buffer.concat(bufs);
 | 
			
		||||
 | 
			
		||||
- `accessKeyId`: access key for the AWS account
 | 
			
		||||
- `secretAccessKey`: secret access key for the AWS account
 | 
			
		||||
- `Bucket`: name of the bucket
 | 
			
		||||
  /* parse */
 | 
			
		||||
  var wb = XLSX.read(Buffer.concat(bufs));
 | 
			
		||||
 | 
			
		||||
5) Run the script:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
node SheetJSReadFromS3.js
 | 
			
		||||
  /* generate CSV from first worksheet */
 | 
			
		||||
  var first_ws = wb.Sheets[wb.SheetNames[0]];
 | 
			
		||||
  var csv = XLSX.utils.sheet_to_csv(first_ws);
 | 
			
		||||
  console.log(csv);
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The program will display the data in CSV format.
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
### Writing Data
 | 
			
		||||
 | 
			
		||||
`S3#upload` directly accepts a Buffer.
 | 
			
		||||
The SheetJS `write` method[^13] with the option `type: "buffer"` will generate
 | 
			
		||||
NodeJS Buffers. `S3#upload` directly accepts these Buffer objects.
 | 
			
		||||
 | 
			
		||||
<details open><summary><b>Demo</b> (click to hide)</summary>
 | 
			
		||||
This example creates a sample workbook object, generates XLSX file data in a
 | 
			
		||||
NodeJS Buffer, and uploads the data to S3:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
/* generate sample workbook */
 | 
			
		||||
var wb = XLSX.read("S,h,e,e,t,J,S\n5,4,3,3,7,9,5", {type: "binary"});
 | 
			
		||||
 | 
			
		||||
/* write to XLSX file in a NodeJS Buffer */
 | 
			
		||||
var Body = XLSX.write(wb, {type: "buffer", bookType: "xlsx"});
 | 
			
		||||
 | 
			
		||||
/* upload buffer */
 | 
			
		||||
s3.upload({ Bucket, Key, Body }, function(err, data) {
 | 
			
		||||
  if(err) throw err;
 | 
			
		||||
  console.log("Uploaded to " + data.Location);
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### S3 Demo
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
At the time of writing, the AWS Free Tier included 5GB of S3 storage with 20,000
 | 
			
		||||
Get requests and 2000 Put requests per month.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
This sample fetches a buffer from S3 and parses the workbook.
 | 
			
		||||
 | 
			
		||||
0) If you do not have an account, create a new AWS free tier account[^14].
 | 
			
		||||
 | 
			
		||||
#### Create S3 Bucket
 | 
			
		||||
 | 
			
		||||
1) Sign into the [AWS Management Console](https://aws.amazon.com/console/) with
 | 
			
		||||
a root user account.
 | 
			
		||||
 | 
			
		||||
2) Type "S3" in the top search box and click S3 (under Services).
 | 
			
		||||
 | 
			
		||||
3) Open "Buckets" in the left sidebar.
 | 
			
		||||
 | 
			
		||||
If the left sidebar is not open, click the `≡` icon in the left edge of the page.
 | 
			
		||||
 | 
			
		||||
4) Click the "Create bucket" button in the main panel.
 | 
			
		||||
 | 
			
		||||
5) Select the following options:
 | 
			
		||||
 | 
			
		||||
- Type a memorable "Bucket Name" ("sheetjsbouquet" when last tested)
 | 
			
		||||
 | 
			
		||||
- In the "Object Ownership" section, select "ACLs disabled"
 | 
			
		||||
 | 
			
		||||
- Check "Block *all* public access"
 | 
			
		||||
 | 
			
		||||
- Look for the "Bucket Versioning" section and select "Disable"
 | 
			
		||||
 | 
			
		||||
6) Click "Create bucket" to create the bucket.
 | 
			
		||||
 | 
			
		||||
#### Create IAM User
 | 
			
		||||
 | 
			
		||||
7) Type "IAM" in the top search box and click IAM (under Services).
 | 
			
		||||
 | 
			
		||||
8) Open "Users" in the left sidebar.
 | 
			
		||||
 | 
			
		||||
If the left sidebar is not open, click the `≡` icon in the left edge of the page.
 | 
			
		||||
 | 
			
		||||
9) Click the "Create user" button in the main panel.
 | 
			
		||||
 | 
			
		||||
10) In step 1, type a memorable "Bucket Name" ("sheetjs-user" when last tested).
 | 
			
		||||
Click "Next".
 | 
			
		||||
 | 
			
		||||
11) In step 2, click "Next"
 | 
			
		||||
 | 
			
		||||
12) In step 3, click "Create user" to create the user.
 | 
			
		||||
 | 
			
		||||
#### Add Permissions
 | 
			
		||||
 | 
			
		||||
13) Click the new user name in the Users table.
 | 
			
		||||
 | 
			
		||||
14) Select the "Permissions" tab
 | 
			
		||||
 | 
			
		||||
15) Click the "Add permissions" dropdown and select "Add permissions".
 | 
			
		||||
 | 
			
		||||
16) Select "Attach policies directly".
 | 
			
		||||
 | 
			
		||||
17) In the "Permissions policies" section, search for "AmazonS3FullAccess".
 | 
			
		||||
There should be one entry.
 | 
			
		||||
 | 
			
		||||
18) Check the checkbox next to "AmazonS3FullAccess" and click the "Next" button.
 | 
			
		||||
 | 
			
		||||
19) In the "Review" screen, click "Add permissions"
 | 
			
		||||
 | 
			
		||||
#### Generate Keys
 | 
			
		||||
 | 
			
		||||
20) Click "Security credentials", then click "Create access key".
 | 
			
		||||
 | 
			
		||||
21) Select the "Local code" option. Check "I understand the above recommendation
 | 
			
		||||
and want to proceed to create an access key." and click "Next"
 | 
			
		||||
 | 
			
		||||
22) Click "Create Access Key" and click "Download .csv file" in the next screen.
 | 
			
		||||
 | 
			
		||||
In the generated CSV:
 | 
			
		||||
 | 
			
		||||
- Cell A2 is the "Access key ID" (`accessKeyId` in the AWS API)
 | 
			
		||||
- Cell B2 is the "Secret access key" (`secretAccessKey` in the AWS API)
 | 
			
		||||
 | 
			
		||||
#### Set up Project
 | 
			
		||||
 | 
			
		||||
23) Create a new NodeJS project:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
mkdir SheetJSS3
 | 
			
		||||
cd SheetJSS3
 | 
			
		||||
npm init -y
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
24) Install dependencies:
 | 
			
		||||
 | 
			
		||||
<CodeBlock language="bash">{`\
 | 
			
		||||
mkdir -p node_modules
 | 
			
		||||
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz aws-sdk@2.1467.0`}
 | 
			
		||||
</CodeBlock>
 | 
			
		||||
 | 
			
		||||
#### Write Test
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
This sample creates a simple workbook, generates a NodeJS buffer, and uploads
 | 
			
		||||
the buffer to S3.
 | 
			
		||||
 | 
			
		||||
1) Save the following script to `SheetJSWriteToS3.js`:
 | 
			
		||||
```
 | 
			
		||||
   | A | B | C | D | E | F | G |
 | 
			
		||||
---+---|---|---|---|---|---|---|
 | 
			
		||||
 1 | S | h | e | e | t | J | S |
 | 
			
		||||
 2 | 5 | 4 | 3 | 3 | 7 | 9 | 5 |
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
25) Save the following script to `SheetJSWriteToS3.js`:
 | 
			
		||||
 | 
			
		||||
```js title="SheetJSWriteToS3.js"
 | 
			
		||||
var XLSX = require("xlsx");
 | 
			
		||||
var AWS = require('aws-sdk');
 | 
			
		||||
 | 
			
		||||
/* replace these constants */
 | 
			
		||||
// highlight-start
 | 
			
		||||
var accessKeyId = "<REPLACE WITH ACCESS KEY ID>";
 | 
			
		||||
var secretAccessKey = "<REPLACE WITH SECRET ACCESS KEY>";
 | 
			
		||||
var Bucket = "<REPLACE WITH BUCKET NAME>";
 | 
			
		||||
// highlight-end
 | 
			
		||||
 | 
			
		||||
var Key = "test.xlsx";
 | 
			
		||||
 | 
			
		||||
@ -308,21 +600,94 @@ s3.upload({ Bucket: Bucket, Key: Key, Body: Body }, function(err, data) {
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2) Create a new bucket (or get the name of an existing bucket).
 | 
			
		||||
 | 
			
		||||
3) Edit `SheetJSWriteToS3.js` and replace the marked constants:
 | 
			
		||||
26) Edit `SheetJSWriteToS3.js` and replace the highlighted lines:
 | 
			
		||||
 | 
			
		||||
- `accessKeyId`: access key for the AWS account
 | 
			
		||||
- `secretAccessKey`: secret access key for the AWS account
 | 
			
		||||
- `Bucket`: name of the bucket
 | 
			
		||||
 | 
			
		||||
4) Run the script:
 | 
			
		||||
The keys are found in the CSV from step 22. The Bucket is the name from step 5.
 | 
			
		||||
 | 
			
		||||
27) Run the script:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
node SheetJSWriteToS3.js
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
5) In the S3 site, select the bucket and download the object named `test.xlsx`.
 | 
			
		||||
Open the file in a spreadsheet editor.
 | 
			
		||||
This file will be stored with the object name `test.xlsx`. It can be manually
 | 
			
		||||
downloaded from the S3 web interface.
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
#### Read Test
 | 
			
		||||
 | 
			
		||||
This sample will download and process the test file from "Write Test".
 | 
			
		||||
 | 
			
		||||
28) Save the following script to `SheetJSReadFromS3.js`:
 | 
			
		||||
 | 
			
		||||
```js title="SheetJSReadFromS3.js"
 | 
			
		||||
var XLSX = require("xlsx");
 | 
			
		||||
var AWS = require('aws-sdk');
 | 
			
		||||
 | 
			
		||||
/* replace these constants */
 | 
			
		||||
// highlight-start
 | 
			
		||||
var accessKeyId = "<REPLACE WITH ACCESS KEY ID>";
 | 
			
		||||
var secretAccessKey = "<REPLACE WITH SECRET ACCESS KEY>";
 | 
			
		||||
var Bucket = "<REPLACE WITH BUCKET NAME>";
 | 
			
		||||
// highlight-end
 | 
			
		||||
 | 
			
		||||
var Key = "test.xlsx";
 | 
			
		||||
 | 
			
		||||
/* Get stream */
 | 
			
		||||
var s3 = new AWS.S3({
 | 
			
		||||
  apiVersion: '2006-03-01',
 | 
			
		||||
  credentials: {
 | 
			
		||||
    accessKeyId: accessKeyId,
 | 
			
		||||
    secretAccessKey: secretAccessKey
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
var f = s3.getObject({ Bucket: Bucket, Key: Key }).createReadStream();
 | 
			
		||||
 | 
			
		||||
/* collect data */
 | 
			
		||||
var bufs = [];
 | 
			
		||||
f.on('data', function(data) { bufs.push(data); });
 | 
			
		||||
f.on('end', function() {
 | 
			
		||||
  /* concatenate and parse */
 | 
			
		||||
  var wb = XLSX.read(Buffer.concat(bufs));
 | 
			
		||||
  console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
29) Edit `SheetJSReadFromS3.js` and replace the highlighted lines:
 | 
			
		||||
 | 
			
		||||
- `accessKeyId`: access key for the AWS account
 | 
			
		||||
- `secretAccessKey`: secret access key for the AWS account
 | 
			
		||||
- `Bucket`: name of the bucket
 | 
			
		||||
 | 
			
		||||
The keys are found in the CSV from Step 22. The Bucket is the name from Step 5.
 | 
			
		||||
 | 
			
		||||
30) Run the script:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
node SheetJSReadFromS3.js
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The program will display the data in CSV format.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
S,h,e,e,t,J,S
 | 
			
		||||
5,4,3,3,7,9,5
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
[^1]: See ["Building Lambda functions with Node.js"](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html) in the AWS documentation
 | 
			
		||||
[^2]: The `busboy` module is distributed [on the public NPM registry](https://npm.im/busboy)
 | 
			
		||||
[^3]: See [`read` in "Reading Files"](/docs/api/parse-options)
 | 
			
		||||
[^4]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details.
 | 
			
		||||
[^5]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)
 | 
			
		||||
[^6]: See [`write` in "Writing Files"](/docs/api/write-options)
 | 
			
		||||
[^7]: Registering for a free account [on the AWS Free Tier](https://aws.amazon.com/free/) requires a valid phone number and a valid credit card.
 | 
			
		||||
[^8]: The `aws-sdk` module is distributed [on the public NPM registry](https://npm.im/aws-sdk)
 | 
			
		||||
[^9]: See ["Managing access keys for IAM users"](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) in the AWS documentation
 | 
			
		||||
[^10]: See [`read` in "Reading Files"](/docs/api/parse-options)
 | 
			
		||||
[^11]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details.
 | 
			
		||||
[^12]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)
 | 
			
		||||
[^13]: See [`write` in "Writing Files"](/docs/api/write-options)
 | 
			
		||||
[^14]: Registering for a free account [on the AWS Free Tier](https://aws.amazon.com/free/) requires a valid phone number and a valid credit card.
 | 
			
		||||
@ -22,7 +22,7 @@ and the "Serverless Function" platform ("Azure Functions").
 | 
			
		||||
 | 
			
		||||
:::note
 | 
			
		||||
 | 
			
		||||
This was tested on 2023 April 29.
 | 
			
		||||
This demo was last tested on 2023 April 29.
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -41,8 +41,8 @@ These instructions were tested on the following platforms:
 | 
			
		||||
|:------------------------------|:-----------|
 | 
			
		||||
| Linux (Steam Deck Holo x64)   | 2023-09-22 |
 | 
			
		||||
| Linux (Ubuntu 18 AArch64)     | 2023-09-07 |
 | 
			
		||||
| MacOS 10.13 (x64)             | 2023-04-04 |
 | 
			
		||||
| MacOS 13.0 (ARM64)            | 2023-04-13 |
 | 
			
		||||
| MacOS 10.13.6 (x64)           | 2023-09-30 |
 | 
			
		||||
| MacOS 13.6 (ARM64)            | 2023-09-30 |
 | 
			
		||||
| Windows 10 (x64) + WSL Ubuntu | 2023-07-23 |
 | 
			
		||||
| Windows 11 (x64) + WSL Ubuntu | 2023-08-31 |
 | 
			
		||||
| Windows 11 (ARM) + WSL Ubuntu | 2023-09-18 |
 | 
			
		||||
@ -59,7 +59,7 @@ tests will pass in Windows XP with NodeJS 5.10.0.
 | 
			
		||||
import Tabs from '@theme/Tabs';
 | 
			
		||||
import TabItem from '@theme/TabItem';
 | 
			
		||||
 | 
			
		||||
<Tabs>
 | 
			
		||||
<Tabs groupId="os">
 | 
			
		||||
  <TabItem value="wsl" label="Windows WSL">
 | 
			
		||||
 | 
			
		||||
A) Ensure WSL ("WSL 2" in Windows 10) and the Ubuntu distribution are installed.
 | 
			
		||||
@ -97,7 +97,7 @@ C) Install NodeJS
 | 
			
		||||
 | 
			
		||||
:::info pass
 | 
			
		||||
 | 
			
		||||
When this was last tested, the script showed a deprecation notice.
 | 
			
		||||
In the most recent test, the script showed a deprecation notice.
 | 
			
		||||
 | 
			
		||||
**The script worked as expected.**
 | 
			
		||||
 | 
			
		||||
@ -164,10 +164,12 @@ sudo apt-get install -y unzip
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="osx" label="MacOS">
 | 
			
		||||
 | 
			
		||||
A) Run `git`. If Xcode or the command-line tools are not installed, you will be
 | 
			
		||||
asked to install.  Click "Install" and run through the steps.
 | 
			
		||||
A) Open a terminal window and run `git`.
 | 
			
		||||
 | 
			
		||||
B) Open a terminal window and install Homebrew:
 | 
			
		||||
If Xcode or the command-line tools are not installed, you will be asked to
 | 
			
		||||
install. Click "Install" and run through the steps.
 | 
			
		||||
 | 
			
		||||
B) Open a terminal window and install the Homebrew package manager:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
 | 
			
		||||
@ -185,7 +187,7 @@ To confirm analytics are disabled, run
 | 
			
		||||
brew analytics state
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It should print `Analytics are disabled.`
 | 
			
		||||
The message should state that analytics are disabled or destroyed.
 | 
			
		||||
 | 
			
		||||
D) Install Mercurial and Subversion:
 | 
			
		||||
 | 
			
		||||
@ -193,15 +195,21 @@ D) Install Mercurial and Subversion:
 | 
			
		||||
brew install mercurial subversion
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
E) Install NodeJS
 | 
			
		||||
E) Install NodeJS.
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
[The official NodeJS site](https://nodejs.org/en/download/) provides installers
 | 
			
		||||
for "LTS" and "Current" releases.  The "LTS" version should be installed.
 | 
			
		||||
 | 
			
		||||
Older versions of macOS are not compatible with newer versions of NodeJS. In
 | 
			
		||||
local testing, macOS 10.13 required NodeJS version `12.22.12`
 | 
			
		||||
**Older versions of macOS are not compatible with newer versions of NodeJS.**
 | 
			
		||||
 | 
			
		||||
In local testing, macOS 10.13 required NodeJS version `12.22.12`:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
curl -LO https://nodejs.org/download/release/v12.22.12/node-v12.22.12.pkg 
 | 
			
		||||
open node-v12.22.12.pkg
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
@ -289,6 +297,28 @@ git clone https://git.sheetjs.com/sheetjs/sheetjs
 | 
			
		||||
cd sheetjs
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::note pass
 | 
			
		||||
 | 
			
		||||
On older platforms, the clone may fail due to SSL certificate problems:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
fatal: unable to access 'https://git.sheetjs.com/sheetjs/sheetjs/': SSL certificate problem: certificate has expired
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The simplest workaround is to disable SSL verification:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
git config --global http.sslVerify false
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**It is strongly recommended to re-enable SSL verification after cloning**:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
git config --global http.sslVerify true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
1) Install NodeJS modules for building the scripts:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
@ -334,6 +364,27 @@ make test_misc
 | 
			
		||||
git checkout -- .
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::info pass
 | 
			
		||||
 | 
			
		||||
In some tests on older releases of macOS, the build failed with an error:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
ReferenceError: n is not defined
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The first error in the call stack points to `dist/xlsx.zahl.js`.
 | 
			
		||||
 | 
			
		||||
Older versions of macOS `sed` are known to misinterpret newline characters. The
 | 
			
		||||
workaround is to upgrade to a newer version of `sed`. On macOS:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
brew install gnu-sed
 | 
			
		||||
echo 'export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"' >> ~/.profile
 | 
			
		||||
. ~/.profile
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
### Reproduce official builds
 | 
			
		||||
 | 
			
		||||
5) Run `git log` and search for the commit that matches a particular release
 | 
			
		||||
@ -374,21 +425,71 @@ make dist
 | 
			
		||||
 | 
			
		||||
The local checksum for the browser script can be computed with:
 | 
			
		||||
 | 
			
		||||
<Tabs groupId="os">
 | 
			
		||||
  <TabItem value="wsl" label="Windows WSL">
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ md5sum dist/xlsx.full.min.js
 | 
			
		||||
0b2f539797f92d35c6394274818f2c22  dist/xlsx.full.min.js
 | 
			
		||||
md5sum dist/xlsx.full.min.js
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="osx" label="MacOS">
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
md5 dist/xlsx.full.min.js
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="l" label="Linux">
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
md5sum dist/xlsx.full.min.js
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
</Tabs>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The checksum for the CDN version can be computed with:
 | 
			
		||||
 | 
			
		||||
<Tabs groupId="os">
 | 
			
		||||
  <TabItem value="wsl" label="Windows WSL">
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ curl -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5sum -
 | 
			
		||||
0b2f539797f92d35c6394274818f2c22  -
 | 
			
		||||
curl -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5sum -
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="osx" label="MacOS">
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
curl -k -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
  <TabItem value="l" label="Linux">
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
curl -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5sum -
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  </TabItem>
 | 
			
		||||
</Tabs>
 | 
			
		||||
 | 
			
		||||
When the demo was last tested on macOS, against version `0.20.0`:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ md5 dist/xlsx.full.min.js
 | 
			
		||||
# highlight-next-line
 | 
			
		||||
MD5 (dist/xlsx.full.min.js) = 0b2f539797f92d35c6394274818f2c22
 | 
			
		||||
$ curl -k -L https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js | md5
 | 
			
		||||
# highlight-next-line
 | 
			
		||||
0b2f539797f92d35c6394274818f2c22
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The two hashes should match.
 | 
			
		||||
 | 
			
		||||
9) To return to the HEAD commit, run
 | 
			
		||||
9) Return to the `HEAD` commit:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
git checkout master
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 81 KiB  | 
| 
		 Before Width: | Height: | Size: 72 KiB  | 
| 
		 Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/salesforce/export.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 68 KiB  | 
| 
		 Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB  | 
| 
		 Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docz/static/salesforce/version.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						| 
		 After Width: | Height: | Size: 76 KiB  | 
| 
		 Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB  |