diff --git a/frontend/.babelrc b/frontend/.babelrc new file mode 100644 index 00000000..84cbe157 --- /dev/null +++ b/frontend/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["next/babel"], + "plugins": [] +} diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index a81b967b..3097ba63 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -51,6 +51,16 @@ }, "multilineDetection": "brackets" }], + "no-restricted-imports": [ + "error", { + "paths": [ + { + "name": "next/image", + "message": "Use next/future/image instead." + } + ] + } + ], "import/newline-after-import": "error", "import/no-duplicates": "error", "import/first": "error", @@ -72,7 +82,7 @@ "position": "after" }, { - "pattern": "next/*", + "pattern": "next/**", "group": "builtin", "position": "after" } @@ -96,6 +106,13 @@ "rules": { "import/exports-last": "off" } + }, + { + "files": ["next.config.js"], + "rules": { + "@typescript-eslint/no-var-requires": "off", + "import/order": "off" + } } ] } diff --git a/frontend/next.config.js b/frontend/next.config.js index af91c49e..53799799 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -26,7 +26,7 @@ const removeImports = require("next-remove-imports")({ }) const nextTranslate = require("next-translate") -module.exports = withPlausibleProxy({ +let app = withPlausibleProxy({ customDomain: "https://stats.decomp.me", })(nextTranslate(removeImports(withPWA({ async redirects() { @@ -84,3 +84,9 @@ module.exports = withPlausibleProxy({ swcMinify: false, experimental: {}, })))) + +if (process.env.ANALYZE == "true") { + app = require("@next/bundle-analyzer")(app) +} + +module.exports = app diff --git a/frontend/package.json b/frontend/package.json index 393eeb26..17aae28c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,8 @@ "build": "next build", "start": "next start --port 8080", "lint": "next lint && yarn stylelint 'src/**/*.css' 'src/**/*.scss'", - "postinstall": "next telemetry disable" + "postinstall": "next telemetry disable", + "analyze": "ANALYZE=true next build" }, "dependencies": { "@codemirror/basic-setup": "^0.20.0", @@ -21,7 +22,7 @@ "fast-myers-diff": "^3.0.1", "framer-motion": "^6.3.5", "is-dark-color": "^1.2.0", - "next": "^12.1.6", + "next": "^12.3.0", "next-plausible": "^3.1.4", "next-pwa": "^5.3.1", "next-translate": "^1.3.5", @@ -44,6 +45,7 @@ }, "devDependencies": { "@babel/core": "^7.17.8", + "@next/bundle-analyzer": "^12.3.0", "@next/eslint-plugin-next": "^12.1.6", "@svgr/webpack": "^6.2.1", "@types/diff": "^5.0.2", @@ -75,5 +77,8 @@ "stylelint-config-standard": "^28.0.0", "typescript": "^4.4.2", "webpack": "5" - } + }, + "browserslist": [ + "last 2 versions and not dead and >= 1% and supports async-functions" + ] } diff --git a/frontend/public/purplefrog-bg-180.png b/frontend/public/purplefrog-bg-180.png new file mode 100644 index 00000000..31d98b65 Binary files /dev/null and b/frontend/public/purplefrog-bg-180.png differ diff --git a/frontend/src/components/Footer.module.scss b/frontend/src/components/Footer.module.scss index 3c1a7d19..d6331edd 100644 --- a/frontend/src/components/Footer.module.scss +++ b/frontend/src/components/Footer.module.scss @@ -52,5 +52,5 @@ font-size: 0.7em; background: var(--g300); - color: var(--g600); + color: var(--g1000); } diff --git a/frontend/src/components/Footer.tsx b/frontend/src/components/Footer.tsx index 37dff693..4be28c9b 100644 --- a/frontend/src/components/Footer.tsx +++ b/frontend/src/components/Footer.tsx @@ -51,7 +51,7 @@ export default function Footer() {
- + {commitHash.slice(0, 7)} diff --git a/frontend/src/components/Nav/LoginState.tsx b/frontend/src/components/Nav/LoginState.tsx index b0f06794..d236603c 100644 --- a/frontend/src/components/Nav/LoginState.tsx +++ b/frontend/src/components/Nav/LoginState.tsx @@ -1,6 +1,6 @@ import { useState } from "react" -import Image from "next/image" +import Image from "next/future/image" import classNames from "classnames" import { useLayer } from "react-laag" @@ -42,9 +42,10 @@ export default function LoginState({ className }: { className?: string }) { Avatar {renderLayer(
diff --git a/frontend/src/components/Nav/Search.module.scss b/frontend/src/components/Nav/Search.module.scss index e20d1105..148e3f10 100644 --- a/frontend/src/components/Nav/Search.module.scss +++ b/frontend/src/components/Nav/Search.module.scss @@ -117,5 +117,5 @@ } .noResults { - color: var(--g500); + color: var(--g1000); } diff --git a/frontend/src/components/Nav/Search.tsx b/frontend/src/components/Nav/Search.tsx index 51d26845..f67852f4 100644 --- a/frontend/src/components/Nav/Search.tsx +++ b/frontend/src/components/Nav/Search.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react" -import Image from "next/image" +import Image from "next/future/image" import { useRouter } from "next/router" import { SearchIcon } from "@primer/octicons-react" diff --git a/frontend/src/components/ProjectIcon.tsx b/frontend/src/components/ProjectIcon.tsx index ba28b5ec..b8748386 100644 --- a/frontend/src/components/ProjectIcon.tsx +++ b/frontend/src/components/ProjectIcon.tsx @@ -1,4 +1,4 @@ -import Image from "next/image" +import Image from "next/future/image" import Link from "next/link" import classNames from "classnames" @@ -28,7 +28,7 @@ export default function ProjectIcon({ projectUrl, size, className }: Props) { return - {data.slug} + {data.slug} } diff --git a/frontend/src/components/ScratchList.module.scss b/frontend/src/components/ScratchList.module.scss index 2f9c181d..622e0b1a 100644 --- a/frontend/src/components/ScratchList.module.scss +++ b/frontend/src/components/ScratchList.module.scss @@ -78,14 +78,14 @@ } .owner { - color: var(--g900); + color: var(--g1200); } .metadata { display: flex; align-items: flex-end; - color: var(--g700); + color: var(--g900); > span { flex-grow: 1; @@ -121,6 +121,6 @@ } .metadata { - color: var(--g700); + color: var(--g1200); } } diff --git a/frontend/src/components/user/UserAvatar.tsx b/frontend/src/components/user/UserAvatar.tsx index 2d3b8456..18c5f45c 100644 --- a/frontend/src/components/user/UserAvatar.tsx +++ b/frontend/src/components/user/UserAvatar.tsx @@ -1,4 +1,4 @@ -import Image from "next/image" +import Image from "next/future/image" import classNames from "classnames" @@ -16,7 +16,7 @@ export default function UserAvatar({ user, className }: Props) { const userIsYou = api.useUserIsYou() return - {api.isAnonUser(user) ? : user.avatar_url && } + {api.isAnonUser(user) ? : user.avatar_url && } {!userIsYou(user) && user.is_online &&
} } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 34c87e8c..27e695a3 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -488,7 +488,11 @@ export function useCompilation(scratch: Scratch | null, autoRecompile = true, au setCompileRequestPromise(null) setIsCompilationOld(false) }).catch(error => { - setCompilation({ "errors": error.json?.detail, "diff_output": null }) + if (error instanceof ResponseError) { + setCompilation({ "errors": error.json?.detail, "diff_output": null }) + } else { + return Promise.reject(error) + } }) setCompileRequestPromise(promise) diff --git a/frontend/src/pages/[project].tsx b/frontend/src/pages/[project].tsx index 0bbebe3f..e158217f 100644 --- a/frontend/src/pages/[project].tsx +++ b/frontend/src/pages/[project].tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react" import { GetStaticPaths, GetStaticProps } from "next" -import Image from "next/image" +import Image from "next/future/image" import Link from "next/link" import { MarkGithubIcon, RepoPullIcon } from "@primer/octicons-react" diff --git a/frontend/src/pages/[project]/[function].tsx b/frontend/src/pages/[project]/[function].tsx index ca07ee11..9e655253 100644 --- a/frontend/src/pages/[project]/[function].tsx +++ b/frontend/src/pages/[project]/[function].tsx @@ -1,6 +1,6 @@ import { GetStaticPaths, GetStaticProps } from "next" -import Image from "next/image" +import Image from "next/future/image" import Link from "next/link" import { useRouter } from "next/router" diff --git a/frontend/src/pages/_document.tsx b/frontend/src/pages/_document.tsx index f0b8444e..de9f9cb7 100644 --- a/frontend/src/pages/_document.tsx +++ b/frontend/src/pages/_document.tsx @@ -10,7 +10,7 @@ export default class MyDocument extends Document { - +
diff --git a/frontend/src/pages/index.module.scss b/frontend/src/pages/index.module.scss index ccb9d4a2..4be1bfc3 100644 --- a/frontend/src/pages/index.module.scss +++ b/frontend/src/pages/index.module.scss @@ -44,6 +44,7 @@ .about { grid-area: about; + color: var(--g1200); background: var(--g300); h1 { @@ -58,7 +59,6 @@ p { padding-top: 4px; - color: var(--g1000); font-size: 0.9em; max-width: 40ch; } diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index c7c61a98..bb63794f 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,4 +1,4 @@ -import Image from "next/image" +import Image from "next/future/image" import Link from "next/link" import { ArrowRightIcon } from "@primer/octicons-react" diff --git a/frontend/src/pages/scratch/[slug].tsx b/frontend/src/pages/scratch/[slug].tsx index cdf1835c..e5baed23 100644 --- a/frontend/src/pages/scratch/[slug].tsx +++ b/frontend/src/pages/scratch/[slug].tsx @@ -2,8 +2,6 @@ import { Suspense, useState, useEffect } from "react" import { GetServerSideProps } from "next" -import Head from "next/head" - import useSWR from "swr" import LoadingSpinner from "../../components/loading.svg" @@ -70,7 +68,6 @@ export interface Props { export default function ScratchPage({ initialScratch, parentScratch, initialCompilation }: Props) { const [scratch, setScratch] = useState(initialScratch) - //const setScratch = useDebouncedCallback(setScratchImmediate, 100, { leading: true, trailing: true }) // reduce layout thrashing useWarnBeforeScratchUnload(scratch) @@ -91,31 +88,16 @@ export default function ScratchPage({ initialScratch, parentScratch, initialComp setScratch(scratch => ({ ...scratch, owner: cached.owner })) } - // Scratch uses suspense but SSR does not support it so we just render a loading state - // in server-side rendering mode. - const [isMounted, setIsMounted] = useState(false) + // Disable page scrolling useEffect(() => { - setIsMounted(true) - document.body.classList.add("no-scroll") return () => { document.body.classList.remove("no-scroll") } }, []) - if (!isMounted) { - return <> - -
- -
- - } return <> - - -
}> =0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -5579,10 +5628,10 @@ style-value-types@5.0.0: hey-listen "^1.0.8" tslib "^2.1.0" -styled-jsx@5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.2.tgz#ff230fd593b737e9e68b630a694d460425478729" - integrity sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ== +styled-jsx@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.6.tgz#fa684790a9cc3badded14badea163418fe568f77" + integrity sha512-xOeROtkK5MGMDimBQ3J6iPId8q0t/BDoG5XN6oKkZClVz9ISF/hihN8OCn2LggMU6N32aXnrXBdn3auSqNS9fA== stylehacks@^5.1.0: version "5.1.0" @@ -5823,6 +5872,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +totalist@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" + integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -5991,10 +6045,10 @@ use-persisted-state@^0.3.3: dependencies: "@use-it/event-listener" "^0.1.2" -use-sync-external-store@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82" - integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ== +use-sync-external-store@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" @@ -6039,6 +6093,21 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +webpack-bundle-analyzer@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz#2f3c0ca9041d5ee47fa418693cf56b4a518b578b" + integrity sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA== + dependencies: + acorn "^8.0.4" + acorn-walk "^8.0.0" + chalk "^4.1.0" + commander "^6.2.0" + gzip-size "^6.0.0" + lodash "^4.17.20" + opener "^1.5.2" + sirv "^1.0.7" + ws "^7.3.1" + webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" @@ -6303,6 +6372,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^7.3.1: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"