Skip to content

Commit

Permalink
Reduce bundlesize by lazy loading framer-motion (#9)
Browse files Browse the repository at this point in the history
Reduce bundlesize by lazy loading framer-motion
  • Loading branch information
kubk authored Feb 4, 2024
1 parent 049c465 commit 3aae698
Show file tree
Hide file tree
Showing 18 changed files with 408 additions and 76 deletions.
286 changes: 286 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"prod:api:logs": "npx wrangler pages deployment tail",
"prod:deploy": "npx wrangler pages publish functions --project-name=memo-card",
"dev:botfather:description": "export $(cat .dev.vars) && node botfather/update.js",
"bundle-visualize": "npx vite-bundle-visualizer",
"export-public-code": "[[ $(git symbolic-ref --short HEAD 2>/dev/null) == \"main\" ]] && npm run __export-public-code || echo \"Current branch is not 'main'\"",
"__export-public-code": "npx betterplace-gitexporter gitexporter.config.json && cd ../memocard-public && git checkout -b main && git remote add origin [email protected]:kubk/memo-card.git && git push",
"generate-db-types": "docker info > /dev/null 2>&1 && export $(cat .dev.vars | xargs) && npx supabase gen types typescript --db-url $DB_URL >./functions/db/databaseTypes.ts"
Expand Down Expand Up @@ -77,6 +78,7 @@
"prettier": "^3.0.3",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-bundle-visualizer": "^1.0.1",
"vitest": "^0.34.6",
"wrangler": "^3.10.1"
}
Expand Down
7 changes: 7 additions & 0 deletions src/lib/animations/tap-scale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const tapScale = {
":active": {
transform: "scale(0.98)",
transition: "transform 0.3s",
transformOrigin: "center center",
},
};
1 change: 1 addition & 0 deletions src/lib/framer-motion/features.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { domAnimation } from "framer-motion";
20 changes: 20 additions & 0 deletions src/lib/framer-motion/lazy-load-framer-motion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { LazyMotion } from "framer-motion";
import { ReactNode } from "react";

type Props = {
children: ReactNode;
};

const loadFeatures = () => {
return import("./features.ts").then((res) => res.domAnimation);
};

export const LazyLoadFramerMotion = (props: Props) => {
const { children } = props;

return (
<LazyMotion features={loadFeatures} strict>
{children}
</LazyMotion>
);
};
9 changes: 4 additions & 5 deletions src/screens/deck-form/card-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import React from "react";
import { reset } from "../../ui/reset.ts";
import { t } from "../../translations/t.ts";
import { Screen } from "../shared/screen.tsx";
import { motion } from "framer-motion";
import { whileTap } from "../../ui/animations.ts";
import { removeAllTags } from "../../lib/sanitize-html/remove-all-tags.ts";
import { tapScale } from "../../lib/animations/tap-scale.ts";

export const CardList = observer(() => {
const deckFormStore = useDeckFormStore();
Expand Down Expand Up @@ -85,8 +84,7 @@ export const CardList = observer(() => {
</>
)}
{deckFormStore.filteredCards.map((cardForm, i) => (
<motion.div
whileTap={whileTap}
<div
onClick={() => {
deckFormStore.editCardFormById(cardForm.id);
}}
Expand All @@ -99,13 +97,14 @@ export const CardList = observer(() => {
// If the card content is too big then hide it
maxHeight: 120,
overflow: 'hidden',
...tapScale,
})}
>
<div>{removeAllTags(cardForm.front.value)}</div>
<div className={css({ color: theme.hintColor })}>
{removeAllTags(cardForm.back.value)}
</div>
</motion.div>
</div>
))}
<Button
onClick={() => {
Expand Down
35 changes: 19 additions & 16 deletions src/screens/deck-review/deck-finished-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { ReactNode } from "react";
import { motion } from "framer-motion";
import { m } from "framer-motion";
import { css } from "@emotion/css";
import { theme } from "../../ui/theme.tsx";
import { LazyLoadFramerMotion } from "../../lib/framer-motion/lazy-load-framer-motion.tsx";

type Props = {
children: ReactNode;
Expand All @@ -25,20 +26,22 @@ export const DeckFinishedModal = (props: Props) => {
};

return (
<motion.div
className={css({
maxWidth: 290,
margin: "0 auto",
padding: 24,
background: theme.secondaryBgColor,
borderRadius: theme.borderRadius,
})}
initial={"hidden"}
animate={"visible"}
exit={"hidden"}
variants={modal}
>
{children}
</motion.div>
<LazyLoadFramerMotion>
<m.div
className={css({
maxWidth: 290,
margin: "0 auto",
padding: 24,
background: theme.secondaryBgColor,
borderRadius: theme.borderRadius,
})}
initial={"hidden"}
animate={"visible"}
exit={"hidden"}
variants={modal}
>
{children}
</m.div>
</LazyLoadFramerMotion>
);
};
39 changes: 21 additions & 18 deletions src/screens/deck-review/review-deck-name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,34 @@ import { useReviewStore } from "./store/review-store-context.tsx";
import { css } from "@emotion/css";
import { theme } from "../../ui/theme.tsx";
import React from "react";
import { AnimatePresence, motion } from "framer-motion";
import { m, AnimatePresence } from "framer-motion";
import { observer } from "mobx-react-lite";
import { LazyLoadFramerMotion } from "../../lib/framer-motion/lazy-load-framer-motion.tsx";

export const ReviewDeckName = observer(() => {
const reviewStore = useReviewStore();

const deckName = reviewStore.currentCard?.deckName;

return (
<AnimatePresence initial={false}>
<motion.div
key={deckName}
exit={{ y: 50, opacity: 0, position: "absolute" }}
animate={{ y: 0, opacity: 1 }}
initial={{ y: -50, opacity: 0 }}
className={css({
position: "absolute",
top: 41,
fontSize: 14,
whiteSpace: "nowrap",
color: theme.hintColor,
})}
>
{deckName}
</motion.div>
</AnimatePresence>
<LazyLoadFramerMotion>
<AnimatePresence initial={false}>
<m.div
key={deckName}
exit={{ y: 50, opacity: 0, position: "absolute" }}
animate={{ y: 0, opacity: 1 }}
initial={{ y: -50, opacity: 0 }}
className={css({
position: "absolute",
top: 41,
fontSize: 14,
whiteSpace: "nowrap",
color: theme.hintColor,
})}
>
{deckName}
</m.div>
</AnimatePresence>
</LazyLoadFramerMotion>
);
});
6 changes: 1 addition & 5 deletions src/screens/deck-review/store/card-under-review-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,9 @@ export class CardUnderReviewStore {
this.state = state;
}

get isSpeakingCardsEnabledSettings() {
return userStore.isSpeakingCardsEnabled;
}

speak() {
if (
!this.isSpeakingCardsEnabledSettings ||
!userStore.isSpeakingCardsEnabled ||
!this.deckSpeakLocale ||
!this.deckSpeakField
) {
Expand Down
3 changes: 2 additions & 1 deletion src/screens/shared/card/card-speaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { theme } from "../../../ui/theme.tsx";
import React from "react";
import { observer } from "mobx-react-lite";
import { LimitedCardUnderReviewStore } from "./card.tsx";
import { userStore } from "../../../store/user-store.ts";

type Props = {
card: LimitedCardUnderReviewStore;
Expand All @@ -17,7 +18,7 @@ export const CardSpeaker = observer((props: Props) => {
!isSpeechSynthesisSupported ||
!card.isOpened ||
type !== card.deckSpeakField ||
!card.isSpeakingCardsEnabledSettings
!userStore.isSpeakingCardsEnabled
) {
return null;
}
Expand Down
24 changes: 18 additions & 6 deletions src/screens/shared/card/card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { motion } from "framer-motion";
import { css } from "@emotion/css";
import { css, cx } from "@emotion/css";
import React from "react";
import { theme } from "../../../ui/theme.tsx";
import { observer } from "mobx-react-lite";
Expand All @@ -15,7 +14,6 @@ export type LimitedCardUnderReviewStore = Pick<
CardUnderReviewStore,
| "isOpened"
| "deckSpeakField"
| "isSpeakingCardsEnabledSettings"
| "speak"
| "front"
| "back"
Expand All @@ -33,7 +31,7 @@ type Props = {

export const Card = observer(({ card }: Props) => {
return (
<motion.div
<div
className={
card.answerType === "remember"
? css({
Expand All @@ -49,13 +47,27 @@ export const Card = observer(({ card }: Props) => {
display: "grid",
placeItems: "center center",
padding: 10,
background: theme.secondaryBgColorComputed,
background: theme.secondaryBgColor,
})
: css({
color: theme.textColor,
})
}
>
{false && (
<div
className={css({
position: "absolute",
top: 0,
right: 12,
transform: "scale(0.8)",
cursor: "pointer",
})}
onClick={() => {}}
>
<i className={cx("mdi mdi-dots-horizontal mdi-24px")} />
</div>
)}
<span
className={css({
textAlign: "center",
Expand Down Expand Up @@ -117,6 +129,6 @@ export const Card = observer(({ card }: Props) => {
</>
) : null}
</span>
</motion.div>
</div>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { observer } from "mobx-react-lite";
import { css } from "@emotion/css";
import { theme } from "../../../ui/theme.tsx";
import React from "react";
import { motion } from "framer-motion";
import { whileTap } from "../../../ui/animations.ts";
import { DeckCardDbTypeWithType } from "../../../store/deck-list-store.ts";
import { CardsToReviewCount } from "./cards-to-review-count.tsx";
import { tapScale } from "../../../lib/animations/tap-scale.ts";

type Props = {
item: {
Expand All @@ -20,8 +19,7 @@ export const DeckRowWithCardsToReview = observer((props: Props) => {
const { item, onClick } = props;

return (
<motion.div
whileTap={whileTap}
<div
onClick={onClick}
className={css({
display: "flex",
Expand All @@ -32,6 +30,7 @@ export const DeckRowWithCardsToReview = observer((props: Props) => {
borderRadius: theme.borderRadius,
padding: 12,
background: theme.secondaryBgColor,
...tapScale,
})}
>
<div
Expand Down Expand Up @@ -59,6 +58,6 @@ export const DeckRowWithCardsToReview = observer((props: Props) => {
color={theme.success}
/>
</div>
</motion.div>
</div>
);
});
1 change: 0 additions & 1 deletion src/screens/user-settings/store/user-settings-store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export class UserSettingsStore {
}

load() {
console.log("load", !!userStore.userInfo);
when(() => !!userStore.userInfo).then(
action(() => {
assert(userStore.userInfo);
Expand Down
4 changes: 4 additions & 0 deletions src/store/user-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class UserStore {
plans?: PlansForUser;
isCardFormattingOn = new BooleanToggle(false);
defaultCardType: CardAnswerType = "remember";
isSpeakingCardsMuted = new BooleanToggle(false);

constructor() {
makeAutoObservable(this, {}, { autoBind: true });
Expand Down Expand Up @@ -51,6 +52,9 @@ export class UserStore {
}

get isSpeakingCardsEnabled() {
if (this.isSpeakingCardsMuted.value) {
return false;
}
return this.user?.is_speaking_card_enabled ?? false;
}

Expand Down
6 changes: 3 additions & 3 deletions src/ui/chevron-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { motion } from "framer-motion";
import { m } from "framer-motion";
import React, { SVGProps } from "react";

type Direction = "top" | "bottom";
Expand All @@ -19,7 +19,7 @@ type Props = Pick<SVGProps<SVGSVGElement>, "onClick" | "className"> & {
export const ChevronIcon = (props: Props) => {
const { direction, ...restProps } = props;
return (
<motion.svg
<m.svg
width="16"
height="16"
viewBox="0 0 16 16"
Expand All @@ -38,6 +38,6 @@ export const ChevronIcon = (props: Props) => {
strokeLinecap="round"
strokeLinejoin="round"
/>
</motion.svg>
</m.svg>
);
};
Loading

0 comments on commit 3aae698

Please sign in to comment.