This project is build with the purpose of given a base code from where to start developing a Ionic Native React app with little effort.
This project is licensed under the terms of the MIT license.
-
Clone (or download) the project, run:
git clone https://github.com/gmullerb/basecode-ts-ionic-native-react
or
git clone https://gitlab.com/gmullerb/basecode-ts-ionic-native-react
-
Run
./gradlew
to install npm dependencies and to check the project. -
Run
./gradlew run
: to run the project. -
Open browser at
localhost:8000
. -
Jump to Inside the Code and Start developing your Ionic Native React app.
- Provide a "well" documented base code from where to start developing Ionic Native(Cordova) React apps[1].
- "Well" documented in order to ease customization of this start point.
- Provide basic React elements: Flux, Router.
- Be untie as possible from Angular CLI [2].
- Test code without typings [3].
- Provide a basic browser platform, browser platform allows to do "platform independent" development, which can be very useful as start point.
Some things may be Opinionated based on best practices and professional experience [4].
[1] This project include native (e.g. cordova), for non-native for that go to basecode-ts-ionic-react project.
[2] Used for building, currently Ionic team is working in an "agnostic" approach.
[3] Main code use Typescript, but some extra configuration changes can be done in order to use Flow can be used.
[4] Some topics in the README files have References so you can dig more on details. And code has been "widely" documented to assist you to understand some fragments.
- All in 1 project.
- Code Style Checking.
- for Project code (Javascript, Typescript & React).
- for Build code (Gradle) [1].
- Test Driven Development.
- Unit, Integration and End to end tests.
- Code coverage checking.
[1] Gradle will allow to have "really" isolated different Node/Npm environments for different projects and incremental builds, but npm or yarn can be used, besides Cordova uses Gradle.
- Building.
- Code Style Checking.
- Test Driven Development.
- Unit Test & Integration Test:
- End to end tests:
- Code coverage checking.
- Java (for running Gradle) [1].
- Git (only if the project is going to be cloned).
- Chromium or Chrome for Tests.
[1] Usually comes with every operating system, Gradle will allow to have "really" isolated different Node/Npm environments for different projects, but npm or yarn can be used.
Clone or download the project[1], in the desired folder execute:
git clone https://github.com/gmullerb/basecode-ts-ionic-native-react
or
git clone https://gitlab.com/gmullerb/basecode-ts-ionic-native-react
- No need, only download and run (It's Gradle! Yes!) [1].
[1]
npm
oryarn
can be used.
Npm scripts are defined in package.json file:
assemble
: will build the Native project, targeting Browser.buildAndroid
: will build the Native project, targeting Android[1], i.e.apk
file.prebuildAndroid
: will update the project in order to have all required files for Cordova Android.- This files should not be upload to the repository.
e2e
: runs E2E tests with Protractor.pree2e
: updates Webdriver.
main-lint
: will assess Main code using TSlint.run
: runs Native Browser Project.- Go to the web browser look at the url: http://localhost:8000/.
test
: runs Tests (Unit & Integration tests) using Karma, Jasmine and Enzyme.
gradle-*
scripts should not be run from npm, since some external files are download from the web, use the respective Gradle task.
[1] This requires "all" the Android SDK to be available.
Gradle tasks are defined in build.gradle file.
This project uses the Gradle Tasks structure define by plugin all.shared.gradle.code-common-tasks
, which define a set of tasks and dependencies:
build
check
assess
assessMain
assessTest
test
assemble
Gradle Tasks structure Diagram
(without doc
task)
Additionally this project define some additional tasks:
assessStyleConfig
: will assess Project JS configuration files using eslint (with rules from base-style-config).assessStyleMain
: will assess Main source code using tslint.assessStyleCss
: will assess CSS files using stylelint (with rules from base-style-config).assessStyleTest
: will assess Test source code files using eslint (with rules from base-style-config).assessStyleE2e
: will assess E2E Test source code files using eslint (with rules from base-style-config).e2e
: runs E2E Tests.run
: runs Native Browser Project.- Go to the web browser look at the url: http://localhost:8000/.
buildAndroid
: will build the Native project, targeting Android[1], i.e.apk
file.
Finally:
build
check
assess
assessMain
assessStyleMain
assessStyleCss
assessStyleTest
assessTest
test
assemble
assessStyleConfig
assessStyleE2e
e2e
buildAndroid
run
Useful tasks:
-
./gradlew tasks --all
: will list all available tasks for the project. -
gradlew
, will run default tasks [1]:assessGradle
: will assess Gradle files, provided by project-style-checker plugin.assessCommon
: will assess All files, provided by project-style-checker plugin.npmInstall
: will install npm dependencies [2].assessStyleConfig
.build
.assessStyleE2e
.e2e
.
[1] Mainly the tasks that should be run before pushing the code to a repository.
[2] If dependencies in package.json
are modified ./gradlew npmInstall
should be run.
Some Gradle tasks match with Node scripts:
assemble
task runsassemble
script.assessStyleConfig
task runsgradle-config-lint
script.assessStyleMain
task runsmain-lint
script.assessStyleCss
task runsgradle-style-lint
script.assessStyleTest
task runsgradle-test-eslint
script.buildAndroid
task runs task runs script.test
task runstest
script.assessStyleE2e
task runsgradle-e2e-lint
script.e2e
task runse2e
script.run
task runsrun
script.
Main project dependencies and versions are defined in package.json file [1]. Some versions are defined in gradle.properties
file [2]:
- NODE_VERSION: Version of Node
- BASE_STYLE_CONFIG_VERSION: Version of base-style-config, basically for
eslint
andstylelint
rules. - CHECKSTYLE_VERSION: Version of Checkstyle, for common assesses
- CODENARC_VERSION: Version of Codenarc, for assessing Gradle files.
[1] Change versions: Some versions may be set to the latest using
*
or+
, so it can get the latest version the first time the project is started, although is recommended to set it to fixed values later. Some test dependencies may remain dynamic since this will not affect production build, and allow to have latest improvements and fixes.
[2] Remember to change the root project name in settings.gradle, even in single project builds set rootProject.name = 'name' to avoid Continuous Integration servers issues.
When building Android apps for a specific version, be aware that the support from Cordova depends on the Cordova version.
e.g.
Cordova version | Android version |
---|---|
8.#.# | 4.4 to 9.0.0 |
6.#.# | 4.1 to 8.0.0 |
Then cordova/package.json and cordova/config.xml must be adjusted together:
"cordova-android": "6.5.0"
<preference name="android-minSdkVersion" value="16" />
- Folder
src/main
: Folder of Main source code. - Folder
src/test
: Folder of Test source code. - Folder
config
: Folder for configuration. - Folder
build
: is a generated folder where build results are placed and also tests and coverage reports. - Folder's name may use
_
, but not-
. - Files' name may use
-
, but not_
. - Test (Unit & Integration) Files name ends with
.test.js
. - E2E Test Files name ends with
.e2e.js
. - Throughout any documentation,
..
will represent an ellipsis to avoid possible confusion with...
programming language operator.
Structure:
config/
ci/
e2e/
main/
test/
typings/
src/
e2e/
environments/
main/
assets/
back/
front/
test/
back/
front/
theme/
cordova/
resources/
android/
browser/
ios/
config
: Configuration files.ci
: Configuration files for CI tasks.android-image-build.sh
: Script for installing the tools necessary for building Android app [1].
e2e
: Configuration files for E2E tasks.main
: Configuration files for Main tasks.test
: Configuration files for Test tasks.typings
: Additional typings require in the project.
src
: Source code.e2e
: E2E source code.environments
: Additional environment variables depending on Target (development, production, etc.).main
: Main source code.assets
: Main assets.back
: Non-Visual Main source code.front
: Visual Main source code.
test
: Main source code.back
: Test source code for Non-Visual Main source code.front
: Test source code for Visual Main source code.
theme
: Basically for Ionic theme assets.
cordova
: Cordova files.resources
: cordova resources, initially icons and splash Screens.cordova/platforms/
,cordova/plugins/
andcordova/www/
are ignore by git.
[1] This script can be extracted to a Docker image, but been here, will allow to give a starting point for that, beside the time for the job was almost the same than the one that used a Docker image (may be due to the size). Be Aware of the Gradle version, for some reason if installing a greater version that the one use by Cordova will give
Java heap space
error, then it is better to install the same version of Gradle used by Cordova.
Since this project use a stable @ionic/angular
[1] two major things occurs:
- The project is tie to Angular Cli [1] (Which is mainly a hard-coded wrapper for building tools). Thereupon the project must work with
angular.json
(Main configuration point of Angular Cli). - The project is tie to Typescript.
Settings:
- Since it is hard-coded it is a little difficult to do some modifications, but this project use mutable-webpack-angular-builder which mainly allows to easily customize the webpack configuration. This is done on the webpack.config.mutator.js, which must be added as a field in the
options
field of thebuild
architect [2] of theangular.json
:
"build": {
"builder": "mutable-webpack-angular-builder:browser",
"options": {
"mutatorFile": "./webpack.config.mutator.js",
[1] Hoping at some point to remove this dependency.
[2] Some Angular CLI predefined architectures were removed (test
,lint
, etc.) to give the project all the freedom that is possible, in order to be easily customized with transparent package.json scripts (nothing is hide behind the curtain) .
In the webpack.config.mutator.js, React configuration is done:
1 . Basically Webpack module rules are defined:
a. reactRule
for processing .tsx
files:
const reactRule = {
test: /\.tsx$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-react", "@babel/preset-typescript"]
}
}
}
b. cssRule
to been able to import .css
files:
const cssRule = {
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
sourceMap: true
}
},
'postcss-loader'
]
}
2 . Webpack configuration is modified:
a. Conflicting rules are totally removed [1]:
const filteredRules = sourceWebpack.module.rules.filter(
(rule) => !rule.test instanceof RegExp || !rule.test.test('.css')
)
b. Previously created rules are added:
filteredRules.push(reactRule)
filteredRules.push(cssRule)
[1] Thanks to mutable-webpack-angular-builder.
Native dependencies that were not required by basecode-ts-ionic-react project needs to be added:
-
First of all,
ionic.config.json
is required. -
Added Ionic Native dependencies and Cordova, root
package.json
:
"@ionic-native/core": "^5.0.0",
"@ionic-native/device": "^5.2.0",
"@ionic-native/geolocation": "^5.2.0",
"@ionic-native/splash-screen": "^5.0.0",
"@ionic-native/status-bar": "^5.0.0",
"cordova": "^8.1.2",
- Target
"ionic-cordova-build"
was added to theangular.json
:
"ionic-cordova-build": {
"builder": "@ionic/angular-toolkit:cordova-build",
"options": {
"browserTarget": "app:build"
},
"configurations": {
"production": {
"browserTarget": "app:build:production"
}
}
}
- Added
cordova/package.json
:
"cordova-browser": "^5.0.4",
"cordova-android": "^8.0.0",
"cordova-plugin-device": "^2.0.2",
"cordova-plugin-geolocation": "^4.0.1",
"cordova-plugin-ionic-keyboard": "^2.1.3",
"cordova-plugin-ionic-webview": "^3.1.2",
"cordova-plugin-splashscreen": "^5.0.2",
"cordova-plugin-statusbar": "^2.4.2",
"cordova-plugin-whitelist": "^1.3.3",
- Added Cordova configuration file
cordova/config.xml
:
<platform name="browser">
<preference name="SplashScreen" value="resources/img/splash.png" />
<preference name="AutoHideSplashScreen" value="true" />
<preference name="SplashScreenDelay" value="500" />
<preference name="ShowSplashScreen" value="true" />
</platform>
Following dependencies were required by @ionic/angular
but not by this project at all[1]:
"@angular/common": "~7.2.10",
"@angular/platform-browser": "~7.2.10",
"@angular/router": "~7.2.10",
"@angular/forms": "~7.2.10",
[1] In the future, when using
@ionic/react
this should not be necessary.
- App uses Flux.
- App uses React Router.
Singletons are defined in the src/main/back/singletons
:
mainActions.js
: Where the set of Actions available for the App are defined.mainStore.js
: Where the reducer function is defined and state is updated based on received Actions.
RoutedContainer.tsx
: Here is where the Flux + Router magic happens:
- It is basically a unusual React
Component
, it does not have props but it does have state.- The state points to the groups of stores, in this case only one,
mainStore.js
.
- The state points to the groups of stores, in this case only one,
- It draws the
MainComponent.tsx
. - It contains the
BrowserRouter
and the different routesRoute
.- As it is been implemented, the order of the routes are important.
The Container constitutes the bridge between the Store and Components:
Ionic is basically a set of predefined controls and styles.
1 . Use ionic components directly as any JSX element:
SecondaryComponent.tsx
fragment:
const Secondary = () => (
<div>
<ion-card>
<ion-card-header>
<ion-card-title id='test'>Base Code for Ionic React App</ion-card-title>
<ion-card-subtitle>Secondary</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<Link to='/'>
Switch
</Link>
</ion-card-content>
</ion-card>
</div>
)
2 . Add any ion-*
typing that is not defined in ionic-declarations.d.ts
.
'ion-chip': React.DetailedHTMLProps<any, any>
When using CSS imported classes, use class
attribute for Ionic elements and className
for the rest:
MainComponent.tsx
fragment:
<ion-card-header>
<ion-card-title id='test'>The Ionic React App</ion-card-title>
<ion-card-subtitle
text-uppercase
class={styles.theText}
>
{this.props.text}
</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<div
className={styles.theInput}
>
<ion-input
color='secondary'
padding
ref={(element: any) => this.textInput = element}
type='text'
placeholder='some input'
/>
</div>
Remember: any imported
.css
will require a.d.ts
file, e.g.:MainComponent.css.d.ts
Any href
[1] attribute in any ion-*
tag needs:
- To be removed.
- Approach must be substituted with React-Router
<Link>
.
e.g.
change:
<ion-button
href='/secondary'
>
Switch
</ion-button>
to:
<Link to='/secondary'>
<ion-button>
Switch
</ion-button>
</Link>
[1]
href
can not be used on native.MemoryRouter
can also be used which will not depend on the address bar.
Tests are presented as a starting point, i.e. tests may not cover all possible cases, neither Unit & Integration tests, nor E2E tests.
Only
back
code has 100% coverage.
Coverage thresholds should be raise and/or sets to the specific project needs.
Test Files name ends with .test.js
and are written using jasmine and enzyme and run with karma [1].
Configuration is defined in: karma.conf.js
-
Four reporters are set:
html
an additional report.mocha
for console report.junit
mainly for CI.coverage-istanbul
for coverage.
-
If working as a root, then use No-sandbox Chrome [2]:
browsers: [ 'ChromeHeadlessNoSandBox' ],
customLaunchers: {
ChromeHeadlessNoSandBox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
Code coverage report requires [3]:
- The use
istanbul-instrumenter-loader
to instrument code for istanbul coverage report. - The generation of source map in the transpiler, tsconfig.test.json:
"sourceMap": true
. - The use Webpack
SourceMapDevToolPlugin
.
[1] A mix of unit and integration tests is done, could be separated as is done in basecode - front, but may be out of the scope of this project intention.
[2] This can be set even if not running tests as a root, useful for Local and CI.
[3] Due to transpilation [Typescript + Webpack].
Currently, the core of Angular CLI uses System.import()
:
- "
System.import() is deprecated and will be removed soon. Use import() instead.
"
Issue-21560 - Remove System.import()
usage in favor of import()
e.g.[1]:
SystemJsNgModuleLoader.prototype.loadAndCompile = function (path) {
var _this = this;
var _a = __read(path.split(_SEPARATOR), 2), module = _a[0], exportName = _a[1];
if (exportName === undefined) {
exportName = 'default';
}
return System.import(module)
.then(function (module) { return module[exportName]; })
.then(function (type) { return checkNotEmpty(type, module, exportName); })
.then(function (type) { return _this._compiler.compileModuleAsync(type); });
};
The issue: System.import(module)
This issue forced to add in the webpack.config.test.js
, in order to remove the warning:
new FilterWarningsPlugin({
exclude: /System\.import/
})
Issue-11580 - "request of a dependency is an expression"`[2]:
e.g.[1]:
SystemJsNgModuleLoader.prototype.loadFactory = function (path) {
var _a = __read(path.split(_SEPARATOR), 2), module = _a[0], exportName = _a[1];
var factoryClassSuffix = FACTORY_CLASS_SUFFIX;
if (exportName === undefined) {
exportName = 'default';
factoryClassSuffix = '';
}
return System.import(this._config.factoryPathPrefix + module + this._config.factoryPathSuffix)
.then(function (module) { return module[exportName + factoryClassSuffix]; })
.then(function (factory) { return checkNotEmpty(factory, module, exportName); });
};
The issue: System.import(this._config.factoryPathPrefix + module + this._config.factoryPathSuffix)
This issue forced to add ContextReplacementPlugin
in the webpack.config.test.js
:
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core/,
path.join(__dirname, '../../src/main'),
{}
),
[1] In,
fesm5/core.js
, a long long file, around 24000 lines (and a soon to be deprecated code), where are the good practices?-2
for Angular (May be Angular CLI was created to avoid developers from finding this issues).
[2] since 2016.
E2E Test Files name ends with .e2e.js
and are written using jasmine and protractor.
Configuration is defined in protractor.conf.js:
- E2E depends on Cordova, so Cordova Server must be started, this is accomplished in
onPrepare()
:
const appServer = cordovaServe()
..
onPrepare() {
browser.wait(appServer.servePlatform('browser', {
port: 8000,
noServerInfo: false,
root: path.join(__dirname, '../../cordova')
}, 60000)
- E2E tests with Protractor requires
browser.ignoreSynchronization = true
to allow e2e for non-angular apps. - Two reporters are set:
SpecReporter
from jasmine-spec-reporter is set for console report.JUnitXmlReporter
from jasmine-reporters for XML report, mainly for CI.
- Headless chrome can be used, if desired:
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: [ '--headless' ]
}
}
- If working as a root, then use No-sandbox Chrome [1]:
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: [ '--no-sandbox' ]
}
}
[1] This can be set even if not running e2e as a root, useful for Local and CI.
"@angular-devkit/build-angular:protractor"
had to be removed from angular.json
(which is used in basecode-ts-ionic-react project), it tries to override dev server configuration, and assumes that dev server will always be "@angular-devkit/build-angular:dev-server"
, so when trying to using "@ionic/angular-toolkit:cordova-serve"
fails:
_startDevServer(options) {
const architect = this.context.architect;
const [project, targetName, configuration] = options.devServerTarget.split(':');
// Override dev server watch setting.
const overrides = { watch: false };
The schema.json
of the "@ionic/angular-toolkit:cordova-serve"
has "additionalProperties": false
which is totally fine and prohibits new fields and then "@angular-devkit/build-angular:protractor"
fails (if "additionalProperties"
is changed to true
(or removed), it will work, but the issue is in the assumption of "@angular-devkit/build-angular:protractor"
).
The only code with typings is Main source code, all the remaining code (configuration and testing) does not require typings (seems unnecessary and in some cases can be painful or harmful), but the remaining code style is checked (eslint).
CHANGELOG.md
: add information of notable changes for each version here, chronologically ordered [1].
[1] Keep a Changelog
- Use code style verification tools => Encourages Best Practices, Efficiency, Readability and Learnability.
- Start testing early => Encourages Reliability and Maintainability.
- Code Review everything => Encourages Functional suitability, Performance Efficiency and Teamwork.
Don't forget:
- Love what you do.
- Learn everyday.
- Learn yourself.
- Share your knowledge.
- Learn from the past, dream on the future, live and enjoy the present to the max!.
At life:
- Let's act, not complain.
- Be flexible.
At work:
- Let's give solutions, not questions.
- Aim to simplicity not intellectualism.