
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


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
- A dedicated setup to choose from and set up popular design libraries
- Support for internationalization
- Setting up Husky Git Hooks with Lint Staged
- Choosing from commonly used ESLint Config
- Setting up code splitting with Lazy Loading and Suspense.
- Support for dockerizing the React app.

Thank You!




Rapid React
By Vinay Sharma
Rapid React
- 461