From f3bf00ed4504ccf8aedc1ce8b1f7db8e067137e6 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 18 Apr 2022 22:19:15 +0800 Subject: [PATCH 01/52] feat: init migration to nuxt3 --- .eslintrc.js | 2 +- .npmrc | 1 + assets/logo.svg | 8 - components/Comment.vue | 39 +- components/Item.vue | 36 +- components/ItemListNav.vue | 43 +- components/LazyWrapper.ts | 20 + components/LazyWrapper.vue | 20 - components/Spinner.vue | 24 +- common/api.js => composables/api.ts | 6 +- composables/store.ts | 82 + common/utils.js => composables/utils.ts | 8 +- layouts/default.vue | 44 +- nuxt.config.js | 62 - nuxt.config.ts | 74 + package.json | 38 +- pages/[feed]/[page].vue | 151 + pages/[feed]/index.vue | 7 + pages/_feed/_page.vue | 160 - pages/index.vue | 8 +- pages/item/{_id.vue => [id].vue} | 60 +- pages/user/{_id.vue => [id].vue} | 45 +- plugins/{filters.js => filters.ts} | 35 +- plugins/swr.js | 26 - plugins/swr.ts | 29 + pnpm-lock.yaml | 6690 +++++++++++++++++ {static => public}/_redirects | 0 public/icon.png | Bin 0 -> 3575 bytes public/logo.svg | 4 + {static => public}/robots.txt | 0 public/sw.js | 17 + static/favicon.ico | Bin 1393 -> 0 bytes static/icon.png | Bin 43916 -> 0 bytes store/index.js | 101 - tsconfig.json | 3 + yarn.lock | 9001 ----------------------- 36 files changed, 7247 insertions(+), 9597 deletions(-) create mode 100644 .npmrc delete mode 100644 assets/logo.svg create mode 100644 components/LazyWrapper.ts delete mode 100644 components/LazyWrapper.vue rename common/api.js => composables/api.ts (57%) create mode 100644 composables/store.ts rename common/utils.js => composables/utils.ts (78%) delete mode 100755 nuxt.config.js create mode 100755 nuxt.config.ts create mode 100644 pages/[feed]/[page].vue create mode 100644 pages/[feed]/index.vue delete mode 100644 pages/_feed/_page.vue rename pages/item/{_id.vue => [id].vue} (74%) rename pages/user/{_id.vue => [id].vue} (71%) rename plugins/{filters.js => filters.ts} (54%) delete mode 100644 plugins/swr.js create mode 100644 plugins/swr.ts create mode 100644 pnpm-lock.yaml rename {static => public}/_redirects (100%) create mode 100644 public/icon.png create mode 100644 public/logo.svg rename {static => public}/robots.txt (100%) create mode 100644 public/sw.js delete mode 100644 static/favicon.ico delete mode 100644 static/icon.png delete mode 100644 store/index.js create mode 100644 tsconfig.json delete mode 100644 yarn.lock diff --git a/.eslintrc.js b/.eslintrc.js index dfbfb8fb..818fc945 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { extends: [ - '@nuxtjs' + '@antfu' ] } diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..bf2e7648 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +shamefully-hoist=true diff --git a/assets/logo.svg b/assets/logo.svg deleted file mode 100644 index d832fcbf..00000000 --- a/assets/logo.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/components/Comment.vue b/components/Comment.vue index 767237f5..428a14b0 100644 --- a/components/Comment.vue +++ b/components/Comment.vue @@ -1,10 +1,27 @@ + + - - diff --git a/pages/[feed]/index.vue b/pages/[feed]/index.vue new file mode 100644 index 00000000..769dd5aa --- /dev/null +++ b/pages/[feed]/index.vue @@ -0,0 +1,7 @@ + + + diff --git a/pages/_feed/_page.vue b/pages/_feed/_page.vue deleted file mode 100644 index c829d724..00000000 --- a/pages/_feed/_page.vue +++ /dev/null @@ -1,160 +0,0 @@ - - - - - diff --git a/pages/index.vue b/pages/index.vue index 13050dfd..5ae86fa5 100755 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,9 +1,9 @@ diff --git a/pages/item/_id.vue b/pages/item/[id].vue similarity index 74% rename from pages/item/_id.vue rename to pages/item/[id].vue index cf05d437..a62f2e0b 100755 --- a/pages/item/_id.vue +++ b/pages/item/[id].vue @@ -1,9 +1,29 @@ + + - diff --git a/pages/[feed]/[page].vue b/pages/[feed]/[page].vue index 5e9d3b0e..a9f69cc0 100644 --- a/pages/[feed]/[page].vue +++ b/pages/[feed]/[page].vue @@ -77,9 +77,9 @@ watch(() => page, (to, old) => pageChanged(to, old)) + - From dd1362365e6b351e2be19bb9f8ef9624940a8ab3 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 9 May 2022 18:34:13 +0800 Subject: [PATCH 42/52] chore: update --- pages/[feed]/[page].vue | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pages/[feed]/[page].vue b/pages/[feed]/[page].vue index a9f69cc0..8b24da1b 100644 --- a/pages/[feed]/[page].vue +++ b/pages/[feed]/[page].vue @@ -71,16 +71,15 @@ watch(() => page, (to, old) => pageChanged(to, old))
- - -
-
    - -
- -
-
-
+ +
+ +
    + +
+ +
+
From 411e5ce3ec79352be6bcf3b487f403ea4dc20b81 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 9 May 2022 19:06:32 +0800 Subject: [PATCH 43/52] chore: fix lazyload on server side --- composables/utils.ts | 2 +- pages/[feed]/[page].vue | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/composables/utils.ts b/composables/utils.ts index 27efb56b..c7d64f6d 100644 --- a/composables/utils.ts +++ b/composables/utils.ts @@ -1,4 +1,4 @@ -export function lazyLoad (commit, task, optimistic, enabled = false) { +export function lazyLoad (commit, task, optimistic, enabled?: boolean) { // By default, do lazy operations only in client if (enabled === undefined) { enabled = process.client diff --git a/pages/[feed]/[page].vue b/pages/[feed]/[page].vue index 8b24da1b..4a82360d 100644 --- a/pages/[feed]/[page].vue +++ b/pages/[feed]/[page].vue @@ -71,13 +71,15 @@ watch(() => page, (to, old) => pageChanged(to, old))
- +
-
    - -
- +
@@ -87,9 +89,9 @@ watch(() => page, (to, old) => pageChanged(to, old)) .news-list { background-color: #fff; border-radius: 2px; -} - -.news-list { + position: absolute; + top: 40px; + left: 0; margin: 10px 0; width: 100%; transition: all 0.3s cubic-bezier(0.55, 0, 0.1, 1); From 6de3e05b0ab456ca89caca109a8930724532d3f5 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 9 May 2022 19:30:59 +0800 Subject: [PATCH 44/52] chore: remove transition --- components/ItemListNav.vue | 1 + layouts/default.vue | 6 +----- pages/[feed]/[page].vue | 20 +++++++++----------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/components/ItemListNav.vue b/components/ItemListNav.vue index 40ac9b46..c546782d 100644 --- a/components/ItemListNav.vue +++ b/components/ItemListNav.vue @@ -32,6 +32,7 @@ const hasMore = $computed(() => props.page < props.maxPage) padding: 15px 30px; text-align: center; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + user-select: none; a { margin: 0 1em; diff --git a/layouts/default.vue b/layouts/default.vue index 1ec444d5..e191d060 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -114,11 +114,7 @@ a { transition: opacity 0.4s ease; } -.page-enter-active, .page-leave-active { - transition: all 0.2s ease; -} - -.appear, .page-enter, .page-leave-active { +.appear { opacity: 0; } diff --git a/pages/[feed]/[page].vue b/pages/[feed]/[page].vue index 4a82360d..80106f10 100644 --- a/pages/[feed]/[page].vue +++ b/pages/[feed]/[page].vue @@ -71,17 +71,15 @@ watch(() => page, (to, old) => pageChanged(to, old))
- -
- - -
-
+
+ + +
From a5fa7e3ca4eff81c101c38e62da37f3428a01603 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Tue, 10 May 2022 07:33:20 +0800 Subject: [PATCH 45/52] feat: rework client fetch --- composables/store.ts | 122 +++++++++++++++++++++++++++++++--------- composables/utils.ts | 24 +------- package.json | 2 +- pages/[feed]/[page].vue | 37 +++++------- pages/item/[id].vue | 17 +++--- pages/user/[id].vue | 10 ++-- pnpm-lock.yaml | 92 +++++++++++++++++------------- server/api/hn/item.ts | 19 +++++-- server/api/hn/user.ts | 10 ++-- types.ts | 2 + 10 files changed, 201 insertions(+), 134 deletions(-) diff --git a/composables/store.ts b/composables/store.ts index e002993c..89e9cff7 100644 --- a/composables/store.ts +++ b/composables/store.ts @@ -2,6 +2,7 @@ import { Item, User } from '~/types' export interface StoreState { items: Record + comments: Record users: Record feeds: Record> } @@ -9,31 +10,42 @@ export interface StoreState { export const useStore = () => useState('store', () => ({ items: {}, users: {}, + comments: {}, feeds: Object.fromEntries(validFeeds.map(i => [i, {}])) })) -export function fetchFeed ({ feed, page, prefetch }: { feed: string; page: number; prefetch?: boolean }) { - const state = $(useStore()) - - // Don't prioritize already fetched feeds - if (state.feeds[feed][page] && state.feeds[feed][page].length) { - prefetch = true +interface FeedQuery { + feed: string; + page: number } - if (!prefetch) { - // if (state.feedCancelSource) { - // state.feedCancelSource.cancel( - // `prioritize feed: ${feed} page: ${page}`, - // ) - // } +export function getFeed (state:StoreState, { feed, page }: FeedQuery) { + const ids = state.feeds?.[feed]?.[page] + if (ids?.length) { + return ids.map(i => state.items[i]) } - return lazyLoad( + return undefined +} + +export function fetchFeed (query: FeedQuery) { + const state = $(useStore()) + + const { feed, page } = query + + return reactiveLoad( + () => getFeed(state, query), (items) => { const ids = items.map(item => item.id) state.feeds[feed][page] = ids items .filter(Boolean) - .forEach((item) => { state.items[item.id] = item }) + .forEach((item) => { + if (state.items[item.id]) { + Object.assign(state.items[item.id], item) + } else { + state.items[item.id] = item + } + }) }, () => $fetch('/api/hn/feeds', { params: { feed, page } }), (state.feeds[feed][page] || []).map(id => state.items[id]) @@ -43,23 +55,81 @@ export function fetchFeed ({ feed, page, prefetch }: { feed: string; page: numbe export function fetchItem (id: string) { const state = $(useStore()) - return lazyLoad( - (item) => { - if (item) { state.items[item.id] = item } - }, - () => $fetch('/api/hn/item', { params: { id } }), - Object.assign({ id, loading: true, comments: [] }, state.items[id]) + return reactiveLoad( + () => state.items[id], + (item) => { state.items[id] = item }, + () => $fetch('/api/hn/item', { params: { id } }) + ) +} + +export function fetchComments (id: string) { + const state = $(useStore()) + + return reactiveLoad( + () => state.comments[id], + (comments) => { state.comments[id] = comments }, + () => $fetch('/api/hn/item', { params: { id } }).then(i => i.comments) ) } export function fetchUser (id: string) { const state = $(useStore()) - return lazyLoad( - (user) => { - state.users[id] = user || false - }, - () => $fetch('/api/hn/user', { params: { id } }), - Object.assign({ id, loading: true }, state.users[id]) + return reactiveLoad( + () => state.users[id], + (user) => { state.users[id] = user }, + () => $fetch('/api/hn/user', { params: { id } }) ) } + +/** + * Create reactive state for SWR + * + * On server side the data will be fetched eagerly + */ +export async function reactiveLoad ( + get: () => T | undefined, + set: (data: T) => void, + fetch: ()=> Promise, + init?: T +) { + const data = computed({ + get, + set + }) + const loading = ref(false) + + if (data.value == null) { + if (init != null) { + data.value = init + } + + const task = async () => { + try { + loading.value = true + const fetched = await fetch() + if (data.value != null) { + data.value = Object.assign(data.value, fetched) + } else { + data.value = fetched + } + } catch (e) { + console.error(e) + data.value = undefined + } finally { + loading.value = false + } + } + + if (process.client) { + task() + } else { + await task() + } + } + + return reactive({ + loading, + data + }) +} diff --git a/composables/utils.ts b/composables/utils.ts index c7d64f6d..5483e110 100644 --- a/composables/utils.ts +++ b/composables/utils.ts @@ -1,31 +1,11 @@ -export function lazyLoad (commit, task, optimistic, enabled?: boolean) { - // By default, do lazy operations only in client - if (enabled === undefined) { - enabled = process.client - } - - // Non lazy mode - if (!enabled) { - return task().then(commit).catch(console.error) - } - - // Do real task in background - Promise.resolve(task(optimistic)) - .then(commit) - .catch(console.error) - - // Commit optimistic value and resolve - return Promise.resolve(commit(optimistic)) -} - -export function host (url) { +export function host (url: string) { const host = url.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace('?id=', '/') const parts = host.split('.').slice(-3) if (parts[0] === 'www') { parts.shift() } return parts.join('.') } -export function timeAgo (time) { +export function timeAgo (time: number | Date) { const between = Date.now() / 1000 - Number(time) if (between < 3600) { return pluralize(~~(between / 60), ' minute') } else if (between < 86400) { return pluralize(~~(between / 3600), ' hour') } else { return pluralize(~~(between / 86400), ' day') } } diff --git a/package.json b/package.json index f58c5398..b15c6cf1 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@nuxtjs/eslint-config-typescript": "^10.0.0", "@nuxtjs/pwa": "3.3.5", "eslint": "^8.15.0", - "nuxt": "3.0.0-rc.2", + "nuxt": "npm:nuxt3@latest", "postcss-nested": "^5.0.6", "typescript": "^4.6.4" } diff --git a/pages/[feed]/[page].vue b/pages/[feed]/[page].vue index 80106f10..c6e37b11 100644 --- a/pages/[feed]/[page].vue +++ b/pages/[feed]/[page].vue @@ -11,32 +11,24 @@ const page = $computed(() => +route.params.page || 1) const feed = $computed(() => route.params.feed as string) const isValidFeed = $computed(() => !!feedsInfo[feed]) -let transition = $ref('slide-right') -const pageNo = $computed(() => { - return Number(page) || 1 -}) -const store = $(useStore()) +// const transition = $ref('slide-right') +const pageNo = $computed(() => Number(page) || 1) const displayedPage = ref(pageNo) useHead({ title: feedsInfo[feed]?.title }) +const state = $(useStore()) + if (isValidFeed) { await fetchFeed({ page: pageNo, feed }) } +const items = $computed(() => getFeed(state, { page: pageNo, feed }) || []) +const loading = $computed(() => items.length === 0) const maxPage = $computed(() => { - return feedsInfo[feed]?.pages -}) -const pageData = $computed(() => { - return store.feeds[feed][pageNo] || [] -}) -const displayedItems = $computed(() => { - return pageData.map(id => store.items[id]) -}) -const loading = $computed(() => { - return displayedItems.length === 0 + return +(feedsInfo[feed]?.pages) || 0 }) function pageChanged (to: number, from = -1) { @@ -50,15 +42,14 @@ function pageChanged (to: number, from = -1) { // Prefetch next page fetchFeed({ feed, - page: page + 1, - prefetch: true + page: page + 1 }).catch(() => {}) - transition = from === -1 - ? null - : to > from - ? 'slide-left' - : 'slide-right' + // transition = from === -1 + // ? null + // : to > from + // ? 'slide-left' + // : 'slide-right' displayedPage.value = to } @@ -75,7 +66,7 @@ watch(() => page, (to, old) => pageChanged(to, old)) diff --git a/pages/item/[id].vue b/pages/item/[id].vue index be0f9a56..df5f90e9 100755 --- a/pages/item/[id].vue +++ b/pages/item/[id].vue @@ -2,19 +2,20 @@ import { host, timeAgo, isAbsolute } from '~/composables/utils' const route = useRoute() -const store = $(useStore()) const id = $computed(() => route.params.id as string) -const item = $computed(() => store.items[id]) + +const resultItem = await fetchItem(id) +const resultComments = await fetchComments(id) +const { data: item } = $(resultItem) +const { data: comments, loading: commentsLoading } = $(resultComments) useHead({ title: item?.title }) - -fetchItem(id)