Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typescript): Basic TypeScript support #59

Merged
merged 15 commits into from
Apr 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ $RECYCLE.BIN/
package-lock.json
coverage/
.idea
yarn-error.log
18 changes: 9 additions & 9 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
{
"dist/index.js": {
"bundled": 25765,
"minified": 12114,
"gzipped": 4384,
"bundled": 26062,
"minified": 12282,
"gzipped": 4404,
"treeshaked": {
"rollup": {
"code": 9879,
"import_statements": 602
"code": 10015,
"import_statements": 647
},
"webpack": {
"code": 11438
"code": 11631
}
}
},
"dist/index.cjs.js": {
"bundled": 29141,
"minified": 13944,
"gzipped": 4648
"bundled": 29439,
"minified": 14081,
"gzipped": 4661
}
}
13 changes: 13 additions & 0 deletions .tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"jsx": "react",
"pretty": true,
"skipLibCheck": true,
"allowJs": true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe enable strict?
Will result in more errors initially, but worth for the long run.

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ksjogo just fixed in #86!

},
"include": ["./src/**/*"],
"exclude": ["./node_modules/**/*"]
}
48 changes: 28 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
"sideEffects": false,
"scripts": {
"prebuild": "rimraf dist",
"build": "rollup -c",
"build": "rollup -c && npm run typegen",
drcmda marked this conversation as resolved.
Show resolved Hide resolved
"prepare": "npm run build",
"eslint": "eslint ./src",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"typecheck": "tsc --noEmit --jsx react src/*",
"typegen": "tsc --jsx react --emitDeclarationOnly -d --declarationDir types src/*"
},
"husky": {
"hooks": {
Expand All @@ -26,7 +28,7 @@
"printWidth": 120
},
"lint-staged": {
"*.{js,}": [
"*.{js,jsx,ts,tsx}": [
drcmda marked this conversation as resolved.
Show resolved Hide resolved
"prettier --write",
"git add"
]
Expand All @@ -49,30 +51,33 @@
},
"homepage": "https://github.com/drcmda/react-three-fiber#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@babel/runtime": "^7.4.3",
"@types/three": "^0.103.2",
"lodash-es": "^4.17.11",
"pointer-events-polyfill": "^0.4.4-pre",
"react-reconciler": "^0.20.1",
"react-reconciler": "^0.20.4",
"resize-observer-polyfill": "^1.5.1",
"scheduler": "^0.13.3"
"scheduler": "^0.14.0"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8",
"three": ">=0.100"
"three": ">=0.103"
},
"devDependencies": {
"@babel/core": "7.3.4",
"@babel/plugin-proposal-class-properties": "7.3.4",
"@babel/core": "7.4.3",
"@babel/plugin-proposal-class-properties": "7.4.0",
"@babel/plugin-proposal-do-expressions": "7.2.0",
"@babel/plugin-proposal-object-rest-spread": "7.3.4",
"@babel/plugin-transform-modules-commonjs": "7.2.0",
"@babel/plugin-transform-parameters": "7.3.3",
"@babel/plugin-transform-runtime": "7.3.4",
"@babel/plugin-proposal-object-rest-spread": "7.4.3",
"@babel/plugin-transform-modules-commonjs": "7.4.3",
"@babel/plugin-transform-parameters": "7.4.3",
"@babel/plugin-transform-runtime": "7.4.3",
"@babel/plugin-transform-template-literals": "7.2.0",
"@babel/preset-env": "7.3.4",
"@babel/preset-env": "7.4.3",
"@babel/preset-react": "7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@types/lodash-es": "^4.17.3",
"@types/react": "^16.8.14",
"babel-eslint": "^10.0.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"eslint": "^5.16.0",
Expand All @@ -81,15 +86,18 @@
"eslint-plugin-react-hooks": "^1.6.0",
"husky": "^1.3.1",
"lint-staged": "^8.1.5",
"prettier": "^1.16.4",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"rollup": "^1.10.1",
"prettier": "^1.17.0",
"react": ">=16.8",
"react-dom": ">=16.8",
"rollup": "^1.4.1",
"rollup": "^1.10.1",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^9.2.1",
"rollup-plugin-node-resolve": "^4.0.1",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^4.2.3",
"rollup-plugin-size-snapshot": "^0.8.0",
"three": ">=0.100",
"typescript": "^3.3.3333"
"three": "^0.103.0",
"typescript": "^3.4.5"
}
}
1 change: 1 addition & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { sizeSnapshot } from 'rollup-plugin-size-snapshot'
const root = process.platform === 'win32' ? path.resolve('/') : '/'
const external = id => !id.startsWith('.') && !id.startsWith(root)
const extensions = ['.js', '.jsx', '.ts', '.tsx']

const getBabelOptions = ({ useESModules }, targets) => ({
babelrc: false,
extensions,
Expand Down
109 changes: 88 additions & 21 deletions src/canvas.js → src/canvas.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,93 @@
import * as THREE from 'three'
import React, { useRef, useEffect, useMemo, useState, useCallback, useContext } from 'react'
import * as React from 'react'
import { useRef, useEffect, useMemo, useState, useCallback } from 'react'
import ResizeObserver from 'resize-observer-polyfill'
import { invalidate, applyProps, render, unmountComponentAtNode } from './reconciler'

export const stateContext = React.createContext()
export type CanvasContext = {
canvas?: React.MutableRefObject<any>
subscribers: Array<Function>
frames: 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these two be numbers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subscribers is an array of callbacks. frames is an int.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I meant to comment on the line below, as the two 0s will be Numeric Literal Types and thus only allow actual 0 as value.

aspect: 0
gl?: THREE.WebGLRenderer
camera?: THREE.Camera
scene?: THREE.Scene
canvasRect?: DOMRectReadOnly
viewport?: { width: number; height: number }
size?: { left: number; top: number; width: number; height: number }
ready: boolean
manual: boolean
active: boolean
captured: boolean
invalidateFrameloop: boolean
subscribe?: (callback: Function, main: any) => () => any
setManual: (takeOverRenderloop: boolean) => any
setDefaultCamera: (camera: THREE.Camera) => any
invalidate: () => any
}

export type CanvasProps = {
children: React.ReactNode
gl: THREE.WebGLRenderer
orthographic: THREE.OrthographicCamera | THREE.PerspectiveCamera
raycaster: THREE.Raycaster
camera?: THREE.Camera
style?: React.CSSProperties
pixelRatio?: number
invalidateFrameloop?: boolean
onCreated: Function
}

export type Measure = [
{ ref: React.MutableRefObject<any> },
{ left: number; top: number; width: number; height: number }
]

export type IntersectObject = Event &
THREE.Intersection & {
ray: THREE.Raycaster
stopped: { current: boolean }
uuid: string
transform: {
x: Function
y: Function
}
}

const defaultRef = {
ready: false,
subscribers: [],
manual: false,
active: true,
canvas: undefined,
gl: undefined,
camera: undefined,
scene: undefined,
size: undefined,
canvasRect: undefined,
frames: 0,
aspect: 0,
viewport: undefined,
captured: undefined,
invalidateFrameloop: false,
subscribe: (fn, main) => () => {},
setManual: takeOverRenderloop => {},
setDefaultCamera: cam => {},
invalidate: () => {},
}

export const stateContext = React.createContext(defaultRef)

function useMeasure() {
function useMeasure(): Measure {
const ref = useRef()

const [bounds, set] = useState({ left: 0, top: 0, width: 0, height: 0 })
const [ro] = useState(() => new ResizeObserver(([entry]) => set(entry.contentRect)))
useEffect(() => {
if (ref.current) ro.observe(ref.current)
return () => ro.disconnect()
}, [ref.current])

return [{ ref }, bounds]
}

Expand All @@ -28,7 +103,7 @@ export const Canvas = React.memo(
invalidateFrameloop = false,
onCreated,
...rest
}) => {
}: CanvasProps) => {
// Local, reactive state
const canvas = useRef()
const [ready, setReady] = useState(false)
Expand All @@ -50,19 +125,7 @@ export const Canvas = React.memo(

// Public state
const state = useRef({
ready: false,
subscribers: [],
manual: false,
active: true,
canvas: undefined,
gl: undefined,
camera: undefined,
scene: undefined,
size: undefined,
canvasRect: undefined,
frames: 0,
viewport: undefined,
captured: undefined,
...defaultRef,
subscribe: (fn, main) => {
state.current.subscribers.push(fn)
return () => (state.current.subscribers = state.current.subscribers.filter(s => s !== fn))
Expand Down Expand Up @@ -187,8 +250,10 @@ export const Canvas = React.memo(
/** Intersects interaction objects using the event input */
const intersect = useCallback((event, prepare = true) => {
if (prepare) prepareRay(event)

const intersects = defaultRaycaster.intersectObjects(state.current.scene.__interaction, true)
const hits = []

for (let intersect of intersects) {
let object = intersect.object
// Bubble event up
Expand All @@ -198,10 +263,10 @@ export const Canvas = React.memo(
}
}
return hits
})
}, [])

/** Handles intersections by forwarding them to handlers */
const handleIntersects = useCallback((event, fn) => {
const handleIntersects = useCallback((event: React.PointerEvent<any>, fn) => {
prepareRay(event)
// If the interaction is captured, take the last known hit instead of raycasting again
const hits =
Expand All @@ -218,6 +283,7 @@ export const Canvas = React.memo(

for (let hit of hits) {
let stopped = { current: false }

fn({
Copy link
Sponsor Contributor Author

@setsun setsun Apr 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given my current knowledge of three.js it was hard to type the Object that is passed back in the handleIntersects callback fn. I've left this alone for now, but would love to revisit this.

...Object.assign({}, event),
...hit,
Expand All @@ -227,6 +293,7 @@ export const Canvas = React.memo(
// Hijack stopPropagation, which just sets a flag
stopPropagation: () => (stopped.current = true),
})

if (stopped.current === true) break
}
}
Expand All @@ -246,7 +313,7 @@ export const Canvas = React.memo(
)

const hovered = useRef({})
const handlePointerMove = useCallback(event => {
const handlePointerMove = useCallback((event: React.PointerEvent<any>) => {
if (!state.current.ready) return
const hits = handleIntersects(event, data => {
const object = data.object
Expand Down Expand Up @@ -278,7 +345,7 @@ export const Canvas = React.memo(
handlePointerCancel(event, hits)
}, [])

const handlePointerCancel = useCallback((event, hits) => {
const handlePointerCancel = useCallback((event: React.PointerEvent<any>, hits?: []) => {
if (!hits) hits = handleIntersects(event, () => null)
Object.values(hovered.current).forEach(data => {
if (!hits.length || !hits.find(i => i.object === data.object)) {
Expand Down
27 changes: 19 additions & 8 deletions src/hooks.js → src/hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React, { useRef, useContext, useEffect, useMemo, useState } from 'react'
import { useRef, useContext, useEffect, useMemo, useState } from 'react'
import { stateContext } from './canvas'

export function useRender(fn, takeOverRenderloop) {
export function useRender(fn: Function, takeOverRenderloop: boolean): any {
const { subscribe, setManual } = useContext(stateContext)

// This calls into the host to inform it whether the render-loop is manual or not
useMemo(() => takeOverRenderloop && setManual(true), [takeOverRenderloop])

useEffect(() => {
// Subscribe to the render-loop
const unsubscribe = subscribe(fn, takeOverRenderloop)

return () => {
// Call subscription off on unmount
unsubscribe()
Expand All @@ -21,21 +24,29 @@ export function useThree() {
return props
}

export function useUpdate(callback, dependents, optionalRef) {
export function useUpdate(
callback: Function,
dependents: [],
optionalRef: React.MutableRefObject<any>
): React.MutableRefObject<any> {
const { invalidate } = useContext(stateContext)
let ref = useRef()
if (optionalRef) ref = optionalRef
const ref = optionalRef ? optionalRef : useRef()

useEffect(() => {
callback(ref.current)
invalidate()
}, dependents)

return ref
}

export function useResource(optionalRef) {
let ref = useRef()
if (optionalRef) ref = optionalRef
export function useResource(
optionalRef: React.MutableRefObject<any>
): React.MutableRefObject<any> {
const [resource, set] = useState()
const ref = optionalRef ? optionalRef : useRef()

useEffect(() => void set(ref.current), [ref.current])

return resource
}
Loading