Skip to content

Commit

Permalink
Merge pull request #126 from plotly/try-to-add-async
Browse files Browse the repository at this point in the history
Add an option to generate async component
  • Loading branch information
Xing Han Lu committed Jul 14, 2021
2 parents c59567c + 84eb6e8 commit 75925fb
Show file tree
Hide file tree
Showing 14 changed files with 295 additions and 80 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Created by .ignore support plugin (hsz.mobi)
### Webpack
.build_cache
### VisualStudioCode template
.vscode/*
!.vscode/settings.json
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ to generate the components in the `build:backends` script of the generated
`package.json`.


## Advanced customization

### Shared cache groups for async chunks

Shared async chunks for code that repeats across multiple async chunks is already supported through our custom `webpack.config.js` optimizations. You can leverage it by manually the path of `{{cookiecutter.project_shortname}}-shared.js` to `_js_dist` inside `{{cookiecutter.project_shortname}}/__init__.py` (as well as the associated external URL).

## More Resources

- Found a bug or have a feature request? [Create an issue](https://github.com/plotly/dash-component-boilerplate/issues/new)
Expand Down
1 change: 1 addition & 0 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"author_email": "Enter your email (For package.json)",
"github_org": "",
"description": "Project Description",
"use_async": ["False", "True"],
"license": [
"MIT License",
"BSD License",
Expand Down
19 changes: 19 additions & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import shlex
import sys
import os
import shutil
import subprocess

install_deps = '{{cookiecutter.install_dependencies}}'
project_shortname = '{{cookiecutter.project_shortname}}'
use_async = '{{cookiecutter.use_async}}'


is_windows = sys.platform == 'win32'
Expand All @@ -33,6 +35,22 @@ def _execute_command(cmd):
return status



# Remove the cookiecutter_templates directory since it only contains
# files that are conditionally included.
template_dir = os.path.join(os.getcwd(), 'cookiecutter_templates')
shutil.rmtree(template_dir)

print("\n\n\nuse_async")
print(use_async)
# If it doesn't use async, we can remove the fragments and lazyloader.js
if use_async != "True":
print('use_async is set to False, your component will not be lazy loaded and fragments will not be created.')
shutil.rmtree(os.path.join(os.getcwd(), 'src', 'lib', 'fragments'))
os.remove(os.path.join(os.getcwd(), 'src', 'lib', 'LazyLoader.js'))



if install_deps != 'True':
print('`install_dependencies` is false!!', file=sys.stderr)
print('Please create a venv in your project root'
Expand Down Expand Up @@ -90,4 +108,5 @@ def _execute_command(cmd):

print('\n{} ready!\n'.format(project_shortname))


sys.exit(0)
2 changes: 2 additions & 0 deletions {{cookiecutter.project_shortname}}/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Created by .ignore support plugin (hsz.mobi)
### Webpack
.build_cache
### VisualStudioCode template
.vscode/*
!.vscode/settings.json
Expand Down
4 changes: 4 additions & 0 deletions {{cookiecutter.project_shortname}}/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
include {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}.min.js
include {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}.min.js.map
include {{cookiecutter.project_shortname}}/async-*.js
include {{cookiecutter.project_shortname}}/async-*.js.map
include {{cookiecutter.project_shortname}}/*-shared.js
include {{cookiecutter.project_shortname}}/*-shared.js.map
include {{cookiecutter.project_shortname}}/metadata.json
include {{cookiecutter.project_shortname}}/package-info.json
include README.md
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { {{cookiecutter.component_name}} as RealComponent } from '../LazyLoader';

/**
* ExampleComponent is an example component.
* It takes a property, `label`, and
* displays it.
* It renders an input with the property `value`
* which is editable by the user.
*/
export default class {{cookiecutter.component_name}} extends Component {
render() {
return (
<React.Suspense fallback={null}>
<RealComponent {...this.props}/>
</React.Suspense>
);
}
}

{{cookiecutter.component_name}}.defaultProps = {};

{{cookiecutter.component_name}}.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
id: PropTypes.string,

/**
* A label that will be printed when this component is rendered.
*/
label: PropTypes.string.isRequired,

/**
* The value displayed in the input.
*/
value: PropTypes.string,

/**
* Dash-assigned callback that should be called to report property changes
* to Dash, to make them available for callbacks.
*/
setProps: PropTypes.func
};


export const defaultProps = {{cookiecutter.component_name}}.defaultProps;
export const propTypes = {{cookiecutter.component_name}}.propTypes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';

/**
* ExampleComponent is an example component.
* It takes a property, `label`, and
* displays it.
* It renders an input with the property `value`
* which is editable by the user.
*/
export default class {{cookiecutter.component_name}} extends Component {
render() {
const {id, label, setProps, value} = this.props;

return (
<div id={id}>
ExampleComponent: {label}&nbsp;
<input
value={value}
onChange={
/*
* Send the new value to the parent component.
* setProps is a prop that is automatically supplied
* by dash's front-end ("dash-renderer").
* In a Dash app, this will update the component's
* props and send the data back to the Python Dash
* app server if a callback uses the modified prop as
* Input or State.
*/
e => setProps({ value: e.target.value })
}
/>
</div>
);
}
}

{{cookiecutter.component_name}}.defaultProps = {};

{{cookiecutter.component_name}}.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
id: PropTypes.string,

/**
* A label that will be printed when this component is rendered.
*/
label: PropTypes.string.isRequired,

/**
* The value displayed in the input.
*/
value: PropTypes.string,

/**
* Dash-assigned callback that should be called to report property changes
* to Dash, to make them available for callbacks.
*/
setProps: PropTypes.func
};
3 changes: 3 additions & 0 deletions {{cookiecutter.project_shortname}}/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
"@babel/preset-env": "^7.5.4",
"@babel/preset-react": "^7.0.0",
"@plotly/webpack-dash-dynamic-import": "^1.2.0",
"@plotly/dash-component-plugins": "^1.2.0",
"babel-eslint": "^10.0.2",
"babel-loader": "^8.0.6",
"copyfiles": "^2.1.1",
Expand All @@ -48,6 +50,7 @@
"react-dom": "^16.8.6",
"styled-jsx": "^3.2.1",
"style-loader": "^0.23.1",
"terser-webpack-plugin": "^2.3.0",
"webpack": "4.36.1",
"webpack-cli": "3.3.6",
"webpack-serve": "3.1.0"
Expand Down
1 change: 1 addition & 0 deletions {{cookiecutter.project_shortname}}/src/lib/LazyLoader.js

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

Original file line number Diff line number Diff line change
@@ -1,61 +1,5 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';

/**
* ExampleComponent is an example component.
* It takes a property, `label`, and
* displays it.
* It renders an input with the property `value`
* which is editable by the user.
*/
export default class {{cookiecutter.component_name}} extends Component {
render() {
const {id, label, setProps, value} = this.props;

return (
<div id={id}>
ExampleComponent: {label}&nbsp;
<input
value={value}
onChange={
/*
* Send the new value to the parent component.
* setProps is a prop that is automatically supplied
* by dash's front-end ("dash-renderer").
* In a Dash app, this will update the component's
* props and send the data back to the Python Dash
* app server if a callback uses the modified prop as
* Input or State.
*/
e => setProps({ value: e.target.value })
}
/>
</div>
);
}
}

{{cookiecutter.component_name}}.defaultProps = {};

{{cookiecutter.component_name}}.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
id: PropTypes.string,

/**
* A label that will be printed when this component is rendered.
*/
label: PropTypes.string.isRequired,

/**
* The value displayed in the input.
*/
value: PropTypes.string,

/**
* Dash-assigned callback that should be called to report property changes
* to Dash, to make them available for callbacks.
*/
setProps: PropTypes.func
};
{%- if cookiecutter.use_async == "True" -%}
{%- include 'cookiecutter_templates/AsyncComponent.react.js' -%}
{%- else -%}
{%- include 'cookiecutter_templates/Component.react.js' -%}
{%- endif -%}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {defaultProps, propTypes} from '../components/{{cookiecutter.component_name}}.react';

/**
* ExampleComponent is an example component.
* It takes a property, `label`, and
* displays it.
* It renders an input with the property `value`
* which is editable by the user.
*/
export default class {{cookiecutter.component_name}} extends Component {
render() {
const {id, label, setProps, value} = this.props;

return (
<div id={id}>
ExampleComponent: {label}&nbsp;
<input
value={value}
onChange={
/*
* Send the new value to the parent component.
* setProps is a prop that is automatically supplied
* by dash's front-end ("dash-renderer").
* In a Dash app, this will update the component's
* props and send the data back to the Python Dash
* app server if a callback uses the modified prop as
* Input or State.
*/
e => setProps({ value: e.target.value })
}
/>
</div>
);
}
}


{{cookiecutter.component_name}}.defaultProps = defaultProps;
{{cookiecutter.component_name}}.propTypes = propTypes;
42 changes: 42 additions & 0 deletions {{cookiecutter.project_shortname}}/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const WebpackDashDynamicImport = require('@plotly/webpack-dash-dynamic-import');
const packagejson = require('./package.json');

const dashLibraryName = packagejson.name.replace(/-/g, '_');
Expand Down Expand Up @@ -46,6 +49,7 @@ module.exports = (env, argv) => {
entry,
output: {
path: path.resolve(__dirname, dashLibraryName),
chunkFilename: '[name].js',
filename,
library: dashLibraryName,
libraryTarget: 'window',
Expand Down Expand Up @@ -77,5 +81,43 @@ module.exports = (env, argv) => {
},
],
},
optimization: {
minimizer: [
new TerserPlugin({
sourceMap: true,
parallel: true,
cache: './.build_cache/terser',
terserOptions: {
warnings: false,
ie8: false
}
})
],
splitChunks: {
name: true,
cacheGroups: {
async: {
chunks: 'async',
minSize: 0,
name(module, chunks, cacheGroupKey) {
return `${cacheGroupKey}-${chunks[0].name}`;
}
},
shared: {
chunks: 'all',
minSize: 0,
minChunks: 2,
name: '{{cookiecutter.project_shortname}}-shared'
}
}
}
},
plugins: [
new WebpackDashDynamicImport(),
new webpack.SourceMapDevToolPlugin({
filename: '[file].map',
exclude: ['async-plotlyjs']
})
]
}
};
Loading

0 comments on commit 75925fb

Please sign in to comment.