Skip to content

Frontend ‐ Tooling

Jeremy Asuncion edited this page Sep 13, 2023 · 1 revision

TypeScript

TypeScript is a superset language of JavaScript that adds static typing to JavaScript, allowing us to catch type errors at build time.

TypeScript is fully supported using Next.js, and is configured using the tsconfig.json file.

Linting

We use a variety of static analysis tools for linting and formatting code. These tools help ensure we're writing code that follows best practices and are formatted in a consistent way.

Linter Config Description
ESLint .eslintrc.js Linter for JavaScript and TypeScript code to find problems in the code before build time. The root configuration is located in .eslintrc.js, and additional configuration is split into separate files in eslint/.

We use AirBnb's config as a base for JavaScript best practices.
Prettier .prettierc.yml A code formatter that can lint and format code so that it's written in a certain style. This ensures that code contributed to the repo follows a consistent style so that developers don't have to spend mental effort worrying about how the code should look.
Stylelint .stylelintrc.yml Linter for SCSS files to ensure best practices when writing SCSS.
lint-staged .lintstagedrc.yml A tool for running scripts on staged files. We currently use this for linting code with prettier on commit. If the code isn't formatted correctly, then the commit fails.
Husky pre-commit Script for setting up frontend Git hooks.

Styling

Component styling is handled entirely by Next.js using PostCSS for transformations and Webpack for modules.

Tailwind

Tailwind is a CSS framework that provides utility classes for styling components. Tailwind is great for minimizing the output of CSS, so its recommended to only use Tailwind classes for styling whenever possible.

SCSS Modules

SCSS modules are different from normal SCSS because class names are exported using a unique class name instead of the class name given in the file. This ensures encapsulation for a particular class and removes the need for name-based encapsulation approaches like BEM.

For example, if we define a module example.module.scss:

.red {
  background: #f00;
}

This will get compiled into a mangled name like:

.jjqbifbo739173 {
  background: #f00;
}

Then a component can import the style like this:

import styles from './example.module.scss';

export function Example() {
  return (
    <div className={styles.red}>
      <h1>Example</h1>
    </div>
  );
}

Under the hood, Webpack will return a JavaScript object with the following mapping:

module.exports = {
  red: 'jjqbifbo739173',
};

For more info on CSS modules, check out the CSS Modules repo.

Type Definitions

When running yarn dev, SCSS modules will also output type definitions. This is because yarn dev runs the yarn dev:tsm script, which runs the typed-scss-modules CLI.

This CLI is used for generating type definitions for SCSS modules so that the exported CSS classes are properly typed. For example, the SCSS module

.example {
  @apply bg-red-500;
}

would output the following type definitions:

export type Styles = {
  example: string;
};

export type ClassNames = keyof Styles;

declare const styles: Styles;

export default styles;

This is beneficial because:

  1. It provides class autocompletion in editors.
  2. It verifies that components are using classes that are actually exported. Otherwise, TypeScript would throw a compile error.

Package Scripts

The package.json file contains scripts for executing different commands on the project. While there are a lot of package scripts, the most important ones you need to know about are:

Script Description
build Builds the application for production.
dev Runs the frontend in development mode.
lint Lints the codebase using prettier, stylelint, and eslint.
e2e Runs E2E tests
e2e:watch Runs E2E tests in watch mode
e2e:update Update E2E test snapshots
test Runs Unit/Integration tests
test:watch Runs Unit/Integration tests in watch mode
test:update Update Unit/Integration test snapshots
type-check Type checks the codebase using TypeScript.

Development Mode

Running the frontend in dev mode will start up a local instance of the napari hub accessible from http://localhost:8080.

To start dev mode, run the command:

yarn dev

Running this starts up a few programs concurrently:

Program Description
Mock Server Server used for mocking the hub backend. This is useful for speeding up development when the feature you're working on is frontend focused.
Next.js Dev Server Next.js application in dev mode. This runs the frontend server with Fast Refresh so that code changes update the browser UI immediately.
tsm Watch Mode typed-scss-modules CLI in watch mode so that any additions and modifications to *.module.scss files update the type definitions in *.module.d.ts.

Disabling the Mock Server

It's possible to disable the mock server when running dev mode using the MOCK_SERVER environment variable:

MOCK_SERVER=false yarn dev

This is useful if you want to run the mock server in another terminal and make edits to the mock server code without having to restart the frontend dev server, or if you want to connect to the backend directly.

Building for Production

Building the frontend for production uses next build to compile the source code into static assets used on the server and client. The compiled assets are minified, obfuscated, and split into chunks to improve performance.

Build Example
$ yarn build

Page                                Size     First Load JS
┌ λ /                               1.1 kB          405 kB
├   /_app                           0 B             404 kB
├ ○ /404                            2.74 kB         407 kB
├ ○ /about                          2.32 kB         406 kB
├ λ /api/health                     0 B             404 kB
├ ○ /contact                        1.61 kB         405 kB
├ ○ /faq                            4.55 kB         408 kB
├ λ /plugins                        499 B           404 kB
├   └ css/53b1c5ef4433886afef5.css  238 B
├ λ /plugins/[name]                 597 B           404 kB
└ ○ /privacy                        2.65 kB         407 kB
+ First Load JS shared by all       404 kB
  ├ chunks/197.0174e2.js            124 kB
  ├ chunks/37.b034fd.js             13.7 kB
  ├ chunks/439.f3cd33.js            174 kB
  ├ chunks/528.8356f5.js            12.5 kB
  ├ chunks/541.f245d6.js            13.2 kB
  ├ chunks/commons.029f7e.js        13.6 kB
  ├ chunks/framework.c65aa9.js      42.6 kB
  ├ chunks/main.9608e0.js           6.82 kB
  ├ chunks/pages/_app.50f555.js     1.33 kB
  ├ chunks/webpack.4d4dd6.js        1.88 kB
  └ css/dc249b959bd8e8431886.css    5.33 kB

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
   (ISR)     incremental static regeneration (uses revalidate in getStaticProps)
.next/
├── BUILD_ID
├── build-manifest.json
├── cache
│   └── webpack
│       ├── client-development
│       │   ├── 0.pack
│       │   └── index.pack
│       ├── client-production
│       │   ├── 0.pack
│       │   └── index.pack
│       ├── server-development
│       │   ├── 0.pack
│       │   └── index.pack
│       └── server-production
│           ├── 0.pack
│           └── index.pack
├── export-marker.json
├── images-manifest.json
├── prerender-manifest.json
├── react-loadable-manifest.json
├── required-server-files.json
├── routes-manifest.json
├── server
│   ├── chunks
│   │   └── 544.08287030a48b44b0ba52.js
│   ├── init-server.js.js
│   ├── on-error-server.js.js
│   ├── pages
│   │   ├── 404.html
│   │   ├── 500.html
│   │   ├── _app.js
│   │   ├── _document.js
│   │   ├── _error.js
│   │   ├── about.html
│   │   ├── api
│   │   │   └── health.js
│   │   ├── contact.html
│   │   ├── faq.html
│   │   ├── index.js
│   │   ├── plugins
│   │   │   └── [name].js
│   │   ├── plugins.js
│   │   └── privacy.html
│   ├── pages-manifest.json
│   ├── src_components_common_Markdown_SyntaxHighlighter_tsx.js
│   └── webpack-runtime.js
└── static
    ├── chunks
    │   ├── 197-0174e29c00d5fa998962.js
    │   ├── 37-b034fdf1a328767a8fda.js
    │   ├── 439-f3cd33813eac15271193.js
    │   ├── 528-8356f53b8e89052619ca.js
    │   ├── 541-f245d62ed4b147c9f375.js
    │   ├── 671.f8163694a7fb29bed992.js
    │   ├── amp.js
    │   ├── commons-029f7e74948cd2c19e78.js
    │   ├── framework-c65aa9ff3e674e9efb3d.js
    │   ├── main-9608e06ddc1767ab0602.js
    │   ├── main.js
    │   ├── node_modules_next_dist_client_dev_noop_js.js
    │   ├── pages
    │   │   ├── _app-50f555b84e1d1c307bc7.js
    │   │   ├── _app.js
    │   │   ├── _error-f742b197cbc7fc99c663.js
    │   │   ├── _error.js
    │   │   ├── about-90ee2ef69a278608507e.js
    │   │   ├── contact-0436bb20655b6d885a85.js
    │   │   ├── faq-20f4b4da8050884dc074.js
    │   │   ├── index-22061676299820a17c8b.js
    │   │   ├── index.js
    │   │   ├── plugins
    │   │   │   └── [name]-8daf5b1ca62f24c43d1d.js
    │   │   ├── plugins-7e2011012cd6099ca1cb.js
    │   │   └── privacy-b5ba2febdefdb03ec557.js
    │   ├── polyfills-bba4e3ae2a1f646feca8.js
    │   ├── polyfills.js
    │   ├── react-refresh.js
    │   ├── react-syntax-highlighter
    │   │   └── refractor-import.56b6068dcf4453eb3f9b.js
    │   ├── src_components_common_Markdown_SyntaxHighlighter_tsx.js
    │   ├── vendors-node_modules_react-syntax-highlighter_dist_esm_index_js-node_modules_react-syntax-hig-61f5cb.js
    │   ├── webpack-4d4dd6112788f822ba84.js
    │   └── webpack.js
    ├── css
    │   ├── 53b1c5ef4433886afef5.css
    │   ├── c7f5b4a528dfe86004a8.css
    │   └── dc249b959bd8e8431886.css
    ├── development
    │   ├── _buildManifest.js
    │   └── _ssgManifest.js
    ├── webpack
    │   ├── 51d4e9504fa97cf5ec8a.hot-update.json
    │   └── webpack.51d4e9504fa97cf5ec8a.hot-update.js
    └── wpMy4GBpNfYW4G42PfGKw
        ├── _buildManifest.js
        └── _ssgManifest.js

20 directories, 76 files

Plop Generator

We use plop to automate some boilerplate when working on the codebase. The plop templates are still WIP, so there may not be a generator for certain things. To use one of the generators, run:

yarn plop <generator>

We currently support the following generators:

Generator Description
component Creates a new component in src/components
page Creates a new page in src/pages