sheetforce

This commit is contained in:
SheetJS 2023-10-02 00:57:11 -04:00
parent 2c60a9483f
commit 5bce4b2705
14 changed files with 1107 additions and 296 deletions

@ -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.
![Custom Component](pathname:///files/sfcustcomp.png)
### 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
![Demo](pathname:///files/sfinitial.png)
## 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`:
![Static Resources](pathname:///files/sfstatic.png)
### 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:
![Version number](pathname:///files/sfversion.png)
## 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:
![Custom Component](pathname:///salesforce/custcomp.png)
### 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:
![Demo](pathname:///salesforce/initial.png)
### 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:
![Static Resources](pathname:///salesforce/static.png)
### 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:
![Version number](pathname:///salesforce/version.png)
:::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:
![SF Export Button](pathname:///files/sfexport.png)
```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:
![Excel Export](pathname:///files/sfxlexport.png)
![SF Export Button](pathname:///salesforce/export.png)
29) Click the "Click to Export!" button. The app will attempt to download a file.
The simple export includes all of the data:
![Excel Export](pathname:///salesforce/xl.png)
:::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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB