Skip to content

rewrite the client in typescript #951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ repos:
- id: isort
name: isort
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.4
rev: v3.0.0-alpha.6
hooks:
- id: prettier
55 changes: 29 additions & 26 deletions docs/source/_custom_js/package-lock.json

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

2 changes: 1 addition & 1 deletion docs/source/_custom_js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
"rollup": "^2.35.1"
},
"dependencies": {
"@reactpy/client": "file:../../../src/client/packages/client"
"@reactpy/client": "file:../../../src/client/packages/@reactpy/client"
}
}
26 changes: 13 additions & 13 deletions docs/source/_custom_js/src/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { mountWithLayoutServer, LayoutServerInfo } from "@reactpy/client";
import { SimpleReactPyClient, mount } from "@reactpy/client";

let didMountDebug = false;

export function mountWidgetExample(
mountID,
viewID,
reactpyServerHost,
useActivateButton
useActivateButton,
) {
let reactpyHost, reactpyPort;
if (reactpyServerHost) {
Expand All @@ -16,27 +16,27 @@ export function mountWidgetExample(
reactpyPort = window.location.port;
}

const serverInfo = new LayoutServerInfo({
host: reactpyHost,
port: reactpyPort,
path: "/_reactpy/",
query: `view_id=${viewID}`,
secure: window.location.protocol == "https:",
const client = new SimpleReactPyClient({
serverLocation: {
url: `${window.location.protocol}//${reactpyHost}:${reactpyPort}`,
route: "/",
query: `?view_id=${viewID}`,
},
});

const mountEl = document.getElementById(mountID);
let isMounted = false;
triggerIfInViewport(mountEl, () => {
if (!isMounted) {
activateView(mountEl, serverInfo, useActivateButton);
activateView(mountEl, client, useActivateButton);
isMounted = true;
}
});
}

function activateView(mountEl, serverInfo, useActivateButton) {
function activateView(mountEl, client, useActivateButton) {
if (!useActivateButton) {
mountWithLayoutServer(mountEl, serverInfo);
mount(mountEl, client);
return;
}

Expand All @@ -51,7 +51,7 @@ function activateView(mountEl, serverInfo, useActivateButton) {
mountEl.setAttribute("class", "interactive widget-container");
mountWithLayoutServer(mountEl, serverInfo);
}
})
}),
);

function fadeOutElementThenCallback(element, callback) {
Expand Down Expand Up @@ -87,7 +87,7 @@ function triggerIfInViewport(element, callback) {
{
root: null,
threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport
}
},
);

observer.observe(element);
Expand Down
110 changes: 68 additions & 42 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def __call__(self, session: Session) -> Callable[[bool], None]:
SRC_DIR = ROOT_DIR / "src"
CLIENT_DIR = SRC_DIR / "client"
REACTPY_DIR = SRC_DIR / "reactpy"
LANGUAGE_TYPES: list[LanguageName] = ["py", "js"]
TAG_PATTERN = re.compile(
# start
r"^"
Expand All @@ -46,7 +45,6 @@ def __call__(self, session: Session) -> Callable[[bool], None]:
# end
r"$"
)
print(TAG_PATTERN.pattern)
REMAINING_ARGS = Option(nargs=REMAINDER, type=str)


Expand All @@ -60,6 +58,7 @@ def __call__(self, session: Session) -> Callable[[bool], None]:
def setup_checks(session: Session) -> None:
session.install("--upgrade", "pip")
session.run("pip", "--version")
session.run("npm", "--version", external=True)


@group.setup("check-javascript")
Expand Down Expand Up @@ -88,6 +87,12 @@ def format(session: Session) -> None:
session.run("npm", "run", "format", external=True)


@group.session
def tsc(session: Session) -> None:
session.chdir(CLIENT_DIR)
session.run("npx", "tsc", "-b", "-w", "packages/app", external=True)


@group.session
def example(session: Session) -> None:
"""Run an example"""
Expand Down Expand Up @@ -232,21 +237,24 @@ def check_docs(session: Session) -> None:


@group.session
def check_javascript_suite(session: Session) -> None:
"""Run the Javascript-based test suite and ensure it bundles succesfully"""
session.run("npm", "run", "test", external=True)
def check_javascript_tests(session: Session) -> None:
session.run("npm", "run", "check:tests", external=True)


@group.session
def check_javascript_build(session: Session) -> None:
"""Run the Javascript-based test suite and ensure it bundles succesfully"""
session.run("npm", "run", "test", external=True)
def check_javascript_format(session: Session) -> None:
session.run("npm", "run", "check:format", external=True)


@group.session
def check_javascript_format(session: Session) -> None:
"""Check that Javascript style guidelines are being followed"""
session.run("npm", "run", "check-format", external=True)
def check_javascript_types(session: Session) -> None:
session.run("npm", "run", "build", external=True)
session.run("npm", "run", "check:types", external=True)


@group.session
def check_javascript_build(session: Session) -> None:
session.run("npm", "run", "build", external=True)


@group.session
Expand All @@ -266,16 +274,38 @@ def build_python(session: Session) -> None:


@group.session
def publish(session: Session, dry_run: bool = False) -> None:
def publish(
session: Session,
publish_dry_run: Annotated[
bool,
Option(help="whether to test the release process"),
] = False,
publish_fake_tags: Annotated[
Sequence[str],
Option(nargs="*", type=str, help="fake tags to use for a dry run release"),
] = (),
) -> None:
packages = get_packages(session)

release_prep: dict[LanguageName, ReleasePrepFunc] = {
"js": prepare_javascript_release,
"py": prepare_python_release,
}

if publish_fake_tags and not publish_dry_run:
session.error("Cannot specify --publish-fake-tags without --publish-dry-run")

parsed_tags: list[TagInfo] = []
for tag in publish_fake_tags or get_current_tags(session):
tag_info = parse_tag(tag)
if tag_info is None:
session.error(
f"Invalid tag {tag} - must be of the form <package>-<language>-<version>"
)
parsed_tags.append(tag_info) # type: ignore

publishers: list[tuple[Path, Callable[[bool], None]]] = []
for tag, tag_pkg, tag_ver in get_current_tags(session):
for tag, tag_pkg, tag_ver in parsed_tags:
if tag_pkg not in packages:
session.error(f"Tag {tag} references package {tag_pkg} that does not exist")

Expand All @@ -293,7 +323,7 @@ def publish(session: Session, dry_run: bool = False) -> None:
for pkg_path, publish in publishers:
session.log(f"Publishing {pkg_path}...")
session.chdir(pkg_path)
publish(dry_run)
publish(publish_dry_run)


# --- Utilities ------------------------------------------------------------------------
Expand Down Expand Up @@ -386,23 +416,28 @@ def get_packages(session: Session) -> dict[str, PackageInfo]:
}

# collect javascript packages
for pkg in (CLIENT_DIR / "packages").glob("*"):
pkg_json_file = pkg / "package.json"
if not pkg_json_file.exists():
session.error(f"package.json not found in {pkg}")
js_package_paths: list[Path] = []
for maybed_pkg in (CLIENT_DIR / "packages").glob("*"):
if not (maybed_pkg / "package.json").exists():
for nmaybe_namespaced_pkg in maybed_pkg.glob("*"):
if (nmaybe_namespaced_pkg / "package.json").exists():
js_package_paths.append(nmaybe_namespaced_pkg)
else:
js_package_paths.append(maybed_pkg)

# get javascript package info
for pkg in js_package_paths:
pkg_json_file = pkg / "package.json" # we already know this exists

pkg_json = json.loads(pkg_json_file.read_text())

pkg_name = pkg_json.get("name")
pkg_version = pkg_json.get("version")

if pkg_version is None:
session.log(f"Skipping - {pkg_name} has no name or version in package.json")
if pkg_version is None or pkg_name is None:
session.log(f"Skipping - {pkg_name} has no name/version in package.json")
continue

if pkg_name is None:
session.error(f"Package {pkg} has no name in package.json")

if pkg_name in packages:
session.error(f"Duplicate package name {pkg_name}")

Expand All @@ -417,7 +452,7 @@ class PackageInfo(NamedTuple):
version: str


def get_current_tags(session: Session) -> list[TagInfo]:
def get_current_tags(session: Session) -> list[str]:
"""Get tags for the current commit"""
# check if unstaged changes
try:
Expand Down Expand Up @@ -465,24 +500,16 @@ def get_current_tags(session: Session) -> list[TagInfo]:
if not tags:
session.error("No tags found for current commit")

parsed_tags: list[TagInfo] = []
for tag in tags:
match = TAG_PATTERN.match(tag)
if not match:
session.error(
f"Invalid tag {tag} - must be of the form <package>-<language>-<version>"
)
parsed_tags.append(
TagInfo(
tag,
match["name"], # type: ignore[index]
match["version"], # type: ignore[index]
)
)
session.log(f"Found tags: {tags}")

return tags

session.log(f"Found tags: {[info.tag for info in parsed_tags]}")

return parsed_tags
def parse_tag(tag: str) -> TagInfo | None:
match = TAG_PATTERN.match(tag)
if not match:
return None
return TagInfo(tag, match["name"], match["version"])


class TagInfo(NamedTuple):
Expand All @@ -506,5 +533,4 @@ def get_reactpy_package_version(session: Session) -> str: # type: ignore[return
# remove the quotes
[1:-1]
)
else:
session.error(f"No version found in {pkg_root_init_file}")
session.error(f"No version found in {pkg_root_init_file}")
2 changes: 2 additions & 0 deletions src/client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tsconfig.tsbuildinfo
packages/**/package-lock.json
Loading