Skip to content

Commit 253675d

Browse files
author
Matthias Giger
committed
feat(general): implement cli and plugin template
Plugin template includes preview, basic plugin implementation and tests.
0 parents  commit 253675d

File tree

16 files changed

+617
-0
lines changed

16 files changed

+617
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
package-lock.json

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# create-react-native-plugin
2+
3+
Starting point for creating React Native plugins without native code.
4+
5+
## Usage
6+
7+
```
8+
npm init react-native-plugin react-native-my-plugin
9+
# or
10+
npx create-react-native-plugin react-native-my-plugin
11+
```
12+
13+
This will bootstrap a new plugin inside a folder named `my-plugin` accordingly. Inside that folder the commands
14+
mentioned hereafter are available.
15+
16+
Start working on your plugin by editing `/src/index.js` which will be the entry point for the plugin.
17+
18+
## App
19+
20+
Since you probably don't want to blind-code the whole plugin use the following command to generate an up-to-date
21+
React Native app which includes the plugin:
22+
23+
```
24+
npm run app
25+
```
26+
27+
This will create an app inside `/app` where except `/app/App.js` all files are gitignored. Here you can try out various
28+
use cases of the plugin and use this as a way to demonstrate the plugin.
29+
30+
```
31+
npm run watch
32+
```
33+
34+
Running the above will watch the plugin `/src/` folder for any kind of changes and copy them over to the app which will
35+
then automatically hot-reload.
36+
37+
Don't forget to always check your plugin both on Android and iOS even though your not using native code the provided components
38+
might still differ depending on the platform.
39+
40+
## Tests
41+
42+
The template is configured to work with Jest out of the box. All non-native functionality can be tested from the terminal.
43+
With the following command you can run the tests which are found in a folder with the same name:
44+
45+
```
46+
npm test
47+
```
48+
49+
## Types
50+
51+
Since React Native will usually run in a modern JavaScript engine there is no need to transpile your source code and
52+
apps can directly use the plugin source code. The Flow type checker is the default type system for react native and the
53+
source code is writting in flow. While TypeScript is the more popular type checker overall flow is more popular in the
54+
React Native ecosystem and can be used out of the box. Therefore this package will only support flow.
55+
56+
## Naming
57+
58+
React Native plugins are usually distributed through npm just as any other kind of browser and node plugins. To better distinguish
59+
the plugin from a browser or node plugin the prefix `react-native-whatever-plugin` is usually added.
60+
61+
## Examples
62+
63+
The following plugins have been created with create-react-native-plugin as a starting point.
64+
65+
- [React Native Indicate](https://github.com/naminho/indicate/tree/master/plugins/react-native)
66+
Scroll indicator for views with overflow.
67+
- [Reactigation](https://github.com/naminho/reactigation)
68+
JS-only navigation for React Native.
69+
- [React Native Cols](https://github.com/naminho/react-native-cols)
70+
Grid for React Native.

customize.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const { join } = require('path')
2+
const { readFileSync, writeFileSync } = require('fs-extra')
3+
4+
const files = [
5+
'app/App.js',
6+
'src/index.js',
7+
'test/basic.test.js',
8+
'test/docs.test.js',
9+
'.flowconfig',
10+
'create-app.js',
11+
'package.json',
12+
'README.md'
13+
]
14+
15+
// Replace template values with plugin name.
16+
module.exports = (name, directory) => {
17+
const replaceTemplateVariables = file => {
18+
const filePath = join(directory, file)
19+
let contents = readFileSync(filePath, 'utf-8')
20+
21+
contents = contents.replace(/<%= name %>/g, name.regular)
22+
contents = contents.replace(/<%= pascal %>/g, name.pascal)
23+
24+
writeFileSync(filePath, contents, 'utf-8')
25+
}
26+
27+
files.forEach(replaceTemplateVariables)
28+
}

index.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env node
2+
3+
const { resolve } = require('path')
4+
const { ensureDirSync, emptyDirSync, existsSync, copySync } = require('fs-extra')
5+
const { execSync } = require('child_process')
6+
const names = require('./names')
7+
const customize = require('./customize')
8+
9+
const args = process.argv
10+
if (args.length < 3) {
11+
return console.error('Please provide a name for your plugin.')
12+
}
13+
14+
const name = names(args[2])
15+
16+
if (existsSync(name.regular)) {
17+
return console.warn(
18+
`A folder or file named ${name.regular} already exists in ${process.cwd()}.`
19+
)
20+
}
21+
22+
ensureDirSync(name.regular)
23+
emptyDirSync(name.regular)
24+
25+
const templateDirectory = resolve(__dirname, 'template')
26+
27+
const destinationDirectory = resolve(process.cwd(), name.regular)
28+
29+
copySync(templateDirectory, destinationDirectory)
30+
31+
customize(name, destinationDirectory)
32+
33+
console.log('Installing dependencies...')
34+
35+
execSync('npm i', { cwd: name.regular, stdio : 'pipe' })
36+
37+
console.log('')
38+
console.log(`😃 Created new plugin called ${name.regular} in ${destinationDirectory}.`)
39+
console.log(`🛠️ Start coding in the file src/index.js.`)
40+
console.log(`🛠️ To preview the plugin edit app/App.js and create a RN installation with:`)
41+
console.log(`🐚 cd ${name.regular}`)
42+
console.log('🐚 npm run app')

names.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { pascalCase } = require('change-case')
2+
3+
module.exports = name => {
4+
// react-native prefix only used to identify package out of RN context.
5+
const unprefixed = name.replace(/^react-native-/, '')
6+
7+
return {
8+
regular: name,
9+
pascal: pascalCase(unprefixed)
10+
}
11+
}

package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "create-react-native-plugin",
3+
"description": "Template for creating React Native plugins without native code.",
4+
"author": "Matthias Giger",
5+
"license": "MIT",
6+
"version": "1.0.0",
7+
"repository": "github:naminho/create-react-native-plugin",
8+
"main": "index.js",
9+
"bin": {
10+
"create-react-native-plugin": "./index.js",
11+
"crnp": "./index.js"
12+
},
13+
"dependencies": {
14+
"change-case": "4.1.0",
15+
"fs-extra": "8.1.0"
16+
},
17+
"prettier": {
18+
"semi": false,
19+
"singleQuote": true
20+
}
21+
}

template/.flowconfig

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
[ignore]
2+
; We fork some components by platform
3+
.*/*[.]android.js
4+
5+
; Ignore "BUCK" generated dirs
6+
<PROJECT_ROOT>/\.buckd/
7+
8+
; Ignore polyfills
9+
node_modules/react-native/Libraries/polyfills/.*
10+
11+
; These should not be required directly
12+
; require from fbjs/lib instead: require('fbjs/lib/warning')
13+
node_modules/warning/.*
14+
15+
; Flow doesn't support platforms
16+
.*/Libraries/Utilities/LoadingView.js
17+
18+
[untyped]
19+
.*/node_modules/@react-native-community/cli/.*/.*
20+
21+
[include]
22+
23+
[libs]
24+
node_modules/react-native/Libraries/react-native/react-native-interface.js
25+
node_modules/react-native/flow/
26+
27+
[options]
28+
emoji=true
29+
30+
esproposal.optional_chaining=enable
31+
esproposal.nullish_coalescing=enable
32+
33+
module.file_ext=.js
34+
module.file_ext=.json
35+
module.file_ext=.ios.js
36+
37+
munge_underscores=true
38+
39+
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/react-native/react-native-implementation'
40+
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
41+
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
42+
module.name_mapper='^<%= name %>$' -> '<PROJECT_ROOT>/src'
43+
44+
suppress_type=$FlowIssue
45+
suppress_type=$FlowFixMe
46+
suppress_type=$FlowFixMeProps
47+
suppress_type=$FlowFixMeState
48+
49+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
50+
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
51+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
52+
53+
[lints]
54+
sketchy-null-number=warn
55+
sketchy-null-mixed=warn
56+
sketchy-number=warn
57+
untyped-type-import=warn
58+
nonstrict-import=warn
59+
deprecated-type=warn
60+
unsafe-getters-setters=warn
61+
inexact-spread=warn
62+
unnecessary-invariant=warn
63+
signature-verification-failure=warn
64+
deprecated-utility=error
65+
66+
[strict]
67+
deprecated-type
68+
nonstrict-import
69+
sketchy-null
70+
unclear-type
71+
unsafe-getters-setters
72+
untyped-import
73+
untyped-type-import
74+
75+
[version]
76+
^0.107.0

template/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
# package-lock.json has no effect when publishing your plugin to npm.
3+
package-lock.json
4+
app/**/*
5+
!app/App.js

template/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# <%= name %>
2+
3+
A plugin for React Native.
4+
5+
## Installation
6+
7+
```
8+
npm i <%= name %>
9+
```
10+
11+
## Usage
12+
13+
```jsx
14+
import React from 'react'
15+
import { Text } from 'react-native'
16+
import <%= pascal %> from '<%= name %>'
17+
18+
export () =>
19+
<<%= pascal %>>
20+
<Text>Hello Plugin</Text>
21+
</<%= pascal %>>
22+
```
23+
24+
## Development
25+
26+
### Tests
27+
28+
Tests configured for React Native can be run as follows
29+
30+
```
31+
npm test
32+
```
33+
34+
### Preview App
35+
36+
To test your plugin on a device run the following to create a React Native app using it.
37+
38+
```
39+
npm install
40+
npm run app --silent
41+
cd app
42+
react-native run-ios / react-native run-android
43+
```
44+
45+
The following command will automatically copy over changes made to the plugin to the app.
46+
47+
```
48+
npm run watch
49+
```

template/app/App.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// @flow
2+
import React from 'react';
3+
import {StyleSheet, View, Text} from 'react-native';
4+
import <%= pascal %> from '<%= name %>';
5+
6+
export default () => (
7+
<View style={styles.screen}>
8+
<Text style={styles.title}><%= pascal %></Text>
9+
<<%= pascal %> />
10+
<<%= pascal %> initialCount={45} />
11+
<Text style={styles.subtitle}>create-react-native-plugin</Text>
12+
</View>
13+
);
14+
15+
const styles = StyleSheet.create({
16+
screen: {
17+
flex: 1,
18+
paddingTop: 70,
19+
paddingBottom: 50,
20+
alignItems: 'center',
21+
justifyContent: 'center',
22+
},
23+
title: {
24+
fontSize: 50,
25+
},
26+
subtitle: {
27+
fontSize: 15,
28+
},
29+
});

0 commit comments

Comments
 (0)