Rapid
React

Introducing Rapid React

Introducing Rapid React

  • What is Rapid React?
  • Features.
  • Under the hood.
  • See in action!
  • Roadmap.

What is Rapid React?

What is Rapid React?

Features πŸŽ‰

Features πŸŽ‰

  • Written in TypeScript.
  • Works on all platforms!
  • As light as 17 KB, minified and gzipped.
  • Supports both JavaScript and TypeScript.
  • Uses Create React App under the hood.
  • Offers export preference as either default or named.
  • Supports popularly used CSS preprocessor SASS!
  • Get a comprehensive notification on version updates.
  • Setups routing with custom routes.
  • Scaffold commonly used or custom folders.
  • Installs dependencies and dev-dependencies.
  • Setups state management
    • MobX with custom stores.
    • Redux with custom reducers along with
      • Leveraging Redux Toolkit
      • Async Middleware (Thunk or Saga)
      • Redux Logger
  • Play a notification soundΒ on setup completion!

Under the hood </>

  • Interface Design
  • Functionality Logic
    • Data Structures
    • Algorithm

User Interface

User Interface

Greetings

Notification Update

Setup

Loaders

ORA

Functionality Logic </>

Functionality Logic </>

Executable?

#!/usr/bin/env node

// rest of the code

Capturing user input

{
  tsUsed,
  routes,
  appName,
  folders,
  scssUsed,
  sagaUsed,
  useLogger,
  namedExport,
  dependencies,
  devDependencies,
  isRoutingNeeded,
  stateManagement,
  storesOrReducers,
};

Running commands

type Command = {
  cmd: string,
  msg: string,
  err: string,
  args: string[],
  success: string,
}

type CommandCollection = {
  [key: string]: Command
}

// config
const commands: CommandCollection = {
  installReact: {
    cmd: 'npx',
    args: ['create-react-app'],
    msg: 'Installing create-react-app',
    err: 'Create react app installation failed.',
    success: 'Create react app successfully installed!',
  },
  installDependencies: {
    cmd: 'npm',
    args: ['i'],
    msg: 'Installing dependencies',
    err: 'Dependencies installation failed.',
    success: 'Dependencies successfully installed!',
  },
  installDevDependencies: {
    cmd: 'npm',
    args: ['i', '-D'],
    msg: 'Installing dev dependencies',
    err: 'Dev dependencies installation failed.',
    success: 'Dev dependencies successfully installed!',
  },
};

// running the commands
spawn(cmd, [...args, ...additionalArgs], { shell: true })

Data structure to
maintain folder structure 🌲

type ScaffoldConfig = {
  name: string;
  children?: string | ScaffoldConfig[];
};
[
  // conditionally scaffold router and routes if any route present
  ...routes.length ? [
    {
      name: 'router',
      children: [{
        name: `index.${cmpExt}`,
        children: routerTemplate(routes, ts, namedExport),
      }],
    },
    {
      name: 'routes',
      children: [
        ...routes.map(route => ({
          name: route,
          children: [{
            // component
            name: `index.${cmpExt}`,
            children: componentTemplate(route, ts, namedExport, stylesExt),
          }, {
            // stylesheet
            name: `styles.${stylesExt}`,
            children: stylesheetTemplate(route),
          }],
        })),
        // conditionally add a root export file in case of named exports
        ...namedExport ? [{
          name: `index.${fileExt}`,
          children: rootExportTemplate('routes', routes),
        }] : [],
      ],
    },
  ] : [],
  // scaffold folder(s) if user chose any
  // generate MobX or Redux template
];
import { toKebabCase } from '../utils';
import { commonTemplates } from './common';

export const routerTemplate = (routes: string[], ts: boolean, namedExport: boolean) => {
  const {
    cmpExport,
    rootImport,
    cmpDefinition,
  } = commonTemplates('Router', ts, namedExport);

  return `${rootImport}

import {
  Route,
  Switch,
  BrowserRouter,
} from 'react-router-dom';

${namedExport ? `import {
${routes.map(route => `  ${route},`).join('\n')}  
} from '../routes';` : routes.map((route) => `import ${route} from '..routes/${route}';`).join('\n')}

${cmpDefinition}
  <BrowserRouter>
    <Switch>
${routes.map((route) => `      <Route path="/${toKebabCase(route)}" component={${route}} />`).join('\n')}
    </Switch>
  </BrowserRouter>
);
${cmpExport}`;
};
[
  {
    "name": "router",
    "children": [
      {
        "name": "index.tsx",
        "children": "routerTemplate(routes, ts, namedExport)"
      }
    ]
  },
  {
    "name": "routes",
    "children": [
      {
        "name": "Foo",
        "children": [
          {
            "name": "index.tsx",
            "children": "componentTemplate(route, ts, namedExport, stylesExt)"
          },
          {
            "name": "styles.scss",
            "children": "stylesheetTemplate(route)"
          }
        ]
      },
      {
        "name": "Bar",
        "children": [
          {
            "name": "index.tsx",
            "children": "componentTemplate(route, ts, namedExport, stylesExt)"
          },
          {
            "name": "styles.scss",
            "children": "stylesheetTemplate(route)"
          }
        ]
      },
      {
        "name": "index.ts",
        "children": "rootExportTemplate('routes', routes)"
      }
    ]
  }
]
// pre order traversal
const flattenScaffoldConfig = (
  dir: string,
  children: ScaffoldConfig['children'],
) => {
  if (typeof children === 'string') {
    // return the file with it's path
    return { path: dir, isFile: true };
  } else {
    // recursively traverse children
    children && children.forEach(({ name, children }) => {
      const { path, isFile } = flattenScaffoldConfig(`${dir}/${name}`, children);

      if (isFile && typeof children === 'string') {
        // maintain all files in an array with path & data
        files.push({ path, data: children });
      } else {
        // maintain all directories with path
        directories.push({ path });
      }
    });

    // return the directory with it's path
    return { path: dir, isFile: false };
  }
};
// directories
[
  { path: 'my-app/src/routes/Bar' },
  { path: 'my-app/src/routes/Foo' },
  { path: 'my-app/src/routes' },
  { path: 'my-app/src/router' }
]

// files
[
  {
    path: 'my-app/src/routes/index.ts',
    data: '// all the named exports of routes are present here\n' +
      '\n' +
      "export * from './Foo';\n" +
      "export * from './Bar';\n"
  },
  {
    path: 'my-app/src/routes/Bar/styles.scss',
    data: '.bar-container {\n  /* styles go here */\n}'
  },
  {
    path: 'my-app/src/routes/Bar/index.tsx',
    data: "import React, { FC } from 'react';\n" +
      '\n' +
      "import './styles.scss';\n" +
      '\n' +
      'export const Bar: FC = () => (\n' +
      "  <div className='bar-container'>\n" +
      '  {/* JSX goes here */}\n' +
      '  </div>\n' +
      ');\n'
  },
  {
    path: 'my-app/src/routes/Foo/styles.scss',
    data: '.foo-container {\n  /* styles go here */\n}'
  },
  {
    path: 'my-app/src/routes/Foo/index.tsx',
    data: "import React, { FC } from 'react';\n" +
      '\n' +
      "import './styles.scss';\n" +
      '\n' +
      'export const Foo: FC = () => (\n' +
      "  <div className='foo-container'>\n" +
      '  {/* JSX goes here */}\n' +
      '  </div>\n' +
      ');\n'
  },
  {
    path: 'my-app/src/router/index.tsx',
    data: "import React, { FC } from 'react';\n" +
      '\n' +
      'import {\n' +
      '  Route,\n' +
      '  Switch,\n' +
      '  BrowserRouter,\n' +
      "} from 'react-router-dom';\n" +
      '\n' +
      'import {\n' +
      '  Foo,\n' +
      '  Bar,  \n' +
      "} from '../routes';\n" +
      '\n' +
      'export const Router: FC = () => (\n' +
      '  <BrowserRouter>\n' +
      '    <Switch>\n' +
      '      <Route path="/foo" component={Foo} />\n' +
      '      <Route path="/bar" component={Bar} />\n' +
      '    </Switch>\n' +
      '  </BrowserRouter>\n' +
      ');\n'
  }
]

// uses mkdir()
await Promise.all(directories.map(({ path }) => createDir(path)));

// uses writeFile()
await Promise.all(files.map(({ path, data }) => writeToFile(path, data)));

// uses readFile() and writeFile()
// wrap the app with provider in case redux is used
if(stateManagement === 'Redux') {
  await replaceFileContents(`${rootPath}/index.${cmpExt}`, [{
    subStr: /import ReactDOM from 'react-dom';/g,
    newSubStr: 'import ReactDOM from \'react-dom\';\nimport { Provider } from \'react-redux\';\n',
  }, {
    subStr: /ReactDOM.render\(/g,
    newSubStr: `import ${namedExport ? '{ store }' : 'store'} from './store';\n\nReactDOM.render(`,
  }, {
    subStr: /\<App \/\>/g,
    newSubStr: `<Provider store={store}>
<App />
</Provider>`,
  }]);
}

// replace default app imports by router if routing needed
if(isRoutingNeeded) {
  await replaceFileContents(`${rootPath}/index.${cmpExt}`, [{
    subStr: /import App from '.\/App';/g,
    newSubStr: `import ${namedExport ? '{ Router }' : 'Router'} from './router';`,
  }, {
    subStr: /\<App \/\>/g,
    newSubStr: '<Router />',
  }]);
}

// decorators need to be enabled explicitly for MobX
const enableExperimentalDecorators = async(projectName: string, ts: boolean) => {
  const path = `${projectName}/${LANG_CONFIG[ts ? 'ts' : 'js']}`;

  if(ts) {
    await replaceFileContents(path, [{
      subStr: /"compilerOptions": {/g,
      newSubStr: tsConfig(),
    }]);
  } else {
    await writeToFile(path, jsConfig());
  }
};

Playing
Notification Sound πŸ›Ž

import { exec } from 'child_process';

const player = require('play-sound')();
const asyncExec = require('util').promisify(exec);

export const notify = async(winPlatform: boolean) => {
  const assetPath = __dirname.replace('scripts', 'assets/notification.mp3');

  if (winPlatform) {
    const command = 'powershell -c';
    const playAudio = '$player.Play();';
    const setVolume = '$player.Volume = 1;';
    const loadAudioFile = `$player.open('${assetPath}');`;
    const addPresentationCore = 'Add-Type -AssemblyName presentationCore;';
    const createMediaPlayer = '$player = New-Object system.windows.media.mediaplayer;';
    const stopAudio = 'Start-Sleep 1; Start-Sleep -s $player.NaturalDuration.TimeSpan.TotalSeconds;Exit;';

    await asyncExec( `${command} ${addPresentationCore} ${createMediaPlayer} ${loadAudioFile} ${setVolume} ${playAudio} ${stopAudio}`);
  } else {
    player.play(assetPath, function(error: string) {
      error && console.error(error);
    });
  }
};

Rapid React in action!

Rapid React in action!

Roadmap

Roadmap

Thank You!

Rapid React

By Vinay Sharma

Rapid React

  • 461