Skip to content

Commit

Permalink
Merge pull request #12 from hmerritt/feat/tanstack-router
Browse files Browse the repository at this point in the history
Feat / tanstack router
  • Loading branch information
hmerritt authored Jan 30, 2024
2 parents 73ad7fd + 284febc commit b18ba0f
Show file tree
Hide file tree
Showing 25 changed files with 505 additions and 144 deletions.
6 changes: 2 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ It should:
- hotfix : urgent bug fix
- feat : new or updated feature
- docs : documentation updates
- BREAKING : if commit is a breaking change
- refactor : code refactoring (no functional change)
- style : UX and display updates
- test : tests and CI updates
- chore : updates on build, tools, configuration ...
- test : tests updates
- chore : miscellaneous housekeeping updates
- Merge branch : when merging branch
- Merge pull request : when merging PR

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Template react app with batteries included 🔋
- [Vitest (testing for Vite)](https://vitest.dev/)
- [Typescript](https://www.typescriptlang.org)
- [Redux](https://redux.js.org)
- [React-Router](https://reactrouter.com)
- [TanStack Router](https://tanstack.com/router/latest)
- [Linaria (SASS-in-JS)](https://github.com/callstack/linaria)
- Custom (hackable) build script
- Custom utils and helper functions
Expand Down
20 changes: 19 additions & 1 deletion config/linaria-rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,29 @@ export default function wywInJS({

let { cssText, dependencies } = result;

if (!cssText) return;
// Heads up, there are three cases:
// 1. cssText is undefined, it means that file was not transformed
// 2. cssText is empty, it means that file was transformed, but it does not contain any styles
// 3. cssText is not empty, it means that file was transformed and it contains styles

if (typeof cssText === "undefined") {
return;
}

if (cssText === "") {
/* eslint-disable-next-line consistent-return */
return {
code: result.code,
map: result.sourceMap
};
}

dependencies ??= [];

const slug = slugify(cssText);

// @IMPORTANT: We *need* to use `.scss` extension here.
// This tiny change is the only difference between this file and using `https://github.com/Anber/wyw-in-js/blob/main/packages/vite/src/index.ts`.
const cssFilename = path
.normalize(`${id.replace(/\.[jt]sx?$/, "")}_${slug}.scss`)
.replace(/\\/g, path.posix.sep);
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
"@linaria/core": "6.0.0",
"@linaria/react": "6.0.0",
"@reduxjs/toolkit": "^2.1.0",
"@tanstack/react-router": "^1.15.5",
"@tanstack/router-devtools": "^1.15.5",
"@tanstack/router-vite-plugin": "^1.15.4",
"dayjs": "^1.11.10",
"plausible-tracker": "^0.3.8",
"react": "^18.2.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-redux": "^9.1.0",
"react-router-dom": "^6.21.3",
"redux-logger": "^3.0.6",
"sass": "^1.70.0"
},
Expand Down
29 changes: 16 additions & 13 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,35 @@ import { screen } from "@testing-library/react";
import { getStyle, render, select } from "tests";
import { expect, test } from "vitest";

import { Home } from "view/screens";

import App from "./App";
import { NotFoundRoute } from "./view/routes/__root";
import { IndexRoute } from "./view/routes/index";

test("renders app", () => {
const r = render(<App />);
test("renders app", async () => {
const r = await render(<App />);

const linkElement = screen.getByText(/Template react app with batteries included/i);
const linkElement = r.getByText(/Template react app with batteries included/i);
expect(linkElement).toBeInTheDocument();
});

test("renders 404 page", () => {
render(<App />, "/wow");
test("renders home", async () => {
const r = await render(<IndexRoute />);

const linkElement = screen.getByText(/Page not found/i);
const linkElement = screen.getByText(/Template react app with batteries included/i);
expect(linkElement).toBeInTheDocument();
});

test("renders Home page", () => {
const r = render(<Home />);

// Test @linaria styles are working.
// Worth doing for a few components to test the `theme` object imports properly.
const style = getStyle(select(r, "h1"));
const style = getStyle(select(r.container, "h1"));
expect(style.color).toBe("#bee3f8");
expect(style.textShadow).toBe(
"0.25px 0.25px 0 #4299e1, 0.5px 0.5px 0 #4299e1, 0.75px 0.75px 0 #4299e1, 1px 1px 0 #4299e1, 1.25px 1.25px 0 #4299e1, 1.5px 1.5px 0 #4299e1, 1.75px 1.75px 0 #4299e1, 2px 2px 0 #4299e1, 2.25px 2.25px 0 #4299e1, 2.5px 2.5px 0 #4299e1, 2.75px 2.75px 0 #4299e1, 3px 3px 0 #4299e1, 3.25px 3.25px 0 #4299e1, 3.5px 3.5px 0 #4299e1, 3.75px 3.75px 0 #4299e1, 4px 4px 0 #4299e1, 4.25px 4.25px 0 #4299e1, 4.5px 4.5px 0 #4299e1, 4.75px 4.75px 0 #4299e1, 5px 5px 0 #4299e1, 5.25px 5.25px 0 #4299e1, 5.5px 5.5px 0 #4299e1, 5.75px 5.75px 0 #4299e1, 6px 6px 0 #4299e1"
);
});

test("renders 404 page", async () => {
await render(<NotFoundRoute />);

const linkElement = screen.getByText(/Page not found/i);
expect(linkElement).toBeInTheDocument();
});
35 changes: 25 additions & 10 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
import { Routes, Route } from "react-router-dom";
import { ErrorComponent, RouterProvider, createRouter } from "@tanstack/react-router";
import { Provider as Redux } from "react-redux";

import { Home, NotFound } from "view/screens";
import store from "state/index";

import { HaloProvider } from "view/components";

import { routeTree } from "./routeTree.gen";

const router = createRouter({
routeTree,
defaultPreload: "intent",
defaultPendingComponent: () => null,
defaultErrorComponent: ({ error }) => <ErrorComponent error={error} />
});

declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}

function App() {
return (
<div>
<Routes>
<Route path="/" element={<Home />} />

{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
</div>
<Redux store={store}>
<HaloProvider>
<RouterProvider router={router} defaultPreload="intent" context={{}} />
</HaloProvider>
</Redux>
);
}

Expand Down
13 changes: 1 addition & 12 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { Provider as Redux } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";
import store from "state";

import { plausibleBootstrap } from "lib/analytics";
import "lib/styles/global/index.scss";

import { HaloProvider } from "view/components";

import * as serviceWorkerRegistration from "./serviceWorkerRegistration";
import App from "./App";

Expand All @@ -19,13 +14,7 @@ const root = createRoot(rootElement as HTMLElement);

root.render(
<StrictMode>
<Redux store={store}>
<Router>
<HaloProvider>
<App />
</HaloProvider>
</Router>
</Redux>
<App />
</StrictMode>
);

Expand Down
14 changes: 8 additions & 6 deletions src/lib/global/env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { setGlobalValue } from "./utils";
import { parseEnv, setGlobalValue } from "./utils";

/**
* Environment variables.
Expand All @@ -11,15 +11,17 @@ export const env = Object.freeze({
appVersion: import.meta.env.VITE_VERSION,
gitBranch: import.meta.env.VITE_GIT_BRANCH,
gitCommitHash: import.meta.env.VITE_GIT_COMMIT,
mode: import.meta.env.MODE,
isDevelopment: import.meta.env.MODE === "development",
isProduction: import.meta.env.MODE === "production",
isTesting: import.meta.env.MODE === "test" || import.meta.env.MODE === "testing",
showDevTools: parseEnv(import.meta.env.VITE_SHOW_DEVTOOLS) || true,
plausible: {
enable: import.meta.env.VITE_PLAUSIBLE_ENABLE === "true",
enable: parseEnv(import.meta.env.VITE_PLAUSIBLE_ENABLE),
domain: import.meta.env.VITE_PLAUSIBLE_DOMAIN,
apiHost: import.meta.env.VITE_PLAUSIBLE_API_HOST
},
mode: import.meta.env.MODE,
isDevelopment: import.meta.env.MODE === "development",
isProduction: import.meta.env.MODE === "production",
isTesting: import.meta.env.MODE === "test" || import.meta.env.MODE === "testing",
isStaging: import.meta.env.MODE === "stage" || import.meta.env.MODE === "staging",
// Features
timerIncrement: import.meta.env.VITE_FEATURE_INCREMENT,
someOtherFeature: false
Expand Down
13 changes: 10 additions & 3 deletions src/lib/global/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ import { versionString } from "./version";

export const globalInit = () => {
// Inject global functions.
injectLog();
injectEnv();
injectLog();
injectFeature();
if (!env.isProduction) injectDevTools();

if (!env.isTesting) console.log(`%c${versionString()}`, "font-size: 1.1em;");
// Log app name+version. Hide for tests to reduce clutter in console.
if (!env.isTesting) {
console.log(`%c${versionString()}`, "font-size: 1.1em;padding: 1rem 0;");
}

if (env.isDevelopment) {
injectDevTools();
console.log("env", env);
}
};

globalInit();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/global/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export const debugn = (namespace: string, logLevel: any, ...args: any[]) => {
};

export const injectLog = () => {
setGlobalValue("logStore", new LogStore());
$global.logStore = new LogStore();
setGlobalValue("log", log);
setGlobalValue("logn", logn);
setGlobalValue("debug", debug);
Expand Down
18 changes: 18 additions & 0 deletions src/lib/global/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,21 @@ export const setGlobalValue = (key: string, value: any) => {
};

export const $global = getGlobal();

/**
* Parse string environment variable into a primitive.
*
* @exmaple `parseEnv("VITE_FEATURE_ENABLED") => true`
*/
export const parseEnv = (value: any, isJson = false) => {
if (value === "true") return true;
if (value === "false") return false;
if (value === "undefined") return undefined;
if (value === "null") return null;
if (isJson) {
try {
return JSON.parse(value);
} catch (e) {}
}
return value;
};
24 changes: 12 additions & 12 deletions src/lib/styles/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getStyle, render, select, selectTestId } from "tests";
import { describe, expect, test } from "vitest";

import StylesMock from "tests/StylesMock";
import { getStyle, render, select } from "tests";

/**
* Test to see if the styles are being compiled and injected correctly.
Expand All @@ -12,8 +12,8 @@ import { getStyle, render, select } from "tests";
*/

describe("@linaria with theme injection", () => {
test("renders colors", () => {
const { container } = render(<StylesMock />);
test("renders colors", async () => {
const { container } = await render(<StylesMock />);

const styleTitle = getStyle(select(container, "h1"));
expect(styleTitle.color).toBe("#38a169");
Expand All @@ -22,29 +22,29 @@ describe("@linaria with theme injection", () => {
expect(styleSubTitle.color).toBe("#dd6b20");
});

test("renders mixins", () => {
const { container } = render(<StylesMock />);
test("renders mixins", async () => {
const { container } = await render(<StylesMock />);

const styleContainer = getStyle(select(container, "div"));
const styleContainer = getStyle(selectTestId(container, "StylesMock"));
expect(styleContainer.maxWidth).toBe("567px");
expect(styleContainer.marginLeft).toBe("auto");
expect(styleContainer.marginRight).toBe("auto");
expect(styleContainer.transition).toBe("all, 80ms, ease");
});

test("renders shadows", () => {
const { container } = render(<StylesMock />);
test("renders shadows", async () => {
const { container } = await render(<StylesMock />);

const styleContainer = getStyle(select(container, "div"));
const styleContainer = getStyle(selectTestId(container, "StylesMock"));
expect(styleContainer.boxShadow).toBe(
"0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)"
);
});

test("renders variables", () => {
const { container } = render(<StylesMock />);
test("renders variables", async () => {
const { container } = await render(<StylesMock />);

const styleContainer = getStyle(select(container, "div"));
const styleContainer = getStyle(selectTestId(container, "StylesMock"));
expect(styleContainer.width).toBe("5678px");
});
});
58 changes: 58 additions & 0 deletions src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// This file is auto-generated by TanStack Router

import { createFileRoute } from '@tanstack/react-router'

// Import Routes

import { Route as rootRoute } from './view/routes/__root'
import { Route as IndexImport } from './view/routes/index'

// Create Virtual Routes

const UserLazyImport = createFileRoute('/user')()
const UserUserIdLazyImport = createFileRoute('/user/$userId')()

// Create/Update Routes

const UserLazyRoute = UserLazyImport.update({
path: '/user',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./view/routes/user.lazy').then((d) => d.Route))

const IndexRoute = IndexImport.update({
path: '/',
getParentRoute: () => rootRoute,
} as any)

const UserUserIdLazyRoute = UserUserIdLazyImport.update({
path: '/$userId',
getParentRoute: () => UserLazyRoute,
} as any).lazy(() =>
import('./view/routes/user.$userId.lazy').then((d) => d.Route),
)

// Populate the FileRoutesByPath interface

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/user': {
preLoaderRoute: typeof UserLazyImport
parentRoute: typeof rootRoute
}
'/user/$userId': {
preLoaderRoute: typeof UserUserIdLazyImport
parentRoute: typeof UserLazyImport
}
}
}

// Create and export the route tree

export const routeTree = rootRoute.addChildren([
IndexRoute,
UserLazyRoute.addChildren([UserUserIdLazyRoute]),
])
Loading

0 comments on commit b18ba0f

Please sign in to comment.