Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Async support - delay loading xlsx #554

Merged
merged 59 commits into from
Oct 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
f754da6
experimental lazy loading for the table
Aug 22, 2019
b931f02
clean up
Aug 22, 2019
9e6638f
Merge branch 'master' into exp-dynamic
Marc-Andre-Rivet Aug 22, 2019
2a1d90a
clean up
Aug 22, 2019
04717fc
Merge branch 'exp-dynamic' of github.com:plotly/dash-table into exp-d…
Aug 22, 2019
f26b139
dev/test - don't use the dash plugin
Aug 22, 2019
27e8165
tests
Aug 23, 2019
1b51246
visual tests
Aug 23, 2019
b3311b8
unit tests
Aug 23, 2019
82e1f37
unit test
Aug 23, 2019
6b53f6d
unit tests
Aug 23, 2019
726ebab
webpackMode for async chunks
Aug 23, 2019
24514f8
update storybook config
Aug 23, 2019
e8f038d
storybook
Aug 23, 2019
4af7619
use plugin repo instead of experiment
Aug 23, 2019
c97ab98
clean up
Aug 23, 2019
e3d0fbc
clean up xlsx usage
Aug 26, 2019
dbcdb31
abstract lazy load
Aug 26, 2019
2ec22c3
Merge branch 'master' into exp-dynamic
Marc-Andre-Rivet Aug 26, 2019
c140d84
Merge branch 'master' into exp-dynamic
Marc-Andre-Rivet Aug 28, 2019
84528dd
async~* in manifest
Aug 28, 2019
73f54da
Merge remote-tracking branch 'origin/master' into exp-dynamic
Aug 29, 2019
e769eed
Instrumented lazy loading
Aug 29, 2019
dca40d0
clean up
Aug 30, 2019
815f6e2
update lazy factory ordering
Aug 30, 2019
e1aefca
lint
Aug 30, 2019
8bc9502
Merge remote-tracking branch 'origin/dev' into exp-dynamic
Sep 17, 2019
1833bc8
update tests
Sep 17, 2019
f4b7f79
Merge branch 'dev' into exp-dynamic
Marc-Andre-Rivet Sep 18, 2019
9dcddff
merge dev onto exp-dynamic
Sep 23, 2019
e880d3f
Merge branch 'exp-dynamic' of github.com:plotly/dash-table into exp-d…
Sep 23, 2019
399fa83
typo
Sep 23, 2019
166f4f0
(wip) Add _dashprivate_isLazyComponentReady
Sep 24, 2019
3bcd994
lazy
Sep 24, 2019
291f5b7
Merge branch 'dev' into exp-dynamic
Marc-Andre-Rivet Sep 24, 2019
4a372e1
percy error
Sep 24, 2019
2874489
LazyFactory
Sep 24, 2019
accee82
Merge branch 'dev' into exp-dynamic
Marc-Andre-Rivet Sep 24, 2019
0c2566c
Merge branch 'dev' into exp-dynamic
Marc-Andre-Rivet Sep 26, 2019
89dca90
add back async~dash-table to __init__
Sep 26, 2019
bce6a72
Merge branch 'dev' into exp-dynamic
Marc-Andre-Rivet Sep 26, 2019
92eab96
add back async~export.js
Sep 26, 2019
89fee8c
[wip] Use `async` for dash eager/lazy support
Sep 26, 2019
85bcdb6
not sure why this is still here
Sep 26, 2019
1653206
Merge remote-tracking branch 'origin/dev' into exp-dynamic
Sep 26, 2019
c71c357
remove lazy table, only keep lazy xlsx for now
Oct 1, 2019
454270b
undo lazy change in tests
Oct 1, 2019
314b518
webpack-dash-dynamic-import alpha
Oct 2, 2019
2547bb8
new packages
Oct 3, 2019
cdca967
packages
Oct 3, 2019
72be9b1
update usage
Oct 3, 2019
1a0a5d1
changelog
Oct 3, 2019
e90a672
Merge branch 'dev' into exp-dynamic
Marc-Andre-Rivet Oct 4, 2019
e1530e6
Merge branch 'dev' into exp-dynamic
Marc-Andre-Rivet Oct 10, 2019
bb90b16
Merge branch 'dev' into exp-dynamic
Marc-Andre-Rivet Oct 17, 2019
b76e26a
fix merge / version
Oct 17, 2019
e9dd645
Merge branch 'exp-dynamic' of github.com:plotly/dash-table into exp-d…
Oct 17, 2019
0c67d00
cleanup
Oct 19, 2019
e0fc646
unreleased ordering
Oct 19, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
python -m venv venv || virtualenv venv
. venv/bin/activate
pip install -r dev-requirements.txt --quiet
git clone --depth 1 [email protected]:plotly/dash.git dash-main
git clone --depth 1 -b exp-dynamic [email protected]:plotly/dash.git dash-main
pip install -e ./dash-main[dev] --quiet
- run:
Expand Down Expand Up @@ -107,7 +107,7 @@ jobs:
python -m venv venv || virtualenv venv
. venv/bin/activate
pip install -r dev-requirements.txt --quiet
git clone --depth 1 [email protected]:plotly/dash.git dash-main
git clone --depth 1 -b exp-dynamic [email protected]:plotly/dash.git dash-main
pip install -e ./dash-main[dev] --quiet
- run:
Expand Down Expand Up @@ -212,7 +212,7 @@ jobs:
name: Install dependencies (dash)
command: |
. venv/bin/activate
git clone --depth 1 [email protected]:plotly/dash.git dash-main
git clone --depth 1 -b exp-dynamic [email protected]:plotly/dash.git dash-main
pip install -e ./dash-main[dev,testing] --quiet
cd dash-main/dash-renderer && npm run build && pip install -e . && cd ../..
Expand Down
24 changes: 21 additions & 3 deletions .config/webpack/base.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@

const path = require('path');
const WebpackDashDynamicImport = require('@plotly/webpack-dash-dynamic-import');

const basePreprocessing = require('./base.preprocessing');
const packagejson = require('./../../package.json');

const dashLibraryName = packagejson.name.replace(/-/g, '_');


module.exports = (options = {}) => {
const babel = options.babel || undefined;
const preprocessor = options.preprocessor || {};
const preprocessor = basePreprocessing(options.preprocessor);
const mode = options.mode || 'development';
const ts = options.ts || {};

Expand All @@ -24,6 +27,7 @@ module.exports = (options = {}) => {
mode: mode,
output: {
path: path.resolve(__dirname, `./../../${dashLibraryName}`),
chunkFilename: '[name].js',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chunks should use the name they were given..

filename: '[name].js',
library: dashLibraryName,
libraryTarget: 'window'
Expand Down Expand Up @@ -87,6 +91,20 @@ module.exports = (options = {}) => {
tests: path.resolve('./tests')
},
extensions: ['.js', '.ts', '.tsx']
}
},
optimization: {
splitChunks: {
chunks: 'async',
name: true,
cacheGroups: {
async: {

}
}
}
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

essentially, prefix async chunks with async

plugins: [
new WebpackDashDynamicImport()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To inject the path resolution code

]
};
};
7 changes: 7 additions & 0 deletions .config/webpack/base.preprocessing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = ({ definitions, variables, ...options } = {}) => ({
...options,
definitions: definitions || [],
variables: Object.assign({
mode: 'lazy'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chunk out the async code by default

}, variables || {})
});
8 changes: 7 additions & 1 deletion .storybook/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
let babel = require('./babel.config.js');
let config = require('./../.config/webpack/base.js')({
babel
babel,
preprocessor: {
variables: {
mode: 'eager'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storybook can't handle chunking too well - eager keeps the chunks in the entry-point instead

}
}
});

config.externals = {};
delete config.plugins;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want that weird path resolution code - this runs a server-side React generation


module.exports = config;
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased
### Changed
- [#554](https://github.com/plotly/dash-table/pull/554) Async loading of `xlsx` library on export

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ordering after merge was incorrect.

## [4.4.1] - 2019-08-17
### Fixed
- [#618](https://github.com/plotly/dash-table/issues/618) Fix a bug with keyboard navigation not working correctly in certain circumstances when the table contains `readonly` columns.
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
include dash_table/bundle.js
include dash_table/bundle.js.map
include dash_table/async~*.js
include dash_table/async~*.js.map
include dash_table/metadata.json
include dash_table/package-info.json
include LICENSE
Expand Down
16 changes: 16 additions & 0 deletions dash_table_base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@
).format(__version__),
'namespace': package_name,
'dynamic': True
},
{
'relative_package_path': 'async~export.js',
'external_url': (
'https://unpkg.com/dash-table@{}/dash_table/async~export.js'
).format(__version__),
'namespace': package_name,
'async': True
},
{
'relative_package_path': 'async~export.js.map',
'external_url': (
'https://unpkg.com/dash-table@{}/dash_table/async~export.js.map'
).format(__version__),
'namespace': package_name,
'dynamic': True
}
]

Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
"preprivate::opentests": "run-s private::wait*",
"preprivate::test.server": "run-s private::wait_dash*",
"preprivate::test.standalone": "run-s private::wait_js",
"pretest.standalone": "run-s private::build:js-test",
"pretest.standalone": "run-s private::build:js-test-standalone",
"private::build": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack --display-reasons --bail",
"private::build:js": "run-s \"private::build -- --mode production\"",
"private::build:js-dev": "run-s \"private::build -- --mode development --config webpack.dev.config.js\"",
"private::build:js-test": "run-s \"private::build -- --mode development --config webpack.test.config.js\"",
"private::build:js-test-standalone": "run-s \"private::build -- --mode development --config webpack.test.standalone.config.js\"",
"private::build:js-test-watch": "run-s \"private::build -- --mode development --config webpack.test.config.js --watch\"",
"private::build:py": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash'",
"private::host_dash8081": "python tests/cypress/dash/v_be_page.py",
Expand Down Expand Up @@ -61,6 +61,7 @@
"devDependencies": {
"@babel/cli": "^7.6.2",
"@babel/core": "^7.6.2",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/polyfill": "^7.6.0",
"@babel/preset-env": "^7.6.2",
"@babel/preset-react": "^7.0.0",
Expand All @@ -70,6 +71,7 @@
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.4",
"@percy/storybook": "^3.2.0",
"@plotly/webpack-dash-dynamic-import": "^1.0.0",
"@storybook/cli": "^5.1.11",
"@storybook/react": "^5.1.11",
"@types/d3-format": "^1.3.1",
Expand Down
5 changes: 5 additions & 0 deletions src/dash-table/LazyLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default class LazyLoader {
public static get xlsx() {
return import(/* webpackChunkName: "export", webpackMode: "$${{mode}}" */ 'xlsx');
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less chances of errors if all the lazy loading / configuration is isolated here

16 changes: 6 additions & 10 deletions src/dash-table/components/Export/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import XLSX from 'xlsx';
import React from 'react';
import { IDerivedData, Columns, ExportHeaders, ExportFormat, ExportColumns } from 'dash-table/components/Table/props';
import { createWorkbook, createHeadings, createWorksheet } from './utils';
import { createWorkbook, createHeadings, exportWorkbook } from './utils';
import getHeaderRows from 'dash-table/derived/header/headerRows';

interface IExportButtonProps {
Expand All @@ -21,19 +20,16 @@ export default React.memo((props: IExportButtonProps) => {

const exportedColumns = export_columns === ExportColumns.Visible ? visibleColumns : columns;

const handleExport = () => {
const handleExport = async () => {
const columnID = exportedColumns.map(column => column.id);
const columnHeaders = exportedColumns.map(column => column.name);

const maxLength = getHeaderRows(columns);
const heading = (export_headers !== ExportHeaders.None) ? createHeadings(columnHeaders, maxLength) : [];
const ws = createWorksheet(heading, virtual_data.data, columnID, export_headers, merge_duplicate_headers);
const wb = createWorkbook(ws);
if (export_format === ExportFormat.Xlsx) {
XLSX.writeFile(wb, 'Data.xlsx', {bookType: 'xlsx', type: 'buffer'});
} else if (export_format === ExportFormat.Csv) {
XLSX.writeFile(wb, 'Data.csv', {bookType: 'csv', type: 'buffer'});
}

const wb = await createWorkbook(heading, virtual_data.data, columnID, export_headers, merge_duplicate_headers);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restructuring the export code a bit to isolate the xlsx usages

await exportWorkbook(wb, export_format);
};

return (<div>
Expand Down
27 changes: 19 additions & 8 deletions src/dash-table/components/Export/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as R from 'ramda';
import XLSX from 'xlsx';
import { WorkBook } from 'xlsx/types';

import { Data, ExportHeaders } from 'dash-table/components/Table/props';
import LazyLoader from 'dash-table/LazyLoader';

interface IMergeObject {
s: {r: number, c: number};
Expand Down Expand Up @@ -47,13 +49,9 @@ export function getMergeRanges(array: string[][]) {
return R.filter((item: IMergeObject) => item.s.c !== item.e.c || item.s.r !== item.e.r, apiMergeArray);
}

export function createWorkbook(ws: XLSX.WorkSheet) {
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'SheetJS');
return wb;
}
export async function createWorkbook(heading: string[][], data: Data, columnID: string[], exportHeader: string, mergeDuplicateHeaders: boolean ) {
const XLSX = await LazyLoader.xlsx;

export function createWorksheet(heading: string[][], data: Data, columnID: string[], exportHeader: ExportHeaders, mergeDuplicateHeaders: boolean) {
const ws = XLSX.utils.aoa_to_sheet([]);

data = R.map(R.pick(columnID))(data);
Expand All @@ -73,7 +71,20 @@ export function createWorksheet(heading: string[][], data: Data, columnID: strin
} else if (exportHeader === ExportHeaders.Ids) {
XLSX.utils.sheet_add_json(ws, data, { header: columnID });
}
return ws;

const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'SheetJS');
return wb;
}

export async function exportWorkbook (wb: WorkBook, format: string) {
const XLSX = await LazyLoader.xlsx;

if (format === 'xlsx') {
XLSX.writeFile(wb, 'Data.xlsx', { bookType: 'xlsx', type: 'buffer' });
} else if (format === 'csv') {
XLSX.writeFile(wb, 'Data.csv', { bookType: 'csv', type: 'buffer' });
}
}

export function createHeadings(columnHeaders: (string | string[])[], maxLength: number) {
Expand Down
2 changes: 1 addition & 1 deletion src/dash-table/dash/DataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -1307,4 +1307,4 @@ DataTable.persistenceTransforms = {
};

DataTable.defaultProps = defaultProps;
DataTable.propTypes = propTypes;
DataTable.propTypes = propTypes;
2 changes: 1 addition & 1 deletion tests/cypress/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const wp = require('@cypress/webpack-preprocessor');

module.exports = on => {
const options = {
webpackOptions: require('../../../webpack.test.config.js')
webpackOptions: require('../../../webpack.test.standalone.config.js')
};

on('file:preprocessor', wp(options));
Expand Down
35 changes: 18 additions & 17 deletions tests/cypress/tests/unit/exportUtils_tests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { transformMultiDimArray, getMergeRanges, createHeadings, createWorksheet } from 'dash-table/components/Export/utils';
import * as R from 'ramda';
import { ExportHeaders } from 'dash-table/components/Table/props';

import { transformMultiDimArray, getMergeRanges, createHeadings, createWorkbook } from 'dash-table/components/Export/utils';

describe('export', () => {

describe('transformMultiDimArray', () => {
Expand Down Expand Up @@ -225,10 +226,10 @@ describe('export', () => {
];

const columnID = ['col1', 'col2', 'col4'];
it('create sheet with column names as headers for name or display header mode', () => {
const wsName = createWorksheet(Headings, data, columnID, ExportHeaders.Names, true);
const wsDisplay = createWorksheet(Headings, data, columnID, ExportHeaders.Display, true);
const wsDisplayNoMerge = createWorksheet(Headings, data, columnID, ExportHeaders.Display, false);
it('create sheet with column names as headers for name or display header mode', async () => {
const wsName = await createWorkbook(Headings, data, columnID, ExportHeaders.Names, true);
const wsDisplay = await createWorkbook(Headings, data, columnID, ExportHeaders.Display, true);
const wsDisplayNoMerge = await createWorkbook(Headings, data, columnID, ExportHeaders.Display, false);
const expectedWS = {
A1: {t: 's', v: 'rows'},
A2: {t: 's', v: 'rows'},
Expand Down Expand Up @@ -256,12 +257,12 @@ describe('export', () => {
expectedWSDisplay['!merges'] = [ {s: {r: 0, c: 0}, e: {r: 0, c: 1}},
{s: {r: 1, c: 1}, e: {r: 1, c: 2}},
{s: {r: 3, c: 0}, e: {r: 3, c: 2}} ];
expect(wsName).to.deep.equal(expectedWS);
expect(wsDisplayNoMerge).to.deep.equal(expectedWS);
expect(wsDisplay).to.deep.equal(expectedWSDisplay);
expect(wsName.Sheets.SheetJS).to.deep.equal(expectedWS);
expect(wsDisplayNoMerge.Sheets.SheetJS).to.deep.equal(expectedWS);
expect(wsDisplay.Sheets.SheetJS).to.deep.equal(expectedWSDisplay);
});
it('create sheet with column ids as headers', () => {
const ws = createWorksheet(Headings, data, columnID, ExportHeaders.Ids, true);
it('create sheet with column ids as headers', async () => {
const ws = await createWorkbook(Headings, data, columnID, ExportHeaders.Ids, true);
const expectedWS = {
A1: {t: 's', v: 'col1'},
A2: {t: 'n', v: 1},
Expand All @@ -276,10 +277,10 @@ describe('export', () => {
C3: {t: 'n', v: 4},
C4: {t: 'n', v: 3}};
expectedWS['!ref'] = 'A1:C4';
expect(ws).to.deep.equal(expectedWS);
expect(ws.Sheets.SheetJS).to.deep.equal(expectedWS);
});
it('create sheet with no headers', () => {
const ws = createWorksheet([], data, columnID, ExportHeaders.None, true);
it('create sheet with no headers', async () => {
const ws = await createWorkbook([], data, columnID, ExportHeaders.None, true);
const expectedWS = {
A1: {t: 'n', v: 1},
A2: {t: 'n', v: 2},
Expand All @@ -291,15 +292,15 @@ describe('export', () => {
C2: {t: 'n', v: 4},
C3: {t: 'n', v: 3}};
expectedWS['!ref'] = 'A1:C3';
expect(ws).to.deep.equal(expectedWS);
expect(ws.Sheets.SheetJS).to.deep.equal(expectedWS);
});
it('create sheet with undefined column for clearable columns', () => {
it('create sheet with undefined column for clearable columns', async () => {
const newData = [
{col2: 2, col4: 3},
{col2: 3, col4: 4},
{col2: 2, col4: 3}
];
const ws = createWorksheet(Headings, newData, columnID, ExportHeaders.Display, false);
const ws = await createWorkbook(Headings, newData, columnID, ExportHeaders.Display, false);
const expectedWS = {A1: {t: 's', v: 'rows'},
A2: {t: 's', v: 'rows'},
A3: {t: 's', v: 'rows'},
Expand All @@ -319,7 +320,7 @@ describe('export', () => {
C6: {t: 'n', v: 4},
C7: {t: 'n', v: 3}};
expectedWS['!ref'] = 'A1:C7';
expect(ws).to.deep.equal(expectedWS);
expect(ws.Sheets.SheetJS).to.deep.equal(expectedWS);
});
});

Expand Down
2 changes: 1 addition & 1 deletion tests/visual/percy-storybook/Border.defaults.percy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ storiesOf('DashTable/Border (available space filled)', module)
fixed_rows={{ headers: true }}
/>))
.add('with no frozen rows and frozen columns', () => (<DataTable
{...props2}DataTable
{...props2} DataTable
fixed_columns={{ headers: true, data: 1 }}
/>))
.add('with frozen rows and frozen columns', () => (<DataTable
Expand Down
Loading