Skip to content

Commit daa3b2e

Browse files
committed
feat(template): publish as TypeScript without build and use Bun
Uses the newly released `bun pm pack` to bundle the package for the demo app. release-npm BREAKING CHANGE: Requires bun for installation and usage. Plugin published as TypeScript with works fine with the regular React Native template and no longer requires a build.
1 parent 219038a commit daa3b2e

File tree

11 files changed

+64
-116
lines changed

11 files changed

+64
-116
lines changed

README.md

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44

55
Starting point for creating React Native plugins in TypeScript without native code.
66

7-
- Bundle plugin in TypeScript with esbuild
7+
- Publish plugin as TypeScript with Bun
88
- Setup demo app with plugin installed
9-
- Watch plugin for changes
9+
- Copy plugin changes over to demo app
1010
- Jest, ESLint and Prettier configured
1111

1212
## Usage
1313

1414
```
15-
bun --bun create react-native-plugin react-native-my-plugin
16-
npm init -y react-native-plugin@latest react-native-my-plugin / npx create-react-native-plugin@latest react-native-my-plugin
15+
bun create react-native-plugin react-native-my-plugin
16+
bunx create-react-native-plugin@latest react-native-my-plugin
1717
```
1818

1919
This will bootstrap a new plugin inside a folder named `react-native-my-plugin` accordingly. Inside that folder the commands mentioned hereafter are available. The prefix `react-native-` is optional and will be removed where the React Native context is implied.
@@ -25,16 +25,16 @@ Start working on your plugin by editing `index.tsx` which will be the entry poin
2525
Since you probably don't want to blind-code the whole plugin use the following command to generate an up-to-date React Native app which includes the plugin:
2626

2727
```
28-
npm run app
28+
bun app
2929
```
3030

31-
This will create an app inside `/app` where except `/app/App.tsx` all files are gitignored. Here you can try out various use cases of the plugin and use this as a way to demonstrate the plugin. The app can be started as usual by running `npm run ios` or `npm run android` inside the `/app` folder.
31+
This will create an app inside `/app` where except `/app/App.tsx` all files are gitignored. Here you can try out various use cases of the plugin and use this as a way to demonstrate the plugin. The app can be started as usual by running `bun ios` or `bun android` inside the `/app` folder.
3232

3333
```
34-
npm run watch
34+
bun copy
3535
```
3636

37-
Running the above in the root folder will watch the plugin source code for any kind of changes, rebuild and copy over the changes to the app which will then automatically hot-reload.
37+
Running the above in the root folder will watch the plugin source code for any kind of changes and copy over the changes to the app which will then automatically hot-reload.
3838

3939
Don't forget to always check your plugin both on Android and iOS even though your not using native code the provided components might still differ depending on the platform.
4040

@@ -43,8 +43,7 @@ Don't forget to always check your plugin both on Android and iOS even though you
4343
The template is configured to work with Jest out of the box. All non-native functionality can be tested from the terminal. With the following command you can run the tests which are found in a folder with the same name:
4444

4545
```
46-
npm test
47-
npm run test:watch # Keep watching and retesting on changes.
46+
bun run test
4847
```
4948

5049
## Troubleshooting
@@ -55,8 +54,6 @@ If you have issues building the app for iOS try the following
5554
- Update Cocoapods with `sudo gem install cocoapods`
5655
- Update Pod dependencies in `app/ios` folder with `pod update`
5756

58-
To avoid bundling additional dependencies with `esbuild` mark them as `--external` in the `npm run build` script.
59-
6057
## Examples
6158

6259
The following plugins have been created with create-react-native-plugin as a starting point.

index.js

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env node
1+
#!/usr/bin/env bun
22
import { execSync } from 'node:child_process'
33
import { cpSync, existsSync, lstatSync, mkdirSync, renameSync, rmdirSync, unlinkSync } from 'node:fs'
44
import { dirname, join } from 'node:path'
@@ -29,9 +29,8 @@ if (existsSync(name.regular)) {
2929

3030
mkdirSync(name.regular)
3131

32-
const npmPackagePath = dirname(new URL(import.meta.url).pathname)
33-
34-
const templateDirectory = join(npmPackagePath, 'template')
32+
const packagePath = dirname(new URL(import.meta.url).pathname)
33+
const templateDirectory = join(packagePath, 'template')
3534
const destinationDirectory = join(process.cwd(), name.regular)
3635

3736
cpSync(templateDirectory, destinationDirectory, { recursive: true })
@@ -48,22 +47,14 @@ customize(name, destinationDirectory)
4847

4948
console.log('Installing dependencies...')
5049

51-
// biome-ignore lint/correctness/noUndeclaredVariables: Used to detect Bun as the runtime.
52-
if (typeof Bun !== 'undefined') {
53-
execSync('bun install', {
54-
cwd: destinationDirectory,
55-
stdio: 'inherit',
56-
})
57-
} else {
58-
execSync('npm install --legacy-peer-deps', {
59-
cwd: destinationDirectory,
60-
stdio: 'inherit',
61-
})
62-
}
50+
execSync('bun install', {
51+
cwd: destinationDirectory,
52+
stdio: 'inherit',
53+
})
6354

6455
console.log('')
6556
console.log(`😃 Created new plugin called ${name.regular} in ${destinationDirectory}.`)
6657
console.log('🛠️ Start coding in the file ./index.tsx.')
67-
console.log('🛠️ To preview the plugin edit app/App.tsx and create a RN installation with:')
58+
console.log('🛠️ To preview the plugin edit app/App.tsx and create a React Native installation with:')
6859
console.log(`🐚 cd ${name.regular}`)
69-
console.log('🐚 npm run app / bun app:bun')
60+
console.log('🐚 bun app')

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
},
1414
"devDependencies": {
1515
"@biomejs/biome": "^1.8.3",
16-
"zero-configuration": "^0.17.2"
16+
"zero-configuration": "^0.17.3"
1717
},
1818
"trustedDependencies": [
1919
"zero-configuration"

template/.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
node_modules
2-
# package-lock.json / bun.lockb has no effect when publishing your plugin to npm.
3-
package-lock.json
2+
# Lockfiles have no effect when publishing your plugin to a registry.
43
bun.lockb
5-
dist
64
<%= pascal %>App
5+
*.tgz
76
app/**/*
87
!app/App.tsx

template/README.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ A plugin for React Native.
55
## Installation
66

77
```
8-
npm i <%= name %>
8+
bun install <%= name %>
99
```
1010

1111
## Usage
@@ -23,26 +23,22 @@ export () =>
2323

2424
## Development
2525

26-
### Build
27-
28-
Run a single build with `npm run build` and find the output in `/dist`.
29-
3026
### Tests
3127

32-
Tests configured for React Native can be run with `npm test` or `npm run test:watch` in watch mode.
28+
Tests configured for React Native can be run with `bun run test`.
3329

3430
### Preview App
3531

3632
To test your plugin on a device run the following to create a React Native app using it.
3733

3834
```
39-
npm run app
35+
bun app
4036
cd app
41-
npm run ios / npm run android
37+
bun ios / bun android
4238
```
4339

44-
The following command will automatically copy over changes made to the plugin to the app.
40+
The following command in the root will automatically copy over changes made to the plugin to the app.
4541

4642
```
47-
npm run watch
43+
bun copy
4844
```

template/create-app.js

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,28 @@
1-
import { execSync } from 'child_process'
2-
import { copyFileSync, cpSync, readFileSync, renameSync, rmSync, mkdirSync } from 'fs'
3-
import { join, resolve } from 'path'
4-
import Arborist from '@npmcli/arborist'
5-
import packlist from 'npm-packlist'
1+
import { execSync } from 'node:child_process'
2+
import { cpSync, renameSync, rmSync } from 'node:fs'
63

74
// This script enhances source files inside /app with a fresh React Native template.
85
const appName = '<%= pascal %>App'
9-
const isBun = typeof Bun !== 'undefined'
106

117
console.log('⌛ Initializing a fresh RN project...')
128

13-
execSync(`${isBun ? 'bunx' : 'npx'} @react-native-community/cli init ${appName} --skip-git-init true --install-pods true`, {
9+
execSync(`bunx @react-native-community/cli init ${appName} --skip-git-init true --install-pods true`, {
1410
// Write output to cnosole.
1511
stdio: 'inherit',
1612
})
1713

18-
copyFileSync('app/App.tsx', `${appName}/App.tsx`)
19-
14+
cpSync('app/App.tsx', `${appName}/App.tsx`)
2015
rmSync('app', { recursive: true })
21-
2216
renameSync(appName, 'app')
2317

24-
// Run build to ensure distributed files for plugin exist.
25-
execSync(`${isBun ? 'bun' : 'npm'} run build`, {
26-
stdio: 'inherit',
18+
// Install package to app.
19+
const output = execSync('bun pm pack', {
20+
encoding: 'utf-8',
21+
})
22+
const tgzFileName = output.match(/[\w.-]+\.tgz/)[0]
23+
execSync(`bun install ../${tgzFileName}`, {
24+
cwd: './app',
2725
})
28-
29-
const packageName = JSON.parse(readFileSync('./package.json')).name
30-
const packageDirectory = resolve(`app/node_modules/${packageName}`)
31-
32-
// Package files and copy them to app node_modules.
33-
// Couldn't get symlinks to work with metro.
34-
const arborist = new Arborist({ path: process.cwd() })
35-
const tree = await arborist.loadActual()
36-
const files = await packlist(tree)
37-
38-
mkdirSync(packageDirectory, { recursive: true })
39-
40-
files.forEach((file) => cpSync(join(process.cwd(), file), join(packageDirectory, file), { recursive: true }))
4126

4227
console.log('')
4328
console.log('🍞 React Native App created inside /app.')

template/package.json

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,33 @@
33
"version": "1.0.0",
44
"license": "MIT",
55
"scripts": {
6-
"app": "node create-app.js",
7-
"app:bun": "bun create-app.js",
8-
"app:install": "npm i --no-save $(npm pack . | tail -1) --prefix app",
9-
"build": "esbuild index.tsx --outdir=dist --bundle --format=esm --sourcemap --external:react-native --external:react",
10-
"watch": "npm-run-all --parallel build:watch copy",
11-
"copy": "cpx 'dist/**/*' app/node_modules/<%= name %>/dist --watch",
12-
"build:watch": "esbuild index.tsx --watch --outdir=dist --bundle --format=esm --sourcemap --external:react-native --external:react",
6+
"app": "bun create-app.js",
7+
"copy": "cpx '*.{ts,tsx}' app/node_modules/<%= name %> --watch",
8+
"format": "prettier \"{,!(app|dist)/**/}*.{ts,tsx}\" --write",
9+
"lint": "eslint . --fix --ext .ts,.tsx",
1310
"test": "jest",
14-
"test:watch": "jest --watchAll",
15-
"types": "tsc",
16-
"lint": "eslint . --fix --ext .js,.jsx,.ts,.tsx",
17-
"format": "prettier \"{,!(app|dist)/**/}*.{ts,tsx}\" --write"
11+
"types": "tsc && tsc --noEmit --project ./test/tsconfig.json"
1812
},
1913
"devDependencies": {
20-
"@npmcli/arborist": "^7.5.4",
21-
"@react-native-community/cli": "^14.0.0",
22-
"@react-native/babel-preset": "^0.75.1",
23-
"@react-native/eslint-config": "^0.75.1",
24-
"@react-native/typescript-config": "^0.75.1",
14+
"@react-native-community/cli": "^14.0.1",
15+
"@react-native/babel-preset": "^0.75.2",
16+
"@react-native/eslint-config": "^0.75.2",
17+
"@react-native/typescript-config": "^0.75.2",
18+
"@types/bun": "^1.1.8",
2519
"@types/jest": "^29.5.12",
26-
"@types/node": "^22.4.0",
27-
"@types/react": "^18.3.3",
20+
"@types/node": "^22.5.4",
21+
"@types/react": "^18.3.5",
2822
"@types/react-native": "^0.73.0",
2923
"@types/react-test-renderer": "^18.3.0",
3024
"babel-jest": "^29.7.0",
3125
"cpx": "^1.5.0",
32-
"esbuild": "^0.23.1",
3326
"eslint": "8.57.0",
3427
"eslint-plugin-flowtype": "^8.0.3",
3528
"eslint-plugin-prettier": "^5.2.1",
3629
"jest": "^29.7.0",
37-
"npm-packlist": "^8.0.2",
38-
"npm-run-all": "^4.1.5",
3930
"prettier": "^3.3.3",
4031
"react": "^18.3.1",
41-
"react-native": "^0.75.1",
32+
"react-native": "^0.75.2",
4233
"react-test-renderer": "^18.3.1",
4334
"typescript": "^5.5.4"
4435
},
@@ -47,19 +38,17 @@
4738
"react-native": ">= 0.70"
4839
},
4940
"type": "module",
50-
"main": "./dist/index.js",
41+
"main": "./index.tsx",
5142
"exports": {
52-
".": {
53-
"types": "./dist/index.d.ts",
54-
"default": "./dist/index.js"
55-
}
43+
".": "./index.tsx"
5644
},
57-
"types": "./dist/index.d.ts",
45+
"types": "./index.tsx",
5846
"files": [
59-
"dist"
47+
"*.ts",
48+
"*.tsx"
6049
],
6150
"prettier": {
62-
"printWidth": 120,
51+
"printWidth": 140,
6352
"semi": false,
6453
"singleQuote": true
6554
},
@@ -69,7 +58,6 @@
6958
"semi": 0
7059
},
7160
"ignorePatterns": [
72-
"dist",
7361
"app"
7462
],
7563
"root": true
@@ -78,13 +66,9 @@
7866
"moduleFileExtensions": [
7967
"ts",
8068
"tsx",
81-
"js",
82-
"jsx",
83-
"json",
84-
"node"
69+
"js"
8570
],
8671
"moduleNameMapper": {
87-
"react-dom": "react-native",
8872
"<%= name %>": "<rootDir>"
8973
},
9074
"preset": "react-native",

template/test/basic.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react'
22
import { StyleSheet, View } from 'react-native'
33
import renderer, { ReactTestRendererJSON } from 'react-test-renderer'
4-
import { <%= pascal %> } from '<%= name %>'
4+
import { <%= pascal %> } from '../index'
55

66
test('Renders without any options.', () => {
77
const rendered = renderer.create(<<%= pascal %> />)

template/test/docs.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react'
22
import { View } from 'react-native'
33
import renderer from 'react-test-renderer'
4-
import { <%= pascal %> } from '<%= name %>'
4+
import { <%= pascal %> } from '../index'
55

66
test('README example renders correctly.', () => {
77
const tree = renderer.create(

template/test/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
22
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"strict": false
5+
},
36
"include": ["*"]
47
}

0 commit comments

Comments
 (0)