02 - tangle frontend

May 25, 2020

this post documents setting up the frontend portion of tangle.

This section covers the technical foundations including

  • bootstrapping the lerna repo
  • adding eslint and prettier
  • setting up the frontend

bootstrap lerna repo

yarn init
yarn add lerna --dev
yarn lerna init

Add to package.json:

...
  'name': 'root',
  'private': true,
  'workspaces': [
    'packages/*'
  ]
...

Update lerna.json:

  'packages': ['packages/*'],
  'version': 'independent',
  'npmClient': 'yarn',
  'useWorkspaces': true

gitignore

eslint/prettier

Copy/paste eslint and prettier

  yarn add babel-eslint eslint eslint-config-airbnb
  eslint-config-prettier eslint-plugin-import
  eslint-plugin-jsx-a11y eslint-plugin-prettier
  eslint-plugin-react prettier -W -D

-W for root flag

-D for dev dependencies

Add to package.json

    'lint': 'eslint --fix . && echo 'Lint complete.'',

Commit [init].

frontend / next.js

  mkdir packages/web
  npx create-next-app packages/web

Select Default starter app

Update name of web app to @tangle/web

Add command to root package.json

'web': 'lerna run --scope @tangle/web dev --stream'

commit [web] init

We want to share react and next across the codebase. Add these as devDependencies to the root and add them as peerDependencies in whatever packages need them.

component library

  mkdir packages/components
  cd packages/components && yarn init -y

Name package @tangle/components.

Add peer dependencies

  yarn workspace @tangle/components add -P react react-dom next

Add dependencies

  yarn workspace @tangle/components add @emotion/styled @theme-ui/color styled-system theme-ui prop-types

Make a component

packages/components/src/components/Button/Button.js

  import React from 'react';
  import PropTypes from 'prop-types';
  import styled from '@emotion/styled';

  const StyledButton = styled.button`
    color: red;
    background-color: blue;
  `;

  function Button({ children }) {
    return <StyledButton>{children}</StyledButton>;
  }

  Button.propTypes = {
    /**
    * content of button
    */
    children: PropTypes.string
  };

  Button.defaultProps = {
    children: null
  };

  export default Button;

Add storybook

  yarn add -W -D @storybook/react @storybook/addon-actions
  @storybook/addon-docs babel-loader @babel/core

Add .storybook directory w/ config.js and main.js

config.js

  import { configure, addDecorator } from '@storybook/react';

  addDecorator(storyFn => (storyFn()))

  configure(require.context('../packages/components/src', true, /\.stories\.js$/), module);

main.js

  module.exports = {
    stories: ['../packages/**/*.stories.js'],
    addons: ['@storybook/addon-docs']
  };

Add a story for the button

Button.stories.js

  import React from 'react';
  import { action } from '@storybook/addon-actions';
  import { storiesOf } from '@storybook/react';
  import Button from './Button';

  storiesOf('Button', module).add('default', () => (
    <Button onClick={action('clicked')}>test</Button>
  ));

update root package.json

  'stories': 'start-storybook'

Run storybook.

clean up and code share

babel-plugin-module-resolver helps us keep things looking clean.

  yarn add -D -W babel-plugin-module-resolver eslint-plugin-import
  eslint-import-resolver-babel-module eslint-import-resolver-alias

add a .babelrc file

  {
    'plugins': [
      ['module-resolver', {
        'root': ['./'],
        'alias': {
          '@components': './packages/components/src',
          '@web': './packages/web'
        }
      }]
    ]
  }

add to .eslintrc.js

  settings: {
    'import/resolver': {
      'babel-module': {},
      alias: {
        map: [['@components', './packages/components/src']],
        extensions: ['.ts', '.js', '.jsx', '.json']
      }
    }
  }

add an index.js file to components/src and export the button component from there. Update the import in the Button.stories.js file to reflect the update

  import { Button } from '@components';

Run storybook to test.

Time to add the button to Next.js

Add a babel.config.js to the next app

  module.exports = {
    babelrcRoots: ['../packages/*'],
    presets: ['next/babel'],
    plugins: [
      [
        'module-resolver',
        {
          root: ['./'],
          alias: {
            '@components': '../components/src'
          }
        }
      ]
    ]
  };

We need to transpile the components as they come in:

yarn workspaces @tangle/web next-transpile-modules

Transpile components in the next.config.js

  const withTM = require('next-transpile-modules')([
    '../components/src'
  ]);

  module.exports = withTM();

Call the Button in your index to test. Run your next app.

previous post01 - tangle