From 19a4c47837940f7f04b863846a3e905a284fa02a Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 26 Sep 2018 16:46:41 -0400 Subject: [PATCH 01/59] Refactor to a cookie cutter. --- tests/__init__.py => .cookiecutterrc | 0 .gitignore | 221 +++++++++++++++--- MANIFEST.in | 4 - cookiecutter.json | 9 + .../.babelrc | 0 .../.eslintignore | 0 .../.eslintrc | 0 {{cookiecutter.project_slug}}/.gitignore | 37 +++ .../.npmignore | 0 .../.prettierrc | 0 .../.pylintrc | 0 {{cookiecutter.project_slug}}/LICENSE | 111 +++++++++ {{cookiecutter.project_slug}}/MANIFEST.in | 4 + .../extract-meta | 0 .../index.html | 0 .../package.json | 12 +- .../setup.py | 2 +- .../src}/demo/App.js | 0 .../src}/demo/index.js | 0 .../lib/components/ExampleComponent.react.js | 0 .../src}/lib/index.js | 0 .../tests}/IntegrationTests.py | 0 .../tests/__init__.py | 0 .../tests}/requirements.txt | 0 .../tests}/test_render.py | 0 .../usage.py | 6 +- .../webpack.config.js | 0 .../webpack.serve.config.js | 0 .../__init__.py | 0 .../{{cookiecutter.project_slug}}}/bundle.js | 0 .../metadata.json | 0 .../package.json | 0 32 files changed, 365 insertions(+), 41 deletions(-) rename tests/__init__.py => .cookiecutterrc (100%) delete mode 100644 MANIFEST.in create mode 100644 cookiecutter.json rename .babelrc => {{cookiecutter.project_slug}}/.babelrc (100%) rename .eslintignore => {{cookiecutter.project_slug}}/.eslintignore (100%) rename .eslintrc => {{cookiecutter.project_slug}}/.eslintrc (100%) create mode 100644 {{cookiecutter.project_slug}}/.gitignore rename .npmignore => {{cookiecutter.project_slug}}/.npmignore (100%) rename .prettierrc => {{cookiecutter.project_slug}}/.prettierrc (100%) rename .pylintrc => {{cookiecutter.project_slug}}/.pylintrc (100%) create mode 100644 {{cookiecutter.project_slug}}/LICENSE create mode 100644 {{cookiecutter.project_slug}}/MANIFEST.in rename extract-meta => {{cookiecutter.project_slug}}/extract-meta (100%) mode change 100755 => 100644 rename index.html => {{cookiecutter.project_slug}}/index.html (100%) rename package.json => {{cookiecutter.project_slug}}/package.json (65%) rename setup.py => {{cookiecutter.project_slug}}/setup.py (84%) rename {src => {{cookiecutter.project_slug}}/src}/demo/App.js (100%) rename {src => {{cookiecutter.project_slug}}/src}/demo/index.js (100%) rename {src => {{cookiecutter.project_slug}}/src}/lib/components/ExampleComponent.react.js (100%) rename {src => {{cookiecutter.project_slug}}/src}/lib/index.js (100%) rename {tests => {{cookiecutter.project_slug}}/tests}/IntegrationTests.py (100%) create mode 100644 {{cookiecutter.project_slug}}/tests/__init__.py rename {tests => {{cookiecutter.project_slug}}/tests}/requirements.txt (100%) rename {tests => {{cookiecutter.project_slug}}/tests}/test_render.py (100%) rename usage.py => {{cookiecutter.project_slug}}/usage.py (81%) rename webpack.config.js => {{cookiecutter.project_slug}}/webpack.config.js (100%) rename webpack.serve.config.js => {{cookiecutter.project_slug}}/webpack.serve.config.js (100%) rename {my_dash_component => {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}}/__init__.py (100%) rename {my_dash_component => {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}}/bundle.js (100%) rename {my_dash_component => {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}}/metadata.json (100%) rename {my_dash_component => {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}}/package.json (100%) diff --git a/tests/__init__.py b/.cookiecutterrc similarity index 100% rename from tests/__init__.py rename to .cookiecutterrc diff --git a/.gitignore b/.gitignore index 2c426dc..b6a769c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,204 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# dependencies -/node_modules +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf -# production -/build -/demo +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml -# testing -/coverage +# Gradle +.idea/**/gradle.xml +.idea/**/libraries -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules -npm-debug.log* -yarn-debug.log* -yarn-error.log* +# CMake +cmake-build-*/ -# virtualenv -vv -venv +# Mongo Explorer plugin +.idea/**/mongoSettings.xml -# python -*.pyc +# File-based project format +*.iws -# builds -my_dash_component.egg-info -dist -*__pycache__* +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +### Python template +# Byte-compiled / optimized / DLL files __pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +### SublimeText template +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache -*.pyc +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 3f83343..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include my_dash_component/bundle.js -include my_dash_component/metadata.json -include my_dash_component/package.json -include README.md diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..bc92716 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,9 @@ +{ + "full_name": "Enter name", + "email": "Enter email", + "description": "description", + "project_name": "my dash component", + "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", + "version": "0.1.0", + "open_source_license": ["MIT license", "BSD license", "ISC license", "Apache Software License 2.0", "GNU General Public License v3", "Not open source"] +} \ No newline at end of file diff --git a/.babelrc b/{{cookiecutter.project_slug}}/.babelrc similarity index 100% rename from .babelrc rename to {{cookiecutter.project_slug}}/.babelrc diff --git a/.eslintignore b/{{cookiecutter.project_slug}}/.eslintignore similarity index 100% rename from .eslintignore rename to {{cookiecutter.project_slug}}/.eslintignore diff --git a/.eslintrc b/{{cookiecutter.project_slug}}/.eslintrc similarity index 100% rename from .eslintrc rename to {{cookiecutter.project_slug}}/.eslintrc diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore new file mode 100644 index 0000000..0734ef3 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +/node_modules + +# production +/build +/demo + +# testing +/coverage + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# virtualenv +vv +venv + +# python +*.pyc + +# builds +*egg-info* +dist +*__pycache__* +__pycache__/ + +*.pyc diff --git a/.npmignore b/{{cookiecutter.project_slug}}/.npmignore similarity index 100% rename from .npmignore rename to {{cookiecutter.project_slug}}/.npmignore diff --git a/.prettierrc b/{{cookiecutter.project_slug}}/.prettierrc similarity index 100% rename from .prettierrc rename to {{cookiecutter.project_slug}}/.prettierrc diff --git a/.pylintrc b/{{cookiecutter.project_slug}}/.pylintrc similarity index 100% rename from .pylintrc rename to {{cookiecutter.project_slug}}/.pylintrc diff --git a/{{cookiecutter.project_slug}}/LICENSE b/{{cookiecutter.project_slug}}/LICENSE new file mode 100644 index 0000000..27fb856 --- /dev/null +++ b/{{cookiecutter.project_slug}}/LICENSE @@ -0,0 +1,111 @@ +{% if cookiecutter.open_source_license == 'MIT license' -%} +MIT License + +Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +{% elif cookiecutter.open_source_license == 'BSD license' %} + +BSD License + +Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. +{% elif cookiecutter.open_source_license == 'ISC license' -%} +ISC License + +Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +{% elif cookiecutter.open_source_license == 'Apache Software License 2.0' -%} +Apache Software License 2.0 + +Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% elif cookiecutter.open_source_license == 'GNU General Public License v3' -%} +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + {{ cookiecutter.project_short_description }} + Copyright (C) {% now 'local', '%Y' %} {{ cookiecutter.full_name }} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. +{% endif %} \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/MANIFEST.in b/{{cookiecutter.project_slug}}/MANIFEST.in new file mode 100644 index 0000000..ae02405 --- /dev/null +++ b/{{cookiecutter.project_slug}}/MANIFEST.in @@ -0,0 +1,4 @@ +include {{cookiecutter.project_slug}}/bundle.js +include {{cookiecutter.project_slug}}/metadata.json +include {{cookiecutter.project_slug}}/package.json +include README.md diff --git a/extract-meta b/{{cookiecutter.project_slug}}/extract-meta old mode 100755 new mode 100644 similarity index 100% rename from extract-meta rename to {{cookiecutter.project_slug}}/extract-meta diff --git a/index.html b/{{cookiecutter.project_slug}}/index.html similarity index 100% rename from index.html rename to {{cookiecutter.project_slug}}/index.html diff --git a/package.json b/{{cookiecutter.project_slug}}/package.json similarity index 65% rename from package.json rename to {{cookiecutter.project_slug}}/package.json index 4acbbfc..746b0d9 100644 --- a/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -1,18 +1,18 @@ { - "name": "my-dash-component", - "version": "0.0.1", - "description": "my-dash-component", + "name": "{{ cookiecutter.project_name }}", + "version": "{{ cookiecutter.version }}", + "description": "{{ cookiecutter.description }}", "main": "build/index.js", "scripts": { "start": "webpack-serve ./webpack.serve.config.js --open", "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", - "build:py": "node ./extract-meta src/lib/components > my_dash_component/metadata.json && copyfiles package.json my_dash_component && python -c \"import dash; dash.development.component_loader.generate_classes('my_dash_component', 'my_dash_component/metadata.json')\"", + "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_slug}}/metadata.json && copyfiles package.json {{cookiecutter.project_slug}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_slug}}', '{{cookiecutter.project_slug}}/metadata.json')\"", "build:all": "npm run build:js & npm run build:py", "build:all-dev": "npm run build:js-dev & npm run build:py" }, - "author": "my-name my-email", - "license": "my-license", + "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", + "license": "{{ cookiecutter.open_source_license }}", "dependencies": { "ramda": "^0.25.0", "react": "15.4.2", diff --git a/setup.py b/{{cookiecutter.project_slug}}/setup.py similarity index 84% rename from setup.py rename to {{cookiecutter.project_slug}}/setup.py index e2cf9ca..9a4c8f4 100644 --- a/setup.py +++ b/{{cookiecutter.project_slug}}/setup.py @@ -3,7 +3,7 @@ from setuptools import setup -with open(os.path.join('my_dash_component', 'package.json')) as f: +with open(os.path.join('{{cookiecutter.project_slug}}', 'package.json')) as f: package = json.load(f) package_name = package["name"].replace(" ", "_").replace("-", "_") diff --git a/src/demo/App.js b/{{cookiecutter.project_slug}}/src/demo/App.js similarity index 100% rename from src/demo/App.js rename to {{cookiecutter.project_slug}}/src/demo/App.js diff --git a/src/demo/index.js b/{{cookiecutter.project_slug}}/src/demo/index.js similarity index 100% rename from src/demo/index.js rename to {{cookiecutter.project_slug}}/src/demo/index.js diff --git a/src/lib/components/ExampleComponent.react.js b/{{cookiecutter.project_slug}}/src/lib/components/ExampleComponent.react.js similarity index 100% rename from src/lib/components/ExampleComponent.react.js rename to {{cookiecutter.project_slug}}/src/lib/components/ExampleComponent.react.js diff --git a/src/lib/index.js b/{{cookiecutter.project_slug}}/src/lib/index.js similarity index 100% rename from src/lib/index.js rename to {{cookiecutter.project_slug}}/src/lib/index.js diff --git a/tests/IntegrationTests.py b/{{cookiecutter.project_slug}}/tests/IntegrationTests.py similarity index 100% rename from tests/IntegrationTests.py rename to {{cookiecutter.project_slug}}/tests/IntegrationTests.py diff --git a/{{cookiecutter.project_slug}}/tests/__init__.py b/{{cookiecutter.project_slug}}/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/requirements.txt b/{{cookiecutter.project_slug}}/tests/requirements.txt similarity index 100% rename from tests/requirements.txt rename to {{cookiecutter.project_slug}}/tests/requirements.txt diff --git a/tests/test_render.py b/{{cookiecutter.project_slug}}/tests/test_render.py similarity index 100% rename from tests/test_render.py rename to {{cookiecutter.project_slug}}/tests/test_render.py diff --git a/usage.py b/{{cookiecutter.project_slug}}/usage.py similarity index 81% rename from usage.py rename to {{cookiecutter.project_slug}}/usage.py index d7a9cfb..7fc7674 100644 --- a/usage.py +++ b/{{cookiecutter.project_slug}}/usage.py @@ -1,15 +1,15 @@ -import my_dash_component +import {{cookiecutter.project_slug}} import dash from dash.dependencies import Input, Output import dash_html_components as html -app = dash.Dash('') +app = dash.Dash(__name__) app.scripts.config.serve_locally = True app.css.config.serve_locally = True app.layout = html.Div([ - my_dash_component.ExampleComponent( + {{cookiecutter.project_slug}}.ExampleComponent( id='input', value='my-value', label='my-label' diff --git a/webpack.config.js b/{{cookiecutter.project_slug}}/webpack.config.js similarity index 100% rename from webpack.config.js rename to {{cookiecutter.project_slug}}/webpack.config.js diff --git a/webpack.serve.config.js b/{{cookiecutter.project_slug}}/webpack.serve.config.js similarity index 100% rename from webpack.serve.config.js rename to {{cookiecutter.project_slug}}/webpack.serve.config.js diff --git a/my_dash_component/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py similarity index 100% rename from my_dash_component/__init__.py rename to {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py diff --git a/my_dash_component/bundle.js b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/bundle.js similarity index 100% rename from my_dash_component/bundle.js rename to {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/bundle.js diff --git a/my_dash_component/metadata.json b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/metadata.json similarity index 100% rename from my_dash_component/metadata.json rename to {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/metadata.json diff --git a/my_dash_component/package.json b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json similarity index 100% rename from my_dash_component/package.json rename to {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json From 7d4e1cc1563006df6c5e1fd67bb9acaaadc1dec4 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 26 Sep 2018 16:50:33 -0400 Subject: [PATCH 02/59] Fix .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index b6a769c..9f0f600 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ From 76f2d9f1531116d97ec7ae2d09fb941b1209af76 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 26 Sep 2018 17:03:23 -0400 Subject: [PATCH 03/59] Support dev packages. --- {{cookiecutter.project_slug}}/.gitignore | 220 +++++++++++++++--- {{cookiecutter.project_slug}}/MANIFEST.in | 3 +- .../webpack.config.js | 6 +- .../{{cookiecutter.project_slug}}/__init__.py | 3 +- 4 files changed, 200 insertions(+), 32 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore index 0734ef3..9f0f600 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -1,37 +1,203 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# dependencies -/node_modules +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf -# production -/build -/demo +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml -# testing -/coverage +# Gradle +.idea/**/gradle.xml +.idea/**/libraries -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules -npm-debug.log* -yarn-debug.log* -yarn-error.log* +# CMake +cmake-build-*/ -# virtualenv -vv -venv +# Mongo Explorer plugin +.idea/**/mongoSettings.xml -# python -*.pyc +# File-based project format +*.iws -# builds -*egg-info* -dist -*__pycache__* +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +### Python template +# Byte-compiled / optimized / DLL files __pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +### SublimeText template +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache -*.pyc +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings diff --git a/{{cookiecutter.project_slug}}/MANIFEST.in b/{{cookiecutter.project_slug}}/MANIFEST.in index ae02405..541bd12 100644 --- a/{{cookiecutter.project_slug}}/MANIFEST.in +++ b/{{cookiecutter.project_slug}}/MANIFEST.in @@ -1,4 +1,5 @@ -include {{cookiecutter.project_slug}}/bundle.js +include {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.min.js +include {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.dev.js include {{cookiecutter.project_slug}}/metadata.json include {{cookiecutter.project_slug}}/package.json include README.md diff --git a/{{cookiecutter.project_slug}}/webpack.config.js b/{{cookiecutter.project_slug}}/webpack.config.js index 6471471..3b386a6 100644 --- a/{{cookiecutter.project_slug}}/webpack.config.js +++ b/{{cookiecutter.project_slug}}/webpack.config.js @@ -3,11 +3,11 @@ const packagejson = require('./package.json'); const dashLibraryName = packagejson.name.replace(/-/g, '_'); -module.exports = { +module.exports = (env, argv) => ({ entry: {main: './src/lib/index.js'}, output: { path: path.resolve(__dirname, dashLibraryName), - filename: 'bundle.js', + filename: `${dashLibraryName}.${argv.mode === 'development' ? 'dev' : 'min'}.js`, library: dashLibraryName, libraryTarget: 'window', }, @@ -38,4 +38,4 @@ module.exports = { }, ], }, -}; +}); diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py index 493683c..e337133 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py @@ -31,7 +31,8 @@ _js_dist = [ { - 'relative_package_path': 'bundle.js', + 'relative_package_path': '{}.min.js'.format(__name__), + 'dev_package_path': '{}.dev.js'.format(__name__), 'external_url': ( 'https://unpkg.com/my_dash_component' '/' + package_name + '/bundle.js' From 26e59ef69643b8eafbd47d264e83160ec7f22176 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 26 Sep 2018 17:04:18 -0400 Subject: [PATCH 04/59] Add requirements file. --- {{cookiecutter.project_slug}}/requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 {{cookiecutter.project_slug}}/requirements.txt diff --git a/{{cookiecutter.project_slug}}/requirements.txt b/{{cookiecutter.project_slug}}/requirements.txt new file mode 100644 index 0000000..e5d114a --- /dev/null +++ b/{{cookiecutter.project_slug}}/requirements.txt @@ -0,0 +1,5 @@ +dash +dash-html-components +dash-core-components +percy +selenium From f2fb3e7e827069091d8767bfb71b0719f3b90a56 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 27 Sep 2018 12:15:44 -0400 Subject: [PATCH 05/59] Add component_name cookiecutter variable. --- .cookiecutterrc | 0 cookiecutter.json | 10 +++++++++- {{cookiecutter.project_slug}}/package.json | 4 ++-- {{cookiecutter.project_slug}}/requirements.txt | 5 +---- {{cookiecutter.project_slug}}/src/demo/App.js | 6 +++--- ...s => {{cookiecutter.component_name}}.react.js} | 4 ++-- {{cookiecutter.project_slug}}/src/lib/index.js | 4 ++-- .../tests/requirements.txt | 1 + .../tests/test_render.py | 4 ++-- {{cookiecutter.project_slug}}/usage.py | 2 +- .../{{cookiecutter.project_slug}}/__init__.py | 3 +++ .../{{cookiecutter.project_slug}}/_imports_.py | 5 +++++ .../{{cookiecutter.project_slug}}/package.json | 15 +++++++++------ 13 files changed, 40 insertions(+), 23 deletions(-) delete mode 100644 .cookiecutterrc rename {{cookiecutter.project_slug}}/src/lib/components/{ExampleComponent.react.js => {{cookiecutter.component_name}}.react.js} (94%) create mode 100644 {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/_imports_.py diff --git a/.cookiecutterrc b/.cookiecutterrc deleted file mode 100644 index e69de29..0000000 diff --git a/cookiecutter.json b/cookiecutter.json index bc92716..7823f48 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,5 +5,13 @@ "project_name": "my dash component", "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", "version": "0.1.0", - "open_source_license": ["MIT license", "BSD license", "ISC license", "Apache Software License 2.0", "GNU General Public License v3", "Not open source"] + "component_name": "{{ cookiecutter.project_slug.replace('_', '').capitalize() }}", + "open_source_license": [ + "MIT license", + "BSD license", + "ISC license", + "Apache Software License 2.0", + "GNU General Public License v3", + "Not open source" + ] } \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index 746b0d9..fa76950 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -33,8 +33,8 @@ "npm": "^6.1.0", "react-docgen": "^2.20.1", "style-loader": "^0.21.0", - "webpack": "^4.8.3", - "webpack-cli": "^2.1.3", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.1", "webpack-serve": "^1.0.2" }, "peerDependencies": { diff --git a/{{cookiecutter.project_slug}}/requirements.txt b/{{cookiecutter.project_slug}}/requirements.txt index e5d114a..0d59a04 100644 --- a/{{cookiecutter.project_slug}}/requirements.txt +++ b/{{cookiecutter.project_slug}}/requirements.txt @@ -1,5 +1,2 @@ +# dash is required to call `build:py` dash -dash-html-components -dash-core-components -percy -selenium diff --git a/{{cookiecutter.project_slug}}/src/demo/App.js b/{{cookiecutter.project_slug}}/src/demo/App.js index fa19afa..83a308b 100644 --- a/{{cookiecutter.project_slug}}/src/demo/App.js +++ b/{{cookiecutter.project_slug}}/src/demo/App.js @@ -3,7 +3,7 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import * as R from 'ramda'; -import {ExampleComponent} from '../lib'; +import {{cookiecutter.component_name}} from '../lib'; class App extends Component { @@ -11,7 +11,7 @@ class App extends Component { super(); this.state = { value: '' - } + }; this.setProps = this.setProps.bind(this); } @@ -22,7 +22,7 @@ class App extends Component { render() { return (
- diff --git a/{{cookiecutter.project_slug}}/src/lib/components/ExampleComponent.react.js b/{{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js similarity index 94% rename from {{cookiecutter.project_slug}}/src/lib/components/ExampleComponent.react.js rename to {{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js index 2fd1652..c1bf7e9 100644 --- a/{{cookiecutter.project_slug}}/src/lib/components/ExampleComponent.react.js +++ b/{{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; * It renders an input with the property `value` * which is editable by the user. */ -export default class ExampleComponent extends Component { +export default class {{cookiecutter.component_name}} extends Component { render() { const {id, label, setProps, value} = this.props; @@ -45,7 +45,7 @@ export default class ExampleComponent extends Component { } } -ExampleComponent.propTypes = { +{{cookiecutter.component_name}}.propTypes = { /** * The ID used to identify this component in Dash callbacks */ diff --git a/{{cookiecutter.project_slug}}/src/lib/index.js b/{{cookiecutter.project_slug}}/src/lib/index.js index e5530c1..1b318cc 100644 --- a/{{cookiecutter.project_slug}}/src/lib/index.js +++ b/{{cookiecutter.project_slug}}/src/lib/index.js @@ -1,6 +1,6 @@ /* eslint-disable import/prefer-default-export */ -import ExampleComponent from './components/ExampleComponent.react'; +import {{cookiecutter.component_name}} from './components/{{cookiecutter.component_name}}.react'; export { - ExampleComponent + {{cookiecutter.component_name}} }; diff --git a/{{cookiecutter.project_slug}}/tests/requirements.txt b/{{cookiecutter.project_slug}}/tests/requirements.txt index c692ef6..7e1c0d3 100644 --- a/{{cookiecutter.project_slug}}/tests/requirements.txt +++ b/{{cookiecutter.project_slug}}/tests/requirements.txt @@ -1,3 +1,4 @@ +# Packages needed to run the tests. # Switch into a virtual environment # pip install -r requirements.txt diff --git a/{{cookiecutter.project_slug}}/tests/test_render.py b/{{cookiecutter.project_slug}}/tests/test_render.py index b995f1e..cc3c538 100644 --- a/{{cookiecutter.project_slug}}/tests/test_render.py +++ b/{{cookiecutter.project_slug}}/tests/test_render.py @@ -5,7 +5,7 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC -from my_dash_component import ExampleComponent # pylint: disable=no-name-in-module +from {{cookiecutter.project_slug}} import {{cookiecutter.component_name}} # pylint: disable=no-name-in-module class Tests(IntegrationTests): @@ -13,7 +13,7 @@ def test_render_component(self): app = dash.Dash(__name__) app.layout = html.Div([ html.Div(id='waitfor'), - ExampleComponent(label='Example Component Label') + {{cookiecutter.component_name}}(label='Example Component Label') ]) self.startServer(app) diff --git a/{{cookiecutter.project_slug}}/usage.py b/{{cookiecutter.project_slug}}/usage.py index 7fc7674..65384ed 100644 --- a/{{cookiecutter.project_slug}}/usage.py +++ b/{{cookiecutter.project_slug}}/usage.py @@ -9,7 +9,7 @@ app.css.config.serve_locally = True app.layout = html.Div([ - {{cookiecutter.project_slug}}.ExampleComponent( + {{cookiecutter.project_slug}}.{{cookiecutter.component_name}}( id='input', value='my-value', label='my-label' diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py index e337133..74047ed 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py @@ -6,6 +6,9 @@ import dash as _dash +# noinspection PyUnresolvedReferences +from _imports_ import * + if not hasattr(_dash, 'development'): print('Dash was not successfully imported. ' 'Make sure you don\'t have a file ' diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/_imports_.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/_imports_.py new file mode 100644 index 0000000..93e70a3 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/_imports_.py @@ -0,0 +1,5 @@ +from .{{cookiecutter.component_name}} import {{cookiecutter.component_name}} + +__all__ = [ + "{{cookiecutter.component_name}}" +] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json index 2f885fc..746b0d9 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json @@ -1,16 +1,18 @@ { - "name": "my-dash-component", - "version": "0.0.1", - "description": "my-dash-component", + "name": "{{ cookiecutter.project_name }}", + "version": "{{ cookiecutter.version }}", + "description": "{{ cookiecutter.description }}", "main": "build/index.js", "scripts": { "start": "webpack-serve ./webpack.serve.config.js --open", "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", - "build:py": "./extract-meta src/lib/components > my_dash_component/metadata.json && cp package.json my_dash_component" + "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_slug}}/metadata.json && copyfiles package.json {{cookiecutter.project_slug}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_slug}}', '{{cookiecutter.project_slug}}/metadata.json')\"", + "build:all": "npm run build:js & npm run build:py", + "build:all-dev": "npm run build:js-dev & npm run build:py" }, - "author": "my-name my-email", - "license": "my-license", + "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", + "license": "{{ cookiecutter.open_source_license }}", "dependencies": { "ramda": "^0.25.0", "react": "15.4.2", @@ -20,6 +22,7 @@ "babel-core": "^6.26.3", "babel-eslint": "^8.2.3", "babel-loader": "^7.1.4", + "copyfiles": "^2.0.0", "babel-preset-env": "^1.7.0", "babel-preset-react": "^6.24.1", "css-loader": "^0.28.11", From 808c13caa63a07e3b9efd161b736429a19431d02 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 27 Sep 2018 13:17:57 -0400 Subject: [PATCH 06/59] Update README --- README.md | 34 +++++------ cookiecutter.json | 2 +- {{cookiecutter.project_slug}}/README.md | 71 ++++++++++++++++++++++ {{cookiecutter.project_slug}}/package.json | 3 +- 4 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/README.md diff --git a/README.md b/README.md index 5480451..9525519 100644 --- a/README.md +++ b/README.md @@ -3,33 +3,33 @@ This repository contains the minimal set of code required to create your own custom Dash component. To create your own Dash component: -1. Fork this repo -2. Find-and-replace: - 1. `my_dash_component` with your component library name. - 2. `my-dash-component` with your component library name. - 3. `my-name` with your name and `my-email` with your email address - 4. `my-license` with your license (e.g. `MIT`) - 5. Rename the folder `my_dash_component/` with your component library name -3. Install the dependencies: + +1. Install cookiecutter: +``` +$ pip install cookiecutter +``` +2. Run cookiecutter on the boilerplate repo: ``` -npm install +$ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` -4. Open up the JavaScript demo environment: +3. Install dependencies ``` -npm run start +$ cd {{cookiecutter.project_slug}} +$ npm install +$ pip install -r requirements.txt ``` -5. Write your component code in `src/lib/components`. There is a sample component called `ExampleComponent.react.js` that you can use for inspiration. The demo app is in `src/demo` and you will import your example component code into your demo app. -6. Test your code in a Python environment: +4. Write your component code in `src/lib/components/{{cookiecutter.component_name}}.react.js`. The demo app is in `src/demo` and you will import your example component code into your demo app. +5. Test your code in a Python environment: 1. Build your code ``` - npm run build:js-dev + npm run build:all npm run build:py ``` 2. Run and modify the `usage.py` sample dash app: ``` python usage.py ``` -7. Create a production build and publish: +6. Create a production build and publish: 1. Build your code: ``` npm run build:js @@ -53,10 +53,10 @@ npm run start ``` twine upload dist/dash_component-0.0.1.tar.gz ``` -8. Share your component with the community! https://community.plot.ly/c/dash +7. Share your component with the community! https://community.plot.ly/c/dash # More details -- Include CSS files in your distribution folder (`my_dash_component`) and reference them in `MANIFEST.in` +- Include CSS files in your distribution folder (`{{cookiecutter.component_name}}.react.js`) and reference them in `MANIFEST.in` - The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: ``` python -m tests.test_render diff --git a/cookiecutter.json b/cookiecutter.json index 7823f48..ac2a179 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,7 +5,7 @@ "project_name": "my dash component", "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", "version": "0.1.0", - "component_name": "{{ cookiecutter.project_slug.replace('_', '').capitalize() }}", + "component_name": "{{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}", "open_source_license": [ "MIT license", "BSD license", diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md new file mode 100644 index 0000000..9525519 --- /dev/null +++ b/{{cookiecutter.project_slug}}/README.md @@ -0,0 +1,71 @@ +# Dash Component Boilerplate + +This repository contains the minimal set of code required to create your own custom Dash component. + +To create your own Dash component: + +1. Install cookiecutter: +``` +$ pip install cookiecutter +``` +2. Run cookiecutter on the boilerplate repo: +``` +$ cookiecutter git@github.com:plotly/dash-component-boilerplate.git +``` +3. Install dependencies +``` +$ cd {{cookiecutter.project_slug}} +$ npm install +$ pip install -r requirements.txt +``` +4. Write your component code in `src/lib/components/{{cookiecutter.component_name}}.react.js`. The demo app is in `src/demo` and you will import your example component code into your demo app. +5. Test your code in a Python environment: + 1. Build your code + ``` + npm run build:all + npm run build:py + ``` + 2. Run and modify the `usage.py` sample dash app: + ``` + python usage.py + ``` +6. Create a production build and publish: + 1. Build your code: + ``` + npm run build:js + npm run build:py + ``` + 2. Create a Python tarball + ``` + python setup.py sdist + ``` + This distribution tarball will get generated in the `dist/` folder + + 3. Test your tarball by copying it into a new environment and installing it locally: + ``` + pip install my_dash_component-0.0.1.tar.gz + ``` + + 4. If it works, then you can publish the component to NPM and PyPI: + ``` + npm run publish + ``` + ``` + twine upload dist/dash_component-0.0.1.tar.gz + ``` +7. Share your component with the community! https://community.plot.ly/c/dash + +# More details +- Include CSS files in your distribution folder (`{{cookiecutter.component_name}}.react.js`) and reference them in `MANIFEST.in` +- The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: + ``` + python -m tests.test_render + ``` + The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) +- Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True`. We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). +- Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. + + +# More Resources +- Learn more about Dash: https://dash.plot.ly +- View the original component boilerplate: https://github.com/plotly/dash-component-boilerplate diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index fa76950..ca37cfa 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -8,8 +8,7 @@ "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_slug}}/metadata.json && copyfiles package.json {{cookiecutter.project_slug}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_slug}}', '{{cookiecutter.project_slug}}/metadata.json')\"", - "build:all": "npm run build:js & npm run build:py", - "build:all-dev": "npm run build:js-dev & npm run build:py" + "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", "license": "{{ cookiecutter.open_source_license }}", From eb3164d1b769a3e934f8308d752b3efdbe7b3ffd Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 27 Sep 2018 15:29:53 -0400 Subject: [PATCH 07/59] Add publish_on_npm cookiecutter option to only serve locally. --- README.md | 60 ++++-- cookiecutter.json | 1 + {{cookiecutter.project_slug}}/README.md | 60 ++++-- {{cookiecutter.project_slug}}/package.json | 2 +- .../requirements.txt | 1 + .../{{cookiecutter.component_name}}.react.js | 2 +- .../{{cookiecutter.project_slug}}/__init__.py | 16 +- .../{{cookiecutter.project_slug}}/bundle.js | 184 ------------------ .../metadata.json | 37 ---- .../package.json | 9 +- 10 files changed, 95 insertions(+), 277 deletions(-) delete mode 100644 {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/bundle.js delete mode 100644 {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/metadata.json diff --git a/README.md b/README.md index 9525519..4065494 100644 --- a/README.md +++ b/README.md @@ -13,53 +13,73 @@ $ pip install cookiecutter $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` 3. Install dependencies -``` -$ cd {{cookiecutter.project_slug}} -$ npm install -$ pip install -r requirements.txt -``` + ``` + $ cd {{cookiecutter.project_slug}} + ``` + 1. Install npm packages + ``` + $ npm install + ``` + 2.1 Create a virtual env and activate (optional) + ``` + $ virtualenv venv + $ venv/Scripts/activate + ``` + __Note: venv\Scripts\activate for windows__ + 2.2 Install python packages (required to build the component) + ``` + $ pip install -r requirements.txt + ``` + 2.3 Install the python packages for testing (optional) + ``` + $ pip install -r tests/requirements.txt + ``` 4. Write your component code in `src/lib/components/{{cookiecutter.component_name}}.react.js`. The demo app is in `src/demo` and you will import your example component code into your demo app. 5. Test your code in a Python environment: 1. Build your code ``` - npm run build:all - npm run build:py + $ npm run build:all ``` 2. Run and modify the `usage.py` sample dash app: ``` - python usage.py + $ python usage.py ``` 6. Create a production build and publish: 1. Build your code: ``` - npm run build:js - npm run build:py + $ npm run build:all ``` 2. Create a Python tarball ``` - python setup.py sdist + $ python setup.py sdist ``` This distribution tarball will get generated in the `dist/` folder 3. Test your tarball by copying it into a new environment and installing it locally: ``` - pip install my_dash_component-0.0.1.tar.gz + $ pip install my_dash_component-0.0.1.tar.gz ``` 4. If it works, then you can publish the component to NPM and PyPI: - ``` - npm run publish - ``` - ``` - twine upload dist/dash_component-0.0.1.tar.gz - ``` + 1. Cleanup the dist folder (optional) + ``` + $ rm -rf dist + ``` + 2. Publish on PyPI + ``` + $ twine upload dist/* + ``` + 3. Publish on NPM (Optional if chosen False in `publish_on_npm`) + ``` + $ npm publish + ``` 7. Share your component with the community! https://community.plot.ly/c/dash # More details -- Include CSS files in your distribution folder (`{{cookiecutter.component_name}}.react.js`) and reference them in `MANIFEST.in` +- Include CSS files in your distribution folder (`{{cookiecutter.project_slug}}`) and reference them in `MANIFEST.in` - The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: ``` - python -m tests.test_render + $ python -m unittest tests.test_render ``` The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) - Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True`. We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). diff --git a/cookiecutter.json b/cookiecutter.json index ac2a179..facc59e 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -6,6 +6,7 @@ "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", "version": "0.1.0", "component_name": "{{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}", + "publish_on_npm": true, "open_source_license": [ "MIT license", "BSD license", diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 9525519..4065494 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -13,53 +13,73 @@ $ pip install cookiecutter $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` 3. Install dependencies -``` -$ cd {{cookiecutter.project_slug}} -$ npm install -$ pip install -r requirements.txt -``` + ``` + $ cd {{cookiecutter.project_slug}} + ``` + 1. Install npm packages + ``` + $ npm install + ``` + 2.1 Create a virtual env and activate (optional) + ``` + $ virtualenv venv + $ venv/Scripts/activate + ``` + __Note: venv\Scripts\activate for windows__ + 2.2 Install python packages (required to build the component) + ``` + $ pip install -r requirements.txt + ``` + 2.3 Install the python packages for testing (optional) + ``` + $ pip install -r tests/requirements.txt + ``` 4. Write your component code in `src/lib/components/{{cookiecutter.component_name}}.react.js`. The demo app is in `src/demo` and you will import your example component code into your demo app. 5. Test your code in a Python environment: 1. Build your code ``` - npm run build:all - npm run build:py + $ npm run build:all ``` 2. Run and modify the `usage.py` sample dash app: ``` - python usage.py + $ python usage.py ``` 6. Create a production build and publish: 1. Build your code: ``` - npm run build:js - npm run build:py + $ npm run build:all ``` 2. Create a Python tarball ``` - python setup.py sdist + $ python setup.py sdist ``` This distribution tarball will get generated in the `dist/` folder 3. Test your tarball by copying it into a new environment and installing it locally: ``` - pip install my_dash_component-0.0.1.tar.gz + $ pip install my_dash_component-0.0.1.tar.gz ``` 4. If it works, then you can publish the component to NPM and PyPI: - ``` - npm run publish - ``` - ``` - twine upload dist/dash_component-0.0.1.tar.gz - ``` + 1. Cleanup the dist folder (optional) + ``` + $ rm -rf dist + ``` + 2. Publish on PyPI + ``` + $ twine upload dist/* + ``` + 3. Publish on NPM (Optional if chosen False in `publish_on_npm`) + ``` + $ npm publish + ``` 7. Share your component with the community! https://community.plot.ly/c/dash # More details -- Include CSS files in your distribution folder (`{{cookiecutter.component_name}}.react.js`) and reference them in `MANIFEST.in` +- Include CSS files in your distribution folder (`{{cookiecutter.project_slug}}`) and reference them in `MANIFEST.in` - The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: ``` - python -m tests.test_render + $ python -m unittest tests.test_render ``` The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) - Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True`. We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index ca37cfa..dd25508 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -1,5 +1,5 @@ { - "name": "{{ cookiecutter.project_name }}", + "name": "{{ cookiecutter.project_slug }}", "version": "{{ cookiecutter.version }}", "description": "{{ cookiecutter.description }}", "main": "build/index.js", diff --git a/{{cookiecutter.project_slug}}/requirements.txt b/{{cookiecutter.project_slug}}/requirements.txt index 0d59a04..0b7f2dd 100644 --- a/{{cookiecutter.project_slug}}/requirements.txt +++ b/{{cookiecutter.project_slug}}/requirements.txt @@ -1,2 +1,3 @@ # dash is required to call `build:py` dash +dash-html-components \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js b/{{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js index c1bf7e9..e56d4b7 100644 --- a/{{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js +++ b/{{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js @@ -14,7 +14,7 @@ export default class {{cookiecutter.component_name}} extends Component { return (
- ExampleComponent: {label} + ExampleComponent: {label}  { diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py index 74047ed..4fc3be2 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py @@ -7,7 +7,8 @@ import dash as _dash # noinspection PyUnresolvedReferences -from _imports_ import * +from ._imports_ import * +from ._imports_ import __all__ if not hasattr(_dash, 'development'): print('Dash was not successfully imported. ' @@ -24,10 +25,6 @@ __version__ = package['version'] _current_path = _os.path.dirname(_os.path.abspath(__file__)) -_components = _dash.development.component_loader.load_components( - _os.path.join(_current_path, 'metadata.json'), - package_name -) _this_module = _sys.modules[__name__] @@ -36,10 +33,12 @@ { 'relative_package_path': '{}.min.js'.format(__name__), 'dev_package_path': '{}.dev.js'.format(__name__), + {% if cookiecutter.publish_on_npm == 'True' -%} 'external_url': ( 'https://unpkg.com/my_dash_component' '/' + package_name + '/bundle.js' ).format(__version__), + {%- endif %} 'namespace': package_name } ] @@ -47,7 +46,6 @@ _css_dist = [] -for _component in _components: - setattr(_this_module, _component.__name__, _component) - setattr(_component, '_js_dist', _js_dist) - setattr(_component, '_css_dist', _css_dist) +for _component in __all__: + setattr(locals()[_component], '_js_dist', _js_dist) + setattr(locals()[_component], '_css_dist', _css_dist) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/bundle.js b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/bundle.js deleted file mode 100644 index fdd783e..0000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/bundle.js +++ /dev/null @@ -1,184 +0,0 @@ -window["my_dash_component"] = -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // create a fake namespace object -/******/ // mode & 1: value is a module id, require it -/******/ // mode & 2: merge all properties of value into the ns -/******/ // mode & 4: return value when already ns object -/******/ // mode & 8|1: behave like require -/******/ __webpack_require__.t = function(value, mode) { -/******/ if(mode & 1) value = __webpack_require__(value); -/******/ if(mode & 8) return value; -/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; -/******/ var ns = Object.create(null); -/******/ __webpack_require__.r(ns); -/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); -/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); -/******/ return ns; -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = "./src/lib/index.js"); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "./node_modules/object-assign/index.js": -/*!*********************************************!*\ - !*** ./node_modules/object-assign/index.js ***! - \*********************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -eval("/*\nobject-assign\n(c) Sindre Sorhus\n@license MIT\n*/\n\n\n/* eslint-disable no-unused-vars */\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar propIsEnumerable = Object.prototype.propertyIsEnumerable;\n\nfunction toObject(val) {\n\tif (val === null || val === undefined) {\n\t\tthrow new TypeError('Object.assign cannot be called with null or undefined');\n\t}\n\n\treturn Object(val);\n}\n\nfunction shouldUseNative() {\n\ttry {\n\t\tif (!Object.assign) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Detect buggy property enumeration order in older V8 versions.\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=4118\n\t\tvar test1 = new String('abc'); // eslint-disable-line no-new-wrappers\n\t\ttest1[5] = 'de';\n\t\tif (Object.getOwnPropertyNames(test1)[0] === '5') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test2 = {};\n\t\tfor (var i = 0; i < 10; i++) {\n\t\t\ttest2['_' + String.fromCharCode(i)] = i;\n\t\t}\n\t\tvar order2 = Object.getOwnPropertyNames(test2).map(function (n) {\n\t\t\treturn test2[n];\n\t\t});\n\t\tif (order2.join('') !== '0123456789') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test3 = {};\n\t\t'abcdefghijklmnopqrst'.split('').forEach(function (letter) {\n\t\t\ttest3[letter] = letter;\n\t\t});\n\t\tif (Object.keys(Object.assign({}, test3)).join('') !==\n\t\t\t\t'abcdefghijklmnopqrst') {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch (err) {\n\t\t// We don't expect any of the above to throw, but better to be safe.\n\t\treturn false;\n\t}\n}\n\nmodule.exports = shouldUseNative() ? Object.assign : function (target, source) {\n\tvar from;\n\tvar to = toObject(target);\n\tvar symbols;\n\n\tfor (var s = 1; s < arguments.length; s++) {\n\t\tfrom = Object(arguments[s]);\n\n\t\tfor (var key in from) {\n\t\t\tif (hasOwnProperty.call(from, key)) {\n\t\t\t\tto[key] = from[key];\n\t\t\t}\n\t\t}\n\n\t\tif (getOwnPropertySymbols) {\n\t\t\tsymbols = getOwnPropertySymbols(from);\n\t\t\tfor (var i = 0; i < symbols.length; i++) {\n\t\t\t\tif (propIsEnumerable.call(from, symbols[i])) {\n\t\t\t\t\tto[symbols[i]] = from[symbols[i]];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn to;\n};\n\n\n//# sourceURL=webpack://my_dash_component/./node_modules/object-assign/index.js?"); - -/***/ }), - -/***/ "./node_modules/prop-types/checkPropTypes.js": -/*!***************************************************!*\ - !*** ./node_modules/prop-types/checkPropTypes.js ***! - \***************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar printWarning = function() {};\n\nif (true) {\n var ReactPropTypesSecret = __webpack_require__(/*! ./lib/ReactPropTypesSecret */ \"./node_modules/prop-types/lib/ReactPropTypesSecret.js\");\n var loggedTypeFailures = {};\n\n printWarning = function(text) {\n var message = 'Warning: ' + text;\n if (typeof console !== 'undefined') {\n console.error(message);\n }\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n throw new Error(message);\n } catch (x) {}\n };\n}\n\n/**\n * Assert that the values match with the type specs.\n * Error messages are memorized and will only be shown once.\n *\n * @param {object} typeSpecs Map of name to a ReactPropType\n * @param {object} values Runtime values that need to be type-checked\n * @param {string} location e.g. \"prop\", \"context\", \"child context\"\n * @param {string} componentName Name of the component for error messages.\n * @param {?Function} getStack Returns the component stack.\n * @private\n */\nfunction checkPropTypes(typeSpecs, values, location, componentName, getStack) {\n if (true) {\n for (var typeSpecName in typeSpecs) {\n if (typeSpecs.hasOwnProperty(typeSpecName)) {\n var error;\n // Prop type validation may throw. In case they do, we don't want to\n // fail the render phase where it didn't fail before. So we log it.\n // After these have been cleaned up, we'll let them throw.\n try {\n // This is intentionally an invariant that gets caught. It's the same\n // behavior as without this statement except with a better message.\n if (typeof typeSpecs[typeSpecName] !== 'function') {\n var err = Error(\n (componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' +\n 'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.'\n );\n err.name = 'Invariant Violation';\n throw err;\n }\n error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);\n } catch (ex) {\n error = ex;\n }\n if (error && !(error instanceof Error)) {\n printWarning(\n (componentName || 'React class') + ': type specification of ' +\n location + ' `' + typeSpecName + '` is invalid; the type checker ' +\n 'function must return `null` or an `Error` but returned a ' + typeof error + '. ' +\n 'You may have forgotten to pass an argument to the type checker ' +\n 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +\n 'shape all require an argument).'\n )\n\n }\n if (error instanceof Error && !(error.message in loggedTypeFailures)) {\n // Only monitor this failure once because there tends to be a lot of the\n // same error.\n loggedTypeFailures[error.message] = true;\n\n var stack = getStack ? getStack() : '';\n\n printWarning(\n 'Failed ' + location + ' type: ' + error.message + (stack != null ? stack : '')\n );\n }\n }\n }\n }\n}\n\nmodule.exports = checkPropTypes;\n\n\n//# sourceURL=webpack://my_dash_component/./node_modules/prop-types/checkPropTypes.js?"); - -/***/ }), - -/***/ "./node_modules/prop-types/factoryWithTypeCheckers.js": -/*!************************************************************!*\ - !*** ./node_modules/prop-types/factoryWithTypeCheckers.js ***! - \************************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar assign = __webpack_require__(/*! object-assign */ \"./node_modules/object-assign/index.js\");\n\nvar ReactPropTypesSecret = __webpack_require__(/*! ./lib/ReactPropTypesSecret */ \"./node_modules/prop-types/lib/ReactPropTypesSecret.js\");\nvar checkPropTypes = __webpack_require__(/*! ./checkPropTypes */ \"./node_modules/prop-types/checkPropTypes.js\");\n\nvar printWarning = function() {};\n\nif (true) {\n printWarning = function(text) {\n var message = 'Warning: ' + text;\n if (typeof console !== 'undefined') {\n console.error(message);\n }\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n throw new Error(message);\n } catch (x) {}\n };\n}\n\nfunction emptyFunctionThatReturnsNull() {\n return null;\n}\n\nmodule.exports = function(isValidElement, throwOnDirectAccess) {\n /* global Symbol */\n var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;\n var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.\n\n /**\n * Returns the iterator method function contained on the iterable object.\n *\n * Be sure to invoke the function with the iterable as context:\n *\n * var iteratorFn = getIteratorFn(myIterable);\n * if (iteratorFn) {\n * var iterator = iteratorFn.call(myIterable);\n * ...\n * }\n *\n * @param {?object} maybeIterable\n * @return {?function}\n */\n function getIteratorFn(maybeIterable) {\n var iteratorFn = maybeIterable && (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]);\n if (typeof iteratorFn === 'function') {\n return iteratorFn;\n }\n }\n\n /**\n * Collection of methods that allow declaration and validation of props that are\n * supplied to React components. Example usage:\n *\n * var Props = require('ReactPropTypes');\n * var MyArticle = React.createClass({\n * propTypes: {\n * // An optional string prop named \"description\".\n * description: Props.string,\n *\n * // A required enum prop named \"category\".\n * category: Props.oneOf(['News','Photos']).isRequired,\n *\n * // A prop named \"dialog\" that requires an instance of Dialog.\n * dialog: Props.instanceOf(Dialog).isRequired\n * },\n * render: function() { ... }\n * });\n *\n * A more formal specification of how these methods are used:\n *\n * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...)\n * decl := ReactPropTypes.{type}(.isRequired)?\n *\n * Each and every declaration produces a function with the same signature. This\n * allows the creation of custom validation functions. For example:\n *\n * var MyLink = React.createClass({\n * propTypes: {\n * // An optional string or URI prop named \"href\".\n * href: function(props, propName, componentName) {\n * var propValue = props[propName];\n * if (propValue != null && typeof propValue !== 'string' &&\n * !(propValue instanceof URI)) {\n * return new Error(\n * 'Expected a string or an URI for ' + propName + ' in ' +\n * componentName\n * );\n * }\n * }\n * },\n * render: function() {...}\n * });\n *\n * @internal\n */\n\n var ANONYMOUS = '<>';\n\n // Important!\n // Keep this list in sync with production version in `./factoryWithThrowingShims.js`.\n var ReactPropTypes = {\n array: createPrimitiveTypeChecker('array'),\n bool: createPrimitiveTypeChecker('boolean'),\n func: createPrimitiveTypeChecker('function'),\n number: createPrimitiveTypeChecker('number'),\n object: createPrimitiveTypeChecker('object'),\n string: createPrimitiveTypeChecker('string'),\n symbol: createPrimitiveTypeChecker('symbol'),\n\n any: createAnyTypeChecker(),\n arrayOf: createArrayOfTypeChecker,\n element: createElementTypeChecker(),\n instanceOf: createInstanceTypeChecker,\n node: createNodeChecker(),\n objectOf: createObjectOfTypeChecker,\n oneOf: createEnumTypeChecker,\n oneOfType: createUnionTypeChecker,\n shape: createShapeTypeChecker,\n exact: createStrictShapeTypeChecker,\n };\n\n /**\n * inlined Object.is polyfill to avoid requiring consumers ship their own\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\n */\n /*eslint-disable no-self-compare*/\n function is(x, y) {\n // SameValue algorithm\n if (x === y) {\n // Steps 1-5, 7-10\n // Steps 6.b-6.e: +0 != -0\n return x !== 0 || 1 / x === 1 / y;\n } else {\n // Step 6.a: NaN == NaN\n return x !== x && y !== y;\n }\n }\n /*eslint-enable no-self-compare*/\n\n /**\n * We use an Error-like object for backward compatibility as people may call\n * PropTypes directly and inspect their output. However, we don't use real\n * Errors anymore. We don't inspect their stack anyway, and creating them\n * is prohibitively expensive if they are created too often, such as what\n * happens in oneOfType() for any type before the one that matched.\n */\n function PropTypeError(message) {\n this.message = message;\n this.stack = '';\n }\n // Make `instanceof Error` still work for returned errors.\n PropTypeError.prototype = Error.prototype;\n\n function createChainableTypeChecker(validate) {\n if (true) {\n var manualPropTypeCallCache = {};\n var manualPropTypeWarningCount = 0;\n }\n function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {\n componentName = componentName || ANONYMOUS;\n propFullName = propFullName || propName;\n\n if (secret !== ReactPropTypesSecret) {\n if (throwOnDirectAccess) {\n // New behavior only for users of `prop-types` package\n var err = new Error(\n 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +\n 'Use `PropTypes.checkPropTypes()` to call them. ' +\n 'Read more at http://fb.me/use-check-prop-types'\n );\n err.name = 'Invariant Violation';\n throw err;\n } else if (\"development\" !== 'production' && typeof console !== 'undefined') {\n // Old behavior for people using React.PropTypes\n var cacheKey = componentName + ':' + propName;\n if (\n !manualPropTypeCallCache[cacheKey] &&\n // Avoid spamming the console because they are often not actionable except for lib authors\n manualPropTypeWarningCount < 3\n ) {\n printWarning(\n 'You are manually calling a React.PropTypes validation ' +\n 'function for the `' + propFullName + '` prop on `' + componentName + '`. This is deprecated ' +\n 'and will throw in the standalone `prop-types` package. ' +\n 'You may be seeing this warning due to a third-party PropTypes ' +\n 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.'\n );\n manualPropTypeCallCache[cacheKey] = true;\n manualPropTypeWarningCount++;\n }\n }\n }\n if (props[propName] == null) {\n if (isRequired) {\n if (props[propName] === null) {\n return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));\n }\n return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));\n }\n return null;\n } else {\n return validate(props, propName, componentName, location, propFullName);\n }\n }\n\n var chainedCheckType = checkType.bind(null, false);\n chainedCheckType.isRequired = checkType.bind(null, true);\n\n return chainedCheckType;\n }\n\n function createPrimitiveTypeChecker(expectedType) {\n function validate(props, propName, componentName, location, propFullName, secret) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== expectedType) {\n // `propValue` being instance of, say, date/regexp, pass the 'object'\n // check, but we can offer a more precise error message here rather than\n // 'of type `object`'.\n var preciseType = getPreciseType(propValue);\n\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createAnyTypeChecker() {\n return createChainableTypeChecker(emptyFunctionThatReturnsNull);\n }\n\n function createArrayOfTypeChecker(typeChecker) {\n function validate(props, propName, componentName, location, propFullName) {\n if (typeof typeChecker !== 'function') {\n return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.');\n }\n var propValue = props[propName];\n if (!Array.isArray(propValue)) {\n var propType = getPropType(propValue);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));\n }\n for (var i = 0; i < propValue.length; i++) {\n var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);\n if (error instanceof Error) {\n return error;\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createElementTypeChecker() {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n if (!isValidElement(propValue)) {\n var propType = getPropType(propValue);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createInstanceTypeChecker(expectedClass) {\n function validate(props, propName, componentName, location, propFullName) {\n if (!(props[propName] instanceof expectedClass)) {\n var expectedClassName = expectedClass.name || ANONYMOUS;\n var actualClassName = getClassName(props[propName]);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createEnumTypeChecker(expectedValues) {\n if (!Array.isArray(expectedValues)) {\n true ? printWarning('Invalid argument supplied to oneOf, expected an instance of array.') : undefined;\n return emptyFunctionThatReturnsNull;\n }\n\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n for (var i = 0; i < expectedValues.length; i++) {\n if (is(propValue, expectedValues[i])) {\n return null;\n }\n }\n\n var valuesString = JSON.stringify(expectedValues);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));\n }\n return createChainableTypeChecker(validate);\n }\n\n function createObjectOfTypeChecker(typeChecker) {\n function validate(props, propName, componentName, location, propFullName) {\n if (typeof typeChecker !== 'function') {\n return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.');\n }\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));\n }\n for (var key in propValue) {\n if (propValue.hasOwnProperty(key)) {\n var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error instanceof Error) {\n return error;\n }\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createUnionTypeChecker(arrayOfTypeCheckers) {\n if (!Array.isArray(arrayOfTypeCheckers)) {\n true ? printWarning('Invalid argument supplied to oneOfType, expected an instance of array.') : undefined;\n return emptyFunctionThatReturnsNull;\n }\n\n for (var i = 0; i < arrayOfTypeCheckers.length; i++) {\n var checker = arrayOfTypeCheckers[i];\n if (typeof checker !== 'function') {\n printWarning(\n 'Invalid argument supplied to oneOfType. Expected an array of check functions, but ' +\n 'received ' + getPostfixForTypeWarning(checker) + ' at index ' + i + '.'\n );\n return emptyFunctionThatReturnsNull;\n }\n }\n\n function validate(props, propName, componentName, location, propFullName) {\n for (var i = 0; i < arrayOfTypeCheckers.length; i++) {\n var checker = arrayOfTypeCheckers[i];\n if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) {\n return null;\n }\n }\n\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.'));\n }\n return createChainableTypeChecker(validate);\n }\n\n function createNodeChecker() {\n function validate(props, propName, componentName, location, propFullName) {\n if (!isNode(props[propName])) {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createShapeTypeChecker(shapeTypes) {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));\n }\n for (var key in shapeTypes) {\n var checker = shapeTypes[key];\n if (!checker) {\n continue;\n }\n var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error) {\n return error;\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createStrictShapeTypeChecker(shapeTypes) {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));\n }\n // We need to check all keys in case some are required but missing from\n // props.\n var allKeys = assign({}, props[propName], shapeTypes);\n for (var key in allKeys) {\n var checker = shapeTypes[key];\n if (!checker) {\n return new PropTypeError(\n 'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +\n '\\nBad object: ' + JSON.stringify(props[propName], null, ' ') +\n '\\nValid keys: ' + JSON.stringify(Object.keys(shapeTypes), null, ' ')\n );\n }\n var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error) {\n return error;\n }\n }\n return null;\n }\n\n return createChainableTypeChecker(validate);\n }\n\n function isNode(propValue) {\n switch (typeof propValue) {\n case 'number':\n case 'string':\n case 'undefined':\n return true;\n case 'boolean':\n return !propValue;\n case 'object':\n if (Array.isArray(propValue)) {\n return propValue.every(isNode);\n }\n if (propValue === null || isValidElement(propValue)) {\n return true;\n }\n\n var iteratorFn = getIteratorFn(propValue);\n if (iteratorFn) {\n var iterator = iteratorFn.call(propValue);\n var step;\n if (iteratorFn !== propValue.entries) {\n while (!(step = iterator.next()).done) {\n if (!isNode(step.value)) {\n return false;\n }\n }\n } else {\n // Iterator will provide entry [k,v] tuples rather than values.\n while (!(step = iterator.next()).done) {\n var entry = step.value;\n if (entry) {\n if (!isNode(entry[1])) {\n return false;\n }\n }\n }\n }\n } else {\n return false;\n }\n\n return true;\n default:\n return false;\n }\n }\n\n function isSymbol(propType, propValue) {\n // Native Symbol.\n if (propType === 'symbol') {\n return true;\n }\n\n // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'\n if (propValue['@@toStringTag'] === 'Symbol') {\n return true;\n }\n\n // Fallback for non-spec compliant Symbols which are polyfilled.\n if (typeof Symbol === 'function' && propValue instanceof Symbol) {\n return true;\n }\n\n return false;\n }\n\n // Equivalent of `typeof` but with special handling for array and regexp.\n function getPropType(propValue) {\n var propType = typeof propValue;\n if (Array.isArray(propValue)) {\n return 'array';\n }\n if (propValue instanceof RegExp) {\n // Old webkits (at least until Android 4.0) return 'function' rather than\n // 'object' for typeof a RegExp. We'll normalize this here so that /bla/\n // passes PropTypes.object.\n return 'object';\n }\n if (isSymbol(propType, propValue)) {\n return 'symbol';\n }\n return propType;\n }\n\n // This handles more types than `getPropType`. Only used for error messages.\n // See `createPrimitiveTypeChecker`.\n function getPreciseType(propValue) {\n if (typeof propValue === 'undefined' || propValue === null) {\n return '' + propValue;\n }\n var propType = getPropType(propValue);\n if (propType === 'object') {\n if (propValue instanceof Date) {\n return 'date';\n } else if (propValue instanceof RegExp) {\n return 'regexp';\n }\n }\n return propType;\n }\n\n // Returns a string that is postfixed to a warning about an invalid type.\n // For example, \"undefined\" or \"of type array\"\n function getPostfixForTypeWarning(value) {\n var type = getPreciseType(value);\n switch (type) {\n case 'array':\n case 'object':\n return 'an ' + type;\n case 'boolean':\n case 'date':\n case 'regexp':\n return 'a ' + type;\n default:\n return type;\n }\n }\n\n // Returns class name of the object, if any.\n function getClassName(propValue) {\n if (!propValue.constructor || !propValue.constructor.name) {\n return ANONYMOUS;\n }\n return propValue.constructor.name;\n }\n\n ReactPropTypes.checkPropTypes = checkPropTypes;\n ReactPropTypes.PropTypes = ReactPropTypes;\n\n return ReactPropTypes;\n};\n\n\n//# sourceURL=webpack://my_dash_component/./node_modules/prop-types/factoryWithTypeCheckers.js?"); - -/***/ }), - -/***/ "./node_modules/prop-types/index.js": -/*!******************************************!*\ - !*** ./node_modules/prop-types/index.js ***! - \******************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nif (true) {\n var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' &&\n Symbol.for &&\n Symbol.for('react.element')) ||\n 0xeac7;\n\n var isValidElement = function(object) {\n return typeof object === 'object' &&\n object !== null &&\n object.$$typeof === REACT_ELEMENT_TYPE;\n };\n\n // By explicitly using `prop-types` you are opting into new development behavior.\n // http://fb.me/prop-types-in-prod\n var throwOnDirectAccess = true;\n module.exports = __webpack_require__(/*! ./factoryWithTypeCheckers */ \"./node_modules/prop-types/factoryWithTypeCheckers.js\")(isValidElement, throwOnDirectAccess);\n} else {}\n\n\n//# sourceURL=webpack://my_dash_component/./node_modules/prop-types/index.js?"); - -/***/ }), - -/***/ "./node_modules/prop-types/lib/ReactPropTypesSecret.js": -/*!*************************************************************!*\ - !*** ./node_modules/prop-types/lib/ReactPropTypesSecret.js ***! - \*************************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';\n\nmodule.exports = ReactPropTypesSecret;\n\n\n//# sourceURL=webpack://my_dash_component/./node_modules/prop-types/lib/ReactPropTypesSecret.js?"); - -/***/ }), - -/***/ "./src/lib/components/ExampleComponent.react.js": -/*!******************************************************!*\ - !*** ./src/lib/components/ExampleComponent.react.js ***! - \******************************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nvar _react = __webpack_require__(/*! react */ \"react\");\n\nvar _react2 = _interopRequireDefault(_react);\n\nvar _propTypes = __webpack_require__(/*! prop-types */ \"./node_modules/prop-types/index.js\");\n\nvar _propTypes2 = _interopRequireDefault(_propTypes);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\n/**\n * ExampleComponent is an example component.\n * It takes a property, `label`, and\n * displays it.\n * It renders an input with the property `value`\n * which is editable by the user.\n */\nvar ExampleComponent = function (_Component) {\n _inherits(ExampleComponent, _Component);\n\n function ExampleComponent() {\n _classCallCheck(this, ExampleComponent);\n\n return _possibleConstructorReturn(this, (ExampleComponent.__proto__ || Object.getPrototypeOf(ExampleComponent)).apply(this, arguments));\n }\n\n _createClass(ExampleComponent, [{\n key: 'render',\n value: function render() {\n var _this2 = this;\n\n var _props = this.props,\n id = _props.id,\n label = _props.label,\n setProps = _props.setProps,\n value = _props.value;\n\n\n return _react2.default.createElement(\n 'div',\n { id: id },\n 'ExampleComponent: ',\n label,\n _react2.default.createElement('input', {\n value: value,\n onChange: function onChange(e) {\n /*\n * Send the new value to the parent component.\n # setProps is a prop that is automatically supplied\n * by dash's front-end (\"dash-renderer\").\n * In a Dash app, this will send the data back to the\n * Python Dash app server.\n * If the component properties are not \"subscribed\"\n * to by a Dash callback, then Dash dash-renderer\n * will not pass through `setProps` and it is expected\n * that the component manages its own state.\n */\n if (setProps) {\n setProps({\n value: e.target.value\n });\n } else {\n _this2.setState({\n value: e.target.value\n });\n }\n }\n })\n );\n }\n }]);\n\n return ExampleComponent;\n}(_react.Component);\n\nexports.default = ExampleComponent;\n\n\nExampleComponent.propTypes = {\n /**\n * The ID used to identify this compnent in Dash callbacks\n */\n id: _propTypes2.default.string,\n\n /**\n * A label that will be printed when this component is rendered.\n */\n label: _propTypes2.default.string.isRequired,\n\n /**\n * The value displayed in the input\n */\n value: _propTypes2.default.string,\n\n /**\n * Dash-assigned callback that should be called whenever any of the\n * properties change\n */\n setProps: _propTypes2.default.func\n};\n\n//# sourceURL=webpack://my_dash_component/./src/lib/components/ExampleComponent.react.js?"); - -/***/ }), - -/***/ "./src/lib/index.js": -/*!**************************!*\ - !*** ./src/lib/index.js ***! - \**************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.ExampleComponent = undefined;\n\nvar _ExampleComponent = __webpack_require__(/*! ./components/ExampleComponent.react */ \"./src/lib/components/ExampleComponent.react.js\");\n\nvar _ExampleComponent2 = _interopRequireDefault(_ExampleComponent);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nexports.ExampleComponent = _ExampleComponent2.default; /* eslint-disable import/prefer-default-export */\n\n//# sourceURL=webpack://my_dash_component/./src/lib/index.js?"); - -/***/ }), - -/***/ "react": -/*!************************!*\ - !*** external "React" ***! - \************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -eval("(function() { module.exports = window[\"React\"]; }());\n\n//# sourceURL=webpack://my_dash_component/external_%22React%22?"); - -/***/ }) - -/******/ }); \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/metadata.json b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/metadata.json deleted file mode 100644 index 67e5ea5..0000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/metadata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "src/lib/components/ExampleComponent.react.js": { - "description": "ExampleComponent is an example component.\nIt takes a property, `label`, and\ndisplays it.\nIt renders an input with the property `value`\nwhich is editable by the user.", - "displayName": "ExampleComponent", - "methods": [], - "props": { - "id": { - "type": { - "name": "string" - }, - "required": false, - "description": "The ID used to identify this compnent in Dash callbacks" - }, - "label": { - "type": { - "name": "string" - }, - "required": true, - "description": "A label that will be printed when this component is rendered." - }, - "value": { - "type": { - "name": "string" - }, - "required": false, - "description": "The value displayed in the input" - }, - "setProps": { - "type": { - "name": "func" - }, - "required": false, - "description": "Dash-assigned callback that should be called whenever any of the\nproperties change" - } - } - } -} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json index 746b0d9..dd25508 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json @@ -1,5 +1,5 @@ { - "name": "{{ cookiecutter.project_name }}", + "name": "{{ cookiecutter.project_slug }}", "version": "{{ cookiecutter.version }}", "description": "{{ cookiecutter.description }}", "main": "build/index.js", @@ -8,8 +8,7 @@ "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_slug}}/metadata.json && copyfiles package.json {{cookiecutter.project_slug}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_slug}}', '{{cookiecutter.project_slug}}/metadata.json')\"", - "build:all": "npm run build:js & npm run build:py", - "build:all-dev": "npm run build:js-dev & npm run build:py" + "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", "license": "{{ cookiecutter.open_source_license }}", @@ -33,8 +32,8 @@ "npm": "^6.1.0", "react-docgen": "^2.20.1", "style-loader": "^0.21.0", - "webpack": "^4.8.3", - "webpack-cli": "^2.1.3", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.1", "webpack-serve": "^1.0.2" }, "peerDependencies": { From 1938ab90d1f3e87f558fdd9b5e1bc8ebd260f092 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 27 Sep 2018 17:28:40 -0400 Subject: [PATCH 08/59] Use SPDX license identifiers. --- cookiecutter.json | 6 +++--- {{cookiecutter.project_slug}}/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cookiecutter.json b/cookiecutter.json index facc59e..f3c0182 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -8,9 +8,9 @@ "component_name": "{{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}", "publish_on_npm": true, "open_source_license": [ - "MIT license", - "BSD license", - "ISC license", + "MIT License", + "BSD License", + "ISC License", "Apache Software License 2.0", "GNU General Public License v3", "Not open source" diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index dd25508..e01091f 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -11,7 +11,7 @@ "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", - "license": "{{ cookiecutter.open_source_license }}", + "license": "{% set _license_identifiers = {'MIT License': 'MIT','BSD License': 'BSD','ISC License': 'ISC','Apache Software License 2.0': 'Apache-2.0','GNU General Public License v3': 'GPL-3.0','Not open source': ''} %} {{ _license_identifiers[cookiecutter.open_source_license] }}", "dependencies": { "ramda": "^0.25.0", "react": "15.4.2", From 902a6086faf490aba8515d4d0338475f49c7ba39 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 27 Sep 2018 18:04:59 -0400 Subject: [PATCH 09/59] Rename slug to shortname, add verify step. --- README.md | 6 +++--- cookiecutter.json | 12 ++++++------ hooks/pre_gen_project.py | 18 ++++++++++++++++++ .../.babelrc | 0 .../.eslintignore | 0 .../.eslintrc | 0 .../.gitignore | 0 .../.npmignore | 0 .../.prettierrc | 0 .../.pylintrc | 0 .../LICENSE | 0 {{cookiecutter.project_shortname}}/MANIFEST.in | 5 +++++ .../README.md | 4 ++-- .../extract-meta | 0 .../index.html | 0 .../package.json | 4 ++-- .../requirements.txt | 0 .../setup.py | 2 +- .../src/demo/App.js | 0 .../src/demo/index.js | 0 .../{{cookiecutter.component_name}}.react.js | 0 .../src/lib/index.js | 0 .../tests/IntegrationTests.py | 0 .../tests/__init__.py | 0 .../tests/requirements.txt | 0 .../tests/test_render.py | 2 +- .../usage.py | 4 ++-- .../webpack.config.js | 0 .../webpack.serve.config.js | 0 .../__init__.py | 0 .../_imports_.py | 0 .../package.json | 4 ++-- {{cookiecutter.project_slug}}/MANIFEST.in | 5 ----- 33 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 hooks/pre_gen_project.py rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/.babelrc (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/.eslintignore (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/.eslintrc (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/.gitignore (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/.npmignore (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/.prettierrc (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/.pylintrc (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/LICENSE (100%) create mode 100644 {{cookiecutter.project_shortname}}/MANIFEST.in rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/README.md (96%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/extract-meta (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/index.html (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/package.json (82%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/requirements.txt (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/setup.py (84%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/src/demo/App.js (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/src/demo/index.js (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/src/lib/components/{{cookiecutter.component_name}}.react.js (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/src/lib/index.js (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/tests/IntegrationTests.py (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/tests/__init__.py (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/tests/requirements.txt (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/tests/test_render.py (86%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/usage.py (82%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/webpack.config.js (100%) rename {{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}}/webpack.serve.config.js (100%) rename {{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}}/__init__.py (100%) rename {{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}}/_imports_.py (100%) rename {{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}} => {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}}/package.json (80%) delete mode 100644 {{cookiecutter.project_slug}}/MANIFEST.in diff --git a/README.md b/README.md index 4065494..f5bb0cb 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` 3. Install dependencies ``` - $ cd {{cookiecutter.project_slug}} + $ cd {{cookiecutter.project_shortname}} ``` 1. Install npm packages ``` @@ -34,7 +34,7 @@ $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` $ pip install -r tests/requirements.txt ``` -4. Write your component code in `src/lib/components/{{cookiecutter.component_name}}.react.js`. The demo app is in `src/demo` and you will import your example component code into your demo app. +4. Write your component code in `src/lib/components/`. The demo app is in `src/demo` and you will import your example component code into your demo app. 5. Test your code in a Python environment: 1. Build your code ``` @@ -76,7 +76,7 @@ $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git 7. Share your component with the community! https://community.plot.ly/c/dash # More details -- Include CSS files in your distribution folder (`{{cookiecutter.project_slug}}`) and reference them in `MANIFEST.in` +- Include CSS files in your distribution folder (`./project_shortname` with `__init__.py` inside) and reference them in `MANIFEST.in` - The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: ``` $ python -m unittest tests.test_render diff --git a/cookiecutter.json b/cookiecutter.json index f3c0182..17d5f2e 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,13 +1,13 @@ { - "full_name": "Enter name", - "email": "Enter email", - "description": "description", "project_name": "my dash component", - "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", - "version": "0.1.0", + "project_shortname": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", "component_name": "{{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}", + "full_name": "Enter your name (For package.json)", + "email": "Enter your email (For package.json)", + "description": "Project Description", "publish_on_npm": true, - "open_source_license": [ + "version": "0.1.0", + "license": [ "MIT License", "BSD License", "ISC License", diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py new file mode 100644 index 0000000..0873293 --- /dev/null +++ b/hooks/pre_gen_project.py @@ -0,0 +1,18 @@ +from __future__ import print_function +import sys + +full_name = '{{cookiecutter.full_name}}' +email = '{{cookiecutter.email}}' + + +def verify(variable_name, s): + if '(For package.json)' in s: + print('Invalid {}: {}'.format(variable_name, s), + file=sys.stderr) + sys.exit(-1) + + +for name, value in (('full_name', full_name), ('email', email)): + verify(name, value) + +sys.exit(0) diff --git a/{{cookiecutter.project_slug}}/.babelrc b/{{cookiecutter.project_shortname}}/.babelrc similarity index 100% rename from {{cookiecutter.project_slug}}/.babelrc rename to {{cookiecutter.project_shortname}}/.babelrc diff --git a/{{cookiecutter.project_slug}}/.eslintignore b/{{cookiecutter.project_shortname}}/.eslintignore similarity index 100% rename from {{cookiecutter.project_slug}}/.eslintignore rename to {{cookiecutter.project_shortname}}/.eslintignore diff --git a/{{cookiecutter.project_slug}}/.eslintrc b/{{cookiecutter.project_shortname}}/.eslintrc similarity index 100% rename from {{cookiecutter.project_slug}}/.eslintrc rename to {{cookiecutter.project_shortname}}/.eslintrc diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_shortname}}/.gitignore similarity index 100% rename from {{cookiecutter.project_slug}}/.gitignore rename to {{cookiecutter.project_shortname}}/.gitignore diff --git a/{{cookiecutter.project_slug}}/.npmignore b/{{cookiecutter.project_shortname}}/.npmignore similarity index 100% rename from {{cookiecutter.project_slug}}/.npmignore rename to {{cookiecutter.project_shortname}}/.npmignore diff --git a/{{cookiecutter.project_slug}}/.prettierrc b/{{cookiecutter.project_shortname}}/.prettierrc similarity index 100% rename from {{cookiecutter.project_slug}}/.prettierrc rename to {{cookiecutter.project_shortname}}/.prettierrc diff --git a/{{cookiecutter.project_slug}}/.pylintrc b/{{cookiecutter.project_shortname}}/.pylintrc similarity index 100% rename from {{cookiecutter.project_slug}}/.pylintrc rename to {{cookiecutter.project_shortname}}/.pylintrc diff --git a/{{cookiecutter.project_slug}}/LICENSE b/{{cookiecutter.project_shortname}}/LICENSE similarity index 100% rename from {{cookiecutter.project_slug}}/LICENSE rename to {{cookiecutter.project_shortname}}/LICENSE diff --git a/{{cookiecutter.project_shortname}}/MANIFEST.in b/{{cookiecutter.project_shortname}}/MANIFEST.in new file mode 100644 index 0000000..35d3766 --- /dev/null +++ b/{{cookiecutter.project_shortname}}/MANIFEST.in @@ -0,0 +1,5 @@ +include {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}.min.js +include {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}.dev.js +include {{cookiecutter.project_shortname}}/metadata.json +include {{cookiecutter.project_shortname}}/package.json +include README.md diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_shortname}}/README.md similarity index 96% rename from {{cookiecutter.project_slug}}/README.md rename to {{cookiecutter.project_shortname}}/README.md index 4065494..c880238 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_shortname}}/README.md @@ -14,7 +14,7 @@ $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` 3. Install dependencies ``` - $ cd {{cookiecutter.project_slug}} + $ cd {{cookiecutter.project_shortname}} ``` 1. Install npm packages ``` @@ -76,7 +76,7 @@ $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git 7. Share your component with the community! https://community.plot.ly/c/dash # More details -- Include CSS files in your distribution folder (`{{cookiecutter.project_slug}}`) and reference them in `MANIFEST.in` +- Include CSS files in your distribution folder (`{{cookiecutter.project_shortname}}`) and reference them in `MANIFEST.in` - The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: ``` $ python -m unittest tests.test_render diff --git a/{{cookiecutter.project_slug}}/extract-meta b/{{cookiecutter.project_shortname}}/extract-meta similarity index 100% rename from {{cookiecutter.project_slug}}/extract-meta rename to {{cookiecutter.project_shortname}}/extract-meta diff --git a/{{cookiecutter.project_slug}}/index.html b/{{cookiecutter.project_shortname}}/index.html similarity index 100% rename from {{cookiecutter.project_slug}}/index.html rename to {{cookiecutter.project_shortname}}/index.html diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_shortname}}/package.json similarity index 82% rename from {{cookiecutter.project_slug}}/package.json rename to {{cookiecutter.project_shortname}}/package.json index e01091f..8189a53 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_shortname}}/package.json @@ -1,5 +1,5 @@ { - "name": "{{ cookiecutter.project_slug }}", + "name": "{{ cookiecutter.project_shortname }}", "version": "{{ cookiecutter.version }}", "description": "{{ cookiecutter.description }}", "main": "build/index.js", @@ -7,7 +7,7 @@ "start": "webpack-serve ./webpack.serve.config.js --open", "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", - "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_slug}}/metadata.json && copyfiles package.json {{cookiecutter.project_slug}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_slug}}', '{{cookiecutter.project_slug}}/metadata.json')\"", + "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", diff --git a/{{cookiecutter.project_slug}}/requirements.txt b/{{cookiecutter.project_shortname}}/requirements.txt similarity index 100% rename from {{cookiecutter.project_slug}}/requirements.txt rename to {{cookiecutter.project_shortname}}/requirements.txt diff --git a/{{cookiecutter.project_slug}}/setup.py b/{{cookiecutter.project_shortname}}/setup.py similarity index 84% rename from {{cookiecutter.project_slug}}/setup.py rename to {{cookiecutter.project_shortname}}/setup.py index 9a4c8f4..3b73f40 100644 --- a/{{cookiecutter.project_slug}}/setup.py +++ b/{{cookiecutter.project_shortname}}/setup.py @@ -3,7 +3,7 @@ from setuptools import setup -with open(os.path.join('{{cookiecutter.project_slug}}', 'package.json')) as f: +with open(os.path.join('{{cookiecutter.project_shortname}}', 'package.json')) as f: package = json.load(f) package_name = package["name"].replace(" ", "_").replace("-", "_") diff --git a/{{cookiecutter.project_slug}}/src/demo/App.js b/{{cookiecutter.project_shortname}}/src/demo/App.js similarity index 100% rename from {{cookiecutter.project_slug}}/src/demo/App.js rename to {{cookiecutter.project_shortname}}/src/demo/App.js diff --git a/{{cookiecutter.project_slug}}/src/demo/index.js b/{{cookiecutter.project_shortname}}/src/demo/index.js similarity index 100% rename from {{cookiecutter.project_slug}}/src/demo/index.js rename to {{cookiecutter.project_shortname}}/src/demo/index.js diff --git a/{{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js b/{{cookiecutter.project_shortname}}/src/lib/components/{{cookiecutter.component_name}}.react.js similarity index 100% rename from {{cookiecutter.project_slug}}/src/lib/components/{{cookiecutter.component_name}}.react.js rename to {{cookiecutter.project_shortname}}/src/lib/components/{{cookiecutter.component_name}}.react.js diff --git a/{{cookiecutter.project_slug}}/src/lib/index.js b/{{cookiecutter.project_shortname}}/src/lib/index.js similarity index 100% rename from {{cookiecutter.project_slug}}/src/lib/index.js rename to {{cookiecutter.project_shortname}}/src/lib/index.js diff --git a/{{cookiecutter.project_slug}}/tests/IntegrationTests.py b/{{cookiecutter.project_shortname}}/tests/IntegrationTests.py similarity index 100% rename from {{cookiecutter.project_slug}}/tests/IntegrationTests.py rename to {{cookiecutter.project_shortname}}/tests/IntegrationTests.py diff --git a/{{cookiecutter.project_slug}}/tests/__init__.py b/{{cookiecutter.project_shortname}}/tests/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/tests/__init__.py rename to {{cookiecutter.project_shortname}}/tests/__init__.py diff --git a/{{cookiecutter.project_slug}}/tests/requirements.txt b/{{cookiecutter.project_shortname}}/tests/requirements.txt similarity index 100% rename from {{cookiecutter.project_slug}}/tests/requirements.txt rename to {{cookiecutter.project_shortname}}/tests/requirements.txt diff --git a/{{cookiecutter.project_slug}}/tests/test_render.py b/{{cookiecutter.project_shortname}}/tests/test_render.py similarity index 86% rename from {{cookiecutter.project_slug}}/tests/test_render.py rename to {{cookiecutter.project_shortname}}/tests/test_render.py index cc3c538..b5078d7 100644 --- a/{{cookiecutter.project_slug}}/tests/test_render.py +++ b/{{cookiecutter.project_shortname}}/tests/test_render.py @@ -5,7 +5,7 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC -from {{cookiecutter.project_slug}} import {{cookiecutter.component_name}} # pylint: disable=no-name-in-module +from {{cookiecutter.project_shortname}} import {{cookiecutter.component_name}} # pylint: disable=no-name-in-module class Tests(IntegrationTests): diff --git a/{{cookiecutter.project_slug}}/usage.py b/{{cookiecutter.project_shortname}}/usage.py similarity index 82% rename from {{cookiecutter.project_slug}}/usage.py rename to {{cookiecutter.project_shortname}}/usage.py index 65384ed..ec13476 100644 --- a/{{cookiecutter.project_slug}}/usage.py +++ b/{{cookiecutter.project_shortname}}/usage.py @@ -1,4 +1,4 @@ -import {{cookiecutter.project_slug}} +import {{cookiecutter.project_shortname}} import dash from dash.dependencies import Input, Output import dash_html_components as html @@ -9,7 +9,7 @@ app.css.config.serve_locally = True app.layout = html.Div([ - {{cookiecutter.project_slug}}.{{cookiecutter.component_name}}( + {{cookiecutter.project_shortname}}.{{cookiecutter.component_name}}( id='input', value='my-value', label='my-label' diff --git a/{{cookiecutter.project_slug}}/webpack.config.js b/{{cookiecutter.project_shortname}}/webpack.config.js similarity index 100% rename from {{cookiecutter.project_slug}}/webpack.config.js rename to {{cookiecutter.project_shortname}}/webpack.config.js diff --git a/{{cookiecutter.project_slug}}/webpack.serve.config.js b/{{cookiecutter.project_shortname}}/webpack.serve.config.js similarity index 100% rename from {{cookiecutter.project_slug}}/webpack.serve.config.js rename to {{cookiecutter.project_shortname}}/webpack.serve.config.js diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py rename to {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/_imports_.py b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/_imports_.py similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/_imports_.py rename to {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/_imports_.py diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json similarity index 80% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json rename to {{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json index dd25508..e25b3ce 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json @@ -1,5 +1,5 @@ { - "name": "{{ cookiecutter.project_slug }}", + "name": "{{ cookiecutter.project_shortname }}", "version": "{{ cookiecutter.version }}", "description": "{{ cookiecutter.description }}", "main": "build/index.js", @@ -7,7 +7,7 @@ "start": "webpack-serve ./webpack.serve.config.js --open", "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", - "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_slug}}/metadata.json && copyfiles package.json {{cookiecutter.project_slug}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_slug}}', '{{cookiecutter.project_slug}}/metadata.json')\"", + "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", diff --git a/{{cookiecutter.project_slug}}/MANIFEST.in b/{{cookiecutter.project_slug}}/MANIFEST.in deleted file mode 100644 index 541bd12..0000000 --- a/{{cookiecutter.project_slug}}/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.min.js -include {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.dev.js -include {{cookiecutter.project_slug}}/metadata.json -include {{cookiecutter.project_slug}}/package.json -include README.md From d35d4b52b42ca596164a4ae7cf0e92094248aa1d Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 27 Sep 2018 18:11:27 -0400 Subject: [PATCH 10/59] Update readme. --- README.md | 4 +++- {{cookiecutter.project_shortname}}/README.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f5bb0cb..0140bcf 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ $ pip install cookiecutter ``` $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` +The prompt will ask questions about the project, then it will create your project in a directory with the `project_shortname` variable. + 3. Install dependencies ``` $ cd {{cookiecutter.project_shortname}} @@ -82,7 +84,7 @@ $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git $ python -m unittest tests.test_render ``` The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) -- Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True`. We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). +- Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash serves the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True` (unless you choose `False` on `publish_on_npm`). We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). - Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. diff --git a/{{cookiecutter.project_shortname}}/README.md b/{{cookiecutter.project_shortname}}/README.md index c880238..7b3623a 100644 --- a/{{cookiecutter.project_shortname}}/README.md +++ b/{{cookiecutter.project_shortname}}/README.md @@ -12,6 +12,8 @@ $ pip install cookiecutter ``` $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` +The prompt will ask questions about the project, then it will create your project in a directory with the `project_shortname` variable. + 3. Install dependencies ``` $ cd {{cookiecutter.project_shortname}} @@ -82,7 +84,7 @@ $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git $ python -m unittest tests.test_render ``` The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) -- Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True`. We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). +- Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True` (unless you choose `False` on `publish_on_npm`). We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). - Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. From 3e70693670567af0c834781b8524f403f391bb24 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 28 Sep 2018 16:44:43 -0400 Subject: [PATCH 11/59] Add install dependencies step. --- cookiecutter.json | 3 +- hooks/post_gen_project.py | 73 +++++++++++++++++++ hooks/pre_gen_project.py | 2 +- {{cookiecutter.project_shortname}}/LICENSE | 10 +-- .../package.json | 4 +- .../package.json | 2 +- 6 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 hooks/post_gen_project.py diff --git a/cookiecutter.json b/cookiecutter.json index 17d5f2e..52392e6 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -14,5 +14,6 @@ "Apache Software License 2.0", "GNU General Public License v3", "Not open source" - ] + ], + "install_dependencies": true } \ No newline at end of file diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py new file mode 100644 index 0000000..cce21b8 --- /dev/null +++ b/hooks/post_gen_project.py @@ -0,0 +1,73 @@ +from __future__ import print_function + +import shlex +import sys +import os +import subprocess +import json +import collections +import time + +install_deps = '{{cookiecutter.install_dependencies}}' +project_shortname = '{{cookiecutter.project_shortname}}' + +if install_deps != 'True': + sys.exit(0) + +print('\n\nInstalling dependencies\n', file=sys.stderr) + + +def _execute_command(cmd): + line = shlex.split(cmd, posix=not sys.platform == 'win32') + + print('Executing: {}'.format(cmd)) + + # call instead of Popen to get realtime output + status = subprocess.call(line) + + if status != 0: + print('post_gen_project command failed: {}'.format(cmd), + file=sys.stderr) + sys.exit(status) + + return status + + +# Create a virtual env +_execute_command('pip install virtualenv --upgrade --quiet') +_execute_command('virtualenv venv') + +# Install dev dependencies in virtualenv +python_executable = os.path.join('venv', 'Scripts', 'python') + +# Patch up the package.json to use the venv python for class generation. +print('Patching build command') + +with open('package.json', 'r+') as package_file: + package_json = json.load(package_file, + object_pairs_hook=collections.OrderedDict) + + package_json['scripts']['build:py'] \ + = package_json['scripts']['build:py'].replace( + '%(python_path)', python_executable) + + package_file.seek(0) + package_file.truncate(0) + + json.dump(package_json, package_file, indent=4) + + +# Install python requirements. +_execute_command( + r'{} -m pip install -r requirements.txt'.format(python_executable)) + +# Install node_modules +_execute_command('npm install') + +# Run the first build +print('Building initial bundles...') +_execute_command('npm run build:all') + +print('\n{} ready!\n'.format(project_shortname)) + +sys.exit(0) diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 0873293..7561591 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -7,7 +7,7 @@ def verify(variable_name, s): if '(For package.json)' in s: - print('Invalid {}: {}'.format(variable_name, s), + print('\nInvalid {}: {}\n'.format(variable_name, s), file=sys.stderr) sys.exit(-1) diff --git a/{{cookiecutter.project_shortname}}/LICENSE b/{{cookiecutter.project_shortname}}/LICENSE index 27fb856..79380b4 100644 --- a/{{cookiecutter.project_shortname}}/LICENSE +++ b/{{cookiecutter.project_shortname}}/LICENSE @@ -1,4 +1,4 @@ -{% if cookiecutter.open_source_license == 'MIT license' -%} +{% if cookiecutter.license == 'MIT license' -%} MIT License Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} @@ -20,7 +20,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -{% elif cookiecutter.open_source_license == 'BSD license' %} +{% elif cookiecutter.license == 'BSD license' %} BSD License @@ -51,7 +51,7 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% elif cookiecutter.open_source_license == 'ISC license' -%} +{% elif cookiecutter.license == 'ISC license' -%} ISC License Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} @@ -59,7 +59,7 @@ Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -{% elif cookiecutter.open_source_license == 'Apache Software License 2.0' -%} +{% elif cookiecutter.license == 'Apache Software License 2.0' -%} Apache Software License 2.0 Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} @@ -75,7 +75,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -{% elif cookiecutter.open_source_license == 'GNU General Public License v3' -%} +{% elif cookiecutter.license == 'GNU General Public License v3' -%} GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 diff --git a/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/package.json index 8189a53..9897c24 100644 --- a/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/package.json @@ -7,11 +7,11 @@ "start": "webpack-serve ./webpack.serve.config.js --open", "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", - "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", + "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && %(python_path) -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", - "license": "{% set _license_identifiers = {'MIT License': 'MIT','BSD License': 'BSD','ISC License': 'ISC','Apache Software License 2.0': 'Apache-2.0','GNU General Public License v3': 'GPL-3.0','Not open source': ''} %} {{ _license_identifiers[cookiecutter.open_source_license] }}", + "license": "{% set _license_identifiers = {'MIT License': 'MIT','BSD License': 'BSD','ISC License': 'ISC','Apache Software License 2.0': 'Apache-2.0','GNU General Public License v3': 'GPL-3.0','Not open source': ''} %} {{ _license_identifiers[cookiecutter.license] }}", "dependencies": { "ramda": "^0.25.0", "react": "15.4.2", diff --git a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json index e25b3ce..76d52c7 100644 --- a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json @@ -11,7 +11,7 @@ "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", - "license": "{{ cookiecutter.open_source_license }}", + "license": "{{ cookiecutter.license }}", "dependencies": { "ramda": "^0.25.0", "react": "15.4.2", From 8e05aabb5fed9ee6c9d7ae35173737fd6b41329d Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 28 Sep 2018 21:21:27 -0400 Subject: [PATCH 12/59] Different root README. --- README.md | 110 +++++++++++----------------------------------- cookiecutter.json | 2 +- 2 files changed, 26 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 0140bcf..9bf13df 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,33 @@ # Dash Component Boilerplate -This repository contains the minimal set of code required to create your own custom Dash component. +This repository contains a cookiecutter template to create your own dash components. -To create your own Dash component: +## Usage -1. Install cookiecutter: -``` -$ pip install cookiecutter -``` -2. Run cookiecutter on the boilerplate repo: -``` -$ cookiecutter git@github.com:plotly/dash-component-boilerplate.git -``` -The prompt will ask questions about the project, then it will create your project in a directory with the `project_shortname` variable. - -3. Install dependencies - ``` - $ cd {{cookiecutter.project_shortname}} - ``` - 1. Install npm packages - ``` - $ npm install - ``` - 2.1 Create a virtual env and activate (optional) - ``` - $ virtualenv venv - $ venv/Scripts/activate - ``` - __Note: venv\Scripts\activate for windows__ - 2.2 Install python packages (required to build the component) - ``` - $ pip install -r requirements.txt - ``` - 2.3 Install the python packages for testing (optional) - ``` - $ pip install -r tests/requirements.txt - ``` -4. Write your component code in `src/lib/components/`. The demo app is in `src/demo` and you will import your example component code into your demo app. -5. Test your code in a Python environment: - 1. Build your code - ``` - $ npm run build:all - ``` - 2. Run and modify the `usage.py` sample dash app: - ``` - $ python usage.py - ``` -6. Create a production build and publish: - 1. Build your code: - ``` - $ npm run build:all - ``` - 2. Create a Python tarball - ``` - $ python setup.py sdist - ``` - This distribution tarball will get generated in the `dist/` folder +To use this boilerplate: - 3. Test your tarball by copying it into a new environment and installing it locally: +1. Install the requirements: ``` - $ pip install my_dash_component-0.0.1.tar.gz + $ pip install cookiecutter + $ pip install virtualenv ``` - - 4. If it works, then you can publish the component to NPM and PyPI: - 1. Cleanup the dist folder (optional) - ``` - $ rm -rf dist - ``` - 2. Publish on PyPI - ``` - $ twine upload dist/* - ``` - 3. Publish on NPM (Optional if chosen False in `publish_on_npm`) - ``` - $ npm publish - ``` -7. Share your component with the community! https://community.plot.ly/c/dash - -# More details -- Include CSS files in your distribution folder (`./project_shortname` with `__init__.py` inside) and reference them in `MANIFEST.in` -- The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: - ``` - $ python -m unittest tests.test_render +2. Run cookiecutter on the boilerplate repo: ``` - The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) -- Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash serves the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True` (unless you choose `False` on `publish_on_npm`). We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). -- Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. - - -# More Resources -- Learn more about Dash: https://dash.plot.ly -- View the original component boilerplate: https://github.com/plotly/dash-component-boilerplate + $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git + ``` +3. Answer the questions about the project. + - project name + - component name + - author info + - description + - version + - license + - publish_on_npm: Set to false to only serve locally from the package data. + - install_dependencies: Set to false to only generate the project structure. +4. The project will be generated in the folder of `project_shorname`. +5. Follow the directions in the generated README to start developing your new dash component. + +Installing the dependencies can take a long time. They will be installed in a +folder named `venv`, created by virtualenv. This ensure that dash is installed +to generate the components in the `build:py` script of the generated +`package.json`. diff --git a/cookiecutter.json b/cookiecutter.json index 52392e6..b24b6ed 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,7 +5,6 @@ "full_name": "Enter your name (For package.json)", "email": "Enter your email (For package.json)", "description": "Project Description", - "publish_on_npm": true, "version": "0.1.0", "license": [ "MIT License", @@ -15,5 +14,6 @@ "GNU General Public License v3", "Not open source" ], + "publish_on_npm": true, "install_dependencies": true } \ No newline at end of file From 634ae3cac103a08fda0ed8cd46cdedb1f27cb832 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 09:56:36 -0400 Subject: [PATCH 13/59] Remove virtualenv install from hook. --- hooks/post_gen_project.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index cce21b8..a5a2495 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -6,16 +6,14 @@ import subprocess import json import collections -import time install_deps = '{{cookiecutter.install_dependencies}}' project_shortname = '{{cookiecutter.project_shortname}}' +python_executable = os.path.join('venv', 'Scripts', 'python') if install_deps != 'True': sys.exit(0) -print('\n\nInstalling dependencies\n', file=sys.stderr) - def _execute_command(cmd): line = shlex.split(cmd, posix=not sys.platform == 'win32') @@ -33,13 +31,6 @@ def _execute_command(cmd): return status -# Create a virtual env -_execute_command('pip install virtualenv --upgrade --quiet') -_execute_command('virtualenv venv') - -# Install dev dependencies in virtualenv -python_executable = os.path.join('venv', 'Scripts', 'python') - # Patch up the package.json to use the venv python for class generation. print('Patching build command') @@ -56,6 +47,10 @@ def _execute_command(cmd): json.dump(package_json, package_file, indent=4) +# Create a virtual env +_execute_command('virtualenv venv') + +print('\n\nInstalling dependencies\n', file=sys.stderr) # Install python requirements. _execute_command( From 802cb7340e1257deb61d17ee24d02325632b65ea Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 11:42:41 -0400 Subject: [PATCH 14/59] Fix extra space in package.json license. --- {{cookiecutter.project_shortname}}/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/package.json index 9897c24..049ff73 100644 --- a/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/package.json @@ -11,7 +11,7 @@ "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", - "license": "{% set _license_identifiers = {'MIT License': 'MIT','BSD License': 'BSD','ISC License': 'ISC','Apache Software License 2.0': 'Apache-2.0','GNU General Public License v3': 'GPL-3.0','Not open source': ''} %} {{ _license_identifiers[cookiecutter.license] }}", + "license": "{% set _license_identifiers = {'MIT License': 'MIT','BSD License': 'BSD','ISC License': 'ISC','Apache Software License 2.0': 'Apache-2.0','GNU General Public License v3': 'GPL-3.0','Not open source': ''} %}{{ _license_identifiers[cookiecutter.license] }}", "dependencies": { "ramda": "^0.25.0", "react": "15.4.2", From 10e0679210ab99eae53067da0b2a3c212955c555 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 11:47:38 -0400 Subject: [PATCH 15/59] Add warning if install_dependencies is false. --- hooks/post_gen_project.py | 40 ++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index a5a2495..356ccb8 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -11,8 +11,23 @@ project_shortname = '{{cookiecutter.project_shortname}}' python_executable = os.path.join('venv', 'Scripts', 'python') -if install_deps != 'True': - sys.exit(0) +# Patch up the package.json to use the venv python for class generation. +# If install_dependencies is false, the venv must be created manually and +# the requirements installed. +print('Patching build command') + +with open('package.json', 'r+') as package_file: + package_json = json.load(package_file, + object_pairs_hook=collections.OrderedDict) + + package_json['scripts']['build:py'] \ + = package_json['scripts']['build:py'].replace( + '%(python_path)', python_executable) + + package_file.seek(0) + package_file.truncate(0) + + json.dump(package_json, package_file, indent=4) def _execute_command(cmd): @@ -31,21 +46,12 @@ def _execute_command(cmd): return status -# Patch up the package.json to use the venv python for class generation. -print('Patching build command') - -with open('package.json', 'r+') as package_file: - package_json = json.load(package_file, - object_pairs_hook=collections.OrderedDict) - - package_json['scripts']['build:py'] \ - = package_json['scripts']['build:py'].replace( - '%(python_path)', python_executable) - - package_file.seek(0) - package_file.truncate(0) - - json.dump(package_json, package_file, indent=4) +if install_deps != 'True': + print('`install_requirements` is false!!', file=sys.stderr) + print('Please create a venv in your project root' + ' and install the dependencies in requirements.txt', + file=sys.stderr) + sys.exit(0) # Create a virtual env _execute_command('virtualenv venv') From 19727fc49d10d2df350c0b40e33eca38904bf65e Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 11:51:48 -0400 Subject: [PATCH 16/59] Add package.json generation test. --- .circleci/config.yml | 48 ++++-------------------------------------- README.md | 1 + pytest.ini | 2 ++ tests/requirements.txt | 3 +++ tests/test_generate.py | 18 ++++++++++++++++ 5 files changed, 28 insertions(+), 44 deletions(-) create mode 100644 pytest.ini create mode 100644 tests/requirements.txt create mode 100644 tests/test_generate.py diff --git a/.circleci/config.yml b/.circleci/config.yml index e7454f2..81e41e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,37 +1,12 @@ version: 2 jobs: - "node": - docker: - - image: circleci/node:8.11.3 - - steps: - - checkout - - - restore_cache: - key: deps1-{{ .Branch }}-{{ checksum "package.json" }} - - - run: - name: Install package.json - command: npm i - - - save_cache: - key: deps1-{{ .Branch }}-{{ checksum "package.json" }} - paths: - - node_modules - - - run: - name: Run eslint - command: ./node_modules/.bin/eslint src - when: always - - "python-3.6": docker: - - image: circleci/python:3.6-stretch-browsers + - image: circleci/python:3.6-stretch-node-browsers environment: - PERCY_ENABLED: False + PERCY_ENABLE: 0 steps: - checkout @@ -56,24 +31,10 @@ jobs: - "venv" - run: - name: Run pylint - command: | - . venv/bin/activate - pylint usage.py tests - when: always - - - run: - name: Run flake8 - command: | - . venv/bin/activate - flake8 usage.py tests - when: always - - - run: - name: Integration Tests + name: Generations tests command: | . venv/bin/activate - python -m unittest tests.test_render + pytest tests when: always @@ -82,4 +43,3 @@ workflows: build: jobs: - "python-3.6" - - "node" diff --git a/README.md b/README.md index 9bf13df..f62fce2 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ To use this boilerplate: $ pip install cookiecutter $ pip install virtualenv ``` + npm is also required. 2. Run cookiecutter on the boilerplate repo: ``` $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..39baf33 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +testpaths = tests/ diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..5a93bd6 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,3 @@ +dash +pytest +pytest-cookies diff --git a/tests/test_generate.py b/tests/test_generate.py new file mode 100644 index 0000000..c33475b --- /dev/null +++ b/tests/test_generate.py @@ -0,0 +1,18 @@ +import json +import os + + +def test_package_json(cookies): + result = cookies.bake(extra_context={ + 'install_dependencies': False, + 'project_name': 'Test Component', + 'full_name': 'test', + 'email': 'test@example.com' + }) + + package_json = json.loads(result.project.join('package.json').read()) + + assert package_json['name'] == 'test_component' + assert package_json['license'] == 'MIT' + assert os.path.join('venv', 'Scripts', 'python')\ + in package_json['scripts']['build:py'] From cf97743068ad6b9df9d7672a4b903dfb53dead50 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 12:36:50 -0400 Subject: [PATCH 17/59] Fix npm install of generated project. --- hooks/post_gen_project.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 356ccb8..a3d379e 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -11,6 +11,23 @@ project_shortname = '{{cookiecutter.project_shortname}}' python_executable = os.path.join('venv', 'Scripts', 'python') + +def _execute_command(cmd): + line = shlex.split(cmd, posix=not sys.platform == 'win32') + + print('Executing: {}'.format(cmd)) + + # call instead of Popen to get realtime output + status = subprocess.call(line, shell=True) + + if status != 0: + print('post_gen_project command failed: {}'.format(cmd), + file=sys.stderr) + sys.exit(status) + + return status + + # Patch up the package.json to use the venv python for class generation. # If install_dependencies is false, the venv must be created manually and # the requirements installed. @@ -30,22 +47,6 @@ json.dump(package_json, package_file, indent=4) -def _execute_command(cmd): - line = shlex.split(cmd, posix=not sys.platform == 'win32') - - print('Executing: {}'.format(cmd)) - - # call instead of Popen to get realtime output - status = subprocess.call(line) - - if status != 0: - print('post_gen_project command failed: {}'.format(cmd), - file=sys.stderr) - sys.exit(status) - - return status - - if install_deps != 'True': print('`install_requirements` is false!!', file=sys.stderr) print('Please create a venv in your project root' From 11cef25c69b3b6b4e7924c56ce72d4582433a736 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 14:16:26 -0400 Subject: [PATCH 18/59] Update root readme. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f62fce2..b27d8de 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dash Component Boilerplate -This repository contains a cookiecutter template to create your own dash components. +This repository contains a [Cookiecutter](https://github.com/audreyr/cookiecutter) template to create your own Dash components. ## Usage @@ -11,7 +11,7 @@ To use this boilerplate: $ pip install cookiecutter $ pip install virtualenv ``` - npm is also required. + [Node.js/npm is also required.](https://nodejs.org/en/) 2. Run cookiecutter on the boilerplate repo: ``` $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git @@ -25,10 +25,10 @@ To use this boilerplate: - license - publish_on_npm: Set to false to only serve locally from the package data. - install_dependencies: Set to false to only generate the project structure. -4. The project will be generated in the folder of `project_shorname`. -5. Follow the directions in the generated README to start developing your new dash component. +4. The project will be generated in the folder of `project_shortname`. +5. Follow the directions in the generated README to start developing your new Dash component. Installing the dependencies can take a long time. They will be installed in a -folder named `venv`, created by virtualenv. This ensure that dash is installed +folder named `venv`, created by virtualenv. This ensures that dash is installed to generate the components in the `build:py` script of the generated `package.json`. From c374a48cba474186d09ec046408f8480d82d8f20 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 14:46:46 -0400 Subject: [PATCH 19/59] Add node .gitignore template. --- .gitignore | 90 +++++++++++++++++-- {{cookiecutter.project_shortname}}/.gitignore | 90 +++++++++++++++++-- 2 files changed, 162 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 9f0f600..a651450 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,10 @@ # Created by .ignore support plugin (hsz.mobi) +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 @@ -60,12 +66,80 @@ fabric.properties # Editor-based Rest Client .idea/httpRequests -### VisualStudioCode template -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless ### Python template # Byte-compiled / optimized / DLL files __pycache__/ @@ -83,6 +157,7 @@ dist/ downloads/ eggs/ .eggs/ +lib/ lib64/ parts/ sdist/ @@ -108,7 +183,6 @@ htmlcov/ .tox/ .coverage .coverage.* -.cache nosetests.xml coverage.xml *.cover @@ -120,7 +194,6 @@ coverage.xml *.pot # Django stuff: -*.log local_settings.py db.sqlite3 @@ -150,7 +223,6 @@ celerybeat-schedule *.sage.py # Environments -.env .venv env/ venv/ diff --git a/{{cookiecutter.project_shortname}}/.gitignore b/{{cookiecutter.project_shortname}}/.gitignore index 9f0f600..a651450 100644 --- a/{{cookiecutter.project_shortname}}/.gitignore +++ b/{{cookiecutter.project_shortname}}/.gitignore @@ -1,4 +1,10 @@ # Created by .ignore support plugin (hsz.mobi) +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 @@ -60,12 +66,80 @@ fabric.properties # Editor-based Rest Client .idea/httpRequests -### VisualStudioCode template -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless ### Python template # Byte-compiled / optimized / DLL files __pycache__/ @@ -83,6 +157,7 @@ dist/ downloads/ eggs/ .eggs/ +lib/ lib64/ parts/ sdist/ @@ -108,7 +183,6 @@ htmlcov/ .tox/ .coverage .coverage.* -.cache nosetests.xml coverage.xml *.cover @@ -120,7 +194,6 @@ coverage.xml *.pot # Django stuff: -*.log local_settings.py db.sqlite3 @@ -150,7 +223,6 @@ celerybeat-schedule *.sage.py # Environments -.env .venv env/ venv/ From 366d3941471d85d5ae0e4479b9db9bb6c613d039 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 14:53:02 -0400 Subject: [PATCH 20/59] Renamed author info. --- cookiecutter.json | 4 ++-- hooks/post_gen_project.py | 2 +- hooks/pre_gen_project.py | 6 +++--- {{cookiecutter.project_shortname}}/LICENSE | 10 +++++----- {{cookiecutter.project_shortname}}/package.json | 2 +- .../{{cookiecutter.project_shortname}}/package.json | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cookiecutter.json b/cookiecutter.json index b24b6ed..03c522d 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -2,8 +2,8 @@ "project_name": "my dash component", "project_shortname": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", "component_name": "{{ cookiecutter.project_name.replace(' ', '').replace('-', '') }}", - "full_name": "Enter your name (For package.json)", - "email": "Enter your email (For package.json)", + "author_name": "Enter your name (For package.json)", + "author_email": "Enter your email (For package.json)", "description": "Project Description", "version": "0.1.0", "license": [ diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index a3d379e..e6a6a63 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -48,7 +48,7 @@ def _execute_command(cmd): if install_deps != 'True': - print('`install_requirements` is false!!', file=sys.stderr) + print('`install_dependencies` is false!!', file=sys.stderr) print('Please create a venv in your project root' ' and install the dependencies in requirements.txt', file=sys.stderr) diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 7561591..8967bcf 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -1,8 +1,8 @@ from __future__ import print_function import sys -full_name = '{{cookiecutter.full_name}}' -email = '{{cookiecutter.email}}' +full_name = '{{cookiecutter.author_name}}' +email = '{{cookiecutter.author_email}}' def verify(variable_name, s): @@ -12,7 +12,7 @@ def verify(variable_name, s): sys.exit(-1) -for name, value in (('full_name', full_name), ('email', email)): +for name, value in (('author_name', full_name), ('author_email', email)): verify(name, value) sys.exit(0) diff --git a/{{cookiecutter.project_shortname}}/LICENSE b/{{cookiecutter.project_shortname}}/LICENSE index 79380b4..edcfd65 100644 --- a/{{cookiecutter.project_shortname}}/LICENSE +++ b/{{cookiecutter.project_shortname}}/LICENSE @@ -1,7 +1,7 @@ {% if cookiecutter.license == 'MIT license' -%} MIT License -Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} +Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.author_name }} Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,7 +24,7 @@ SOFTWARE. BSD License -Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} +Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.author_name }} All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -54,7 +54,7 @@ OF THE POSSIBILITY OF SUCH DAMAGE. {% elif cookiecutter.license == 'ISC license' -%} ISC License -Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} +Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.author_name }} Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. @@ -62,7 +62,7 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE {% elif cookiecutter.license == 'Apache Software License 2.0' -%} Apache Software License 2.0 -Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.full_name }} +Copyright (c) {% now 'local', '%Y' %}, {{ cookiecutter.author_name }} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -80,7 +80,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 {{ cookiecutter.project_short_description }} - Copyright (C) {% now 'local', '%Y' %} {{ cookiecutter.full_name }} + Copyright (C) {% now 'local', '%Y' %} {{ cookiecutter.author_name }} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/package.json index 049ff73..ce5d19a 100644 --- a/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/package.json @@ -10,7 +10,7 @@ "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && %(python_path) -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, - "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", + "author": "{{ cookiecutter.author_name }} {{ cookiecutter.author_email }}", "license": "{% set _license_identifiers = {'MIT License': 'MIT','BSD License': 'BSD','ISC License': 'ISC','Apache Software License 2.0': 'Apache-2.0','GNU General Public License v3': 'GPL-3.0','Not open source': ''} %}{{ _license_identifiers[cookiecutter.license] }}", "dependencies": { "ramda": "^0.25.0", diff --git a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json index 76d52c7..1d8551f 100644 --- a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json @@ -10,7 +10,7 @@ "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && python -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, - "author": "{{ cookiecutter.full_name }} {{ cookiecutter.email }}", + "author": "{{ cookiecutter.author_name }} {{ cookiecutter.author_email }}", "license": "{{ cookiecutter.license }}", "dependencies": { "ramda": "^0.25.0", From 8d8db1c59ad68d42d28d7706af5c5ffdae7fe4ae Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 1 Oct 2018 15:22:19 -0400 Subject: [PATCH 21/59] Update generated readme. --- {{cookiecutter.project_shortname}}/README.md | 75 +++++++++----------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/{{cookiecutter.project_shortname}}/README.md b/{{cookiecutter.project_shortname}}/README.md index 7b3623a..653be0f 100644 --- a/{{cookiecutter.project_shortname}}/README.md +++ b/{{cookiecutter.project_shortname}}/README.md @@ -1,83 +1,77 @@ -# Dash Component Boilerplate +# {{cookiecutter.project_name}} -This repository contains the minimal set of code required to create your own custom Dash component. +This project was generated by the [dash-component-boilerplate](https://github.com/plotly/dash-component-boilerplate) it contains the minimal set of code required to create your own custom Dash component. -To create your own Dash component: +### Install dependencies -1. Install cookiecutter: -``` -$ pip install cookiecutter -``` -2. Run cookiecutter on the boilerplate repo: -``` -$ cookiecutter git@github.com:plotly/dash-component-boilerplate.git -``` -The prompt will ask questions about the project, then it will create your project in a directory with the `project_shortname` variable. +If you have selected install_dependencies during the prompt, you can skip this part. -3. Install dependencies - ``` - $ cd {{cookiecutter.project_shortname}} - ``` - 1. Install npm packages +1. Install npm packages ``` $ npm install ``` - 2.1 Create a virtual env and activate (optional) +2. Create a virtual env and activate. ``` $ virtualenv venv $ venv/Scripts/activate ``` - __Note: venv\Scripts\activate for windows__ - 2.2 Install python packages (required to build the component) + _Note: venv\Scripts\activate for windows_ + +3. Install python packages required to build components. ``` $ pip install -r requirements.txt ``` - 2.3 Install the python packages for testing (optional) +4. Install the python packages for testing (optional) ``` $ pip install -r tests/requirements.txt ``` -4. Write your component code in `src/lib/components/{{cookiecutter.component_name}}.react.js`. The demo app is in `src/demo` and you will import your example component code into your demo app. -5. Test your code in a Python environment: + +### Write your component code in `src/lib/components/{{cookiecutter.component_name}}.react.js`. + +- The demo app is in `src/demo` and you will import your example component code into your demo app. +- Test your code in a Python environment: 1. Build your code - ``` - $ npm run build:all - ``` + ``` + $ npm run build:all + ``` 2. Run and modify the `usage.py` sample dash app: - ``` - $ python usage.py - ``` -6. Create a production build and publish: - 1. Build your code: + ``` + $ python usage.py + ``` + +### Create a production build and publish: + +1. Build your code: ``` $ npm run build:all ``` - 2. Create a Python tarball +2. Create a Python tarball ``` $ python setup.py sdist ``` This distribution tarball will get generated in the `dist/` folder - 3. Test your tarball by copying it into a new environment and installing it locally: +3. Test your tarball by copying it into a new environment and installing it locally: ``` $ pip install my_dash_component-0.0.1.tar.gz ``` - 4. If it works, then you can publish the component to NPM and PyPI: - 1. Cleanup the dist folder (optional) +4. If it works, then you can publish the component to NPM and PyPI: + 1. Cleanup the dist folder (optional) ``` $ rm -rf dist ``` - 2. Publish on PyPI + 2. Publish on PyPI ``` $ twine upload dist/* ``` - 3. Publish on NPM (Optional if chosen False in `publish_on_npm`) + 3. Publish on NPM (Optional if chosen False in `publish_on_npm`) ``` $ npm publish ``` -7. Share your component with the community! https://community.plot.ly/c/dash +5. Share your component with the community! https://community.plot.ly/c/dash -# More details +## More details - Include CSS files in your distribution folder (`{{cookiecutter.project_shortname}}`) and reference them in `MANIFEST.in` - The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: ``` @@ -88,6 +82,7 @@ The prompt will ask questions about the project, then it will create your projec - Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. -# More Resources +## More Resources - Learn more about Dash: https://dash.plot.ly - View the original component boilerplate: https://github.com/plotly/dash-component-boilerplate +- [React guide for python developers](https://dash.plot.ly/react-for-python-developers) From 75cd2c994b628b3372c6489563d6622c68a533f8 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 13:37:59 -0400 Subject: [PATCH 22/59] Fix virtualenv creation for linux. --- hooks/post_gen_project.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index e6a6a63..7fba0f3 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -9,16 +9,23 @@ install_deps = '{{cookiecutter.install_dependencies}}' project_shortname = '{{cookiecutter.project_shortname}}' -python_executable = os.path.join('venv', 'Scripts', 'python') + + +is_windows = sys.platform == 'win32' + +if is_windows: + python_executable = os.path.join('venv', 'Scripts', 'python') +else: + python_executable = os.path.join('venv', 'bin', 'python') def _execute_command(cmd): - line = shlex.split(cmd, posix=not sys.platform == 'win32') + line = shlex.split(cmd, posix=not is_windows) print('Executing: {}'.format(cmd)) # call instead of Popen to get realtime output - status = subprocess.call(line, shell=True) + status = subprocess.call(line, shell=is_windows) if status != 0: print('post_gen_project command failed: {}'.format(cmd), From a6d14e8f5a37d2dfef9e7cfd570b21b5858d046e Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 13:42:33 -0400 Subject: [PATCH 23/59] Fix webpack configs. --- .../webpack.config.js | 92 ++++++++++++------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/{{cookiecutter.project_shortname}}/webpack.config.js b/{{cookiecutter.project_shortname}}/webpack.config.js index 3b386a6..cfee2ad 100644 --- a/{{cookiecutter.project_shortname}}/webpack.config.js +++ b/{{cookiecutter.project_shortname}}/webpack.config.js @@ -3,39 +3,61 @@ const packagejson = require('./package.json'); const dashLibraryName = packagejson.name.replace(/-/g, '_'); -module.exports = (env, argv) => ({ - entry: {main: './src/lib/index.js'}, - output: { - path: path.resolve(__dirname, dashLibraryName), - filename: `${dashLibraryName}.${argv.mode === 'development' ? 'dev' : 'min'}.js`, - library: dashLibraryName, - libraryTarget: 'window', - }, - externals: { - react: 'React', - 'react-dom': 'ReactDOM', - 'plotly.js': 'Plotly', - }, - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader', - }, - }, - { - test: /\.css$/, - use: [ - { - loader: 'style-loader', - }, - { - loader: 'css-loader', +module.exports = (env, argv) => { + + let mode; + + + // if user specified mode flag take that value + if (argv && argv.mode) { + mode = argv.mode; + } + + // else if configuration object is already set (module.exports) use that value + else if (module.exports && module.exports.mode) { + mode = module.exports = mode; + } + + // else take webpack default + else { + mode = 'production'; // webpack default + } + + return { + entry: {main: './src/lib/index.js'}, + output: { + path: path.resolve(__dirname, dashLibraryName), + filename: `${dashLibraryName}.${mode === 'development' ? 'dev' : 'min'}.js`, + library: dashLibraryName, + libraryTarget: 'window', + }, + externals: { + react: 'React', + 'react-dom': 'ReactDOM', + 'plotly.js': 'Plotly', + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', }, - ], - }, - ], - }, -}); + }, + { + test: /\.css$/, + use: [ + { + loader: 'style-loader', + }, + { + loader: 'css-loader', + }, + ], + }, + ], + }, + devtool: mode === 'development' ? "eval-source-map" : 'none' + } +}; From fb0478e885a92bb6ba2c4ebc83e8d56b87421a18 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 13:45:15 -0400 Subject: [PATCH 24/59] Add install test, fix test_generate. --- tests/test_generate.py | 17 +++++++----- tests/test_install.py | 59 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 tests/test_install.py diff --git a/tests/test_generate.py b/tests/test_generate.py index c33475b..2533430 100644 --- a/tests/test_generate.py +++ b/tests/test_generate.py @@ -1,14 +1,16 @@ import json import os +default_context = { + 'install_dependencies': False, + 'project_name': 'Test Component', + 'author_name': 'test', + 'author_email': 'test@example.com' +} + def test_package_json(cookies): - result = cookies.bake(extra_context={ - 'install_dependencies': False, - 'project_name': 'Test Component', - 'full_name': 'test', - 'email': 'test@example.com' - }) + result = cookies.bake(extra_context=default_context) package_json = json.loads(result.project.join('package.json').read()) @@ -16,3 +18,6 @@ def test_package_json(cookies): assert package_json['license'] == 'MIT' assert os.path.join('venv', 'Scripts', 'python')\ in package_json['scripts']['build:py'] + assert package_json['author'] == \ + '{} {}'.format(default_context['author_name'], + default_context['author_email']) diff --git a/tests/test_install.py b/tests/test_install.py new file mode 100644 index 0000000..6925084 --- /dev/null +++ b/tests/test_install.py @@ -0,0 +1,59 @@ +import shutil +import time +import sys +import pytest +import threading +import runpy + +import flask +import requests + + +@pytest.fixture +def app(): + + def create_app(app_file): + app_file = runpy.run_path(app_file) + app = app_file['app'] + + @app.server.route('/stop') + def _stop_server(): + stopper = flask.request.environ['werkzeug.server.shutdown'] + stopper() + return 'stop' + + def run(): + app.scripts.config.serve_locally = True + app.css.config.serve_locally = True + app.run_server(debug=False, port=8050, threaded=True) + + t = threading.Thread(target=run) + t.daemon = True + t.start() + time.sleep(3) + + return app + + yield create_app + + # Stop the server in teardown + requests.get('http://localhost:8050/stop') + + +def test_install(cookies, app, selenium): + results = cookies.bake(extra_context={ + 'project_name': 'Test Component', + 'author_name': 'test', + 'author_email': 'test', + # 'install_dependencies': False + }) + + sys.path.insert(0, str(results.project)) + + app(str(results.project.join('usage.py'))) + + selenium.get('http://localhost:8050') + time.sleep(1) + + shutil.rmtree('\\\\?\\' + str(results.project.join('node_modules'))) + From d829ec62a7063dee5a8f1b5257562f48c8bf8789 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 13:52:52 -0400 Subject: [PATCH 25/59] Add pytest-selenium tests requirements. --- .circleci/config.yml | 2 +- tests/requirements.txt | 1 + tests/test_install.py | 8 +++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 81e41e4..ae0345b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,7 +34,7 @@ jobs: name: Generations tests command: | . venv/bin/activate - pytest tests + pytest tests --drive Chrome when: always diff --git a/tests/requirements.txt b/tests/requirements.txt index 5a93bd6..a763ff5 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,4 @@ dash pytest pytest-cookies +pytest-selenium \ No newline at end of file diff --git a/tests/test_install.py b/tests/test_install.py index 6925084..a265cb5 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -55,5 +55,11 @@ def test_install(cookies, app, selenium): selenium.get('http://localhost:8050') time.sleep(1) - shutil.rmtree('\\\\?\\' + str(results.project.join('node_modules'))) + node_modules = str(results.project.join('node_modules')) + + if sys.platform == 'win32': + # Fix delete long names on windows. + node_modules = '\\\\?\\' + node_modules + + shutil.rmtree(node_modules) From 498621a6a647ecf14fe9dfe370eab8a613575cc2 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 13:56:05 -0400 Subject: [PATCH 26/59] Fix test_generate. --- .circleci/config.yml | 2 +- tests/test_generate.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ae0345b..725659d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,7 +34,7 @@ jobs: name: Generations tests command: | . venv/bin/activate - pytest tests --drive Chrome + pytest tests --driver Chrome when: always diff --git a/tests/test_generate.py b/tests/test_generate.py index 2533430..fecf309 100644 --- a/tests/test_generate.py +++ b/tests/test_generate.py @@ -1,5 +1,7 @@ import json import os +import sys + default_context = { 'install_dependencies': False, @@ -14,10 +16,14 @@ def test_package_json(cookies): package_json = json.loads(result.project.join('package.json').read()) + if sys.platform == 'win32': + python = os.path.join('venv', 'Scripts', 'python') + else: + python = os.path.join('venv', 'bin', 'python') + assert package_json['name'] == 'test_component' assert package_json['license'] == 'MIT' - assert os.path.join('venv', 'Scripts', 'python')\ - in package_json['scripts']['build:py'] + assert python in package_json['scripts']['build:py'] assert package_json['author'] == \ '{} {}'.format(default_context['author_name'], default_context['author_email']) From 876e728e2da8ebb6242dc6eb4034c2865e7b8585 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 14:02:07 -0400 Subject: [PATCH 27/59] Add dash-html-components/dcc to tests requirements. --- tests/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/requirements.txt b/tests/requirements.txt index a763ff5..4599e89 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,6 @@ dash +dash-html-components +dash-core-components pytest pytest-cookies pytest-selenium \ No newline at end of file From 42ac52e74b088b4b4130f79b43ebf67eead9c323 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 14:32:10 -0400 Subject: [PATCH 28/59] Add callback check to install test. --- tests/test_install.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_install.py b/tests/test_install.py index a265cb5..5cc621d 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -48,6 +48,7 @@ def test_install(cookies, app, selenium): # 'install_dependencies': False }) + # Add the generated project to the path so it can be loaded from usage.py sys.path.insert(0, str(results.project)) app(str(results.project.join('usage.py'))) @@ -55,6 +56,14 @@ def test_install(cookies, app, selenium): selenium.get('http://localhost:8050') time.sleep(1) + input_component = selenium.find_element_by_id('input') + input_component.send_keys('Hello dash component') + time.sleep(1) + + output = selenium.find_element_by_id('output') + + assert output.text == 'You have entered Hello dash component' + node_modules = str(results.project.join('node_modules')) if sys.platform == 'win32': From 15f8adf85405e51707da91beaf5f7f780aeddf4c Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 14:49:47 -0400 Subject: [PATCH 29/59] Get the test input by xpath. --- tests/test_install.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_install.py b/tests/test_install.py index 5cc621d..8dc8cc2 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -56,7 +56,8 @@ def test_install(cookies, app, selenium): selenium.get('http://localhost:8050') time.sleep(1) - input_component = selenium.find_element_by_id('input') + input_component = selenium.find_element_by_xpath( + '//div[@id="input"]/input') input_component.send_keys('Hello dash component') time.sleep(1) From 345df18a30206a54f5f9b78f16c93405b746f7db Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 14:55:16 -0400 Subject: [PATCH 30/59] Clear the input before sending keys. --- tests/test_install.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_install.py b/tests/test_install.py index 8dc8cc2..dc750c2 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -58,6 +58,7 @@ def test_install(cookies, app, selenium): input_component = selenium.find_element_by_xpath( '//div[@id="input"]/input') + input_component.clear() input_component.send_keys('Hello dash component') time.sleep(1) From ddc22fa0fe3d2a69cc859ff22c698e97c3e2e012 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 15:05:36 -0400 Subject: [PATCH 31/59] Remove lib from gitignore. --- .gitignore | 1 - {{cookiecutter.project_shortname}}/.gitignore | 1 - 2 files changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index a651450..53c7e67 100644 --- a/.gitignore +++ b/.gitignore @@ -157,7 +157,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ diff --git a/{{cookiecutter.project_shortname}}/.gitignore b/{{cookiecutter.project_shortname}}/.gitignore index a651450..53c7e67 100644 --- a/{{cookiecutter.project_shortname}}/.gitignore +++ b/{{cookiecutter.project_shortname}}/.gitignore @@ -157,7 +157,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ From 63f40d28f6a0d79af9b789d8893f0c7f30a161a4 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 2 Oct 2018 20:43:27 -0400 Subject: [PATCH 32/59] Add default props to react component template. --- .../src/lib/components/{{cookiecutter.component_name}}.react.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/{{cookiecutter.project_shortname}}/src/lib/components/{{cookiecutter.component_name}}.react.js b/{{cookiecutter.project_shortname}}/src/lib/components/{{cookiecutter.component_name}}.react.js index e56d4b7..bb0f989 100644 --- a/{{cookiecutter.project_shortname}}/src/lib/components/{{cookiecutter.component_name}}.react.js +++ b/{{cookiecutter.project_shortname}}/src/lib/components/{{cookiecutter.component_name}}.react.js @@ -45,6 +45,8 @@ export default class {{cookiecutter.component_name}} extends Component { } } +{{cookiecutter.component_name}}.defaultProps = {}; + {{cookiecutter.component_name}}.propTypes = { /** * The ID used to identify this component in Dash callbacks From ec9d233f623a12021f75cfa18cf4b474d59ce58b Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 3 Oct 2018 15:38:55 -0400 Subject: [PATCH 33/59] Fix generated init external_url. --- .../{{cookiecutter.project_shortname}}/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py index 4fc3be2..640efd8 100644 --- a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py +++ b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py @@ -34,10 +34,8 @@ 'relative_package_path': '{}.min.js'.format(__name__), 'dev_package_path': '{}.dev.js'.format(__name__), {% if cookiecutter.publish_on_npm == 'True' -%} - 'external_url': ( - 'https://unpkg.com/my_dash_component' - '/' + package_name + '/bundle.js' - ).format(__version__), + 'external_url': 'https://unpkg.com/{0}@{1}/{0}/{0}.min.js'.format( + __name__, __version__), {%- endif %} 'namespace': package_name } From 035bf17ba1366948ef283caebc978718e038f207 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 3 Oct 2018 16:51:04 -0400 Subject: [PATCH 34/59] Replace unittest by pytest-dash. --- .../tests/IntegrationTests.py | 108 ------------------ .../tests/requirements.txt | 1 + .../tests/test_render.py | 25 ---- .../tests/test_usage.py | 4 + 4 files changed, 5 insertions(+), 133 deletions(-) delete mode 100644 {{cookiecutter.project_shortname}}/tests/IntegrationTests.py delete mode 100644 {{cookiecutter.project_shortname}}/tests/test_render.py create mode 100644 {{cookiecutter.project_shortname}}/tests/test_usage.py diff --git a/{{cookiecutter.project_shortname}}/tests/IntegrationTests.py b/{{cookiecutter.project_shortname}}/tests/IntegrationTests.py deleted file mode 100644 index 1487a39..0000000 --- a/{{cookiecutter.project_shortname}}/tests/IntegrationTests.py +++ /dev/null @@ -1,108 +0,0 @@ -from __future__ import absolute_import - -import logging -import os -import multiprocessing -import sys -import time -import unittest -import percy -import threading -import platform -import flask -import requests - -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -class IntegrationTests(unittest.TestCase): - def percy_snapshot(self, name=''): - if os.environ.get('PERCY_ENABLED', False): - snapshot_name = '{} - {}'.format(name, sys.version_info) - self.percy_runner.snapshot( - name=snapshot_name - ) - - @classmethod - def setUpClass(cls): - super(IntegrationTests, cls).setUpClass() - - options = Options() - if 'DASH_TEST_CHROMEPATH' in os.environ: - options.binary_location = os.environ['DASH_TEST_CHROMEPATH'] - - cls.driver = webdriver.Chrome(chrome_options=options) - - if os.environ.get('PERCY_ENABLED', False): - loader = percy.ResourceLoader( - webdriver=cls.driver - ) - cls.percy_runner = percy.Runner(loader=loader) - cls.percy_runner.initialize_build() - - @classmethod - def tearDownClass(cls): - super(IntegrationTests, cls).tearDownClass() - - cls.driver.quit() - if os.environ.get('PERCY_ENABLED', False): - cls.percy_runner.finalize_build() - - def setUp(self): - pass - - def tearDown(self): - time.sleep(3) - if platform.system() == 'Windows': - requests.get('http://localhost:8050/stop') - else: - self.server_process.terminate() - time.sleep(3) - - def startServer(self, app): - if 'DASH_TEST_PROCESSES' in os.environ: - processes = int(os.environ['DASH_TEST_PROCESSES']) - else: - processes = 4 - - def run(): - app.scripts.config.serve_locally = True - app.css.config.serve_locally = True - app.run_server( - port=8050, - debug=False, - processes=processes - ) - - def run_windows(): - app.scripts.config.serve_locally = True - app.css.config.serve_locally = True - - @app.server.route('/stop') - def _stop_server_windows(): - stopper = flask.request.environ['werkzeug.server.shutdown'] - stopper() - return 'stop' - - app.run_server( - port=8050, - debug=False, - threaded=True - ) - - # Run on a separate process so that it doesn't block - - system = platform.system() - if system == 'Windows': - self.server_thread = threading.Thread(target=run_windows) - self.server_thread.start() - else: - self.server_process = multiprocessing.Process(target=run) - self.server_process.start() - logging.getLogger('werkzeug').setLevel(logging.ERROR) - time.sleep(5) - - # Visit the dash page - self.driver.get('http://localhost:8050') - time.sleep(0.5) diff --git a/{{cookiecutter.project_shortname}}/tests/requirements.txt b/{{cookiecutter.project_shortname}}/tests/requirements.txt index 7e1c0d3..1fcc228 100644 --- a/{{cookiecutter.project_shortname}}/tests/requirements.txt +++ b/{{cookiecutter.project_shortname}}/tests/requirements.txt @@ -12,3 +12,4 @@ percy selenium flake8 pylint +pytest-dash diff --git a/{{cookiecutter.project_shortname}}/tests/test_render.py b/{{cookiecutter.project_shortname}}/tests/test_render.py deleted file mode 100644 index b5078d7..0000000 --- a/{{cookiecutter.project_shortname}}/tests/test_render.py +++ /dev/null @@ -1,25 +0,0 @@ -from .IntegrationTests import IntegrationTests -import dash -import dash_html_components as html -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC - -from {{cookiecutter.project_shortname}} import {{cookiecutter.component_name}} # pylint: disable=no-name-in-module - - -class Tests(IntegrationTests): - def test_render_component(self): - app = dash.Dash(__name__) - app.layout = html.Div([ - html.Div(id='waitfor'), - {{cookiecutter.component_name}}(label='Example Component Label') - ]) - - self.startServer(app) - - WebDriverWait(self.driver, 10).until( - EC.presence_of_element_located((By.ID, "waitfor")) - ) - - self.percy_snapshot('Simple Render') diff --git a/{{cookiecutter.project_shortname}}/tests/test_usage.py b/{{cookiecutter.project_shortname}}/tests/test_usage.py new file mode 100644 index 0000000..2ba646d --- /dev/null +++ b/{{cookiecutter.project_shortname}}/tests/test_usage.py @@ -0,0 +1,4 @@ +def test_render_component(dash_app, percy_snapshot): + app = dash_app('usage.py') + + percy_snapshot('{{cookiecutter.project_name}} usage.py') From 7b837682fbc4800c6ca46f4463f008cc3582d746 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 4 Oct 2018 14:53:12 -0400 Subject: [PATCH 35/59] Use pytest-dash in install test. --- tests/requirements.txt | 3 +- tests/test_install.py | 44 ++----------------- .../tests/test_usage.py | 7 ++- 3 files changed, 11 insertions(+), 43 deletions(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index 4599e89..becc970 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,4 +3,5 @@ dash-html-components dash-core-components pytest pytest-cookies -pytest-selenium \ No newline at end of file +pytest-selenium +pytest-dash diff --git a/tests/test_install.py b/tests/test_install.py index dc750c2..e63e32b 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1,46 +1,9 @@ import shutil import time import sys -import pytest -import threading -import runpy -import flask -import requests - -@pytest.fixture -def app(): - - def create_app(app_file): - app_file = runpy.run_path(app_file) - app = app_file['app'] - - @app.server.route('/stop') - def _stop_server(): - stopper = flask.request.environ['werkzeug.server.shutdown'] - stopper() - return 'stop' - - def run(): - app.scripts.config.serve_locally = True - app.css.config.serve_locally = True - app.run_server(debug=False, port=8050, threaded=True) - - t = threading.Thread(target=run) - t.daemon = True - t.start() - time.sleep(3) - - return app - - yield create_app - - # Stop the server in teardown - requests.get('http://localhost:8050/stop') - - -def test_install(cookies, app, selenium): +def test_install(cookies, dash_app, selenium): results = cookies.bake(extra_context={ 'project_name': 'Test Component', 'author_name': 'test', @@ -51,7 +14,7 @@ def test_install(cookies, app, selenium): # Add the generated project to the path so it can be loaded from usage.py sys.path.insert(0, str(results.project)) - app(str(results.project.join('usage.py'))) + dash_app(str(results.project.join('usage.py'))) selenium.get('http://localhost:8050') time.sleep(1) @@ -60,7 +23,8 @@ def test_install(cookies, app, selenium): '//div[@id="input"]/input') input_component.clear() input_component.send_keys('Hello dash component') - time.sleep(1) + + time.sleep(2) output = selenium.find_element_by_id('output') diff --git a/{{cookiecutter.project_shortname}}/tests/test_usage.py b/{{cookiecutter.project_shortname}}/tests/test_usage.py index 2ba646d..7e2b806 100644 --- a/{{cookiecutter.project_shortname}}/tests/test_usage.py +++ b/{{cookiecutter.project_shortname}}/tests/test_usage.py @@ -1,4 +1,7 @@ -def test_render_component(dash_app, percy_snapshot): +# Basic test for the component rendering. +def test_render_component(dash_app, selenium): + # Start a dash app contained in `usage.py` app = dash_app('usage.py') - percy_snapshot('{{cookiecutter.project_name}} usage.py') + # Get the generated component with selenium + my_component = selenium.find_element_by_id('input') From 291a3e6561368e1f229d1603729f6440b12519e5 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 4 Oct 2018 17:25:22 -0400 Subject: [PATCH 36/59] Use venv module instead of virtualenv if python >= 3.3 --- hooks/post_gen_project.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 7fba0f3..b0a864c 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -7,6 +7,8 @@ import json import collections +import six + install_deps = '{{cookiecutter.install_dependencies}}' project_shortname = '{{cookiecutter.project_shortname}}' @@ -62,7 +64,13 @@ def _execute_command(cmd): sys.exit(0) # Create a virtual env -_execute_command('virtualenv venv') +if sys.version.split(' ')[0] > '3.2': + venv = 'python -m venv venv' +else: + venv = 'virtualenv venv' + +_execute_command(venv) + print('\n\nInstalling dependencies\n', file=sys.stderr) From ee171becf03d3a0e6f0f2cb2a3f7d185f04b386c Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 4 Oct 2018 17:28:36 -0400 Subject: [PATCH 37/59] Add python 2.7 circle tests. --- .circleci/config.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 725659d..2369ff7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2 jobs: - "python-3.6": + "python-3.6": &test-template docker: - image: circleci/python:3.6-stretch-node-browsers @@ -37,9 +37,15 @@ jobs: pytest tests --driver Chrome when: always + "python-2.7": + <<: *test-template + docker: + - image: circleci/python:2.7-stretch-node-browsers + workflows: version: 2 build: jobs: - "python-3.6" + - "python-2.7" From 4ff34fa97e53fa26db17babd2fbd28c95b0d80c1 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 4 Oct 2018 17:31:21 -0400 Subject: [PATCH 38/59] Fix circle venv. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2369ff7..219c536 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ jobs: - run: name: Create virtualenv command: | - python3 -m venv venv + python -m venv venv || virtualenv venv - run: name: Install requirements From 161b660de921311d0c23cfa1420ec27eb034162b Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 4 Oct 2018 17:36:31 -0400 Subject: [PATCH 39/59] Install virtualenv on circle container. --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 219c536..182ddf2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,7 @@ jobs: - run: name: Create virtualenv command: | + sudo pip install virtualenv python -m venv venv || virtualenv venv - run: From 6615b5fc6120ad2f98ac896497b9ba2ce442dbc5 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 4 Oct 2018 17:50:39 -0400 Subject: [PATCH 40/59] Fix circle cache. --- .circleci/config.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 182ddf2..265d628 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,8 +11,12 @@ jobs: steps: - checkout + - run: + name: Write job name. + command: echo $CIRCLE_JOB > circlejob.txt + - restore_cache: - key: deps1-{{ .Branch }}-{{ checksum "tests/requirements.txt" }} + key: deps1-{{ .Branch }}-{{ checksum "circlejob.txt" }}-{{ checksum "tests/requirements.txt" }}-{{ checksum ".circleci/config.yml" }} - run: name: Create virtualenv @@ -27,7 +31,7 @@ jobs: pip install -r tests/requirements.txt --quiet - save_cache: - key: deps1-{{ .Branch }}-{{ checksum "tests/requirements.txt" }} + key: deps1-{{ .Branch }}-{{ checksum "circlejob.txt" }}-{{ checksum "tests/requirements.txt" }}-{{ checksum ".circleci/config.yml" }} paths: - "venv" From 1b45037037c43ad75f76edbb8db5e250da88c2a3 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 5 Oct 2018 14:34:35 -0400 Subject: [PATCH 41/59] Validate project_shortname. --- hooks/pre_gen_project.py | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 8967bcf..942cbd5 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -3,16 +3,46 @@ full_name = '{{cookiecutter.author_name}}' email = '{{cookiecutter.author_email}}' +project_shortname = '{{cookiecutter.project_shortname}}' +invalid_package_message = 'Invalid {variable}: {value}' +project_shortname_message = ''' +({variable}={value}) should be a valid python package name. -def verify(variable_name, s): - if '(For package.json)' in s: - print('\nInvalid {}: {}\n'.format(variable_name, s), +Only lowercase letters and `_` are allowed. +''' + + +def verify(check, variable_name, value, message): + if check(value): + print(message.format(variable=variable_name, value=value), file=sys.stderr) sys.exit(-1) -for name, value in (('author_name', full_name), ('author_email', email)): - verify(name, value) +def package_check(s): + return '(For package.json)' in s + + +def _check_specials_characters(s): + i = ord(s) + if i == 95: + # Allow for `_` + return False + return not 96 < i < 123 + + +def check_specials_characters(s): + return any(_check_specials_characters(x) for x in s) + + +for values in ( + (package_check, 'author_name', full_name, invalid_package_message), + (package_check, 'author_email', email, invalid_package_message), + (check_specials_characters, 'project_shortname', + project_shortname, project_shortname_message) +): + verify(*values) + sys.exit(0) From 65e2cc93c8f4b02f631fea3f0f438f388a99c33c Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 5 Oct 2018 14:38:29 -0400 Subject: [PATCH 42/59] Display message to install virtualenv if not found on python 2. --- hooks/post_gen_project.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index b0a864c..1924a86 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -7,8 +7,6 @@ import json import collections -import six - install_deps = '{{cookiecutter.install_dependencies}}' project_shortname = '{{cookiecutter.project_shortname}}' @@ -69,8 +67,18 @@ def _execute_command(cmd): else: venv = 'virtualenv venv' -_execute_command(venv) - +# noinspection PyBroadException +try: + _execute_command(venv) +except BaseException: + print( + ''' + venv creation failed. + Make sure you have installed virtualenv on python 2. + ''', + file=sys.stderr + ) + raise print('\n\nInstalling dependencies\n', file=sys.stderr) From cc3260c11da2aeeed9a03437d67dde5f0f1e78ef Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 5 Oct 2018 14:40:24 -0400 Subject: [PATCH 43/59] Default version -> 0.0.1 --- cookiecutter.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookiecutter.json b/cookiecutter.json index 03c522d..442a1c7 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,7 +5,7 @@ "author_name": "Enter your name (For package.json)", "author_email": "Enter your email (For package.json)", "description": "Project Description", - "version": "0.1.0", + "version": "0.0.1", "license": [ "MIT License", "BSD License", From 8d684d1cbe017c185e7b3174742cabd2c649e256 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 5 Oct 2018 15:00:37 -0400 Subject: [PATCH 44/59] Add warnings for missing descriptions. --- .../{extract-meta => extract-meta.js} | 18 +++++++++++++++++- .../package.json | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) rename {{cookiecutter.project_shortname}}/{extract-meta => extract-meta.js} (80%) diff --git a/{{cookiecutter.project_shortname}}/extract-meta b/{{cookiecutter.project_shortname}}/extract-meta.js similarity index 80% rename from {{cookiecutter.project_shortname}}/extract-meta rename to {{cookiecutter.project_shortname}}/extract-meta.js index 33e1152..8394bcf 100644 --- a/{{cookiecutter.project_shortname}}/extract-meta +++ b/{{cookiecutter.project_shortname}}/extract-meta.js @@ -35,6 +35,21 @@ function writeError(msg, filePath) { } } +function checkWarn(name, value) { + if (value.length < 1) { + process.stderr.write(`\nDescription for ${name} is missing!\n`) + } +} + +function docstringWarning(doc) { + checkWarn(doc.displayName, doc.description); + + Object.entries(doc.props).forEach( + ([name, p]) => checkWarn(`${doc.displayName}.${name}`, p.description) + ); +} + + function parseFile(filepath) { const urlpath = filepath.split(path.sep).join('/'); let src; @@ -45,7 +60,8 @@ function parseFile(filepath) { try { src = fs.readFileSync(filepath); - metadata[urlpath] = reactDocs.parse(src); + const doc = metadata[urlpath] = reactDocs.parse(src); + docstringWarning(doc); } catch (error) { writeError(error, filepath); } diff --git a/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/package.json index ce5d19a..4cdfe56 100644 --- a/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/package.json @@ -7,7 +7,7 @@ "start": "webpack-serve ./webpack.serve.config.js --open", "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", - "build:py": "node ./extract-meta src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && %(python_path) -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", + "build:py": "node ./extract-meta.js src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && %(python_path) -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", "build:all": "npm run build:js & npm run build:js-dev & npm run build:py" }, "author": "{{ cookiecutter.author_name }} {{ cookiecutter.author_email }}", From 0988a63a2fe5fc798c7621da32d6012e3738240a Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 5 Oct 2018 17:02:43 -0400 Subject: [PATCH 45/59] Validate js/css dist for files in the package dir, run on prepublish. --- .../_validate_init.py | 68 +++++++++++++++++++ .../package.json | 2 + .../__init__.py | 4 +- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 {{cookiecutter.project_shortname}}/_validate_init.py diff --git a/{{cookiecutter.project_shortname}}/_validate_init.py b/{{cookiecutter.project_shortname}}/_validate_init.py new file mode 100644 index 0000000..0f472f6 --- /dev/null +++ b/{{cookiecutter.project_shortname}}/_validate_init.py @@ -0,0 +1,68 @@ +""" +DO NOT MODIFY +This file is used to validate your publish settings. +""" +from __future__ import print_function + +import os +import sys +import importlib + + +components_package = '{{cookiecutter.project_shortname}}' + +components_lib = importlib.import_module(components_package) + +missing_dist_msg = 'Warning {} was not found in `{}.__init__.{}`!!!' +missing_manifest_msg = ''' +Warning {} was not found in `MANIFEST.in`! +It will not be included in the build! +''' + +with open('MANIFEST.in', 'r') as f: + manifest = f.read() + + +def check_dist(dist, filename): + # Support the dev bundle. + if filename.endswith('dev.js'): + return True + + return any( + filename in x + for d in dist + for x in ( + [d.get('relative_package_path')] + if isinstance(d.get('relative_package_path'), str) + else d.get('relative_package_path') + ) + ) + + +def check_manifest(filename): + return filename in manifest + + +for cur, _, files in os.walk(components_package): + for f in files: + + if f.endswith('js'): + # noinspection PyProtectedMember + if not check_dist(components_lib._js_dist, f): + print( + missing_dist_msg.format(f, components_package, '_js_dist'), + file=sys.stderr + ) + if not check_manifest(f): + print(missing_manifest_msg.format(f), + file=sys.stderr) + elif f.endswith('css'): + # noinspection PyProtectedMember + if not check_dist(components_lib._css_dist, f): + print( + missing_dist_msg.format(f, components_package, '_css_dist'), + file=sys.stderr + ) + if not check_manifest(f): + print(missing_manifest_msg.format(f), + file=sys.stderr) diff --git a/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/package.json index 4cdfe56..5efa7cf 100644 --- a/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/package.json @@ -5,6 +5,8 @@ "main": "build/index.js", "scripts": { "start": "webpack-serve ./webpack.serve.config.js --open", + "validate-init": "python _validate_init.py", + "prepublish": "npm run validate-init", "build:js-dev": "webpack --mode development", "build:js": "webpack --mode production", "build:py": "node ./extract-meta.js src/lib/components > {{cookiecutter.project_shortname}}/metadata.json && copyfiles package.json {{cookiecutter.project_shortname}} && %(python_path) -c \"import dash; dash.development.component_loader.generate_classes('{{cookiecutter.project_shortname}}', '{{cookiecutter.project_shortname}}/metadata.json')\"", diff --git a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py index 640efd8..4328b22 100644 --- a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py +++ b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/__init__.py @@ -31,8 +31,8 @@ _js_dist = [ { - 'relative_package_path': '{}.min.js'.format(__name__), - 'dev_package_path': '{}.dev.js'.format(__name__), + 'relative_package_path': '{{cookiecutter.project_shortname}}.min.js', + 'dev_package_path': '{{cookiecutter.project_shortname}}.dev.js', {% if cookiecutter.publish_on_npm == 'True' -%} 'external_url': 'https://unpkg.com/{0}@{1}/{0}/{0}.min.js'.format( __name__, __version__), From f3630c115bfe76081ac3b84dbda118b75ad596f5 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 5 Oct 2018 17:06:08 -0400 Subject: [PATCH 46/59] Ignore scripts when install npm deps. --- hooks/post_gen_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 1924a86..d9b8130 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -87,7 +87,7 @@ def _execute_command(cmd): r'{} -m pip install -r requirements.txt'.format(python_executable)) # Install node_modules -_execute_command('npm install') +_execute_command('npm install --ignore-scripts') # Run the first build print('Building initial bundles...') From 4e824a97fbbf2124fd1160c03a83e5a648fd27fb Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 5 Oct 2018 23:36:51 -0400 Subject: [PATCH 47/59] :camel: assets check. --- .../_validate_init.py | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/{{cookiecutter.project_shortname}}/_validate_init.py b/{{cookiecutter.project_shortname}}/_validate_init.py index 0f472f6..037c20d 100644 --- a/{{cookiecutter.project_shortname}}/_validate_init.py +++ b/{{cookiecutter.project_shortname}}/_validate_init.py @@ -43,26 +43,25 @@ def check_manifest(filename): return filename in manifest +def check_file(dist, filename): + if not check_dist(dist, filename): + print( + missing_dist_msg.format(filename, components_package, '_js_dist'), + file=sys.stderr + ) + if not check_manifest(filename): + print(missing_manifest_msg.format(filename), + file=sys.stderr) + + for cur, _, files in os.walk(components_package): for f in files: if f.endswith('js'): # noinspection PyProtectedMember - if not check_dist(components_lib._js_dist, f): - print( - missing_dist_msg.format(f, components_package, '_js_dist'), - file=sys.stderr - ) - if not check_manifest(f): - print(missing_manifest_msg.format(f), - file=sys.stderr) + check_file(components_lib._js_dist, f) elif f.endswith('css'): # noinspection PyProtectedMember - if not check_dist(components_lib._css_dist, f): - print( - missing_dist_msg.format(f, components_package, '_css_dist'), - file=sys.stderr - ) - if not check_manifest(f): - print(missing_manifest_msg.format(f), - file=sys.stderr) + check_file(components_lib._css_dist, f) + elif not f.endswith('py'): + check_manifest(f) From 34e6b9fa548d9776eb51ac1129d4837633185df4 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 11 Oct 2018 17:53:02 -0400 Subject: [PATCH 48/59] Remove unused imports from demo. --- {{cookiecutter.project_shortname}}/src/demo/App.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/{{cookiecutter.project_shortname}}/src/demo/App.js b/{{cookiecutter.project_shortname}}/src/demo/App.js index 83a308b..fd83c64 100644 --- a/{{cookiecutter.project_shortname}}/src/demo/App.js +++ b/{{cookiecutter.project_shortname}}/src/demo/App.js @@ -1,7 +1,5 @@ /* eslint no-magic-numbers: 0 */ import React, {Component} from 'react'; -import PropTypes from 'prop-types'; -import * as R from 'ramda'; import {{cookiecutter.component_name}} from '../lib'; From cb37408232061518aea5ef282f9d1828257c95f9 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 11 Oct 2018 18:11:15 -0400 Subject: [PATCH 49/59] Remove unused from test, add comments. --- tests/test_install.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_install.py b/tests/test_install.py index e63e32b..f353b2d 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -8,15 +8,15 @@ def test_install(cookies, dash_app, selenium): 'project_name': 'Test Component', 'author_name': 'test', 'author_email': 'test', - # 'install_dependencies': False }) # Add the generated project to the path so it can be loaded from usage.py + # It lies somewhere in a temp directory created by pytest-cookies sys.path.insert(0, str(results.project)) + # Test that `usage.py` works after building the default component. dash_app(str(results.project.join('usage.py'))) - selenium.get('http://localhost:8050') time.sleep(1) input_component = selenium.find_element_by_xpath( @@ -34,6 +34,7 @@ def test_install(cookies, dash_app, selenium): if sys.platform == 'win32': # Fix delete long names on windows. + # pytest-cookies have trouble deleting some file generated by webpack. node_modules = '\\\\?\\' + node_modules shutil.rmtree(node_modules) From 1acb39d22ce3b847089434c5d475871a8d5951fe Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 11 Oct 2018 18:24:26 -0400 Subject: [PATCH 50/59] Add pytest-dash usage comment in the generate test. --- {{cookiecutter.project_shortname}}/tests/test_usage.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/{{cookiecutter.project_shortname}}/tests/test_usage.py b/{{cookiecutter.project_shortname}}/tests/test_usage.py index 7e2b806..b84d650 100644 --- a/{{cookiecutter.project_shortname}}/tests/test_usage.py +++ b/{{cookiecutter.project_shortname}}/tests/test_usage.py @@ -1,6 +1,9 @@ # Basic test for the component rendering. def test_render_component(dash_app, selenium): # Start a dash app contained in `usage.py` + # dash_app is a fixture by pytest-dash + # It will load a py file containing a Dash instance named `app` + # and start it in a thread. app = dash_app('usage.py') # Get the generated component with selenium From d5f1bc3e26aef1f656a445230dc7a231689ceed8 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 11 Oct 2018 18:32:48 -0400 Subject: [PATCH 51/59] Replace test command. --- {{cookiecutter.project_shortname}}/README.md | 2 +- {{cookiecutter.project_shortname}}/_validate_init.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_shortname}}/README.md b/{{cookiecutter.project_shortname}}/README.md index 653be0f..62dc2d0 100644 --- a/{{cookiecutter.project_shortname}}/README.md +++ b/{{cookiecutter.project_shortname}}/README.md @@ -75,7 +75,7 @@ If you have selected install_dependencies during the prompt, you can skip this p - Include CSS files in your distribution folder (`{{cookiecutter.project_shortname}}`) and reference them in `MANIFEST.in` - The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: ``` - $ python -m unittest tests.test_render + $ pytest tests ``` The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) - Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True` (unless you choose `False` on `publish_on_npm`). We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). diff --git a/{{cookiecutter.project_shortname}}/_validate_init.py b/{{cookiecutter.project_shortname}}/_validate_init.py index 037c20d..1a25d49 100644 --- a/{{cookiecutter.project_shortname}}/_validate_init.py +++ b/{{cookiecutter.project_shortname}}/_validate_init.py @@ -33,7 +33,7 @@ def check_dist(dist, filename): for d in dist for x in ( [d.get('relative_package_path')] - if isinstance(d.get('relative_package_path'), str) + if not isinstance(d.get('relative_package_path'), list) else d.get('relative_package_path') ) ) From 08bddf092c0aa7c3e06774fb0c1cbbc24d0d3ef5 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 15 Oct 2018 14:48:04 -0400 Subject: [PATCH 52/59] Update generated readme from #18. --- {{cookiecutter.project_shortname}}/README.md | 22 ++++----- .../review_checklist.md | 46 +++++++++++++++++++ 2 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 {{cookiecutter.project_shortname}}/review_checklist.md diff --git a/{{cookiecutter.project_shortname}}/README.md b/{{cookiecutter.project_shortname}}/README.md index 62dc2d0..092a538 100644 --- a/{{cookiecutter.project_shortname}}/README.md +++ b/{{cookiecutter.project_shortname}}/README.md @@ -38,6 +38,14 @@ If you have selected install_dependencies during the prompt, you can skip this p ``` $ python usage.py ``` +- Write tests for your component. + - A sample test is available in `tests/test_usage.py`, it will load `usage.py` and you can then automate interactions with selenium. + - Run the tests with `$ pytest tests`. + - The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) +- Add custom styles to your component by putting your custom CSS files into your distribution folder (`{{cookiecutter.project_shortname}}`). + - Make sure that they are referenced in `MANIFEST.in` so that they get properly included when you're ready to publish your component. + - Make sure the stylesheets are added to the `_css_dist` dict in `{{cookiecutter.project_shortname}}/__init__.py` so dash will serve them automatically when the component suite is requested. +- [Review your code](./review_checklist.md) ### Create a production build and publish: @@ -69,20 +77,10 @@ If you have selected install_dependencies during the prompt, you can skip this p ``` $ npm publish ``` + _Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True` (unless you choose `False` on `publish_on_npm`). We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284)._ 5. Share your component with the community! https://community.plot.ly/c/dash -## More details -- Include CSS files in your distribution folder (`{{cookiecutter.project_shortname}}`) and reference them in `MANIFEST.in` -- The `tests` folder contains a sample integration test. This will run a sample Dash app in a browser. Run this with: - ``` - $ pytest tests - ``` - The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components) -- Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True` (unless you choose `False` on `publish_on_npm`). We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284). -- Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. - - ## More Resources - Learn more about Dash: https://dash.plot.ly -- View the original component boilerplate: https://github.com/plotly/dash-component-boilerplate +- Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. - [React guide for python developers](https://dash.plot.ly/react-for-python-developers) diff --git a/{{cookiecutter.project_shortname}}/review_checklist.md b/{{cookiecutter.project_shortname}}/review_checklist.md new file mode 100644 index 0000000..5dc4663 --- /dev/null +++ b/{{cookiecutter.project_shortname}}/review_checklist.md @@ -0,0 +1,46 @@ +# Code Review Checklist + +## Code quality & design + +- Is your code clear? If you had to go back to it in a month, would you be happy to? If someone else had to contribute to it, would they be able to? + + A few suggestions: + + - Make your variable names descriptive and use the same naming conventions throughout the code. + + - For more complex pieces of logic, consider putting a comment, and maybe an example. + + - In the comments, focus on describing _why_ the code does what it does, rather than describing _what_ it does. The reader can most likely read the code, but not necessarily understand why it was necessary. + + - Don't overdo it in the comments. The code should be clear enough to speak for itself. Stale comments that no longer reflect the intent of the code can hurt code comprehension. + +* Don't repeat yourself. Any time you see that the same piece of logic can be applied in multiple places, factor it out into a function, or variable, and reuse that code. +* Scan your code for expensive operations (large computations, DOM queries, React re-renders). Have you done your possible to limit their impact? If not, it is going to slow your app down. + +## Component API + +- Have you tested your component on the Python side by creating an app in `usage.py` ? + + Do all of your component's props work when set from the back-end? + + Should all of them be settable from the back-end or are some only relevant to user interactions in the front-end? + +- Have you provided some basic documentation about your component? The Dash community uses [react docstrings](https://github.com/plotly/dash-docs/blob/master/tutorial/plugins.py#L45) to provide basic information about dash components. Take a look at this [Checklist component example](https://github.com/plotly/dash-core-components/blob/master/src/components/Checklist.react.js) and others from the dash-core-components repository. + + At a minimum, you should describe what your component does, and describe its props and the features they enable. + + Be careful to use the correct formatting for your docstrings for them to be properly recognized. + +## Tests + +- The Dash team uses integration tests extensively, and we highly encourage you to write tests for the main functionality of your component. In the `tests` folder of the boilerplate, you can see a sample integration test. By launching it, you will run a sample Dash app in a browser. You can run the test with: + ``` + python -m tests.test_render + ``` + [Browse the Dash component code on GitHub for more examples of testing.](https://github.com/plotly/dash-core-components) + +## Ready to publish? Final scan + +- Take a last look at the external resources that your component is using. Are all the external resources used [referenced in `MANIFEST.in`](https://github.com/plotly/dash-docs/blob/0b2fd8f892db720a7f3dc1c404b4cff464b5f8d4/tutorial/plugins.py#L55)? + +- You're ready to publish! Continue to [step #9 here](README.md#publishing). \ No newline at end of file From cfeaddc5ae9f87072e70017ee117ff3bae9c78dc Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 15 Oct 2018 14:52:13 -0400 Subject: [PATCH 53/59] Add more info to the cookiecutters variable in root readme. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b27d8de..40b5583 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,12 @@ To use this boilerplate: $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` 3. Answer the questions about the project. - - project name - - component name - - author info - - description - - version - - license + - project name: Clean project name, can contains spaces and special characters. + - component name: derived from project_name, lowercase, spaces and '-' are replaced by '_', ascii only. + - author info: author_name and author_email for package.json metadata. + - description: the project description, included in package.json. + - version: The initial version. + - license: License type for the component lib. - publish_on_npm: Set to false to only serve locally from the package data. - install_dependencies: Set to false to only generate the project structure. 4. The project will be generated in the folder of `project_shortname`. From 6a657410971311b5284d63929a4de1dd856f5a30 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 16 Oct 2018 10:59:05 -0400 Subject: [PATCH 54/59] Interpolate missing project_name. --- {{cookiecutter.project_shortname}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_shortname}}/README.md b/{{cookiecutter.project_shortname}}/README.md index 092a538..87e05cb 100644 --- a/{{cookiecutter.project_shortname}}/README.md +++ b/{{cookiecutter.project_shortname}}/README.md @@ -61,7 +61,7 @@ If you have selected install_dependencies during the prompt, you can skip this p 3. Test your tarball by copying it into a new environment and installing it locally: ``` - $ pip install my_dash_component-0.0.1.tar.gz + $ pip install {{cookiecutter.project_shortname}}-0.0.1.tar.gz ``` 4. If it works, then you can publish the component to NPM and PyPI: From 6bf9ca450aff56f265827d651d249bf57fbfdcff Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 16 Oct 2018 11:04:28 -0400 Subject: [PATCH 55/59] Remove version interpolations, start at 0.0.1 --- README.md | 1 - cookiecutter.json | 1 - {{cookiecutter.project_shortname}}/package.json | 2 +- .../{{cookiecutter.project_shortname}}/package.json | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 40b5583..d7bc114 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ To use this boilerplate: - component name: derived from project_name, lowercase, spaces and '-' are replaced by '_', ascii only. - author info: author_name and author_email for package.json metadata. - description: the project description, included in package.json. - - version: The initial version. - license: License type for the component lib. - publish_on_npm: Set to false to only serve locally from the package data. - install_dependencies: Set to false to only generate the project structure. diff --git a/cookiecutter.json b/cookiecutter.json index 442a1c7..8377f96 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,7 +5,6 @@ "author_name": "Enter your name (For package.json)", "author_email": "Enter your email (For package.json)", "description": "Project Description", - "version": "0.0.1", "license": [ "MIT License", "BSD License", diff --git a/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/package.json index 5efa7cf..9e18ef8 100644 --- a/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/package.json @@ -1,6 +1,6 @@ { "name": "{{ cookiecutter.project_shortname }}", - "version": "{{ cookiecutter.version }}", + "version": "0.0.1", "description": "{{ cookiecutter.description }}", "main": "build/index.js", "scripts": { diff --git a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json index 1d8551f..977d2e8 100644 --- a/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json +++ b/{{cookiecutter.project_shortname}}/{{cookiecutter.project_shortname}}/package.json @@ -1,6 +1,6 @@ { "name": "{{ cookiecutter.project_shortname }}", - "version": "{{ cookiecutter.version }}", + "version": "0.0.1", "description": "{{ cookiecutter.description }}", "main": "build/index.js", "scripts": { From e468ff4cf59036909a7a3e24220650f7c5958602 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 16 Oct 2018 11:04:58 -0400 Subject: [PATCH 56/59] lib->library --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7bc114..691d878 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ To use this boilerplate: - component name: derived from project_name, lowercase, spaces and '-' are replaced by '_', ascii only. - author info: author_name and author_email for package.json metadata. - description: the project description, included in package.json. - - license: License type for the component lib. + - license: License type for the component library. - publish_on_npm: Set to false to only serve locally from the package data. - install_dependencies: Set to false to only generate the project structure. 4. The project will be generated in the folder of `project_shortname`. From a0cdd16784ea65c5b9159f9eef4ee649c7be60ed Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 16 Oct 2018 11:10:48 -0400 Subject: [PATCH 57/59] Move more resources to root readme. --- README.md | 11 +++++++++++ {{cookiecutter.project_shortname}}/README.md | 5 +---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 691d878..400e170 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,14 @@ Installing the dependencies can take a long time. They will be installed in a folder named `venv`, created by virtualenv. This ensures that dash is installed to generate the components in the `build:py` script of the generated `package.json`. + + +## More Resources + +- Learn more about Dash: https://dash.plot.ly +- Questions about this project? Create an issue: https://github.com/plotly/dash-component-boilerplate/issues/new +- Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. +- [React guide for python developers](https://dash.plot.ly/react-for-python-developers) +- Need help with your component? View the Dash Community Forum: https://community.plot.ly/c/dash +- Examples of Dash component libraries include `dash-core-components`: https://github.com/plotly/dash-core-components` and `dash-html-components`: https://github.com/plotly/dash-html-components. +- To get a feel for what's involved in creating a component, read through the [README.MD file that this cookiecutter project generates](/cookie-cutter/%7B%7Bcookiecutter.project_shortname%7D%7D/README.md) diff --git a/{{cookiecutter.project_shortname}}/README.md b/{{cookiecutter.project_shortname}}/README.md index 87e05cb..dbb2436 100644 --- a/{{cookiecutter.project_shortname}}/README.md +++ b/{{cookiecutter.project_shortname}}/README.md @@ -80,7 +80,4 @@ If you have selected install_dependencies during the prompt, you can skip this p _Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True` (unless you choose `False` on `publish_on_npm`). We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284)._ 5. Share your component with the community! https://community.plot.ly/c/dash -## More Resources -- Learn more about Dash: https://dash.plot.ly -- Watch the [component boilerplate repository](https://github.com/plotly/dash-component-boilerplate) to stay informed of changes to our components. -- [React guide for python developers](https://dash.plot.ly/react-for-python-developers) + From ea6580095837446b7f4f2a65604cbc99cffb9c64 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 16 Oct 2018 11:22:23 -0400 Subject: [PATCH 58/59] Add getting started, add generated CONTRIBUTING.md --- .../CONTRIBUTING.md | 4 ++++ {{cookiecutter.project_shortname}}/README.md | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 {{cookiecutter.project_shortname}}/CONTRIBUTING.md diff --git a/{{cookiecutter.project_shortname}}/CONTRIBUTING.md b/{{cookiecutter.project_shortname}}/CONTRIBUTING.md new file mode 100644 index 0000000..fc39028 --- /dev/null +++ b/{{cookiecutter.project_shortname}}/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# CONTRIBUTING + +This project was generated by the [dash-component-boilerplate](https://github.com/plotly/dash-component-boilerplate) it contains the minimal set of code required to create your own custom Dash component. + diff --git a/{{cookiecutter.project_shortname}}/README.md b/{{cookiecutter.project_shortname}}/README.md index dbb2436..a33401f 100644 --- a/{{cookiecutter.project_shortname}}/README.md +++ b/{{cookiecutter.project_shortname}}/README.md @@ -1,6 +1,15 @@ # {{cookiecutter.project_name}} -This project was generated by the [dash-component-boilerplate](https://github.com/plotly/dash-component-boilerplate) it contains the minimal set of code required to create your own custom Dash component. +{{cookiecutter.project_name}} is a Dash component library. + +Get started with: +1. Install Dash and its dependencies: https://dash.plot.ly/installation +2. Run `python usage.py` +3. Visit http://localhost:8050 in your web browser + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md) ### Install dependencies @@ -79,5 +88,7 @@ If you have selected install_dependencies during the prompt, you can skip this p ``` _Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash servers the component library's CSS and JS from the remote unpkg CDN, so if you haven't published the component package to NPM you'll need to set the `serve_locally` flags to `True` (unless you choose `False` on `publish_on_npm`). We will eventually make `serve_locally=True` the default, [follow our progress in this issue](https://github.com/plotly/dash/issues/284)._ 5. Share your component with the community! https://community.plot.ly/c/dash - + 1. Publish this repository to GitHub + 2. Tag your GitHub repository with the plotly-dash tag so that it appears here: https://github.com/topics/plotly-dash + 3. Create a post in the Dash community forum: https://community.plot.ly/c/dash From ab9263d742b300f5decaf7a33509b01359c753d0 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 16 Oct 2018 11:35:21 -0400 Subject: [PATCH 59/59] Improve readme cookiecutter variables explanation. --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 400e170..6b2c4f6 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,14 @@ To use this boilerplate: $ cookiecutter git@github.com:plotly/dash-component-boilerplate.git ``` 3. Answer the questions about the project. - - project name: Clean project name, can contains spaces and special characters. - - component name: derived from project_name, lowercase, spaces and '-' are replaced by '_', ascii only. - - author info: author_name and author_email for package.json metadata. - - description: the project description, included in package.json. - - license: License type for the component library. - - publish_on_npm: Set to false to only serve locally from the package data. - - install_dependencies: Set to false to only generate the project structure. + - `project_name`: This is the "human-readable" name of your project. For example, "Dash Core Components". + - `project_shortname`: is derived from the project name, it is the name of the "python library" for your project. By default, this is generated from your `project_name` by lowercasing the name and replacing spaces & `-` with underscores. For example, for "Dash Core Components" this would be "dash_core_components". + - `component_name`: This is the name of the initial component that is generated. The default takes the `project_name` and remove the whitespace and `-`. As a javascript class name it should be in PascalCase. + - `author info`: author_name and author_email for package.json metadata. + - `description`: the project description, included in package.json. + - `license`: License type for the component library. + - `publish_on_npm`: Set to false to only serve locally from the package data. + - `install_dependencies`: Set to false to only generate the project structure. 4. The project will be generated in the folder of `project_shortname`. 5. Follow the directions in the generated README to start developing your new Dash component.