diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cd893bd6c..5ec29eefaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed - [#1675](https://github.com/plotly/dash/pull/1675) Remove the constraint that `requests_pathname_prefix` ends with `routes_pathname_prefix`. When you are serving your app behind a reverse proxy that rewrites URLs that constraint needs to be violated. -- [#1611](https://github.com/plotly/dash/pull/1611) Package dash-renderer artifacts and dependencies with Dash, and source renderer resources from within Dash. +- [#1611](https://github.com/plotly/dash/pull/1611) and [#1685](https://github.com/plotly/dash/pull/1685) Package dash-renderer artifacts and dependencies with Dash, and source renderer resources from within Dash. - [#1567](https://github.com/plotly/dash/pull/1567) Julia component generator puts components into `src/jl` - fixes an issue on case-insensitive filesystems when the component name and module name match (modulo case) and no prefix is used. Also reduces JS/Julia clutter in the overloaded `src` directory. ### Fixed diff --git a/MANIFEST.in b/MANIFEST.in index 22ae78dc6d..00f145dff6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,3 +5,5 @@ include dash/favicon.ico include dash/extract-meta.js include dash/deps/*.js include dash/deps/*.map +include dash/dash-renderer/build/*.js +include dash/dash-renderer/build/*.map diff --git a/dash/_dash_renderer.py b/dash/_dash_renderer.py index 25a3c03b93..ba9d9f1dab 100644 --- a/dash/_dash_renderer.py +++ b/dash/_dash_renderer.py @@ -37,15 +37,15 @@ _js_dist = [ { - "relative_package_path": "deps/dash_renderer.min.js", - "dev_package_path": "deps/dash_renderer.dev.js", + "relative_package_path": "dash-renderer/build/dash_renderer.min.js", + "dev_package_path": "dash-renderer/build/dash_renderer.dev.js", "external_url": "https://unpkg.com/dash-renderer@1.9.1" - "/dash_renderer/dash_renderer.min.js", + "/build/dash_renderer.min.js", "namespace": "dash", }, { - "relative_package_path": "deps/dash_renderer.min.js.map", - "dev_package_path": "deps/dash_renderer.dev.js.map", + "relative_package_path": "dash-renderer/build/dash_renderer.min.js.map", + "dev_package_path": "dash-renderer/build/dash_renderer.dev.js.map", "namespace": "dash", "dynamic": True, }, diff --git a/dash/dash-renderer/init.template b/dash/dash-renderer/init.template index a5e593ca49..4395a8a29a 100644 --- a/dash/dash-renderer/init.template +++ b/dash/dash-renderer/init.template @@ -37,15 +37,15 @@ _js_dist_dependencies = [ _js_dist = [ { - "relative_package_path": "deps/dash_renderer.min.js", - "dev_package_path": "deps/dash_renderer.dev.js", + "relative_package_path": "dash-renderer/build/dash_renderer.min.js", + "dev_package_path": "dash-renderer/build/dash_renderer.dev.js", "external_url": "https://unpkg.com/dash-renderer@$version" - "/dash_renderer/dash_renderer.min.js", + "/build/dash_renderer.min.js", "namespace": "dash", }, { - "relative_package_path": "deps/dash_renderer.min.js.map", - "dev_package_path": "deps/dash_renderer.dev.js.map", + "relative_package_path": "dash-renderer/build/dash_renderer.min.js.map", + "dev_package_path": "dash-renderer/build/dash_renderer.dev.js.map", "namespace": "dash", "dynamic": True, }, diff --git a/dash/dash-renderer/package.json b/dash/dash-renderer/package.json index 039b1add4e..21789e2b5c 100644 --- a/dash/dash-renderer/package.json +++ b/dash/dash-renderer/package.json @@ -2,7 +2,7 @@ "name": "dash-renderer", "version": "1.9.1", "description": "render dash components in react", - "main": "../deps/dash_renderer.min.js", + "main": "build/dash_renderer.min.js", "scripts": { "prepublishOnly": "rm -rf lib && babel src --extensions=\".ts,.tsx,.js,.jsx\" --out-dir lib --copy-files", "private::format.eslint": "eslint --quiet --fix src tests", @@ -85,7 +85,7 @@ "whatwg-fetch": "^3.6.2" }, "files": [ - "../deps/*{.js,.map}", + "build/*{.js,.map}", "/lib/**" ], "prettier": "@plotly/prettier-config-dash" diff --git a/dash/dash-renderer/webpack.base.config.js b/dash/dash-renderer/webpack.base.config.js index db7f1419ec..0952601440 100644 --- a/dash/dash-renderer/webpack.base.config.js +++ b/dash/dash-renderer/webpack.base.config.js @@ -40,7 +40,7 @@ const rendererOptions = { main: ['whatwg-fetch', './src/index.js'], }, output: { - path: path.resolve(__dirname, "..", "deps"), + path: path.resolve(__dirname, "build"), filename: `${dashLibraryName}.dev.js`, library: dashLibraryName, libraryTarget: 'window', @@ -64,7 +64,7 @@ module.exports = options => [ { mode: 'production', output: { - path: path.resolve(__dirname, "..", "deps"), + path: path.resolve(__dirname, "build"), filename: `${dashLibraryName}.min.js`, library: dashLibraryName, libraryTarget: 'window', diff --git a/dash/development/build_process.py b/dash/development/build_process.py index a00548e1de..c83ffa5c60 100644 --- a/dash/development/build_process.py +++ b/dash/development/build_process.py @@ -21,19 +21,20 @@ class BuildProcess(object): def __init__(self, main, deps_info): self.logger = logger self.main = main + self.build_folder = self._concat(self.main, "build") self.deps_info = deps_info self.npm_modules = self._concat(self.main, "node_modules") self.package_lock = self._concat(self.main, "package-lock.json") self.package = self._concat(self.main, "package.json") self._parse_package(path=self.package) - self.asset_paths = (self.build_folder, self.npm_modules) + self.asset_paths = (self.deps_folder, self.npm_modules) def _parse_package(self, path): with open(path, "r") as fp: package = json.load(fp) self.version = package["version"] self.name = package["name"] - self.build_folder = self._concat(self.main, os.pardir, "deps") + self.deps_folder = self._concat(self.main, os.pardir, "deps") self.deps = package["dependencies"] @staticmethod @@ -79,25 +80,27 @@ def build(self, build=None): @job("compute the hash digest for assets") def digest(self): - if not os.path.exists(self.build_folder): + if not os.path.exists(self.deps_folder): try: - os.makedirs(self.build_folder) + os.makedirs(self.deps_folder) except OSError: - logger.exception("🚨 having issues manipulating %s", self.build_folder) + logger.exception("🚨 having issues manipulating %s", self.deps_folder) sys.exit(1) - copies = tuple( - _ - for _ in os.listdir(self.build_folder) - if os.path.splitext(_)[-1] in {".js", ".map"} - ) - logger.info("bundles in %s %s", self.build_folder, copies) - payload = {self.name: self.version} - for copy in copies: - payload["MD5 ({})".format(copy)] = compute_md5( - self._concat(self.build_folder, copy) + + for folder in (self.deps_folder, self.build_folder): + copies = tuple( + _ + for _ in os.listdir(folder) + if os.path.splitext(_)[-1] in {".js", ".map"} ) + logger.info("bundles in %s %s", folder, copies) + + for copy in copies: + payload["MD5 ({})".format(copy)] = compute_md5( + self._concat(folder, copy) + ) with open(self._concat(self.main, "digest.json"), "w") as fp: json.dump(payload, fp, sort_keys=True, indent=4, separators=(",", ":")) @@ -108,11 +111,11 @@ def digest(self): @job("copy and generate the bundles") def bundles(self, build=None): - if not os.path.exists(self.build_folder): + if not os.path.exists(self.deps_folder): try: - os.makedirs(self.build_folder) + os.makedirs(self.deps_folder) except OSError: - logger.exception("🚨 having issues manipulating %s", self.build_folder) + logger.exception("🚨 having issues manipulating %s", self.deps_folder) sys.exit(1) self._parse_package(self.package_lock) @@ -138,12 +141,12 @@ def bundles(self, build=None): shutil.copyfile( self._concat(self.npm_modules, scope, name, subfolder, filename), - self._concat(self.build_folder, target), + self._concat(self.deps_folder, target), ) _script = "build:dev" if build == "local" else "build:js" logger.info("run `npm run %s`", _script) - os.chdir(self._concat(self.build_folder, os.pardir, "dash-renderer")) + os.chdir(self.main) run_command_with_process("npm run {}".format(_script)) logger.info("generate the `__init__.py` from template and versions") @@ -151,7 +154,7 @@ def bundles(self, build=None): t = string.Template(fp.read()) with open( - self._concat(self.build_folder, os.pardir, "_dash_renderer.py"), "w" + self._concat(self.deps_folder, os.pardir, "_dash_renderer.py"), "w" ) as fp: fp.write(t.safe_substitute(versions)) diff --git a/tests/integration/test_scripts.py b/tests/integration/test_scripts.py index 441543e337..f86c6b95b8 100644 --- a/tests/integration/test_scripts.py +++ b/tests/integration/test_scripts.py @@ -1,8 +1,6 @@ import time import pytest -from selenium.webdriver.common.by import By - import dash_html_components as html import dash_core_components as dcc @@ -12,20 +10,26 @@ from dash.exceptions import PreventUpdate -def findSyncPlotlyJs(scripts): - for script in scripts: - if "dash_core_components/plotly" in script.get_attribute("src"): - return script +def get_script_sources(dash_duo): + return [s.get_attribute("src") for s in dash_duo.find_elements("script")] + + +def hasSyncPlotlyJs(dash_duo): + return any("dash_core_components/plotly" in s for s in get_script_sources(dash_duo)) + + +def hasAsyncPlotlyJs(dash_duo): + return any( + "dash_core_components/async-plotlyjs" in s for s in get_script_sources(dash_duo) + ) -def findAsyncPlotlyJs(scripts): - for script in scripts: - if "dash_core_components/async-plotlyjs" in script.get_attribute("src"): - return script +def hasWindowPlotly(dash_duo): + return dash_duo.driver.execute_script("return !!window.Plotly") @pytest.mark.parametrize("is_eager", [True, False]) -def test_scripts(dash_duo, is_eager): +def test_scri001_scripts(dash_duo, is_eager): app = Dash(__name__, eager_loading=is_eager) app.layout = html.Div([dcc.Graph(id="output", figure={"data": [{"y": [3, 1, 2]}]})]) @@ -37,16 +41,18 @@ def test_scripts(dash_duo, is_eager): dev_tools_hot_reload=False, ) - # Give time for the async dependency to be requested (if any) - time.sleep(2) + # Wait for the graph to appear + dash_duo.find_element(".js-plotly-plot") - scripts = dash_duo.driver.find_elements(By.CSS_SELECTOR, "script") + assert hasSyncPlotlyJs(dash_duo) is is_eager - assert (findSyncPlotlyJs(scripts) is None) is not is_eager - assert (findAsyncPlotlyJs(scripts) is None) is is_eager + # Webpack 5 deletes the script tag immediately after evaluating it + # https://github.com/plotly/dash/pull/1685#issuecomment-877199466 + assert hasAsyncPlotlyJs(dash_duo) is False + assert hasWindowPlotly(dash_duo) is True -def test_scripts_on_request(dash_duo): +def test_scri002_scripts_on_request(dash_duo): app = Dash(__name__, eager_loading=False) app.layout = html.Div(id="div", children=[html.Button(id="btn")]) @@ -68,15 +74,16 @@ def load_chart(n_clicks): # Give time for the async dependency to be requested (if any) time.sleep(2) - scripts = dash_duo.driver.find_elements(By.CSS_SELECTOR, "script") - assert findSyncPlotlyJs(scripts) is None - assert findAsyncPlotlyJs(scripts) is None + assert hasSyncPlotlyJs(dash_duo) is False + assert hasAsyncPlotlyJs(dash_duo) is False + assert hasWindowPlotly(dash_duo) is False dash_duo.find_element("#btn").click() - # Give time for the async dependency to be requested (if any) - time.sleep(2) + # Wait for the graph to appear + dash_duo.find_element(".js-plotly-plot") - scripts = dash_duo.driver.find_elements(By.CSS_SELECTOR, "script") - assert findSyncPlotlyJs(scripts) is None - assert findAsyncPlotlyJs(scripts) is not None + assert hasSyncPlotlyJs(dash_duo) is False + # Again, webpack 5 deletes the script tag immediately after evaluating it + assert hasAsyncPlotlyJs(dash_duo) is False + assert hasWindowPlotly(dash_duo) is True