Skip to content

Migrate from Angular 9 to React 18 + TypeScript + Vite#243

Open
devin-ai-integration[bot] wants to merge 3 commits into
masterfrom
devin/1778600163-migrate-to-react
Open

Migrate from Angular 9 to React 18 + TypeScript + Vite#243
devin-ai-integration[bot] wants to merge 3 commits into
masterfrom
devin/1778600163-migrate-to-react

Conversation

@devin-ai-integration
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot commented May 12, 2026

Summary

Full migration of this Hacker News PWA from Angular 9 + RxJS + Angular CLI to React 18 + TypeScript + Vite, preserving every feature, the SCSS theme system, and PWA capabilities.

What changed

  • Build tool: Angular CLI / Webpack → Vite 5 + @vitejs/plugin-react
  • Framework: Angular components/services → React 18 function components + hooks
  • Routing: @angular/routerreact-router-dom v6 (BrowserRouter, Routes, useParams, useNavigate, useLocation)
  • State: SettingsService (Angular DI) → SettingsContext (React Context) — same localStorage keys, same prefers-color-scheme listener
  • Data fetching: RxJS Observable<T>plain async/await using unfetch; poll items fetched with Promise.all
  • Pipes / utils: CommentPipeformatComment util function
  • Lazy loading: loadChildren / Angular module lazy routes → React.lazy + Suspense for ItemDetails and User pages
  • PWA / SW: @angular/service-worker + ngsw-config.jsonvite-plugin-pwa (Workbox) with registerType: 'autoUpdate' and a NetworkFirst runtime cache for the HN API
  • GA: router.events subscription → useLocation() + useEffect to fire ga('set', 'page', …) / ga('send', 'pageview') on each navigation
  • SCSS: theme system (_themes.scss, _theme_variables.scss, _media.scss) preserved unchanged; only :host >>> (Angular-specific) selectors rewritten to plain class selectors

File layout (new)

  • index.html (root, Vite convention) — same meta tags, theme-color, GA snippet, with <div id="root">
  • vite.config.ts — React + PWA plugins, sass deprecation silencing
  • src/
    • main.tsx — React 18 createRoot
    • App.tsxBrowserRouter + SettingsProvider + theme class wrapper + Suspense
    • types/story.ts, comment.ts, user.ts, settings.ts, poll-result.ts, feed-type.ts, unfetch.d.ts
    • services/hackernews-api.ts
    • contexts/SettingsContext.tsx
    • utils/formatComment.ts
    • components/Header.tsx, Footer.tsx, Settings.tsx, Loader.tsx, ErrorMessage.tsx, Item.tsx, Comment.tsx (+ component scss alongside)
    • pages/Feed.tsx, ItemDetails.tsx (lazy), User.tsx (lazy)
    • scss/_themes.scss, _theme_variables.scss, _media.scss (unchanged)
  • public/assets/, public/favicon.ico, public/manifest.json (preserved)
  • vercel.json — SPA rewrite to index.html for client-side routing

Removed

angular.json, tsconfig.app.json, tsconfig.spec.json, tslint.json, karma.conf.js, protractor.conf.js, ngsw-config.json, the entire src/app/, src/main.ts, src/polyfills.ts, src/test.ts, plus all @angular/*, rxjs, zone.js, codelyzer, karma*, protractor, tslint, etc. from package.json.

Local verification

  • npm run build succeeds (tsc --noEmit + Vite build, PWA SW generated).
  • npm run dev renders the news feed at /news/1, item detail pages (with recursive comment trees), user profile pages, and the settings overlay. External story links open correctly; internal Ask HN-style items deep-link via /item/:id.

Review & Testing Checklist for Human

This is a large, risky change — full framework swap — so please run through:

  • Pull the branch, npm install, then npm run dev and visit /news/1, /newest/1, /show/1, /ask/1, /jobs/1 — verify pagination (Prev / More) and that the job header text only appears on /jobs/*.
  • Open an item with a poll (e.g. one of the YC Ask HN: Who is hiring? polls) and confirm the per-option pollBar width matches points / poll_votes_count * 100%.
  • Open an item, expand/collapse a deeply nested comment subtree, navigate to a /user/:id page from a comment, then press the back button — verify useNavigate(-1) returns to the item.
  • In settings: toggle "Open links in new tab", switch all three themes (default / night / amoledblack), change font size and list spacing, then hard-refresh — verify all four persist via localStorage (same keys as Angular version: openLinkInNewTab, theme, titleFontSize, listSpacing).
  • In an incognito window with no saved theme, set OS-level dark mode preference and load the app — verify it boots in night theme, then toggle OS dark mode while the app is open and verify the theme follows.
  • Run npm run build && npm run preview, open DevTools Network tab, navigate to /item/:id and /user/:id for the first time — verify separate lazy chunks load (ItemDetails-*.js and User-*.js).
  • Run npm run build && npm run preview, install the PWA, then go offline and reload a page that was visited online — verify the app shell renders and previously-fetched API responses are served from the Workbox hn-api-cache.

Notes

  • unfetch was kept (rather than switching to native fetch) to mirror the Angular service's dependency choice. Pinned to ^4.2.0 because 5.0.0 has a broken exports field in its package.json that Vite/Rollup cannot resolve.
  • The original SCSS uses the legacy / division operator and the darken() color function which are deprecated in modern Sass. Rather than touching the theme files (the brief says "Keep the existing SCSS theme system as-is"), the corresponding deprecations are silenced in vite.config.ts via css.preprocessorOptions.scss.silenceDeprecations.
  • The Angular component scss files used :host >>> (deep selectors) — these don't translate cleanly to React. Replaced them with descendant class selectors in comment.component.scss (.comment-text a) and user.component.scss (.other-details pre) which give equivalent scoping in practice.
  • The GA tracking ID (UA-66348622-3) is preserved from the original index.html. Update if a new property is desired.
  • vercel.json adds a single rewrite so that direct loads of e.g. /item/123 are served by index.html instead of returning 404.

Link to Devin session: https://app.devin.ai/sessions/4cde8dc6643742a1a1e90fa77ad38230
Requested by: @charityquinn-cognition


Open in Devin Review

- Replace Angular CLI / Webpack with Vite + @vitejs/plugin-react
- Replace RxJS Observables with async/await fetch
- Replace Angular services with React Context (SettingsContext)
- Replace router with react-router-dom v6 (BrowserRouter, lazy routes)
- Replace Angular Service Worker with vite-plugin-pwa (Workbox)
- Port all components, types, scss to React structure
- Preserve SCSS theme system and PWA capabilities

Co-Authored-By: Charity Quinn <charity.quinn@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

- SettingsContext: don't persist system-derived theme to localStorage so
  the prefers-color-scheme listener continues to fire on subsequent
  changes (was self-disabling after the first OS dark-mode flip).
- App: add id="content" to the main wrapper div so the skip-navigation
  link in index.html points at a real element again (target was removed
  along with the old Angular app-loader).

Co-Authored-By: Charity Quinn <charity.quinn@cognition.ai>
devin-ai-integration[bot]

This comment was marked as resolved.

Without the ok-check, a non-2xx poll sub-response would be parsed as a
PollResult with undefined fields, causing poll_votes_count to become NaN
and poll bars to render at 0% width.

Co-Authored-By: Charity Quinn <charity.quinn@cognition.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant