diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 378e8f849..60ecd5e0c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -7,7 +7,8 @@ jobs:
runs-on: ubuntu-latest
outputs:
kernel: ${{ steps.filter.outputs.kernel }}
- tornado-e2e: ${{ steps.filter.outputs.tornado-e2e }}
+ # tornado-e2e: ${{ steps.filter.outputs.tornado-e2e }}
+ tornado-e2e: true # This step does not detect changes in the `streamlit` submodule that is needed to trigger the test-tornado-e2e job (https://github.com/dorny/paths-filter/issues/143), so skip checking and make it always return true as a workaround.
mountable: ${{ steps.filter.outputs.mountable }}
sharing-editor: ${{ steps.filter.outputs.sharing-editor }}
sharing-common: ${{ steps.filter.outputs.sharing-common }}
@@ -21,10 +22,10 @@ jobs:
kernel:
- 'packages/kernel/**/*'
# - '!packages/kernel/py/**/*' # Not supported by paths-filter now: https://github.com/dorny/paths-filter/issues/106
- tornado-e2e:
- - 'packages/kernel/py/tornado'
- - 'packages/kernel/py/e2etests/**/*'
- - 'streamlit/**/*'
+ # tornado-e2e: # We run this job anytime. See above.
+ # - 'packages/kernel/py/tornado'
+ # - 'packages/kernel/py/e2etests/**/*'
+ # - 'streamlit/**/*'
mountable:
- 'packages/mountable/**/*'
sharing-editor:
@@ -107,6 +108,13 @@ jobs:
- name: Create virtualenv
run: python -m venv .venv
+ - name: Install dependencies
+ shell: bash
+ run: |
+ . .venv/bin/activate
+ cd packages/kernel/py/e2etests
+ poetry install
+
# Set up the /streamlit submodule
## Set up apt packages. Ref: https://github.com/streamlit/streamlit/wiki/Contributing#ubuntu
- name: Install Streamlit build dependencies
@@ -117,26 +125,26 @@ jobs:
cd streamlit
make mini-init
- - name: Install dependencies
- shell: bash
- run: poetry install
- working-directory: packages/kernel/py/e2etests
-
- name: Run linter and code formatter to the test code module
- working-directory: packages/kernel/py/e2etests
run: |
+ . .venv/bin/activate
+ cd packages/kernel/py/e2etests
poetry run black . --check
poetry run isort . --check
poetry run flake8
- name: Run mypy
- working-directory: packages/kernel/py/e2etests
- run: poetry run mypy .
+ run: |
+ . .venv/bin/activate
+ cd packages/kernel/py/e2etests
+ poetry run mypy .
- name: Run pytest
shell: bash
- run: poetry run python -m pytest -v tests
- working-directory: packages/kernel/py/e2etests
+ run: |
+ . .venv/bin/activate
+ cd packages/kernel/py/e2etests
+ poetry run python -m pytest -v tests
test-mountable:
needs: changes
diff --git a/Makefile b/Makefile
index 8bc3b0c07..e0a49628c 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ kernel := packages/kernel/dist/*
pyarrow_wheel := packages/kernel/py/stlite-pyarrow/dist/stlite_pyarrow-0.1.0-py3-none-any.whl
tornado_wheel := packages/kernel/py/tornado/dist/tornado-6.2-py3-none-any.whl
streamlit_proto := streamlit/frontend/src/autogen
-streamlit_wheel := packages/kernel/py/streamlit/lib/dist/streamlit-1.17.0-py2.py3-none-any.whl
+streamlit_wheel := packages/kernel/py/streamlit/lib/dist/streamlit-1.18.1-py2.py3-none-any.whl
.PHONY: all
all: init mountable sharing sharing-editor
@@ -124,4 +124,4 @@ $(streamlit_wheel): $(VENV) $(streamlit_proto) streamlit/lib/streamlit/**/*.py s
cd streamlit && \
$(MAKE) distribution
mkdir -p `dirname $(streamlit_wheel)`
- cp streamlit/lib/dist/streamlit-1.17.0-py2.py3-none-any.whl $(streamlit_wheel)
+ cp streamlit/lib/dist/streamlit-1.18.1-py2.py3-none-any.whl $(streamlit_wheel)
diff --git a/packages/common-react/package.json b/packages/common-react/package.json
index 1269938f9..54ebc2e77 100644
--- a/packages/common-react/package.json
+++ b/packages/common-react/package.json
@@ -14,17 +14,17 @@
"check:prettier": "prettier --check ."
},
"dependencies": {
- "streamlit-browser": "^1.12.0"
+ "streamlit-browser": "^1.18.1"
},
"devDependencies": {
- "@typescript-eslint/eslint-plugin": "^5.46.1",
- "@typescript-eslint/parser": "^5.46.1",
- "eslint": "^8.21.0",
- "eslint-plugin-react": "^7.31.11",
+ "@typescript-eslint/eslint-plugin": "^5.49.0",
+ "@typescript-eslint/parser": "^5.49.0",
+ "eslint": "^8.33.0",
+ "eslint-plugin-react": "^7.32.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-toastify": "^9.1.1",
- "typescript": "^4.6.4"
+ "typescript": "^4.9.4"
},
"peerDependencies": {
"react": "^17.0.2",
diff --git a/packages/common-react/src/toastify-components/theme.ts b/packages/common-react/src/toastify-components/theme.ts
index 9c5c6d700..86a9a69be 100644
--- a/packages/common-react/src/toastify-components/theme.ts
+++ b/packages/common-react/src/toastify-components/theme.ts
@@ -1,16 +1,28 @@
-import { LocalStore } from "streamlit-browser/src/lib/storageUtils";
+import {
+ LocalStore,
+ localStorageAvailable,
+} from "streamlit-browser/src/lib/storageUtils";
import { darkTheme } from "streamlit-browser/src/theme/themeConfigs";
-export function isDarkTheme() {
+function isSystemDarkTheme(): boolean {
+ // Detect the system color mode. Ref: https://stackoverflow.com/a/57795495/13103190
+ return (
+ window.matchMedia &&
+ window.matchMedia("(prefers-color-scheme: dark)").matches
+ );
+}
+
+export function isDarkTheme(): boolean {
+ if (!localStorageAvailable()) {
+ return isSystemDarkTheme();
+ }
+
// Ref: https://github.com/streamlit/streamlit/blob/1.12.0/frontend/src/theme/utils.ts#L544
const cachedThemeStr = window.localStorage.getItem(LocalStore.ACTIVE_THEME);
if (!cachedThemeStr) {
- // Detect the system color mode Ref: https://stackoverflow.com/a/57795495/13103190
- return (
- window.matchMedia &&
- window.matchMedia("(prefers-color-scheme: dark)").matches
- );
+ return isSystemDarkTheme();
}
+
const { name } = JSON.parse(cachedThemeStr);
return name === darkTheme.name;
diff --git a/packages/common/package.json b/packages/common/package.json
index 41fde927e..d7aa7a766 100644
--- a/packages/common/package.json
+++ b/packages/common/package.json
@@ -19,10 +19,10 @@
"check:prettier": "prettier --check ."
},
"devDependencies": {
- "@typescript-eslint/eslint-plugin": "^5.33.0",
- "@typescript-eslint/parser": "^5.33.0",
- "eslint": "^8.21.0",
- "typescript": "^4.6.4",
+ "@typescript-eslint/eslint-plugin": "^5.49.0",
+ "@typescript-eslint/parser": "^5.49.0",
+ "eslint": "^8.33.0",
+ "typescript": "^4.9.4",
"vitest": "^0.21.1"
}
}
diff --git a/packages/desktop/bin/dump_artifacts.ts b/packages/desktop/bin/dump_artifacts.ts
index 98ff8b115..820212b42 100755
--- a/packages/desktop/bin/dump_artifacts.ts
+++ b/packages/desktop/bin/dump_artifacts.ts
@@ -100,7 +100,7 @@ async function createSitePackagesSnapshot(
pyodide,
path.join(
stliteKernelPyDir,
- "streamlit/lib/dist/streamlit-1.17.0-py2.py3-none-any.whl"
+ "streamlit/lib/dist/streamlit-1.18.1-py2.py3-none-any.whl"
)
);
} else {
diff --git a/packages/desktop/craco.config.js b/packages/desktop/craco.config.js
index 86db01d53..a0070e280 100644
--- a/packages/desktop/craco.config.js
+++ b/packages/desktop/craco.config.js
@@ -3,17 +3,7 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
babel: {
- plugins: [
- "@emotion",
- // Stlite: This specific syntax plugin is needed since the syntax was started being used in the upstream codebase (https://github.com/streamlit/streamlit/pull/5913/files#diff-845917f3a07167e741db444532fae1e083d5b9f84ac8e8e38d3a34818a311ad8R242).
- // With the browserslist setting in the upstream project, this plugin is automatically chosen by @babel/preset-env and the syntax is transpiled nicely,
- // however, this `desktop` package has a different browserslist optimized for the Electron runtime
- // and it leads to an error maybe because of CRA's bug.
- // So we had to specify this single plugin here explicitly.
- // See https://github.com/whitphx/stlite/issues/471 for the details.
- // TODO: When CRA is updated to v5, the bug should be gone away so this config can be removed.
- "@babel/plugin-proposal-logical-assignment-operators",
- ],
+ plugins: ["@emotion"],
loaderOptions: {
cacheDirectory: true,
},
@@ -37,8 +27,9 @@ module.exports = {
"script-src 'wasm-unsafe-eval' 'unsafe-eval'",
// style-src is necessary because of emotion. In dev, style-loader with injectType=styleTag is also the reason.
"style-src 'self' 'unsafe-inline'",
- // The worker is inlined as blob: https://github.com/whitphx/stlite/blob/v0.7.1/packages/stlite-kernel/src/kernel.ts#L16
- "worker-src blob:",
+ // - 'self': The stlite kernel worker is bundled as a separate file via Webpack 5's worker feature.
+ // - blob: : Some third party libraries such as Mapbox used in st.map() create workers from blob.
+ "worker-src 'self' blob:",
// For tag permissions.
// - 'self': The main scripts
// - 'unsafe-inline': Allow the inline scripts from custom components
@@ -73,9 +64,11 @@ module.exports = {
]
.filter(Boolean)
.join("; ");
- htmlWebpackPlugin.options.meta["Content-Security-Policy"] = {
- "http-equiv": "Content-Security-Policy",
- content: csp,
+ htmlWebpackPlugin.userOptions.meta = {
+ "Content-Security-Policy": {
+ "http-equiv": "Content-Security-Policy",
+ content: csp,
+ },
};
// Let Babel compile outside of src/.
@@ -94,6 +87,12 @@ module.exports = {
};
/* To build Streamlit. These configs are copied from streamlit/frontend/craco.config.js */
+ webpackConfig.resolve.mainFields = ["module", "main"];
+ // Webpack 5 requires polyfills. We don't need them, so resolve to an empty module
+ webpackConfig.resolve.fallback ||= {};
+ webpackConfig.resolve.fallback.tty = false;
+ webpackConfig.resolve.fallback.os = false;
+
// Apache Arrow uses .mjs
webpackConfig.module.rules.push({
include: /node_modules/,
@@ -109,16 +108,15 @@ module.exports = {
// then we don't obtain the expected result.
// So we turn off Asset Modules here by setting `type: 'javascript/auto'`.
// See https://webpack.js.org/guides/asset-modules/
- // TODO: Enable when using Webpack 5.
- // webpackConfig.module.rules.push({
- // test: /\.whl$/i,
- // use: [
- // {
- // loader: 'file-loader',
- // }
- // ],
- // type: 'javascript/auto'
- // })
+ webpackConfig.module.rules.push({
+ test: /\.whl$/i,
+ use: [
+ {
+ loader: "file-loader",
+ },
+ ],
+ type: "javascript/auto",
+ });
return webpackConfig;
},
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index ee4fad8d3..9a91aa375 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -56,14 +56,14 @@
"yargs": "^17.5.1"
},
"devDependencies": {
- "@craco/craco": "^6.1.2",
+ "@craco/craco": "^7.0.0",
"@stlite/common": "^0.25.0",
"@stlite/common-react": "^0.25.0",
"@stlite/kernel": "^0.25.0",
- "@testing-library/react": "^11.2.7",
- "@testing-library/user-event": "^13.1.9",
- "@types/jest": "^26.0.19",
- "@types/node": "^14.14.16",
+ "@testing-library/react": "^12.1.4",
+ "@testing-library/user-event": "^14.0.0",
+ "@types/jest": "^27.4.3",
+ "@types/node": "^16.18.12",
"@types/react": "^17.0.7",
"@types/react-dom": "^17.0.5",
"@types/yargs": "^17.0.13",
@@ -74,10 +74,11 @@
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
- "react-scripts": "4.0.3",
+ "react-scripts": "5.0.1",
"react-toastify": "^9.1.1",
+ "streamlit-browser": "^1.18.1",
"ts-node": "^10.9.1",
- "typescript": "^4.6.3"
+ "typescript": "^4.9.4"
},
"///": "build.productName is necessary because electron-builder uses the package name for its purpose but the scoped name including '@' makes a problem: https://github.com/electron-userland/electron-builder/issues/3230",
"build": {
diff --git a/packages/kernel/package.json b/packages/kernel/package.json
index 82878491e..b57672094 100644
--- a/packages/kernel/package.json
+++ b/packages/kernel/package.json
@@ -20,23 +20,22 @@
},
"devDependencies": {
"@testing-library/react": "^12",
- "@typescript-eslint/eslint-plugin": "^5.33.0",
- "@typescript-eslint/parser": "^5.33.0",
+ "@types/path-browserify": "^1.0.0",
+ "@typescript-eslint/eslint-plugin": "^5.49.0",
+ "@typescript-eslint/parser": "^5.49.0",
"blob-polyfill": "^7.0.20220408",
- "eslint": "^8.21.0",
+ "eslint": "^8.33.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"pyodide": "0.22.1",
- "typescript": "^4.6.4",
+ "typescript": "^4.9.4",
"vitest": "^0.21.1"
},
"dependencies": {
"@jupyterlab/coreutils": "^5.4.2",
"@lumino/coreutils": "^1.12.0",
"form-data-encoder": "^2.0.1",
- "streamlit-browser": "^1.12.0"
- },
- "peerDependencies": {
- "worker-loader": "^3.0.8"
+ "path-browserify": "^1.0.1",
+ "streamlit-browser": "^1.18.1"
}
}
diff --git a/packages/kernel/py/e2etests/pyproject.toml b/packages/kernel/py/e2etests/pyproject.toml
index 625ddd830..52324c41b 100644
--- a/packages/kernel/py/e2etests/pyproject.toml
+++ b/packages/kernel/py/e2etests/pyproject.toml
@@ -11,7 +11,6 @@ python = "^3.10"
[tool.poetry.dev-dependencies]
pytest = "^7.1.2"
requests = "^2.28.1"
-streamlit = "1.10.0"
black = "^22.6.0"
flake8 = "^5.0.4"
isort = "^5.10.1"
@@ -30,3 +29,8 @@ profile = "black"
[tool.mypy]
check_untyped_defs = true
+mypy_path = "../tornado"
+
+[[tool.mypy.overrides]]
+module = ["tornado", "tornado.*"]
+ignore_errors = true
diff --git a/packages/kernel/py/e2etests/tests/conftest.py b/packages/kernel/py/e2etests/tests/conftest.py
index d5dbc28b7..17a889aa7 100644
--- a/packages/kernel/py/e2etests/tests/conftest.py
+++ b/packages/kernel/py/e2etests/tests/conftest.py
@@ -13,6 +13,9 @@
TORNADO_PYTHONPATH = TORNADO_SUBMODULE
sys.path.insert(0, str(TORNADO_PYTHONPATH))
+PYARROW_DIR = HERE / "../../stlite-pyarrow"
+PYARROW_PYTHONPATH = PYARROW_DIR
+sys.path.insert(0, str(PYARROW_PYTHONPATH))
sys.modules["pyodide"] = Mock()
sys.modules["pyodide.ffi"] = Mock()
diff --git a/packages/kernel/py/e2etests/tests/test_httpserver.py b/packages/kernel/py/e2etests/tests/test_httpserver.py
index 68bc06a9d..f8b7bdaf4 100644
--- a/packages/kernel/py/e2etests/tests/test_httpserver.py
+++ b/packages/kernel/py/e2etests/tests/test_httpserver.py
@@ -5,18 +5,17 @@
import pytest
import requests
-import streamlit
import tornado
from streamlit import config
from streamlit.hello import Hello
-from streamlit.server.server import Server
+from streamlit.runtime.runtime import Runtime
+from streamlit.web.server.server import Server
@pytest.fixture
def run_streamlit_background():
- """Mimic streamlit.cli.main_hello()"""
+ """Mimic streamlit.web.cli.main_hello()"""
filename = Hello.__file__
- streamlit._is_running_with_streamlit = True
config.set_option(
"server.fileWatcherType", "none", ""
@@ -34,15 +33,12 @@ def run_streamlit_background():
data_from_thread: dict[str, Any] = {"server": None, "exception": None}
async def init_server():
- """Mimic streamlit.bootstrap.run()"""
-
- def on_start(server: Server):
- data_from_thread["server"] = server
- event.set()
-
- ioloop = tornado.ioloop.IOLoop.current()
- server = Server(ioloop, filename, None)
- server.start(on_start)
+ """Mimic streamlit.web.bootstrap.run()"""
+ tornado.ioloop.IOLoop.current()
+ server = Server(filename, None)
+ await server.start()
+ data_from_thread["server"] = server
+ event.set()
def start():
asyncio.set_event_loop(server_evloop)
@@ -65,39 +61,43 @@ def start():
yield
server.stop()
+ Runtime._instance = None
thread.join()
- Server._singleton = None
def test_http_server_is_set(run_streamlit_background):
- from tornado.httpserver import HTTP_SERVER # type: ignore
+ from tornado.httpserver import HTTP_SERVER
assert HTTP_SERVER is not None
-@patch("streamlit.server.server.AppSession")
+@patch("streamlit.runtime.websocket_session_manager.AppSession")
def test_http_server_websocket(AppSession, run_streamlit_background):
session = AppSession()
from streamlit.proto import BackMsg_pb2
- from tornado.httpserver import HTTP_SERVER # type: ignore
+ from tornado.httpserver import HTTP_SERVER
+
+ assert HTTP_SERVER is not None
backMsg = BackMsg_pb2.BackMsg()
backMsg.stop_script = True
on_websocket_message = Mock()
- HTTP_SERVER.start_websocket("/stream", on_websocket_message)
+ HTTP_SERVER.start_websocket("/_stcore/stream", on_websocket_message)
HTTP_SERVER.receive_websocket(backMsg.SerializeToString())
- session.handle_stop_script_request.assert_called()
+ session.handle_backmsg.assert_called_with(backMsg)
HTTP_SERVER.websocket_handler.write_message(b"foobar", binary=True)
on_websocket_message.assert_called_with(b"foobar", binary=True)
def test_http_get(run_streamlit_background):
- from tornado.httpserver import HTTP_SERVER # type: ignore
+ from tornado.httpserver import HTTP_SERVER
+
+ assert HTTP_SERVER is not None
on_response = Mock()
@@ -109,23 +109,29 @@ def test_http_get(run_streamlit_background):
on_response.assert_called_with(200, ANY, b"ok")
-def test_http_file_upload(run_streamlit_background):
- from tornado.httpserver import HTTP_SERVER # type: ignore
+@patch("streamlit.runtime.websocket_session_manager.AppSession")
+def test_http_file_upload(AppSession, run_streamlit_background):
+ from tornado.httpserver import HTTP_SERVER
+
+ assert HTTP_SERVER is not None
+
+ app_session = AppSession.return_value
+ app_session.id = (
+ "foo" # Every mocked AppSession's ID is fixed to be "foo" for test simplicity.
+ )
# Initiate the session
receive_websocket = Mock()
- HTTP_SERVER.start_websocket("/stream", receive_websocket)
+ HTTP_SERVER.start_websocket("/_stcore/stream", receive_websocket)
- server = Server.get_current()
- session_ids = server._session_info_by_id.keys()
- session_id = list(session_ids)[0]
+ active_session = Runtime.instance()._session_mgr.list_active_sessions()[0].session
req = requests.Request(
"POST",
- "http://example.com:55555/upload_file",
+ "http://example.com:55555/_stcore/upload_file",
files={"file": ("foo.txt", "Foo\nBar\nBaz")},
data={
- "sessionId": session_id,
+ "sessionId": active_session.id,
"widgetId": "$$GENERATED_WIDGET_KEY-23195dab12a102415c4621538530154c-None",
},
)
@@ -133,8 +139,11 @@ def test_http_file_upload(run_streamlit_background):
on_response = Mock()
+ headers = dict(r.headers)
+ body = r.body
+ assert body is not None
task = HTTP_SERVER.receive_http(
- "POST", "/upload_file", r.headers, r.body, on_response
+ "POST", "/_stcore/upload_file", headers, body, on_response
)
loop = task.get_loop()
diff --git a/packages/kernel/src/cross-origin-worker.ts b/packages/kernel/src/cross-origin-worker.ts
new file mode 100644
index 000000000..5a8564270
--- /dev/null
+++ b/packages/kernel/src/cross-origin-worker.ts
@@ -0,0 +1,29 @@
+// A hack to load a worker script from a different origin.
+// Webpack 5's built-in Web Workers feature does not support inlining the worker code
+// into the main bundle and always emits it to a separate file,
+// which is not compatible with the cross-origin worker.
+// So we use this hack to load the separate worker code from a domain different from the parent page.
+// Webpack 5 deals with the special syntax `new Worker(new URL("worker.ts", import.meta.url))` for the worker build,
+// so this `CrossOriginWorkerMaker` class must be defined in a separate file and
+// be imported as the `Worker` alias into the file where the syntax is used to load the worker.
+// This technique was introduced at https://github.com/webpack/webpack/discussions/14648#discussioncomment-1589272
+export class CrossOriginWorkerMaker {
+ public readonly worker: Worker;
+
+ constructor(url: URL) {
+ try {
+ // This is the normal way to load a worker script, which is the best straightforward if possible.
+ this.worker = new Worker(url);
+ } catch (e) {
+ console.debug(
+ `Failed to load a worker script from ${url.toString()}. Trying to load a cross-origin worker...`
+ );
+ const workerBlob = new Blob([`importScripts("${url.toString()}");`], {
+ type: "text/javascript",
+ });
+ const workerBlobUrl = URL.createObjectURL(workerBlob);
+ this.worker = new Worker(workerBlobUrl);
+ URL.revokeObjectURL(workerBlobUrl);
+ }
+ }
+}
diff --git a/packages/kernel/src/declarations.d.ts b/packages/kernel/src/declarations.d.ts
index 276496aa9..0791a8450 100644
--- a/packages/kernel/src/declarations.d.ts
+++ b/packages/kernel/src/declarations.d.ts
@@ -9,28 +9,6 @@ declare module "*.whl" {
return res;
}
-declare module "*?raw" {
- const res: string;
- return res;
-}
-
-declare module "!!raw-loader!*" {
- const res: string;
- export default res;
-}
-
-// Ref: https://v4.webpack.js.org/loaders/worker-loader/
-declare module "!!worker-loader?*" {
- // You need to change `Worker`, if you specified a different value for the `workerType` option
- class WebpackWorker extends Worker {
- constructor();
- }
-
- // Uncomment this if you set the `esModule` option to `false`
- // export = WebpackWorker;
- export default WebpackWorker;
-}
-
// Declarations for worker.ts where some variables are dynamically loaded through importScript.
declare let loadPyodide: any;
diff --git a/packages/kernel/src/file.ts b/packages/kernel/src/file.ts
index 13eef0f6c..c2aa7d45f 100644
--- a/packages/kernel/src/file.ts
+++ b/packages/kernel/src/file.ts
@@ -1,4 +1,4 @@
-import path from "path";
+import path from "path-browserify";
import { PyodideInterface } from "pyodide";
function ensureParent(pyodide: PyodideInterface, filePath: string): void {
diff --git a/packages/kernel/src/kernel.ts b/packages/kernel/src/kernel.ts
index e65883df6..1abb0da0d 100644
--- a/packages/kernel/src/kernel.ts
+++ b/packages/kernel/src/kernel.ts
@@ -3,6 +3,7 @@
import { PromiseDelegate } from "@lumino/coreutils";
import { makeAbsoluteWheelURL } from "./url";
+import { CrossOriginWorkerMaker as Worker } from "./cross-origin-worker";
// Since v0.19.0, Pyodide raises an exception when importing not pure Python 3 wheels, whose path does not end with "py3-none-any.whl",
// so configuration on file-loader here is necessary so that the hash is not included in the bundled URL.
@@ -11,9 +12,7 @@ import { makeAbsoluteWheelURL } from "./url";
// https://pyodide.org/en/stable/project/changelog.html#micropip
import TORNADO_WHEEL from "!!file-loader?name=pypi/[name].[ext]&context=.!../py/tornado/dist/tornado-6.2-py3-none-any.whl"; // TODO: Extract the import statement to an auto-generated file like `_pypi.ts` in JupyterLite: https://github.com/jupyterlite/jupyterlite/blob/f2ecc9cf7189cb19722bec2f0fc7ff5dfd233d47/packages/pyolite-kernel/src/_pypi.ts
import PYARROW_WHEEL from "!!file-loader?name=pypi/[name].[ext]&context=.!../py/stlite-pyarrow/dist/stlite_pyarrow-0.1.0-py3-none-any.whl";
-import STREAMLIT_WHEEL from "!!file-loader?name=pypi/[name].[ext]&context=.!../py/streamlit/lib/dist/streamlit-1.17.0-py2.py3-none-any.whl";
-
-import Worker from "!!worker-loader?inline=no-fallback!./worker";
+import STREAMLIT_WHEEL from "!!file-loader?name=pypi/[name].[ext]&context=.!../py/streamlit/lib/dist/streamlit-1.18.1-py2.py3-none-any.whl";
// Ref: https://github.com/streamlit/streamlit/blob/1.12.2/frontend/src/lib/UriUtil.ts#L32-L33
const FINAL_SLASH_RE = /\/+$/;
@@ -100,7 +99,11 @@ export class StliteKernel {
this.onLoad = options.onLoad;
this.onError = options.onError;
- this._worker = new Worker();
+ // HACK: Use `CrossOriginWorkerMaker` imported as `Worker` here.
+ // Read the comment in `cross-origin-worker.ts` for the detail.
+ const workerMaker = new Worker(new URL("./worker.js", import.meta.url));
+ this._worker = workerMaker.worker;
+
this._worker.onmessage = (e) => {
this._processWorkerMessage(e.data);
};
diff --git a/packages/kernel/src/streamlit-replacements/lib/ConnectionManager.ts b/packages/kernel/src/streamlit-replacements/lib/ConnectionManager.ts
index a349c3114..1e91f5411 100644
--- a/packages/kernel/src/streamlit-replacements/lib/ConnectionManager.ts
+++ b/packages/kernel/src/streamlit-replacements/lib/ConnectionManager.ts
@@ -158,10 +158,10 @@ export class ConnectionManager {
}
private async connect(): Promise {
- const WEBSOCKET_STREAM_PATH = "/stream" // The original is defined in streamlit/frontend/src/lib/WebsocketConnection.tsx
+ const WEBSOCKET_STREAM_PATH = "_stcore/stream" // The original is defined in streamlit/frontend/src/lib/WebsocketConnection.tsx
try {
- await this.props.kernel.connectWebSocket(WEBSOCKET_STREAM_PATH)
+ await this.props.kernel.connectWebSocket("/" + WEBSOCKET_STREAM_PATH)
this.setConnectionState(ConnectionState.CONNECTED)
} catch (e) {
const err = ensureError(e)
diff --git a/packages/kernel/src/streamlit-replacements/lib/FileUploadClient.ts b/packages/kernel/src/streamlit-replacements/lib/FileUploadClient.ts
index 9fa8496d4..e80ddea20 100644
--- a/packages/kernel/src/streamlit-replacements/lib/FileUploadClient.ts
+++ b/packages/kernel/src/streamlit-replacements/lib/FileUploadClient.ts
@@ -96,7 +96,7 @@ export class FileUploadClient {
return this.kernel
.sendHttpRequest({
method: "POST",
- path: "/upload_file",
+ path: "/_stcore/upload_file",
body,
headers: { ...encoder.headers },
})
diff --git a/packages/mountable/DEVELOPMENT.md b/packages/mountable/DEVELOPMENT.md
index 7d1dd6402..164e3d78d 100644
--- a/packages/mountable/DEVELOPMENT.md
+++ b/packages/mountable/DEVELOPMENT.md
@@ -1,5 +1,10 @@
# `@stlite/mountable`
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and its TypeScript template. The CRA version was v4.0.3, which is compatible with the Streamlit frontend.
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and its TypeScript template. The CRA version was v5.0.1, which is compatible with the Streamlit frontend [since v1.18.0](https://github.com/streamlit/streamlit/commit/5565e996b5be4426795e7d95c392d1ff5aa71053#diff-da6498268e99511d9ba0df3c13e439d10556a812881c9d03955b2ef7c6c1c655R199).
+(Before 1.18.0, the project was based on CRA 4.0.3.)
-The build script has been ejected and customized to make the production build as a library that does not contain the files in `public/` while keeping the development build as an app with hot-reload. See https://github.com/whitphx/stlite/pull/15 about it.
+The build script has been ejected and customized to make the production build as a library that does not contain the files in `public/` while keeping the development build as an app with hot-reload.
+See the following PRs about it.
+
+- https://github.com/whitphx/stlite/pull/15: The first introduction of such customizations. At this point, the project was based on CRA 4.0.3.
+- https://github.com/whitphx/stlite/pull/482: We upgraded `react-scripts` to 5.0.1 in this PR. The ejected files are recreated from a CRA 5.0.1-based project and the necessary diffs for the customization are applied to them manually.
diff --git a/packages/mountable/config/env.js b/packages/mountable/config/env.js
index 3d1411bd0..ffa7e496a 100644
--- a/packages/mountable/config/env.js
+++ b/packages/mountable/config/env.js
@@ -86,8 +86,6 @@ function getClientEnvironment(publicUrl) {
WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
// Whether or not react-refresh is enabled.
- // react-refresh is not 100% stable at this time,
- // which is why it's disabled by default.
// It is defined here so it is available in the webpackHotDevClient.
FAST_REFRESH: process.env.FAST_REFRESH !== 'false',
}
diff --git a/packages/mountable/config/jest/babelTransform.js b/packages/mountable/config/jest/babelTransform.js
index dabf5a8cb..5b391e405 100644
--- a/packages/mountable/config/jest/babelTransform.js
+++ b/packages/mountable/config/jest/babelTransform.js
@@ -1,6 +1,6 @@
'use strict';
-const babelJest = require('babel-jest');
+const babelJest = require('babel-jest').default;
const hasJsxRuntime = (() => {
if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
diff --git a/packages/mountable/config/paths.js b/packages/mountable/config/paths.js
index 4e3d30e26..f0a6cd9c9 100644
--- a/packages/mountable/config/paths.js
+++ b/packages/mountable/config/paths.js
@@ -66,6 +66,8 @@ module.exports = {
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
+ appWebpackCache: resolveApp('node_modules/.cache'),
+ appTsBuildInfoFile: resolveApp('node_modules/.cache/tsconfig.tsbuildinfo'),
swSrc: resolveModule(resolveApp, 'src/service-worker'),
publicUrlOrPath,
};
diff --git a/packages/mountable/config/pnpTs.js b/packages/mountable/config/pnpTs.js
deleted file mode 100644
index d1b0539f8..000000000
--- a/packages/mountable/config/pnpTs.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-const { resolveModuleName } = require('ts-pnp');
-
-exports.resolveModuleName = (
- typescript,
- moduleName,
- containingFile,
- compilerOptions,
- resolutionHost
-) => {
- return resolveModuleName(
- moduleName,
- containingFile,
- compilerOptions,
- resolutionHost,
- typescript.resolveModuleName
- );
-};
-
-exports.resolveTypeReferenceDirective = (
- typescript,
- moduleName,
- containingFile,
- compilerOptions,
- resolutionHost
-) => {
- return resolveModuleName(
- moduleName,
- containingFile,
- compilerOptions,
- resolutionHost,
- typescript.resolveTypeReferenceDirective
- );
-};
diff --git a/packages/mountable/config/webpack.config.js b/packages/mountable/config/webpack.config.js
index 3ee9e4ccc..f58c909bb 100644
--- a/packages/mountable/config/webpack.config.js
+++ b/packages/mountable/config/webpack.config.js
@@ -4,17 +4,15 @@ const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
-const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
+const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
-const safePostCssParser = require('postcss-safe-parser');
-const ManifestPlugin = require('webpack-manifest-plugin');
+const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
+const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
-const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const ESLintPlugin = require('eslint-webpack-plugin');
@@ -22,23 +20,29 @@ const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
-const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
-const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
+const ForkTsCheckerWebpackPlugin =
+ process.env.TSC_COMPILE_ON_ERROR === 'true'
+ ? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin')
+ : require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
-const postcssNormalize = require('postcss-normalize');
-
-const appPackageJson = require(paths.appPackageJson);
+const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash');
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
-const webpackDevClientEntry = require.resolve(
- 'react-dev-utils/webpackHotDevClient'
+const reactRefreshRuntimeEntry = require.resolve('react-refresh/runtime');
+const reactRefreshWebpackPluginRuntimeEntry = require.resolve(
+ '@pmmmwh/react-refresh-webpack-plugin'
);
-const reactRefreshOverlayEntry = require.resolve(
- 'react-dev-utils/refreshOverlayInterop'
+const babelRuntimeEntry = require.resolve('babel-preset-react-app');
+const babelRuntimeEntryHelpers = require.resolve(
+ '@babel/runtime/helpers/esm/assertThisInitialized',
+ { paths: [babelRuntimeEntry] }
);
+const babelRuntimeRegenerator = require.resolve('@babel/runtime/regenerator', {
+ paths: [babelRuntimeEntry],
+});
const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === 'true';
const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true';
@@ -50,6 +54,11 @@ const imageInlineSizeLimit = parseInt(
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
+// Check if Tailwind config exists
+const useTailwind = fs.existsSync(
+ path.join(paths.appPath, 'tailwind.config.js')
+);
+
// Get the path to the uncompiled service worker (if it exists).
const swSrc = paths.swSrc;
@@ -108,22 +117,42 @@ module.exports = function (webpackEnv) {
// package.json
loader: require.resolve('postcss-loader'),
options: {
- // Necessary for external CSS imports to work
- // https://github.com/facebook/create-react-app/issues/2677
- ident: 'postcss',
- plugins: () => [
- require('postcss-flexbugs-fixes'),
- require('postcss-preset-env')({
- autoprefixer: {
- flexbox: 'no-2009',
- },
- stage: 3,
- }),
- // Adds PostCSS Normalize as the reset css with default options,
- // so that it honors browserslist config in package.json
- // which in turn let's users customize the target behavior as per their needs.
- postcssNormalize(),
- ],
+ postcssOptions: {
+ // Necessary for external CSS imports to work
+ // https://github.com/facebook/create-react-app/issues/2677
+ ident: 'postcss',
+ config: false,
+ plugins: !useTailwind
+ ? [
+ 'postcss-flexbugs-fixes',
+ [
+ 'postcss-preset-env',
+ {
+ autoprefixer: {
+ flexbox: 'no-2009',
+ },
+ stage: 3,
+ },
+ ],
+ // Adds PostCSS Normalize as the reset css with default options,
+ // so that it honors browserslist config in package.json
+ // which in turn let's users customize the target behavior as per their needs.
+ 'postcss-normalize',
+ ]
+ : [
+ 'tailwindcss',
+ 'postcss-flexbugs-fixes',
+ [
+ 'postcss-preset-env',
+ {
+ autoprefixer: {
+ flexbox: 'no-2009',
+ },
+ stage: 3,
+ },
+ ],
+ ],
+ },
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
},
},
@@ -149,6 +178,9 @@ module.exports = function (webpackEnv) {
};
return {
+ target: ['browserslist'],
+ // Webpack noise constrained to errors and warnings
+ stats: 'errors-warnings',
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
@@ -159,34 +191,10 @@ module.exports = function (webpackEnv) {
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
- entry:
- isEnvDevelopment && !shouldUseReactRefresh
- ? [
- // Include an alternative client for WebpackDevServer. A client's job is to
- // connect to WebpackDevServer by a socket and get notified about changes.
- // When you save a file, the client will either apply hot updates (in case
- // of CSS changes), or refresh the page (in case of JS changes). When you
- // make a syntax error, this client will display a syntax error overlay.
- // Note: instead of the default WebpackDevServer client, we use a custom one
- // to bring better experience for Create React App users. You can replace
- // the line below with these two lines if you prefer the stock client:
- //
- // require.resolve('webpack-dev-server/client') + '?/',
- // require.resolve('webpack/hot/dev-server'),
- //
- // When using the experimental react-refresh integration,
- // the webpack plugin takes care of injecting the dev client for us.
- webpackDevClientEntry,
- // Finally, this is your app's code:
- paths.appIndexJs,
- // We include the app code last so that if there is a runtime error during
- // initialization, it doesn't blow up the WebpackDevServer client, and
- // changing JS code would still trigger a refresh.
- ]
- : paths.appIndexJs,
+ entry: paths.appIndexJs,
output: {
// The build folder.
- path: isEnvProduction ? paths.appBuild : undefined,
+ path: paths.appBuild,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
@@ -194,14 +202,13 @@ module.exports = function (webpackEnv) {
filename: isEnvProduction
? 'stlite.js'
: isEnvDevelopment && 'static/js/bundle.js',
- // TODO: remove this when upgrading to webpack 5
- futureEmitAssets: true,
- // For production, build as a library without public/*. For development, run as an app with public/*.
+ // Stlite: For production, build as a library without public/*. For development, run as an app with public/*.
library: isEnvProduction ? "stlite" : undefined,
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
+ assetModuleFilename: 'static/media/[name].[hash][ext]',
// webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// We inferred the "public path" (such as / or /my-project) from homepage.
@@ -214,12 +221,22 @@ module.exports = function (webpackEnv) {
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
- // Prevents conflicts when multiple webpack runtimes (from different apps)
- // are used on the same page.
- jsonpFunction: `webpackJsonp${appPackageJson.name}`,
- // this defaults to 'window', but by setting it to 'this' then
- // module chunks which are built will work in web workers as well.
- globalObject: 'this',
+ },
+ cache: {
+ type: 'filesystem',
+ version: createEnvironmentHash(env.raw),
+ cacheDirectory: paths.appWebpackCache,
+ store: 'pack',
+ buildDependencies: {
+ defaultWebpack: ['webpack/lib/'],
+ config: [__filename],
+ tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
+ fs.existsSync(f)
+ ),
+ },
+ },
+ infrastructureLogging: {
+ level: 'none',
},
optimization: {
minimize: isEnvProduction,
@@ -263,42 +280,10 @@ module.exports = function (webpackEnv) {
ascii_only: true,
},
},
- sourceMap: shouldUseSourceMap,
}),
// This is only used in production mode
- new OptimizeCSSAssetsPlugin({
- cssProcessorOptions: {
- parser: safePostCssParser,
- map: shouldUseSourceMap
- ? {
- // `inline: false` forces the sourcemap to be output into a
- // separate file
- inline: false,
- // `annotation: true` appends the sourceMappingURL to the end of
- // the css file, helping the browser find the sourcemap
- annotation: true,
- }
- : false,
- },
- cssProcessorPluginOptions: {
- preset: ['default', { minifyFontValues: { removeQuotes: false } }],
- },
- }),
+ new CssMinimizerPlugin(),
],
- // TODO: Chunking with these plugins below makes the library not working somehow. Investigate it.
- // // Automatically split vendor and commons
- // // https://twitter.com/wSokra/status/969633336732905474
- // // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
- // splitChunks: {
- // chunks: 'all',
- // name: isEnvDevelopment,
- // },
- // // Keep the runtime chunk separated to enable long term caching
- // // https://twitter.com/wSokra/status/969679223278505985
- // // https://github.com/facebook/create-react-app/issues/5358
- // runtimeChunk: {
- // name: entrypoint => `runtime-${entrypoint.name}`,
- // },
},
resolve: {
// This allows you to set a fallback for where webpack should look for modules.
@@ -331,9 +316,6 @@ module.exports = function (webpackEnv) {
...(modules.webpackAliases || {}),
},
plugins: [
- // Adds support for installing with Plug'n'Play, leading to faster installs and adding
- // guards against forgotten dependencies and such.
- PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
@@ -341,22 +323,30 @@ module.exports = function (webpackEnv) {
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [
paths.appPackageJson,
- reactRefreshOverlayEntry,
+ reactRefreshRuntimeEntry,
+ reactRefreshWebpackPluginRuntimeEntry,
+ babelRuntimeEntry,
+ babelRuntimeEntryHelpers,
+ babelRuntimeRegenerator,
]),
],
- },
- resolveLoader: {
- plugins: [
- // Also related to Plug'n'Play, but this time it tells webpack to load its loaders
- // from the current package.
- PnpWebpackPlugin.moduleLoader(module),
- ],
+ // Stlite: Customization to build the Streamlit frontend, copied from https://github.com/streamlit/streamlit/blob/1.18.1/frontend/craco.config.js#L48-L52
+ mainFields: ["module", "main"],
+ fallback: {
+ tty: false,
+ os: false,
+ },
},
module: {
strictExportPresence: true,
rules: [
- // Disable require.ensure as it's not a standard language feature.
- { parser: { requireEnsure: false } },
+ // Handle node_modules packages that contain sourcemaps
+ shouldUseSourceMap && {
+ enforce: 'pre',
+ exclude: /@babel(?:\/|\\{1,2})runtime/,
+ test: /\.(js|mjs|jsx|ts|tsx|css)$/,
+ loader: require.resolve('source-map-loader'),
+ },
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
@@ -366,11 +356,12 @@ module.exports = function (webpackEnv) {
// https://github.com/jshttp/mime-db
{
test: [/\.avif$/],
- loader: require.resolve('url-loader'),
- options: {
- limit: imageInlineSizeLimit,
- mimetype: 'image/avif',
- name: 'static/media/[name].[hash:8].[ext]',
+ type: 'asset',
+ mimetype: 'image/avif',
+ parser: {
+ dataUrlCondition: {
+ maxSize: imageInlineSizeLimit,
+ },
},
},
// "url" loader works like "file" loader except that it embeds assets
@@ -378,10 +369,37 @@ module.exports = function (webpackEnv) {
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
- loader: require.resolve('url-loader'),
- options: {
- limit: imageInlineSizeLimit,
- name: 'static/media/[name].[hash:8].[ext]',
+ type: 'asset',
+ parser: {
+ dataUrlCondition: {
+ maxSize: imageInlineSizeLimit,
+ },
+ },
+ },
+ {
+ test: /\.svg$/,
+ use: [
+ {
+ loader: require.resolve('@svgr/webpack'),
+ options: {
+ prettier: false,
+ svgo: false,
+ svgoConfig: {
+ plugins: [{ removeViewBox: false }],
+ },
+ titleProp: true,
+ ref: true,
+ },
+ },
+ {
+ loader: require.resolve('file-loader'),
+ options: {
+ name: 'static/media/[name].[hash].[ext]',
+ },
+ },
+ ],
+ issuer: {
+ and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
// Process application JS with Babel.
@@ -408,18 +426,6 @@ module.exports = function (webpackEnv) {
plugins: [
"@emotion", // Stlite: This line has been added after ejection. The emotion babel plugin is necessary for the Streamlit frontend: https://github.com/streamlit/streamlit/blob/786da40420cd5b6d7682da42837c6ecf861e8464/frontend/craco.config.js#L44
- "@babel/plugin-proposal-logical-assignment-operators", // Stlite: This specific plugin is needed to parse the upstream source code with the CRA default browserslist setting. See https://github.com/whitphx/stlite/issues/471
- [
- require.resolve('babel-plugin-named-asset-import'),
- {
- loaderMap: {
- svg: {
- ReactComponent:
- '@svgr/webpack?-svgo,+titleProp,+ref![path]',
- },
- },
- },
- ],
isEnvDevelopment &&
shouldUseReactRefresh &&
require.resolve('react-refresh/babel'),
@@ -475,6 +481,9 @@ module.exports = function (webpackEnv) {
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
+ modules: {
+ mode: 'icss',
+ },
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
@@ -492,6 +501,7 @@ module.exports = function (webpackEnv) {
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
+ mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
}),
@@ -508,6 +518,9 @@ module.exports = function (webpackEnv) {
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
+ modules: {
+ mode: 'icss',
+ },
},
'sass-loader'
),
@@ -528,6 +541,7 @@ module.exports = function (webpackEnv) {
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
+ mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
@@ -540,27 +554,42 @@ module.exports = function (webpackEnv) {
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
- loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
- exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
- options: {
- name: 'static/media/[name].[hash:8].[ext]',
- },
+ exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
+ type: 'asset/resource',
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
- // Stlite: Apache Arrow uses .mjs
+ // Stlite: Customization to build the Streamlit frontend, copied from https://github.com/streamlit/streamlit/blob/1.18.1/frontend/craco.config.js#L54
+ // Apache Arrow uses .mjs
{
include: /node_modules/,
test: /\.mjs$/,
type: "javascript/auto",
- }
- ],
+ },
+ // Stlite:
+ // Since Webpack5, Asset Modules has been introduced to cover what file-loader had done.
+ // However, in this project, we use the inline loader setting like `import * from "!!file-loader!/path/to/file"` to use file-loader
+ // but it does not turn off Asset Modules and leads to duplicate assets generated.
+ // To make matters worse, the actually resolved paths from such import statements point to the URL from Asset Modules, not the file-loader specified with the inline syntax,
+ // then we don't obtain the expected result.
+ // So we turn off Asset Modules here by setting `type: 'javascript/auto'`.
+ // See https://webpack.js.org/guides/asset-modules/
+ {
+ test: /\.whl$/i,
+ use: [
+ {
+ loader: "file-loader",
+ },
+ ],
+ type: "javascript/auto",
+ },
+ ].filter(Boolean),
},
plugins: [
// Generates an `index.html` file with the