betslip v2.3.2
Betslip
The application-toolstack is the base for future web projects. It allows to use newer JavaScript versions (via Babel), provides libraries such as React and Redux, and allows asset bundling using webpack. More features and details about the toolstack can be found below.
Installation
~~No installation needed. Just clone the repository and start working with it.
How to clone:
git clone --recurse-submodules GIT-REPO-URL
We check in the node_modules
folder because it allows us to be independent from npm (e.g. in case a npm package gets unpublished).~~
At this stage we are not yet committing the nod_modules
folder because it will dramatically increase the size of the repo and it will get worse with each dependency update, so we try to delay this until it will be necessary.
In order to get started, just run npm update
.
Note: the installation is tested with node
v9.4.0
and npmv6.1.0
, or with node8.5.0
and npmv5.3.0
. If something goes wrong during the installation, try to check those. You might want to use a tool such as nvm to install and manage various node/npm versions.
Submodules
Betslip uses a submodule for assets that live in an external repository and you will have to initialise it.
If the betslip assets submodule is not initialised you might get the following error: Module not found: Error: Can't resolve '../betslip-assets/styles/index.scss'
Solve this by running the commands bellow.
git submodule init
git submodule update
Usage
The repo includes a sample index.html file that can be used to run the betslip standalone with a bunch of basic buttons to enable isolated testing.
Running
npm run devServer:dev
This command starts a webpack-dev-server and sets the environment to dev
(meaning the config build-tools/webpack.dev.js
will be used). It also passes the initEvent
env variable to start the react application. The dev server is served at http://localhost:1236, but the application is accessible via http://localhost:1236/betslip/.
npm run devServer:prod
This command starts a webpack-dev-server and sets the environment to prod
(meaning the config build-tools/webpack.prod.js
will be used). The production server is served at http://localhost:8080.
Bundling
After running the build commands, the code will be bundled into dist/
folder that will contain a main.js
file.
Include the file at the bottom of an HTML page and fire the initBetslip
event to load Bet Slip component.
npm run build:dev -- --env.initEvent=initBetslip
npm run build:dev
starts the building/bundling process and sets the environment to dev
(meaning the config build-tools/webpack.dev.js
will be used).
npm run build:prod -- --env.initEvent=initBetslip
npm run build:prod
starts the building/bundling process and sets the environment to prod
(meaning the config build-tools/webpack.prod.js
will be used).
npm run build:watch:dev
npm run build:watch:dev
starts the building/bundling process and sets the environment to dev
(meaning the config build-tools/webpack.dev.js
will be used). Additionally, the watch mode will be started, meaning the building/bundling process starts automatically when a file changes/is saved.
npm run build:watch:prod
npm run build:watch:prod
starts the building/bundling process and sets the environment to prod
(meaning the config build-tools/webpack.prod.js
will be used). Additionally, the watch mode will be started, meaning the building/bundling process starts automatically when a file changes/is saved.
npm run build:dev:bundleanalyzer
npm run build:dev:bundleanalyzer
starts the building/bundling process and sets the environment to dev
(meaning the config build-tools/webpack.dev.js
will be used). Additionally, the addon bundleanalyzer
(in build-utils/addons/webpack.bundleanalyzer.js
) will get started. More information on bundleanalyzer
can be found here.
npm run build:prod:bundleanalyzer
npm run build:prod:bundleanalyzer
starts the building/bundling process and sets the environment to prod
(meaning the config build-tools/webpack.prod.js
will be used). Additionally, the addon bundleanalyzer
(in build-utils/addons/webpack.bundleanalyzer.js
) will get started. More information on bundleanalyzer
can be found here.
Code structure
The following folders and files can be found inside the /src
folder. Files inside this folder will end up (in some form or another) in the final bundle generated by webpack.
/assets
/betslip
/lineManager
/utilities
/actions
/api
/components
/containers
/middleware
/reducers
/sagas
/store
index.html
index.tsx
typings.d.ts
The two main components are lineManager
and betslip
. The former is a api that listens to calls from the Market Offer, validates the payload and forwards it to the betslip
component. The betslip
displays the lines, manages the betting types, the stakes and all that, and places the tickets with the backend.
They are both redux applications, each with its own store. This added complexity is justified by the possibility of having them as possible standalone features in the future. The betslip has a UI written in React.
The utilities
folder includes a set of functionality that are shared between the other two components, like error handling and sessionStorage
functionality.
Line Manager
/actions
In /actions
we can define the actions as well as the action creators.
/api
In /api
we can define the listeners and senders to manage communications to and from the Market Offer and the Betslip. API calls are done using postRobot
to allow for cross-window communication in the case we will have to integrate with other systems that require rendering within separate iframes.
/core
In /core
we define the core functionality. The API calls get processed here before being dispatched to the betslip and saved to the store.
/invalidator
The invalidator handles line expiration. When a line is added, we set a timer that will expire the line in the betslip. The timers ids are saved in the store so we can easily manage them and avoid the weird behaviours witnessed in the old RGS.
/reducers
In /reducers
we can define various reducers that manage various parts of the application state.
/store
In /store
we can configure the store. Here, we inject our sagas as well as our middleware.
/validator
The validator makes sure that the payload received via the API is correctly shaped and that the lines are formally valid and not expired. For the lines validation we use ajv
and json-schema
s.
Betslip
As said, the betslip is a redux application as well, so I will avoid repeting the redux-specific components and focus on the differences.
/betslip-assets
In /betslip-assets
we keep fonts, images and styles. It is fetched from an external git repository.
/components
In /components
we can define the React components. We decided to keep in here both presentational components have access to state and actions via props
and container components. The former don't have any logic (except maybe some UI logic), while the latter are connected to the Redux store and pass the state and actions down (via props
).
/sagas
In /sagas
we can define sagas. Sagas are like separate threads that help us to manage asynchronous calls.
/services
In /services
we include functionality that will rely on external services like the configuration service.
/validator
The betslip validator does not validate the lines (they are validated in the lineManager). Instead it validates the data that we are passing around internally. It also validates the ticket before sending it out to the server.
Utilities
/errorHandler
The errorHandler
file contains error classes we can create which can include useful information about what was going on when the error occurred. Error messages are contained in the errorStrings.json
file and are referenced using an errorCode
property in the error object.
/formatNum
The formatNum
file includes a bunch of helper functions used to format numbers in specific ways depending on where we are using them.
/lineGenerator
The lineGenerator
is a helper function we use in the tests to generate compliant random lines.
/logger
The log
functions is used to send frontend error logs to an external service so that we can keep track of what is going wrong in the frontend and can fix bugs that would otherwise be very difficult to reproduce or would simply never be reported.
/sessionStorage
The betslip state can optionally be saved in the sessionStorage. In this file we implement a bunch of checks to ensure a smooth and predictable user experience.
genertic.ts
In this file we have a few functions that abstract simple, generic problems.
index.html
The index.html
file includes the #root
div container in which the application will be renderd. This file also is the template file for the html-webpack-plugin
plugin which automatically includes extracted stylesheets and bundled scripts into that template.
index.tsx
The index.tsx
file "boots up" the whole application. Here, the store is created and injected into the Root
component. An event listener is added to window
that listens to __INIT_EVENT__
that can be set via CLI. For an event initBetslip
it would look something like that:
npm run build:prod -- --env.initEvent=initBetslip
The event initBetslip
is eventually sent by the host application (e.g SRLive
). After the event was received, the callback will execute the renderApplication
function which will call the react-dom
render
function.
typings.d.ts
The typings.d.ts
file is a TypeScript type definition file. In this file we have to define the same constants (e.g. __ENVIRONMENT__
and __INIT_EVENT__
) as we do in webpack.common.js
with the DefinePlugin. Otherwise, the TypeScript compiler will spit out errors.
build-utils
Within the build-utils
folder are a variety of files that are used to create the bundle. Below is a list of all the currently available files and an explanation to each.
common-paths.js
The common-paths.js
file defines paths that may be used by different modules. Those paths usually are the path/location of the source code (entryPath
) and the path/location of the final bundle (outputPath
).
customer-config.js
The customer-config.js
is used to create custom bundles. A config function is exported which in turn exports a config object. Currently, the function accepts the parameters customer
and stlye
. customer
defines which clientsetup should be used (default value is 'client1'
). style
defines which style should be used (default value is 'style.scss'
). The config object currently has a property CUSTOMER_STYLES_PATH
which is simply the path to the customer style (e.g. client1/style.scss
).
vendors.js
In the vendors.js
file we export an array of installed modules. These modules will be bundled into a separate file because there unlikely change and thus will be cached by the browser. There is an array vendors
which inlcudes all the modules we want to bundle separately.
webpack.common.js
In the webpack.common.js
file we define the webpack configurations that are common to the production build as well as to the development build (ie. dev
and prod
). Here we tell webpack to look for our source code, where to put the bundle, what code transformations (e.g. typescript, babel, ...) do we want to have, and we also define some global variables that we can use during development.
webpack.dev.js
The webpack.dev.js
file will be used when we set the environment to dev
. Here we tell webpack to look for SCSS/CSS and put it directly (inline) into our bundle. Since during development we want everything to work as fast as possible we also won't make any minifications or optimizations.
webpack.prod.js
The webpack.prod.js
file will be used when we set the environment to prod
. Here we tell webpack to extract SCSS/CSS into a separate file, execute some minifications/optimizations, and split out bundle into different files. We added a new entry for vendors. Vendors are modules that unlikely to change, and we want them in a separate package because the browser can cache them for us.
addons
In the addons
folder we put webpack plugins that can help us optimize our build process. Currently, we have the following addons available:
webpack.bundleanalyzer.js
:bundleanalyzer
will analyze the bundle and show what modules are in the bundle as well as their size. A small web page is automatically created that visualizes the bundle.
root directory
Below is a list of files within the root directory of this project and an explanation to each file.
.babelrc
The .babelrc
file is used to configure Babel. Babel is used to transpile newer (even stage-0) ECMAScript features down to ES5. Babel is also used to transpile JSX via babel-preset-react
.
package-lock.json
The package-lock.json
file is generated by npm
. This file defines the exact dependencies for the dependencies listed in package.json
. This is supposed to guarantee that the exact same versions of dependencies are installed whenever npm install
is executed.
package.json
The package.json
file defines, i.a., scripts, and dependencies needed for this particular project.
tsconfig.json
The tsconfig.json
file configures the TypeScript compiler.
webpack.config.js
The webpack.config.js
file composes the webpack configuration. When building, webpack will look for a file with this name.
Testing
For testing the framework Jest is used. Jest allows to simply create unit tests, code coverage, and snapshot testing. If you want to write a test, e.g. for a reducer or an action, you should put your test in the same folder as the thing you want to test. If you want to test a reducer reducers/myReducer.ts
then you colocate the file reducers/myReducer.test.ts
. A test suite could look like as follows:
import myReducer from './myReducer'
import * as actionTypes from '../actions/actionTypes'
import {increment, decrement} from '../actions/actions'
// Describe test suite for reducer 'myReducer'
describe('myReducer', () => {
let state = {
count: 1
}
it('should handle INCREMENT', () => {
expect(myReducer(state, {type: actionTypes.INCREMENT})).toEqual({count: 2})
})
it('should handle DECREMENT', () => {
expect(myReducer(state, {type: actionTypes.DECREMENT})).toEqual({count: 0})
})
})
// Describe test suite for action 'increment'
describe('increment', () => {
it('should return an object with the value \'INCREMENT\' for the key \'type\'', () => {
expect(increment()).toEqual({type: INCREMENT})
})
})
// Describe test suite for action 'decrement'
describe('decrement', () => {
it('should return an object with the value \'DECREMENT\' for the key \'type\'', () => {
expect(decrement()).toEqual({type: DECREMENT})
})
})
To run all tests, you simply have to run npm run test
. The script test
will call jest
with options specified in the jest.config.json
file.
Only run specific tests
Jest provides the --testNamePattern=<regex>
(alias is -t
) option for only running tests that comply to the provided regex. It will check the string passed to describe
or test
.
If npm run test -- -t 'action'
is executed then every suite that didn't match with action
is skipped. In the following example only the second suite (action myAction
) would be tested.
describe('reducer myReducer', () => {
it('should do something', () => {
// the test
})
})
describe('action myAction', () => {
it('should do something', () => {
// the test
})
})
Linting
For linting TSLint is used. TSLint is a customizable tool that checks TypeScript code for readability, maintainability, and functionality errors. This linter is setup to perform standard checks that can suits different coding styles, giving developers the possibility to add their own custom rules on top of the default ones.
Packages
This project uses tslint and tslint-react as development dependencies.
TSLint rules
Default TSLint rules are defined in the tslint.json
file. You can modify Linter behaviour by adding your own rules there. TSLint core rules can be found here
Running linter in your code editor or IDE
Subime Text 3
- Be sure you have TSLint installed globally on your machine;
- Using Package Control install SublimeLinter, a Sublime Text framework supporting different types of linter;
- Install a specific linter supporting TSLint such as SublimeLinter-contrib-tslint;
You can choose lint mode via the command palette of Sublime Text by typing
Sublime Linter: Choose Lint Mode
IntelliJ Idea
Detailed configuration instructions can be found here https://www.jetbrains.com/help/idea/tslint.html;
Running linter via terminal
Linter can be also launched directly via terminal by typing the command npm run lint
.
2 years ago