Skip to content
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

Live React app execution with ESBuild #193

Merged
merged 14 commits into from
Apr 8, 2022
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
- [Flutter] Poligon Node support with XImage (svg)
- [Lint] Primal naming & grouping linting for better code export quality. this is tracked sperately on [lint](https://github.com/bridgedxyz/lint)

## [2022.4.0.1] - 2022-04-08

- D2C: CSS Duplication reduction
- UI: Prettier & Dart formatter added
- Preview: React executable live scripting feature added

## [2022.3.4] - 2022-03-30

- fix incomplete autolayout flex mapping
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
"packages/design-to-code/packages/builder-*",
"packages/design-to-code/packages/support-*",
"packages/design-to-code/packages/reflect-detection",
"packages/design-to-code/externals/coli/packages/*"
"packages/design-to-code/externals/coli/packages/*",
"packages/design-to-code/editor-packages/editor-services-esbuild",
"packages/design-to-code/editor-packages/editor-services-prettier",
"packages/design-to-code/editor-packages/editor-services-jsx-syntax-highlight",
"packages/design-to-code/editor-packages/editor-services-webworker-core"
]
},
"repository": "https://github.com/gridaco/assistant",
Expand All @@ -39,7 +43,7 @@
"sketch": "yarn workspace sketch run render",
"web": "yarn workspace web run dev",
"xd": "yarn workspace xd run build",
"test": "cd figma-native && yarn build",
"test": "cd figma && yarn build",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
Expand Down
19 changes: 19 additions & 0 deletions packages/_utils/debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function debounce<T extends Function>(
func: T,
wait: number = 50,
immediate?: boolean
) {
var timeout;
return function () {
// @ts-ignore
var context = this,
args = arguments;
var later = function () {
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
5 changes: 5 additions & 0 deletions packages/_utils/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@app/utils",
"version": "0.0.0",
"private": false
}
3 changes: 2 additions & 1 deletion packages/app-design-to-code/__plugin/design-to-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
react_presets,
flutter_presets,
vanilla_presets,
preview_presets,
} from "@grida/builder-config-preset";

type O = output.ComponentOutput;
Expand Down Expand Up @@ -135,7 +136,7 @@ export async function designToFixedPreviewVanilla(
id: reflectDesign.id,
entry: reflectDesign,
},
framework: vanilla_presets.vanilla_default,
framework: preview_presets.default,
build_config: build_config,
asset_config: {
// the asset replacement on assistant is handled on ui thread.
Expand Down
17 changes: 13 additions & 4 deletions packages/app-design-to-code/__plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ async function _handle_code_gen_request(req: CodeGenRequest) {
};
const transportable_config = { type: transportable_config_map[mode] };
// host images
const transportableImageAssetRepository = await repo_assets.MainImageRepository.instance
.get("fill-later-assets")
.makeTransportable(transportable_config);
const transportableImageAssetRepository =
await repo_assets.MainImageRepository.instance
.get("fill-later-assets")
.makeTransportable(transportable_config);
figma.ui.postMessage({
type: EK_IMAGE_ASSET_REPOSITORY_MAP,
data: transportableImageAssetRepository,
Expand All @@ -87,6 +88,7 @@ async function _handle_code_gen_request(req: CodeGenRequest) {
case "vanilla": {
const vanillaBuild = await designToVanilla(rnode);
post_cb({
name: vanillaBuild.name,
code: vanillaBuild.code,
app: vanillaBuild.scaffold,
vanilla_preview_source: vanilla_preview_source,
Expand All @@ -96,6 +98,7 @@ async function _handle_code_gen_request(req: CodeGenRequest) {
case "react": {
const reactBuild = await designToReact(rnode);
post_cb({
name: reactBuild.name,
code: reactBuild.code,
app: reactBuild.scaffold,
vanilla_preview_source: vanilla_preview_source,
Expand All @@ -105,6 +108,7 @@ async function _handle_code_gen_request(req: CodeGenRequest) {
case "flutter": {
const flutterBuild = await designToFlutter(rnode, asset_export_job);
post_cb({
name: flutterBuild.name,
code: flutterBuild.code,
app: flutterBuild.scaffold,
vanilla_preview_source: vanilla_preview_source,
Expand All @@ -118,7 +122,12 @@ async function _handle_code_gen_request(req: CodeGenRequest) {
//#endregion
}

function post_cb(data: { code; app; vanilla_preview_source }) {
function post_cb(data: {
code;
app;
vanilla_preview_source: string;
name: string;
}) {
figma.ui.postMessage({
type: EK_GENERATED_CODE_PLAIN,
data: data,
Expand Down
75 changes: 66 additions & 9 deletions packages/app-design-to-code/code-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Resizable } from "re-resizable";
import { useScrollTriggeredAnimation } from "app/lib/components/motions";
import { useSetRecoilState } from "recoil";
import { hide_navigation } from "app/lib/main/global-state-atoms";
import bundler from "@code-editor/esbuild-services";
import { debounce } from "../_utils/debounce";

const resizeBarBase = 5;
const resizeBarVerPadding = 5;
Expand All @@ -22,10 +24,15 @@ const default_responsive_preview_height_for_code_screen = 300;
export function CodeScreen() {
const selection = useSingleSelection();

const [vanilla_preview_source, set_vanilla_preview_source] =
useState<string>();
const [isBuilding, setIsBuilding] = useState(true);
const [initialPreviewData, setInitialPreviewData] = useState<string>();
const [esbuildPreviewData, setEsbuildPreviewData] = useState<string>();
const [previewMode, setPreviewMode] = useState<"responsive" | "esbuild">(
"responsive"
);
const [source, setSource] = useState<string>();
const [app, setApp] = useState<string>();
const [name, setName] = useState<string>();
const [useroption, setUseroption] = useState<DesigntoCodeUserOptions>();

const onCopyClicked = (e) => {
Expand All @@ -40,15 +47,60 @@ export function CodeScreen() {
v: string,
r?: repo_assets.TransportableImageRepository
) => {
set_vanilla_preview_source(utils.inject_assets_source_to_vanilla(v, r));
setIsBuilding(false);
setInitialPreviewData(utils.inject_assets_source_to_vanilla(v, r));
};

// ------------------------
// ------ for esbuild -----

useEffect(() => {
// reset preview mode when switching framework
setPreviewMode("responsive");
}, [useroption?.framework]);

const onCodeChangeHandler = debounce((code) => {
handle_esbuild_preview_source(code);
}, 500);

const handle_esbuild_preview_source = (v: string) => {
if (!initialPreviewData) {
// the vanilla preview must be loaded first
return;
}
const transform = (s, n) => {
return `import React from 'react'; import ReactDOM from 'react-dom';
${s}
const App = () => <><${n}/></>
ReactDOM.render(<App />, document.querySelector('#root'));`;
};

if (useroption.framework == "react") {
setIsBuilding(true);
bundler(transform(v, name), "tsx")
.then((d) => {
if (d.err == null) {
if (d.code && d.code !== esbuildPreviewData) {
setEsbuildPreviewData(d.code);
setPreviewMode("esbuild");
}
}
})
.finally(() => {
setIsBuilding(false);
});
}
};
// ------------------------

// region scrolling handling -------------------
const code_scrolling_area_ref = useRef<HTMLDivElement>(null);
const set_hide_navigation_state = useSetRecoilState(hide_navigation);
const hide = useScrollTriggeredAnimation(code_scrolling_area_ref);
useEffect(() => {
set_hide_navigation_state(hide);
}, [hide]);
// ---------------------------------------------

const [previewHeight, setPreviewHeight] = useState<number>(
default_responsive_preview_height_for_code_screen
Expand Down Expand Up @@ -95,10 +147,13 @@ export function CodeScreen() {
maxHeight="75vh"
>
<Preview
key={vanilla_preview_source}
// auto
type="responsive"
data={vanilla_preview_source}
key={selection?.id}
type={previewMode}
data={
previewMode === "esbuild"
? esbuildPreviewData
: initialPreviewData
}
id={selection?.id}
origin_size={{
width: selection?.node?.width,
Expand Down Expand Up @@ -137,13 +192,15 @@ export function CodeScreen() {
<CodeViewWithControl
key={selection?.id}
targetid={selection?.id}
onGeneration={(app, src, vanilla_preview_source) => {
onGeneration={({ name, app, src, vanilla_preview_source }) => {
setName(name);
setApp(app);
setSource(src);
handle_vanilla_preview_source(vanilla_preview_source);
}}
onCodeChange={onCodeChangeHandler}
onAssetsLoad={(r) => {
handle_vanilla_preview_source(vanilla_preview_source, r);
handle_vanilla_preview_source(initialPreviewData, r);
}}
onUserOptionsChange={setUseroption}
/>
Expand Down
25 changes: 16 additions & 9 deletions packages/app-design-to-code/code-view-with-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function CodeViewWithControl({
targetid,
editor = "monaco",
onUserOptionsChange,
onCodeChange,
disabled,
onGeneration,
onAssetsLoad,
Expand All @@ -32,12 +33,14 @@ export function CodeViewWithControl({
}: {
targetid: string;
editor?: "monaco" | "prism";
onCodeChange?: (code: string) => void;
onUserOptionsChange?: (options: DesigntoCodeUserOptions) => void;
onGeneration?: (
app: string,
src: string,
vanilla_preview_source?: string
) => void;
onGeneration?: (d: {
name: string;
app: string;
src: string;
vanilla_preview_source?: string;
}) => void;
onAssetsLoad?: (r: repo_assets.TransportableImageRepository) => void;
customMessages?: string[];
automaticRemoteFormatting?: boolean;
Expand Down Expand Up @@ -103,17 +106,19 @@ export function CodeViewWithControl({
setUseroption(op);
};

const __onGeneration__cb = (app, src, vanilla_preview_source) => {
const __onGeneration__cb = (name, app, src, vanilla_preview_source) => {
cacheStore.setCache(src);
const _source = typeof src == "string" ? src : src?.raw;
onGeneration?.(app, _source, vanilla_preview_source);
onGeneration?.({ name, app, src: _source, vanilla_preview_source });
};

const handleSourceInput = ({
name,
app,
code,
vanilla_preview_source,
}: {
name: string;
app: string;
code: SourceInput;
vanilla_preview_source?: string;
Expand All @@ -122,7 +127,7 @@ export function CodeViewWithControl({
app,
useroption.language,
(s) => {
__onGeneration__cb(s, source, vanilla_preview_source);
__onGeneration__cb(name, s, source, vanilla_preview_source);
},
{
disable_remote_format: !automaticRemoteFormatting,
Expand All @@ -136,7 +141,7 @@ export function CodeViewWithControl({
useroption.language,
(s) => {
setSource(s);
__onGeneration__cb(app, s, vanilla_preview_source);
__onGeneration__cb(name, app, s, vanilla_preview_source);
},
{
disable_remote_format: !automaticRemoteFormatting,
Expand All @@ -150,6 +155,7 @@ export function CodeViewWithControl({
switch (msg.type) {
case EK_GENERATED_CODE_PLAIN:
handleSourceInput({
name: msg.data.name,
app: msg.data.app,
code: msg.data.code,
vanilla_preview_source: msg.data.vanilla_preview_source,
Expand Down Expand Up @@ -188,6 +194,7 @@ export function CodeViewWithControl({
<CodeBox
disabled={disabled}
editor={editor}
onChange={onCodeChange}
language={_src_view_language(useroption.framework)}
code={source}
/>
Expand Down
2 changes: 1 addition & 1 deletion packages/design-to-code
10 changes: 9 additions & 1 deletion packages/ui-code-box/codebox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function CodeBox({
code,
codeActions,
disabled,
onChange,
}: {
language: "dart" | "jsx" | "tsx" | "ts" | "js" | string;
editor?: "monaco" | "prism";
Expand All @@ -21,12 +22,19 @@ export function CodeBox({
code: SourceInput;
codeActions?: Array<JSX.Element>;
disabled?: true;
} & {
onChange?: (code: string) => void;
}) {
const raw = (code && (typeof code == "string" ? code : code.raw)) || "";

const Editor =
editor == "monaco" ? (
<MonacoEditor key={language} src={raw} language={language} />
<MonacoEditor
key={language}
src={raw}
language={language}
onChange={onChange}
/>
) : (
<PrismView src={raw} language={language} />
);
Expand Down
Loading