From 0953555936a36e9df55ca5a4ccba2f3328487bb1 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 6 Oct 2022 09:27:37 -0400 Subject: [PATCH 1/8] experimental option to use react 18.2 --- dash/_dash_renderer.py | 91 ++++++++++++++++++++----------- dash/dash-renderer/init.template | 91 ++++++++++++++++++++----------- dash/development/build_process.py | 39 ++++++++----- 3 files changed, 141 insertions(+), 80 deletions(-) diff --git a/dash/_dash_renderer.py b/dash/_dash_renderer.py index 8c56ad77fa..aa12dda26a 100644 --- a/dash/_dash_renderer.py +++ b/dash/_dash_renderer.py @@ -1,39 +1,64 @@ +import os + __version__ = "1.14.2" -_js_dist_dependencies = [ - { - "external_url": { - "prod": [ - "https://unpkg.com/@babel/polyfill@7.12.1/dist/polyfill.min.js", - "https://unpkg.com/react@16.14.0/umd/react.production.min.js", - "https://unpkg.com/react-dom@16.14.0/umd/react-dom.production.min.js", - "https://unpkg.com/prop-types@15.8.1/prop-types.min.js", - ], - "dev": [ - "https://unpkg.com/@babel/polyfill@7.12.1/dist/polyfill.min.js", - "https://unpkg.com/react@16.14.0/umd/react.development.js", - "https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js", - "https://unpkg.com/prop-types@15.8.1/prop-types.js", - ], - }, - "relative_package_path": { - "prod": [ - "deps/polyfill@7.12.1.min.js", - "deps/react@16.14.0.min.js", - "deps/react-dom@16.14.0.min.js", - "deps/prop-types@15.8.1.min.js", - ], - "dev": [ - "deps/polyfill@7.12.1.min.js", - "deps/react@16.14.0.js", - "deps/react-dom@16.14.0.js", - "deps/prop-types@15.8.1.js", - ], - }, - "namespace": "dash", - } -] +_available_react_versions = {"16.14.0", "18.2.0"} +_available_reactdom_versions = {"16.14.0", "18.2.0"} +_js_dist_dependencies = [] # to be set by _set_react_version + + +def _set_react_version(v_react, v_reactdom=None): + if not v_reactdom: + v_reactdom = v_react + + react_err = f"looking for one of {_available_react_versions}, found {v_react}" + reactdom_err = ( + f"looking for one of {_available_reactdom_versions}, found {v_reactdom}" + ) + assert v_react in _available_react_versions, react_err + assert v_reactdom in _available_reactdom_versions, reactdom_err + + _js_dist_dependencies[:] = [ + { + "external_url": { + "prod": [ + "https://unpkg.com/@babel/polyfill@7.12.1/dist/polyfill.min.js", + f"https://unpkg.com/react@{v_react}/umd/react.production.min.js", + f"https://unpkg.com/react-dom@{v_reactdom}/umd/react-dom.production.min.js", + "https://unpkg.com/prop-types@15.8.1/prop-types.min.js", + ], + "dev": [ + "https://unpkg.com/@babel/polyfill@7.12.1/dist/polyfill.min.js", + f"https://unpkg.com/react@{v_react}/umd/react.development.js", + f"https://unpkg.com/react-dom@{v_reactdom}/umd/react-dom.development.js", + "https://unpkg.com/prop-types@15.8.1/prop-types.js", + ], + }, + "relative_package_path": { + "prod": [ + "deps/polyfill@7.12.1.min.js", + f"deps/react@{v_react}.min.js", + f"deps/react-dom@{v_reactdom}.min.js", + "deps/prop-types@15.8.1.min.js", + ], + "dev": [ + "deps/polyfill@7.12.1.min.js", + f"deps/react@{v_react}.js", + f"deps/react-dom@{v_reactdom}.js", + "deps/prop-types@15.8.1.js", + ], + }, + "namespace": "dash", + } + ] + +_env_react_version = os.getenv("REACT_VERSION") +if _env_react_version: + _set_react_version(_env_react_version) + print(f"EXPERIMENTAL: Using react version from env: {_env_react_version}") +else: + _set_react_version("16.14.0", "16.14.0") _js_dist = [ { diff --git a/dash/dash-renderer/init.template b/dash/dash-renderer/init.template index 4395a8a29a..6b3b6b7832 100644 --- a/dash/dash-renderer/init.template +++ b/dash/dash-renderer/init.template @@ -1,39 +1,64 @@ +import os + __version__ = "$version" -_js_dist_dependencies = [ - { - "external_url": { - "prod": [ - "https://unpkg.com/@babel/polyfill@$polyfill/dist/polyfill.min.js", - "https://unpkg.com/react@$react/umd/react.production.min.js", - "https://unpkg.com/react-dom@$reactdom/umd/react-dom.production.min.js", - "https://unpkg.com/prop-types@$proptypes/prop-types.min.js", - ], - "dev": [ - "https://unpkg.com/@babel/polyfill@$polyfill/dist/polyfill.min.js", - "https://unpkg.com/react@$react/umd/react.development.js", - "https://unpkg.com/react-dom@$reactdom/umd/react-dom.development.js", - "https://unpkg.com/prop-types@$proptypes/prop-types.js", - ], - }, - "relative_package_path": { - "prod": [ - "deps/polyfill@$polyfill.min.js", - "deps/react@$react.min.js", - "deps/react-dom@$reactdom.min.js", - "deps/prop-types@$proptypes.min.js", - ], - "dev": [ - "deps/polyfill@$polyfill.min.js", - "deps/react@$react.js", - "deps/react-dom@$reactdom.js", - "deps/prop-types@$proptypes.js", - ], - }, - "namespace": "dash", - } -] +_available_react_versions = {"$react", $extra_react_versions} +_available_reactdom_versions = {"$reactdom", $extra_reactdom_versions} +_js_dist_dependencies = [] # to be set by _set_react_version + + +def _set_react_version(v_react, v_reactdom=None): + if not v_reactdom: + v_reactdom = v_react + + react_err = f"looking for one of {_available_react_versions}, found {v_react}" + reactdom_err = ( + f"looking for one of {_available_reactdom_versions}, found {v_reactdom}" + ) + assert v_react in _available_react_versions, react_err + assert v_reactdom in _available_reactdom_versions, reactdom_err + + global _js_dist_dependencies + _js_dist_dependencies = [ + { + "external_url": { + "prod": [ + "https://unpkg.com/@babel/polyfill@$polyfill/dist/polyfill.min.js", + f"https://unpkg.com/react@{v_react}/umd/react.production.min.js", + f"https://unpkg.com/react-dom@{v_reactdom}/umd/react-dom.production.min.js", + "https://unpkg.com/prop-types@$proptypes/prop-types.min.js", + ], + "dev": [ + "https://unpkg.com/@babel/polyfill@$polyfill/dist/polyfill.min.js", + f"https://unpkg.com/react@{v_react}/umd/react.development.js", + f"https://unpkg.com/react-dom@{v_reactdom}/umd/react-dom.development.js", + "https://unpkg.com/prop-types@$proptypes/prop-types.js", + ], + }, + "relative_package_path": { + "prod": [ + "deps/polyfill@$polyfill.min.js", + f"deps/react@{v_react}.min.js", + f"deps/react-dom@{v_reactdom}.min.js", + "deps/prop-types@$proptypes.min.js", + ], + "dev": [ + "deps/polyfill@$polyfill.min.js", + f"deps/react@{v_react}.js", + f"deps/react-dom@{v_reactdom}.js", + "deps/prop-types@$proptypes.js", + ], + }, + "namespace": "dash", + } + ] + +_env_react_version = os.getenv("REACT_VERSION") +if _env_react_version: + _set_react_version(_env_react_version) +else: + _set_react_version("$react", "$reactdom") _js_dist = [ { diff --git a/dash/development/build_process.py b/dash/development/build_process.py index ff1bcffcf6..2e9901def1 100644 --- a/dash/development/build_process.py +++ b/dash/development/build_process.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import sys import json @@ -7,6 +6,7 @@ import logging import coloredlogs import fire +import requests from .._utils import run_command_with_process, compute_md5, job @@ -107,7 +107,7 @@ def digest(self): ) @job("copy and generate the bundles") - def bundles(self, build=None): + def bundles(self, build=None): # pylint:disable=too-many-locals if not os.path.exists(self.deps_folder): try: os.makedirs(self.deps_folder) @@ -124,19 +124,32 @@ def bundles(self, build=None): "package": self.name.replace(" ", "_").replace("-", "_"), } - for scope, name, subfolder, filename, target in self.deps_info: + for scope, name, subfolder, filename, extras in self.deps_info: version = self.deps["/".join(filter(None, [scope, name]))]["version"] - versions[name.replace("-", "").replace(".", "")] = version + name_squashed = name.replace("-", "").replace(".", "") + versions[name_squashed] = version logger.info("copy npm dependency => %s", filename) ext = "min.js" if "min" in filename.split(".") else "js" - target = target.format(version) if target else f"{name}@{version}.{ext}" + target = f"{name}@{version}.{ext}" shutil.copyfile( self._concat(self.npm_modules, scope, name, subfolder, filename), self._concat(self.deps_folder, target), ) + if extras: + extras_str = '", "'.join(extras) + versions[f"extra_{name_squashed}_versions"] = f'"{extras_str}"' + + for extra_version in extras: + url = f"https://unpkg.com/{name}@{extra_version}/umd/{filename}" + res = requests.get(url) + extra_target = f"{name}@{extra_version}.{ext}" + extra_path = self._concat(self.deps_folder, extra_target) + with open(extra_path, "wb") as fp: + fp.write(res.content) + _script = "build:dev" if build == "local" else "build:js" logger.info("run `npm run %s`", _script) os.chdir(self.main) @@ -146,25 +159,23 @@ def bundles(self, build=None): with open(self._concat(self.main, "init.template"), encoding="utf-8") as fp: t = string.Template(fp.read()) - with open( - self._concat(self.deps_folder, os.pardir, "_dash_renderer.py"), - "w", - encoding="utf-8", - ) as fp: + renderer_init = self._concat(self.deps_folder, os.pardir, "_dash_renderer.py") + with open(renderer_init, "w", encoding="utf-8") as fp: fp.write(t.safe_substitute(versions)) class Renderer(BuildProcess): def __init__(self): """dash-renderer's path is binding with the dash folder hierarchy.""" + extras = ["18.2.0"] # versions to include beyond what's in package.json super().__init__( self._concat(os.path.dirname(__file__), os.pardir, "dash-renderer"), ( ("@babel", "polyfill", "dist", "polyfill.min.js", None), - (None, "react", "umd", "react.production.min.js", None), - (None, "react", "umd", "react.development.js", None), - (None, "react-dom", "umd", "react-dom.production.min.js", None), - (None, "react-dom", "umd", "react-dom.development.js", None), + (None, "react", "umd", "react.production.min.js", extras), + (None, "react", "umd", "react.development.js", extras), + (None, "react-dom", "umd", "react-dom.production.min.js", extras), + (None, "react-dom", "umd", "react-dom.development.js", extras), (None, "prop-types", None, "prop-types.min.js", None), (None, "prop-types", None, "prop-types.js", None), ), From ae83e8f8a582abf148431a1a1ebd31f1bb459f53 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 6 Oct 2022 09:31:45 -0400 Subject: [PATCH 2/8] add react-18 jobs on CI --- .circleci/config.yml | 89 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bcbd27b390..104f247a91 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -204,6 +204,24 @@ jobs: - store_artifacts: path: /tmp/dash_artifacts + test-39-react-18: + <<: *test + docker: + - image: cimg/python:3.9.9-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PERCY_ENABLE: 0 + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: True + PYVERSION: python36 + REDIS_URL: redis://localhost:6379 + REACT_VERSION: "18.2.0" + - image: cimg/redis:6.2.6 + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + test-36: <<: *test docker: @@ -258,7 +276,7 @@ jobs: PYVERSION: python36 PERCY_ENABLE: 0 - dcc-test-39: &dcc-test + dcc-39: &dcc-test working_directory: ~/dash docker: - image: cimg/python:3.9.9-browsers @@ -302,7 +320,19 @@ jobs: - store_artifacts: path: /tmp/dash_artifacts - dcc-test-36: + dcc-39-react-18: + <<: *dcc-test + docker: + - image: cimg/python:3.9.9-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PYVERSION: python36 + PERCY_ENABLE: 0 + REACT_VERSION: "18.2.0" + + dcc-36: <<: *dcc-test docker: - image: cimg/python:3.6.15-browsers @@ -313,7 +343,7 @@ jobs: PYVERSION: python36 PERCY_ENABLE: 0 - html-python-39: &html-test + html-39: &html-test working_directory: ~/dash docker: - image: cimg/python:3.9.9-browsers @@ -365,7 +395,19 @@ jobs: - store_artifacts: path: /tmp/dash_artifacts - html-python-36: + html-39-react-18: + <<: *html-test + docker: + - image: cimg/python:3.9.9-browsers + auth: + username: dashautomation + password: $DASH_PAT_DOCKERHUB + environment: + PYVERSION: python36 + PERCY_ENABLE: 0 + REACT_VERSION: "18.2.0" + + html-36: <<: *html-test docker: - image: cimg/python:3.6.15-browsers @@ -376,7 +418,7 @@ jobs: PYVERSION: python36 PERCY_ENABLE: 0 - table-server-test: + table-server: &table-server working_directory: ~/dash docker: - image: cimg/python:3.9.9-browsers @@ -423,6 +465,15 @@ jobs: - store_artifacts: path: /tmp/dash_artifacts + table-server-react-18: + <<: *table-server + docker: + - image: cimg/python:3.9.9-browsers + environment: + PYVERSION: python39 + PERCY_ENABLE: 0 + REACT_VERSION: "18.2.0" + table-unit-test: working_directory: ~/dash docker: @@ -552,6 +603,9 @@ workflows: - test-39: requires: - install-dependencies-39 + - test-39-react-18: + requires: + - install-dependencies-39 - test-36: requires: - install-dependencies-36 @@ -563,17 +617,23 @@ workflows: requires: - install-dependencies-36 - - dcc-test-39: + - dcc-39: requires: - install-dependencies-39 - - dcc-test-36: + - dcc-39-react-18: + requires: + - install-dependencies-39 + - dcc-36: requires: - install-dependencies-36 - - html-python-39: + - html-39: + requires: + - install-dependencies-39 + - html-39-react-18: requires: - install-dependencies-39 - - html-python-36: + - html-36: requires: - install-dependencies-36 @@ -584,16 +644,19 @@ workflows: requires: - install-dependencies-39 - table-visual-test - - table-server-test: + - table-server: + requires: + - install-dependencies-39 + - table-server-react-18: requires: - install-dependencies-39 - percy/finalize_all: requires: - test-39 - - dcc-test-39 - - html-python-39 - - table-server-test + - dcc-39 + - html-39 + - table-server - artifacts: requires: - percy/finalize_all From 54315e594653d8f7668bc54a8a2346db0f9cbd71 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 6 Oct 2022 09:46:58 -0400 Subject: [PATCH 3/8] fix PYVERSION env var in react 18 CI jobs --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 104f247a91..20b189a46c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -214,7 +214,7 @@ jobs: environment: PERCY_ENABLE: 0 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: True - PYVERSION: python36 + PYVERSION: python39 REDIS_URL: redis://localhost:6379 REACT_VERSION: "18.2.0" - image: cimg/redis:6.2.6 @@ -328,7 +328,7 @@ jobs: username: dashautomation password: $DASH_PAT_DOCKERHUB environment: - PYVERSION: python36 + PYVERSION: python39 PERCY_ENABLE: 0 REACT_VERSION: "18.2.0" @@ -403,7 +403,7 @@ jobs: username: dashautomation password: $DASH_PAT_DOCKERHUB environment: - PYVERSION: python36 + PYVERSION: python39 PERCY_ENABLE: 0 REACT_VERSION: "18.2.0" From f396b4832404129a2e2e3b6d4c5675ccec637a3a Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 6 Oct 2022 12:01:49 -0400 Subject: [PATCH 4/8] oops, update the template, not just _dash_renderer.py --- dash/dash-renderer/init.template | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dash/dash-renderer/init.template b/dash/dash-renderer/init.template index 6b3b6b7832..395a327b49 100644 --- a/dash/dash-renderer/init.template +++ b/dash/dash-renderer/init.template @@ -18,8 +18,7 @@ def _set_react_version(v_react, v_reactdom=None): assert v_react in _available_react_versions, react_err assert v_reactdom in _available_reactdom_versions, reactdom_err - global _js_dist_dependencies - _js_dist_dependencies = [ + _js_dist_dependencies[:] = [ { "external_url": { "prod": [ From c8787f7eaef38e7734c574617b89d88b9fed6116 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 6 Oct 2022 13:07:25 -0400 Subject: [PATCH 5/8] tyop --- components/dash-table/tests/selenium/test_header.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/dash-table/tests/selenium/test_header.py b/components/dash-table/tests/selenium/test_header.py index 5d1dd7626c..4bb3315b17 100644 --- a/components/dash-table/tests/selenium/test_header.py +++ b/components/dash-table/tests/selenium/test_header.py @@ -23,7 +23,7 @@ def get_app(props=dict()): return app -def get_sigle_row_app(props=dict()): +def get_single_row_app(props=dict()): app = dash.Dash(__name__) baseProps = get_props() @@ -115,7 +115,7 @@ def test_head003_preserves_column_name_on_cancel(test): def test_head004_change_single_row_header(test): - test.start_server(get_sigle_row_app()) + test.start_server(get_single_row_app()) target = test.table("table") From 0168242641b569e7a3ecc2bc76078e9d03316d1f Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 6 Oct 2022 13:09:02 -0400 Subject: [PATCH 6/8] render -> createRoot --- dash/dash-renderer/src/DashRenderer.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dash/dash-renderer/src/DashRenderer.js b/dash/dash-renderer/src/DashRenderer.js index 2a4d7b0bf1..82b8fe6c31 100644 --- a/dash/dash-renderer/src/DashRenderer.js +++ b/dash/dash-renderer/src/DashRenderer.js @@ -1,14 +1,19 @@ import React from 'react'; import ReactDOM from 'react-dom'; +// import {createRoot} from 'react-dom/client'; import AppProvider from './AppProvider.react'; class DashRenderer { constructor(hooks) { // render Dash Renderer upon initialising! - ReactDOM.render( - , - document.getElementById('react-entry-point') - ); + const container = document.getElementById('react-entry-point'); + + if (ReactDOM.createRoot) { + const root = ReactDOM.createRoot(container); + root.render(); + } else { + ReactDOM.render(, container); + } } } From f4efee95c619ecd5e6c4e8ff98ce7f4c15acb9e9 Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 22 Feb 2023 11:32:34 -0500 Subject: [PATCH 7/8] Remove commented import. --- dash/dash-renderer/src/DashRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/dash-renderer/src/DashRenderer.js b/dash/dash-renderer/src/DashRenderer.js index 82b8fe6c31..d1ce51a5c7 100644 --- a/dash/dash-renderer/src/DashRenderer.js +++ b/dash/dash-renderer/src/DashRenderer.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -// import {createRoot} from 'react-dom/client'; + import AppProvider from './AppProvider.react'; class DashRenderer { From b75da3b89ada03e198fe0aaec7f8c1058b09de98 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 22 Feb 2023 13:12:52 -0500 Subject: [PATCH 8/8] changelog for react 18 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 082123f444..6aaf8a9d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#2417](https://github.com/plotly/dash/pull/2417) Add wait_timeout property to customize the behavior of the default wait timeout used for by wait_for_page, fix [#1595](https://github.com/plotly/dash/issues/1595) - [#2417](https://github.com/plotly/dash/pull/2417) Add the element target text for wait_for_text* error message, fix [#945](https://github.com/plotly/dash/issues/945) - [#2425](https://github.com/plotly/dash/pull/2425) Add `add_log_handler=True` to Dash init, if you don't want a log stream handler at all. +- [#2260](https://github.com/plotly/dash/pull/2260) Experimental support for React 18. The default is still React v16.14.0, but to use React 18 you can either set the environment variable `REACT_VERSION=18.2.0` before running your app, or inside the app call `dash._dash_renderer._set_react_version("18.2.0")`. THIS FEATURE IS EXPERIMENTAL. It has not been tested with component suites outside the Dash core, and we may add or remove available React versions in any future release. ## Fixed