No notes :(
There are many ways to do the same thing in CSS. When different developers try to build the same thing independently, they might get a slightly different result.
The way to unify styles is to create a design system, that will specify colors, font sizes. These elements are called design tokens. Using limited number of design tokes enforces consistency.
Tokens should also make sense, for example when defining colors, you should define text and background colors together, in order to maintain contrast ratio and keep the text readable.
The number of tokens should be minimal to reduce number of unwanted deviation.
On the higher level we have components. But they should be configurable
Configuration via boolean flags:
<Text primary />
<Text secondary />
<Text primary secondary /> // an impossible state!
Rigidity and complexity:
<Text variant="primary" />
Configuring an underlying html tag
<Text /> // renders <p/>
<Text inline/> // <span />
<Text as="label" /> // allows to specify any tag
<Heading level={2} />
With these tokens it is still hard to achieve consistency.
Spacing
- Linear progression
- Geometric progression - preferable, since it reduces number of choices to very different
Naming
- x-small small medium large - full names
- sa s m l xl - short names
- 1 2 3 4 5 - numbers
Definition
- pixes
- rem
<Box space="m"></Box>
<Flexbox />
<Grid gridGap="m" layout="..." />
Implementation
- Use css-classes under the hood – does not scale
- Un Javascript – more compact, but still requires a lot of manual code
- Styled-system – good helper! https://github.com/styled-system/styled-system
Why primitive tokens as components
- Consistency
- Discoverable API
- Faster development
- Code reuse
- Code reviews
- Documentation
- Accessibility
- Common language with designers
With a good design system you should be able to create a good design even on a napkin.
How to build a maintainable interface
- How fast can I understand?
- How easy to make a change
- How can I predict future iterations?
Spacing is important to build maintainable layout. Even on a small piece of interface there can be various spacings.
Spacing declaration
- padding/margin directly
- via abstractions
- css grid – allows to describe a complex layout in a single place (grid template areas)
Atomic layout
CSS-grid + react components
import { Composition } from "atomic-layout";
function Header() {
return (
<Composition areas="logo menu actions">
{(Logo, Menu, Actions) => (
<div>
<Logo />
<Menu />
<Actions />
</div>
)}
</Composition>
);
}
Responsive components
<Box padding={10} paddingSm={20} />
<Only from="sm" to="lg" />
<Only except for="md" />
Same for composition
<Composition
areas={`
logo
menu
`}
areasMd={`
logo menu
search search
`}
/>
This approach allows you to compose complex layouts from simple and explicit levels that are easy to maintain and join together.
Composition exposes you an API very close to the actuall CSS-grid properties. It should help you to learn the approach, not just a tool.
Composition component will own spacing, and then you will be using other components to define visuals:
<Card>
<Composition areas={areas}>
<Thumbnail>
<Image />
</Thumbnail>
<Header>
<h1>{title}</h1>
</Header>
<Actions>
<Button />
</Actions>
</Composition>
</Card>
We can also use advanced properties from css-grid, like auto-fit
or minmax
to have nice responsive layouts.
Using extra props like paddingMd
or gridGapLg
we can add overrides for a certain viewport size.
https://github.com/kettanaito/atomic-layout
Typescript adoption is growing in JS community accroding to the State of JS survey.
Why typescript?
- It enforces contracts in your code
- Confidence when making changes
- Helps your code to scale fast. You can easily add things
We define an interface and make all our code to follow that. When we need to make a change, we update the interface and Typescript shows us places that need to be updated upon the interface change.
Design system is similar to Typescript. It also give you a contract. A contract for UI components, if you have a Button, you know that this is a button and works as you expect. With design system you can build UI with confidence.
There are many design system building tools, like react-styleguidist. What if we will give them a power of Typescript?
Monaco editor – the core part of VSCode. We can bring it into Styleguidist and make code examples editable with autocomplete based on our types information.
How is this built? We use transpile
to convert TS to JS and then evaluate it inside of the error boundary:
import { transpile } from "typescript";
<ErrorBoundary>{transpile(eval(example))}</ErrorBoundary>;
Monaco editor component. It does not have ready-to-use react component so we will build one:
- receive a ref and render the editor inside
- receive changes and adjust the editor size
- handle
triggerSuggest
event, rendering the suggest UI
Monaco has an abstraction to work with file system, where we can provide a custom adapter to have auto-complete for import paths in the browser.
Monaco is an awesome library, very easy to get started, they have good getting started guide in their docs: https://github.com/Microsoft/monaco-editor/blob/master/docs/integrate-esm.md
We can create virtual modules for auto-completion via
monaco.editor.createModel(
`module content`,
"typescript",
"file:///node_modules/my-lib/index.d.ts"
);
- Static types analysis is valuable
- Working with Monaco editor is facinating
- Hacks lead to new ideas
How can we get started with a big legacy code base very quickly?
StackOverflow survey says that average time to be efficient with a code base is ~6 months
React? Does not really help. You still have many components that do not have clear relationship. Where is this component actually used?
- https://bogdan-lyashenko.github.io/Under-the-hood-ReactJS/ – my attempt to read and explain the react code base
- https://github.com/Bogdan-Lyashenko/codecrumbs – my current project that visualizes any provided code
Usage: codecrumbs -d project-src-dir
and you will get visualization for the code in project-src-dir
.
We can add special comments to our code to annotate certain product features to simplify navigation. We can see user flows and how they are represented in different parts of the source.
Codecrumbs can also build multi-project charts.
We can add extra information to our chart that should help contributors to get started with the codebase.
Live demo: https://codecrumbs.io/#showcase=todo-react-redux
Headless CMS – new generation of CMS:
Content edit APP –> CMS API -> client app
Example: Gatsby + Wordpress
User -> publishes content to -> WP -> triggers a build -> Gatsby -> deploy -> static side
This approach is also called JAM Stack – JavaScript, APIs, and Markup
Cons of this approach
- Not possible to use huge ecosystem of WP plugins
- Content editing is limited (can't change layout or colors in CMS)
- No live preview, because it requires a build
- Not applicable to legacy CMS
Pros: We own the frontend and can use all modern techs and approaches (React!)
For people who are not able to use JAM stack due to some special requirements, we offer a solution. We can build react widgets and then embed them into a classic CMS.
React union: https://github.com/lundegaard/react-union
<main>
<p>Static content produced by your favorite CMS.</p>
<div id="hero-container"></div>
<p>More static content produced by your favorite CMS.</p>
<!-- A widget descriptor – configuration of your React widget. -->
<script
data-union-widget="hero"
data-union-container="hero-container"
type="application/json"
></script>
</main>
We provide a script with configuration via data-attributes that will be picked up by React-union runtime. Initializing the root union component:
import React from "react";
import { Union } from "react-union";
import { route } from "./Hero";
// `Union` renders found widgets into their containers
// but retains a single virtual DOM. Yes, it uses React portals :)
const Root = () => <Union routes={[route]} />;
export default Root;
All your widgets will have a shared react context where you can store theming, localization and other config.
We have adapter for popular CMSes so our widgets can be added to pages using standard CMS mechanisms, for example drag-n-drop or Gutenberg widgets in new WordPress.
React-Union widgets use the same Redux store. If we want, we can scope our data using namespace
that is injected into our widget. React-union gives you helpers to register reducers into the global store.
Tooling
- Typescript
- Rollup
- https://github.com/ezolenko/rollup-plugin-typescript2 (the original typescript-plugin is abandoned and outdated)
Rollup config
- We import both formats: commonjs and esm
- Do not forget to configure externals
- Terser plugin for minification
Typescript config
- "declaration": true to emit d.ts files for consumers
- "module": "esnext" to delegate import/export resolution to Rollup or other bundler
- "moduleResolution": "node" – the other options are legacy in TS
Package.json
- main – for node.js
- module – for tools that understand es-modules (Webpack and other bundlers)
- jsnext:main – same as above but for some older tools
- types – for typescript
- files – whitelist of files to publish
We publish files processed via rollup. Rollup is more recommended for libraries, since it does not produce extra runtime, just builds modules together.
Issues that identify performance problems
- Slow loading time
- Slow interactions
React native basics
- UI Thread
- RN Bridge
- JS Thread
React Native developer menu has "Performance monitor" feature. Using this monitor we have found that our bottleneck was in the JS thread. We looked into flamechart provided by RN Dev tools (custom version of Chrome dev tools). We have found unnecessary full-renders that took ~5s of JS execution.
Rendering in React
- Change -> call render functions -> build new vdom tree -> reconciliation -> rendering
Possible optimization: shouldComponentUpdate
. Leads to possible bugs, because it is out of sync with the state of the render
method.
Optimize reconciliation: react detects changes by shallow properties compare. We can optimize it by not producing new objects if content does not change.
Sources of new instances when nothing changes:
- Inline arrow functions
- Redux. When store changes, all connected components will change.
mapDispatchToProps
produces you new instances. Use only static object form to avoid extra updates - Redux selectors. Use special helpers to memoize data
- Props-dependent state. running an extra re-render from
componentDidUpdate
is an anti-pattern. You can move stuff to the parent component, redux store orgetDerivedStateFromProps
- Prefer local state over the Redux. Changing it will produce a smaller re-render
- Avoid dispatching multiple actions. Unify actions to get it done with only one
- You can try using MobX that has smarter updating logic
React native specific optimization
- React native built-in animation engine
- Inline requires. Avoid loading all modules at start, place
require
call into components to make them called when component is rendered
<Image src={require('./heavy-pic.jpg')}>
- Use low-level profiler
- React router native – wastes re-renders. We stopped using it
- React navigation – the recommended library
Loading time is important and using framework like React has a cost there.
In the modern era Javascript is also a very popular compilation target for other languages. Compiling from one language to another very high-level lang is not a good idea. We need a more low-level API. This should be solved by WebAssembly.
Will WASM kill Javascript?
No. JS already plays important role in front-end. WASM is mostly built to make languages more suitable to be run in browsers.
Should we all use WASM?
WASM does not support any arbitrary JS object. Work with DOM still should be done in JS.
Rust in WebAssembly
Rust supports WebAssembly as a first-class compilation target. They also provide bindings to Javascript to call Rust code from Javascript and Javascript code from Rust
It is very easy: shows a demo
Webpack has support for WASM. You can just import .wasm
files into your javascript and call functions from there
Reasons to rewrite existing code:
- Inexperience with the current implementation
- It is fun
- Better solution available
- Technical debt
The definition of tech debt is very vague
- Code I did not write
- Code before I knew what I was doing
- Old libraries
- Features that no one uses
- Code that negatively and repeatedly affects your development efficiency
Recurring technical debt
Second system effect – https://en.wikipedia.org/wiki/Second-system_effect
The real cost of a software is not an initial development, but the cost of maintaining it over the time.
How to write a system that does not require a rewrite
- "Good architecture"
- Architecture as enabling constraints
- Example in real life: autobahn – no speed limit, but side-barriers
Paradigms are also enabled constraints
- OOP – classes – for independently deployable components
- Functional – immutable data – to eliminate race conditions
- var->const – no re-assign – predictable data
- jQuery->React – no direct access to DOM – predictable UI and testing
- CSS->CSS-in-JS – not using explicit class names – no conflicts
Another good set of constraints
Constraint: code dependencies must point inward
Module approaches
- big ball of mud (chaotic dependencies)
- layered dependencies
- modular (huge blocks with very few connections)
Enables: predictable impart of changing a thing, avoidable cross-team conflicts In frontend it means that a single page should work independently, like other pages do not exist. Shared components should go into design system layer or you can copy-paste them otherwise (DRY is overrated).
Constraint: be conservative about code reuse
Code-reuse sometimes leads to brittle and over-engineered code. It also impacts the time to develop, because new requirements affect shared components if they were not truly reusable. When you have half-full and half-empty glasses, sometimes you just really need to glasses.
https://twitter.com/rakyll/status/1088586877072887809
Enables: lower coupling of the code, faster changes
Constraint: enforce your boundaries.
Not every similarly looking code should be merged for reuse. There is a forbidden dependencies tests. They check the structure of the modules and check for unwanted dependencies to ensure clean architecture.
https://github.com/sverweij/dependency-cruiser
Enables: preserves architecture over time
Every time you create a function or add a feature, they all are architecture decisions that affect maintainability of your code. Make these decisions wisely and consider constraints above.
There are many technologies that people are struggling with.
Typescript – typed superset of Javascript. Or maybe not?
- has many new constructs
- has a special compiler
- new set of challenges
- number of new file types – ts, tsx, d.ts
Redux – confusing at the first look
- Can be learned via Elm architecture documentation – not a simple way
- Many new words to learn
- Middlewares that are very different from each other
GraphQL – "I use it, but I do not get it"
Gatsby
- Has too much prerequisites to know
- react
- graphql
- frontend tooling
Flexbox
- counter intuitive, so many similarly-looking names
- https://css-tricks.com/snippets/css/a-guide-to-flexbox/
the list can continue...
There is a common pattern to explain everything as "simple". We mistake familiarity for simplicity. After some time spent with a technology you are loosing tracking what is technology specific and what is a general knowledge.
https://www.youtube.com/watch?v=1vvjiJFsT-Y&t=2367s
Spiral of silence – people have a fear of isolation. People are afraid of saying unpopular opinion in a fear of being excluded from a community.
https://wir-sprechen-online.com/2011/11/25/spiral-of-silence/
Increased Empathy. It is especially important in education.
-
Take ownership of communities, make people feel comfortable to express an opinion
-
Make learning exciting, but showing on your example that mistakes are natural part of learning
-
Start an internal mentorship program. It should be someone out of your organization to avoid bias (definitely not your manager)
-
Try out empathy driven teaching
-
Relate your own experiences. "I have felt the same way, when ..."
-
Ban words like "easy" from your vocabulary, be more specific
-
Be absolute. Show, don't tell
-
Deconstruct our biases. Do not assume previous knowledge
-
Keep a learning journal. This will help you to recap
-
If you understand something it does not mean that everyone does
-
People learn at different speed
-
Brains are weird
-
Everyone is a beginner at some point
-
It is ok if the things are hard