The following is technical documentation for key architectural decisions made in the library.
In order to properly maintain the library, the following conventions that MUST be upheld
File naming and folder structure:
├── componentFolderName # should exist at the root of /components
│ ├── componentFolderName_{themeName}.module.scss # theme stylesheets must follow this naming convention
├── ComponentFolderName.js # the main component of the folder should share the same name
├── ComponentFolderNameAddon.js # secondary components can exist, but are expected to share the same stylesheet as the main component
Note: further folder nesting has not been tested yet.
Component level ThemeWrapper usage:
Every component needs to leverage the ThemeWrapper higher order component in the following way. See the "ThemeWrapper and Dynamic Imports" section below for more info.
// in: ComponentName.js
import ThemeWrapper from '../../utils/ThemeWrapper'
export const ComponentName = ({ styling, ...rest }) => (
<div className={styling.container}>
)
export default ThemeWrapper(themeName => `[componentFolderName]/[componentFolderName]_${themeName}.module.scss`)(ComponentName)
Utility function exports:
// in: stringUtils.js
const manipulationMethod = str => {
// ...
}
export default {
manipulationMethod
}
// this is then used in projects via:
// import { stringUtils } from '@robotsandpencils/react-robits'
// stringUtils.manipulationMethod()
The components in this library leverage Webpack's Dynamic Import to manage SCSS stylesheets. This allows use to have theme specific stylesheets, without having them all bundled together.
It also comes with some reported performance gains around code splitting. Haven't tested at scale yet, but I have been able to verify that the inital bundle size decreases, and that newly requested bundles from dynamic imports are delayed until needed and cached (so it only ever requests a component's stylesheet once).
The promised-based import is governed centrally by the ThemeWrapper.js
higher order component (HOC) for all components, which gives us the advantage of ripping out dynamic imports globally as part of the ejection process, since at that point it's overkill.
The HOC is set up to resolve and pass through a styling
prop from one of 3 sources, by order of important (trump-power):
- A
themeObj
object prop, that is expected to be a fully realized CSS Module style object. This will be passed directly and dynamic file imports will be "skipped" - A
themeName
string prop, that specifies the string to use to build the file path from a string literal template provided by the component (see the "Component level ThemeWrapper usage" section above for the string literal template and how thestyling
prop is leveraged) - A
REACT_APP_ROBITS_THEME
environment variable, which is a string same as the previous bullet. This is the final fall back / default
Nuances:
- Since the stylesheets are dynamic loaded as they are needed, this can impact the order in which the
<style>
tags show up in the document, which effects how CSS is applied to elements. So in the case where you provide custom classes via props to Robits to tweak styling at the instance level, if those those CSS rules are loaded statically by your application, its<style>
tag would preceed that of the targeted Robit that ultimately gets dynamically referenced - would could lead to your instance level customization getting trumped by the Robits CSS. And since that order is dynamic and session dependent, it may be inconsistent. Specificity levels and!important
will be your saviors here. - Note that with
create-react-app
, the presence ofexport REACT_APP_ROBITS_THEME=something
in a served up.env
file will automagically be accessible from within the React app code, by sheer naming convention. If not usingcreate-react-app
, you would need to wire up your environment variables manually through Webpack
This library is set up with CSS Modules and leverage Sass Resources Loader, which is a nice little tool that automatically @imports
each .scss file found at the targeted location into any SCSS file matching the Webpack rule (excluding the resource files themselves), which you can see configured in ./.storybook/main.js
. This just saves some typing and helps centralize shared tokens. These configurations should be mirrored at the project level, see "Using Robits in Your Project" on the README
Couple things to note:
- The resources are included in the order they are found in the directory, and since order matters with CSS, we're using a number prefix naming convention to bend it to our will
- Since these files are injected into essentially every other .scss file, the intent is for them to only contain SASS variables, mixins, or functions. They can not contain any actuall CSS rule sets, or in combination with the CSS Modules setup, they will get duplicated in EVERY file (no good).
All theme SCSS is located in ./src/core/styles/...
with the following breakouts:
/tokens/
: tokens that are shared across all themes, or are theme-agnostic/themes/[themeName]
: the theme specific styles. The name of the folder is the identifying string used as common thread across the implementation/themes/[themeName]/global.module.scss
: every theme should have a global file that sets up global rules like fonts, base HTML normalizations (body, anchors, headers, etc.), and imports shared rules. Everything in this file and what it imports should work from CSS Modules':global
mindset. It is recommended to then, at the project level, set up a global SCSS file that imports the theme globals via@import '../../node_modules/@robotsandpencils/react-robits/src/core/styles/themes/[themeName]/global.module.scss';
and then makes any addendums needed for the project./themes/[themeName]/tokens.module.scss
: it is recommended to break out theme-specific token as their own file, alongside (and imported into) the global file. This is the extension or overriders of the cross-theme tokens
Beyond that, to date we've tried to bubble SASS variables to the top of files, and name partials with a _
prefix. Each component is the expected to have 1 stylesheet file per theme, which imports the theme-specific tokens at the top like @import '../../styles/themes/[themeName]/tokens';
(cross theme tokens will be automatically injected with Sass Resources Loader).
Below are the Node scripts and a summary of what they do:
eject.js
: Provides a series of prompts for the developer to answer that guides the ejection process. It then runs each of the below scripts to carry it out. After each has been executed, it runsnpm uninstall @robotsandpencils/react-robits -D -S && npm install
, which removes the package from node modules, and reinstalls fresh in order to pick up any changes resulting from themerge-dependencies.js
scriptmerge-dependencies.js
: Merge the package.json files of the project and thereact-robits
library, so that all Robit dependencies are migrated to the project for the nextnpm install
. We defer to the project here wherever possible, and any package version conflicts have to be manually resolved, so we don’t accidentally break something the project relies on.pluck-components.js
: Copy all the Robit files (components, utilities, styles, constants, etc.) out from the library and into a project directory defined by the developerupdate-references.js
: Update references in all project level Javascript files that point to thereact-robits
package, and redirect them to the new location of that file. So changeimport { Button } from ‘@robotsandpencils/react-robits'
toimport Button from ‘…[relative path to project file location]…'
- It will optionally prune (based on the answer to the initial prompt) the copied over components to only what the project references in above step. So if the project doesn’t use the Avatar component, delete it. While you might not directly reference a component, some other Robit might rely on it, so it builds a Robits inter-dependent tree to map out which components to “save from the shredder"
- Scopes the theme stylesheets to the theme name the developer specified in the initial prompt, which deletes out all other unneeded theme files
- Optionally breaks reliance on the ThemeWrapper higher order component. Since this HOC was only needed to dynamically deliver a theme, and at this point the theme is static, it’s not technically needed. Breaking reliance here updates each exported Robit file to instead directly import the needed theme stylesheet, and changes the export from something like
export default ThemeWrapper(...)(Button)
toexport default Button
erase-footprint.js
: Erase the Robits footprint by removing any reference to it in the project-level package.json
Nuances:
- After successfully ejecting, you will need to update your Sass Resources Loader configuration to point to the new location of the style tokens. Then restart the app.
- The success of the reference updates within your project files depends on the format of the import statements:
import { Button, Avatar, FormInput, Badge } from '@robotsandpencils/react-robits' // linear format works
import {
Button,
Avatar,
FormInput,
Badge
} from '@robotsandpencils/react-robits' // one-per-line format works
import {
Button, Avatar,
FormInput, Badge
} from '@robotsandpencils/react-robits' // the combination is untested and might break