Skip to content

Hot restart preview support with Local Flutter daemon server #174

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 5 commits into from
Aug 11, 2022
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
12 changes: 12 additions & 0 deletions cli/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import dotenv from "dotenv";
import fs from "fs";
import { checkForUpdate } from "./update";
import { login, logout } from "./auth";
import { startFlutterDaemonServer } from "./flutter/daemon";

function loadenv(argv) {
const { cwd } = argv;
Expand Down Expand Up @@ -113,6 +114,17 @@ export default async function cli() {
},
[loadenv]
)
.command(
"flutter daemon start",
"Starts local flutter daemon server for grida services",
() => {},
() => {
startFlutterDaemonServer();
}
)
.option("port", {
requiresArg: false,
})
.option("figma-personal-access-token", {
description: "figma personal access token",
alias: ["fpat", "figma-pat"],
Expand Down
6 changes: 6 additions & 0 deletions cli/flutter/daemon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Daemon from "@flutter-daemon/server";

export function startFlutterDaemonServer() {
const server = new Daemon();
server.listen({});
}
Empty file added cli/flutter/index.ts
Empty file.
7 changes: 4 additions & 3 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "grida",
"version": "0.0.13",
"version": "0.0.14",
"private": false,
"license": "Apache-2.0",
"description": "grida CLI",
"homepage": "https://grida.co/cli",
"repository": "https://github.com/gridaco/code",
"dependencies": {
"@base-sdk-fp/auth": "^0.1.4",
"@flutter-daemon/server": "^0.0.1",
"dotenv": "^16.0.1",
"enquirer": "^2.3.6",
"glob": "^8.0.3",
Expand All @@ -18,7 +19,6 @@
"open": "^8.4.0",
"ora": "^5.4.0",
"semver": "^7.3.7",
"ts-node": "^10.9.1",
"which": "^2.0.2",
"yaml": "^2.1.1",
"yargs": "^17.2.1"
Expand All @@ -28,7 +28,7 @@
"dev": "ts-node index.ts",
"dev:watch": "ts-node-dev index.ts --watch",
"test": "jest",
"build": "ncc build index.ts -o dist -e keytar",
"build": "ncc build index.ts -o dist -e keytar -e glob -e dotenv",
"prepack": "yarn test && yarn clean && yarn build"
},
"devDependencies": {
Expand All @@ -40,6 +40,7 @@
"@vercel/ncc": "^0.34.0",
"jest": "^28.1.3",
"ts-jest": "^28.0.7",
"ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0",
"typescript": "^4.7.4"
},
Expand Down
12 changes: 12 additions & 0 deletions cli/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"

"@flutter-daemon/server@^0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@flutter-daemon/server/-/server-0.0.1.tgz#b01f2b2742490fe21c2de2a4a82fae409263131a"
integrity sha512-eGnGOzhLr5jtSek9ynyQmFO46lXrH/C9AS9vyO8h8mhn1lNUXk8qChnL3i0Z2R3iED4lMnOk8uqel6aif5/OQQ==
dependencies:
ws "^8.8.1"

"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
Expand Down Expand Up @@ -2881,6 +2888,11 @@ write-file-atomic@^4.0.1:
imurmurhash "^0.1.4"
signal-exit "^3.0.7"

ws@^8.8.1:
version "8.8.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0"
integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==

xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { useEffect, useState } from "react";
import Client, { FlutterProject } from "@flutter-daemon/client";

/**
* cached client
*/
let _clinet: Client;
function useClinet() {
const [client, setClient] = useState<Client>(_clinet);
useEffect(() => {
if (!_clinet) {
_clinet = new Client("ws://localhost:43070");
setClient(_clinet);
}
}, []);

return client;
}

/**
* cached project
*/
let _project: FlutterProject;
function useMainProject(initial?: string) {
const client = useClinet();
const [project, setProject] = useState<FlutterProject>(_project);
useEffect(() => {
if (!client) return;
if (!_project) {
client
.project("tmp", "tmp", {
"lib/main.dart": initial,
})
.then((project) => {
_project = project;
setProject(_project);
});
}
}, [client]);
return project;
}

/**
* A flutter render view uses `@flutter-daemon/client` with present `@flutter-daemon/server` connection.
* @returns
*/
export function SingleFileMainFlutterDaemonView({
src,
loading,
}: {
src: string;
loading?: JSX.Element;
}) {
const project = useMainProject(src);
const [booted, setBooted] = useState(false);
const [weblaunchUrl, setWeblaunchUrl] = useState<string>();

const [refreshKey, setRefreshKey] = useState<number>(0);

useEffect(() => {
if (project) {
project.webLaunchUrl().then((url) => {
setBooted(true);
setWeblaunchUrl(url);
});
}
}, [project]);

useEffect(() => {
console.log("src changed");
if (project) {
console.log("writing file...");
project
.writeFile("lib/main.dart", src, true)
.then(() => {
console.log("file written");
setRefreshKey(refreshKey + 1);
})
.catch(console.error);
// project.restart().then(() => {});
}
}, [src]);

if (booted) {
return (
<iframe
key={refreshKey}
style={{
width: "100%",
height: "100%",
border: "none",
}}
src={weblaunchUrl}
/>
);
} else {
return loading ?? <></>;
}
}
2 changes: 2 additions & 0 deletions editor-packages/editor-preview-flutter-daemon-view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { SingleFileMainFlutterDaemonView } from "./flutter-daemon-view";
export default SingleFileMainFlutterDaemonView;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@code-editor/flutter-daemon-view",
"version": "0.0.0",
"description": "Flutter render view uses @flutter-daemon/clinet to communicate with the Flutter engine.",
"dependencies": {
"@flutter-daemon/client": "^0.0.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"jsx": "react",
"esModuleInterop": true
}
}
18 changes: 17 additions & 1 deletion editor/components/app-runner/flutter-app-runner.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import React, { useEffect, useRef, useState } from "react";
import type { IScenePreviewDataFlutterPreview } from "core/states";
import FlutterDaemonView from "@code-editor/flutter-daemon-view";

const dartservices_html_src = "https://dartpad.dev/scripts/frame_dark.html";

export function VanillaFlutterRunner({
updatedAt,
widgetKey,
loader,
source,
}: IScenePreviewDataFlutterPreview) {
switch (loader) {
case "vanilla-flutter-template": {
return <DartpadServedHtmlIframe js={source} />;
return <DartpadServedHtmlIframe key={widgetKey.id} js={source} />;
}
case "flutter-daemon-view": {
// return <FlutterDaemonView key={widgetKey.id} src={source} />;
return (
<iframe
key={updatedAt.toString()}
style={{
width: "100%",
height: "100%",
border: "none",
}}
src={source}
/>
);
}
default: {
throw new Error(`Unsupported loader: ${loader}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function VanillaDedicatedPreviewRenderer({
/>
);
}
case "flutter-daemon-view":
case "vanilla-flutter-template": {
return (
<VanillaFlutterRunner
Expand Down
3 changes: 3 additions & 0 deletions editor/components/canvas/isolated-canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ export function IsolatedCanvas({
building = false,
onExit,
onFullscreen,
onReload,
}: {
defaultSize: { width: number; height: number };
children?: React.ReactNode;
building?: boolean;
onExit?: () => void;
onFullscreen?: () => void;
onReload?: () => void;
}) {
const _margin = 20;
const [canvasSizingRef, canvasBounds] = useMeasure();
Expand Down Expand Up @@ -140,6 +142,7 @@ export function IsolatedCanvas({
{onExit && (
<ActionButton onClick={onExit}>End Isolation</ActionButton>
)}
{onReload && <ActionButton onClick={onReload}>Reload</ActionButton>}
</Controls>
{/* <ScalingAreaStaticRoot> */}
<TransformContainer
Expand Down
4 changes: 2 additions & 2 deletions editor/core/states/editor-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export interface IScenePreviewData<T> {
initialSize: { width: number; height: number };
isBuilding: boolean;
meta: {
bundler: "vanilla" | "esbuild-wasm" | "dart-services";
bundler: "vanilla" | "esbuild-wasm" | "dart-services" | "flutter-daemon";
framework: FrameworkConfig["framework"];
reason: "fill-assets" | "initial" | "update";
};
Expand All @@ -86,7 +86,7 @@ export interface IScenePreviewDataVanillaPreview

export interface IScenePreviewDataFlutterPreview
extends IScenePreviewData<string> {
loader: "vanilla-flutter-template";
loader: "vanilla-flutter-template" | "flutter-daemon-view";
source: string;
}

Expand Down
5 changes: 5 additions & 0 deletions editor/scaffolds/canvas/isolate-mode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function IsolateModeCanvas({
onEnterFullscreen: () => void;
}) {
const [state] = useEditorState();
const [renderkey, setRenderkey] = useState(0);

const {
fallbackSource,
Expand All @@ -35,6 +36,9 @@ export function IsolateModeCanvas({
building={isBuilding}
onExit={onClose}
onFullscreen={onEnterFullscreen}
onReload={() => {
setRenderkey(renderkey + 1);
}}
defaultSize={{
width: initialSize?.width ?? 375,
height: initialSize?.height ?? 812,
Expand All @@ -43,6 +47,7 @@ export function IsolateModeCanvas({
<>
{source ? (
<VanillaDedicatedPreviewRenderer
key={renderkey + ""}
{...state.currentPreview}
enableIspector
/>
Expand Down
Loading