├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_CN.md ├── afterbuild.js ├── azure-pipelines.yml ├── benchmarks ├── DBMonster │ ├── ENV.js │ ├── app.js │ ├── build.js │ ├── index.html │ ├── memory-stats.js │ ├── monitor.js │ ├── readme.md │ ├── style.css │ └── webpack.config.js ├── isomorphic-ui-benchmarks │ └── README.md ├── js-framwork-bench │ ├── .babelrc │ ├── index.html │ ├── readme.md │ ├── src │ │ ├── Main.jsx │ │ └── Store.es6.js │ └── webpack.config.js └── uibench │ ├── app.js │ ├── build.js │ ├── index.html │ ├── readme.md │ └── rollup.config.js ├── browsers ├── ie8.js ├── karma.js └── polyfill.js ├── index.js ├── jest.config.js ├── karma.conf.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── nerv-create-class │ ├── .npmignore │ ├── CHANGELOG.md │ ├── index.js │ ├── package.json │ ├── rollup.config.js │ └── src │ │ └── index.ts ├── nerv-devtools │ ├── .npmignore │ ├── CHANGELOG.md │ ├── index.js │ ├── package.json │ ├── rollup.config.js │ └── src │ │ ├── index.ts │ │ ├── renderer.ts │ │ └── utils.ts ├── nerv-is │ ├── index.js │ ├── package.json │ ├── rollup.config.js │ └── src │ │ └── index.ts ├── nerv-redux │ ├── .npmignore │ ├── CHANGELOG.md │ ├── __tests__ │ │ ├── provider.spec.js │ │ └── util.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.js │ └── src │ │ ├── index.js │ │ ├── invariant.js │ │ └── prop-types.js ├── nerv-server │ ├── .npmignore │ ├── CHANGELOG.md │ ├── __tests__ │ │ └── render.spec.js │ ├── index.js │ ├── package.json │ ├── rollup.config.js │ └── src │ │ ├── index.ts │ │ ├── is.ts │ │ └── utils.ts ├── nerv-shared │ ├── .npmignore │ ├── CHANGELOG.md │ ├── index.js │ ├── package.json │ ├── rollup.config.js │ └── src │ │ └── index.ts ├── nerv-test-utils │ ├── .npmignore │ ├── CHANGELOG.md │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── test.spec.js.snap │ │ └── test.spec.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.js │ └── src │ │ └── index.ts ├── nerv-utils │ ├── .npmignore │ ├── CHANGELOG.md │ ├── __tests__ │ │ ├── next-tick.spec.js │ │ ├── shallow-equal.spec.js │ │ ├── simple-map.spec.js │ │ └── util.spec.js │ ├── index.js │ ├── package.json │ ├── rollup.config.js │ └── src │ │ ├── env.ts │ │ ├── index.ts │ │ ├── is.ts │ │ ├── next-tick.ts │ │ ├── shallow-equal.ts │ │ └── simple-map.ts └── nerv │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ ├── children.spec.js │ ├── cloneElement.spec.js │ ├── component.spec.js │ ├── componentDidCatch.spec.js │ ├── context.spec.js │ ├── createContext.spec.js │ ├── createElement.spec.js │ ├── dom.spec.js │ ├── event.spec.js │ ├── export.spec.js │ ├── fragments.spec.js │ ├── h.spec.js │ ├── hooks.spec.js │ ├── keys.spec.js │ ├── lifecycle.spec.js │ ├── patch.spec.js │ ├── portal.spec.js │ ├── refs.spec.js │ ├── render.spec.js │ ├── svg.spec.js │ ├── util │ │ ├── dom.js │ │ └── index.js │ └── vtypes.spec.js │ ├── index.js │ ├── package.json │ ├── rollup.config.js │ └── src │ ├── children.ts │ ├── clone-element.ts │ ├── component.ts │ ├── create-context.ts │ ├── create-element.ts │ ├── create-ref.ts │ ├── current-owner.ts │ ├── dom.ts │ ├── emiter.ts │ ├── event.ts │ ├── fragment.ts │ ├── full-component.ts │ ├── hooks.ts │ ├── hydrate.ts │ ├── index.ts │ ├── lifecycle.ts │ ├── memo.ts │ ├── options.ts │ ├── passive-event.ts │ ├── prop-types.ts │ ├── pure-component.ts │ ├── render-queue.ts │ ├── render.ts │ ├── vdom │ ├── create-element.ts │ ├── create-portal.ts │ ├── create-vnode.ts │ ├── create-void.ts │ ├── create-vtext.ts │ ├── h.ts │ ├── patch.ts │ ├── ref.ts │ ├── svg-property-config.ts │ └── unmount.ts │ └── version.ts ├── release.js ├── tsconfig.json ├── tslint.json ├── typing.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "spec": true, 5 | "useBuiltIns": false 6 | }], ["stage-0"], ["es3"] 7 | ], 8 | "plugins": [ 9 | [ 10 | "transform-react-jsx", 11 | { 12 | "pragma": "createElement" 13 | } 14 | ], 15 | [ 16 | "transform-runtime", 17 | { 18 | "helpers": false, 19 | "polyfill": false, 20 | "regenerator": true, 21 | "moduleName": "babel-runtime" 22 | } 23 | ] 24 | ] 25 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | benchmarks/* 3 | lib/* 4 | build/build.js 5 | devtools.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['standard', 'standard-jsx'], 3 | ecmaFeatures: { 4 | classes: true 5 | }, 6 | env: { 7 | browser: true, 8 | node: true, 9 | es6: true 10 | }, 11 | rules: { 12 | 'no-unused-expressions': 0, 13 | 'prefer-const': ['error'] 14 | }, 15 | globals: { 16 | describe: true, 17 | it: true, 18 | beforeAll: true, 19 | beforeEach: true, 20 | afterEach: true, 21 | afterAll: true, 22 | expect: true, 23 | jasmine: true, 24 | jest: true 25 | }, 26 | parser: 'babel-eslint' 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | lib 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | build/build.js 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 29 | node_modules 30 | .DS_Store 31 | dist 32 | 33 | # editor config 34 | .vscode/ 35 | .npm-debug.log 36 | 37 | # istanbul 38 | coverage 39 | 40 | # Rollup 41 | .rpt2_cache 42 | yarn-error.log 43 | 44 | # Additional bundles 45 | /devtools.js 46 | /devtools.js.map 47 | /debug.js 48 | /debug.js.map 49 | devtools/*.d.ts -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | 4 | node_js: 5 | - "8" 6 | 7 | cache: 8 | directories: 9 | - node_modules 10 | 11 | # Make chrome browser available for testing 12 | before_install: 13 | - export CHROME_BIN=chromium-browser 14 | - export DISPLAY=:99.0 15 | # - sh -e /etc/init.d/xvfb start 16 | 17 | install: 18 | - npm install -g yarn 19 | - yarn 20 | 21 | script: 22 | - npm run build 23 | - npm run test:coverage 24 | - BROWSER=true COVERAGE=false FLAKEY=false npm run test:karma 25 | - ./node_modules/coveralls/bin/coveralls.js < ./coverage/lcov.info 26 | 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | 7 | ## [1.3.2-beta.0](https://github.com/NervJS/nerv/compare/v1.3.1...v1.3.2-beta.0) (2018-07-13) 8 | 9 | 10 | ### Bug Fixes 11 | 12 | * **nerv:** fix the buggy behavior of modifying context when calling getChildContext ([56911c6](https://github.com/NervJS/nerv/commit/56911c6)) 13 | 14 | 15 | 16 | 17 | 18 | ## [1.3.1](https://github.com/NervJS/nerv/compare/v1.3.0-beta.0...v1.3.1) (2018-06-29) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * set empty methods to propTypes ([1dbbdd0](https://github.com/NervJS/nerv/commit/1dbbdd0)) 24 | 25 | 26 | 27 | 28 | 29 | # [1.3.0-beta.0](https://github.com/NervJS/nerv/compare/v1.2.18...v1.3.0-beta.0) (2018-05-31) 30 | 31 | 32 | ### Features 33 | 34 | * export PropTypes as top-level API ([f45010f](https://github.com/NervJS/nerv/commit/f45010f)) 35 | 36 | 37 | 38 | 39 | 40 | ## [1.2.17](https://github.com/NervJS/nerv/compare/v1.2.16...v1.2.17) (2018-04-01) 41 | 42 | 43 | 44 | 45 | **Note:** Version bump only for package nerv-build 46 | 47 | 48 | ## [1.2.16](https://github.com/NervJS/nerv/compare/v1.2.15...v1.2.16) (2018-03-26) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * fix cloneElement's bug ([dbceca8](https://github.com/NervJS/nerv/commit/dbceca8)) 54 | * patch void element lost dom reference ([32f403d](https://github.com/NervJS/nerv/commit/32f403d)) 55 | 56 | 57 | 58 | 59 | 60 | ## [1.2.15](https://github.com/NervJS/nerv/compare/v1.2.14...v1.2.15) (2018-03-15) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * cloneElement should clone children as well ([682fe15](https://github.com/NervJS/nerv/commit/682fe15)) 66 | * Stateless component should support function returning null. ([386f07a](https://github.com/NervJS/nerv/commit/386f07a)) 67 | * **nerv-build:** fix error during installing nerv-build on windows ([6f9caf6](https://github.com/NervJS/nerv/commit/6f9caf6)) 68 | * **test:** add test for commit 386f07a and 682fe15. ([f307fec](https://github.com/NervJS/nerv/commit/f307fec)) 69 | * **test:** normalize html and format the test file ([a0de170](https://github.com/NervJS/nerv/commit/a0de170)) 70 | 71 | 72 | 73 | 74 | 75 | ## [1.2.14](https://github.com/NervJS/nerv/compare/v1.2.14-beta.0...v1.2.14) (2018-03-08) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * as per React, functional ref should also detach ([9338a0a](https://github.com/NervJS/nerv/commit/9338a0a)) 81 | * cloneElement should clone void vnode as well ([b007de9](https://github.com/NervJS/nerv/commit/b007de9)) 82 | * findDOMNode should return null while input is invalid ([76bbec5](https://github.com/NervJS/nerv/commit/76bbec5)) 83 | * patch do nothing when lastVnode and nextVnode are both Portal ([cd99801](https://github.com/NervJS/nerv/commit/cd99801)) 84 | * ref should exec after componentDidMount hook ([7bf5efa](https://github.com/NervJS/nerv/commit/7bf5efa)) 85 | * ref should exeute before render() function ([a4cb918](https://github.com/NervJS/nerv/commit/a4cb918)) 86 | 87 | 88 | 89 | 90 | 91 | ## [1.2.13-beta.0](https://github.com/NervJS/nerv/compare/v1.2.12...v1.2.13-beta.0) (2018-02-26) 92 | 93 | 94 | ### Bug Fixes 95 | 96 | * unkeyed children diffing error. close [#40](https://github.com/NervJS/nerv/issues/40). ([218563a](https://github.com/NervJS/nerv/commit/218563a)) 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Li,Weitao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /afterbuild.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const cp = require('child_process') 4 | const isCI = require('is-ci') 5 | 6 | const karmaPath = path.join(__dirname, 'node_modules', 'karma') 7 | 8 | function upgradeKarma () { 9 | if (!isCI) { 10 | return 11 | } 12 | console.log(`you are using socket.io version under 2.x, upgrading now...`) 13 | cp.execSync('npm install socket.io@2.0.3', { 14 | cwd: karmaPath 15 | }) 16 | } 17 | 18 | try { 19 | const { version } = JSON.parse( 20 | fs.readFileSync( 21 | path.join(karmaPath, 'node_modules', 'socket.io', 'package.json') 22 | ) 23 | ) 24 | if (parseInt(version, 10) < 2) { 25 | upgradeKarma() 26 | } 27 | } catch (e) { 28 | upgradeKarma() 29 | } 30 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Node.js 2 | # Build a general Node.js project with npm. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'ubuntu-latest' 11 | 12 | steps: 13 | - task: NodeTool@0 14 | inputs: 15 | versionSpec: '8.x' 16 | displayName: 'Install Node.js' 17 | 18 | - script: | 19 | npm install -g yarn 20 | yarn 21 | npm run build 22 | displayName: 'npm install and build' 23 | 24 | - script: | 25 | npm run test:coverage 26 | displayName: 'testing' -------------------------------------------------------------------------------- /benchmarks/DBMonster/app.js: -------------------------------------------------------------------------------- 1 | const { createElement, Component, render } = require('nervjs') 2 | 3 | class Query extends Component { 4 | shouldComponentUpdate({ query, elapsed }) { 5 | return query !== this.props.query || elapsed !== this.props.elapsed 6 | } 7 | 8 | render() { 9 | const { query, elapsed, formatElapsed, elapsedClassName } = this.props 10 | return ( 11 | 12 | {formatElapsed || ' '} 13 |
14 |
{query}
15 |
16 |
17 | 18 | ) 19 | } 20 | } 21 | 22 | class Database extends Component { 23 | shouldComponentUpdate({ lastMutationId }) { 24 | return lastMutationId !== this.props.lastMutationId 25 | } 26 | 27 | renderQuery(query) { 28 | return ( 29 | 35 | ) 36 | } 37 | 38 | render() { 39 | const { lastSample, dbname } = this.props 40 | return ( 41 | 42 | {dbname} 43 | 44 | {lastSample.nbQueries} 45 | 46 | {lastSample.topFiveQueries.map(this.renderQuery)} 47 | 48 | ) 49 | } 50 | } 51 | 52 | class DBMon extends Component { 53 | state = { 54 | databases: [] 55 | } 56 | 57 | loadSamples() { 58 | this.setState({ 59 | databases: ENV.generateData(true).toArray() 60 | }) 61 | Monitoring.renderRate.ping() 62 | setTimeout(::this.loadSamples, ENV.timeout) 63 | } 64 | 65 | componentDidMount() { 66 | this.loadSamples() 67 | } 68 | 69 | renderDatabase(database) { 70 | return ( 71 | 77 | ) 78 | } 79 | 80 | render() { 81 | const { databases } = this.state 82 | return ( 83 |
84 | 85 | {databases.map(this.renderDatabase)} 86 |
87 |
88 | ) 89 | } 90 | } 91 | 92 | render(, document.getElementById('dbmon')) 93 | -------------------------------------------------------------------------------- /benchmarks/DBMonster/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | dbmon (Nerv) 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /benchmarks/DBMonster/memory-stats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author jetienne / http://jetienne.com/ 4 | * @author paulirish / http://paulirish.com/ 5 | */ 6 | var MemoryStats = function() { 7 | var msMin = 100 8 | var msMax = 0 9 | 10 | var container = document.createElement('div') 11 | container.id = 'stats' 12 | container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer' 13 | 14 | var msDiv = document.createElement('div') 15 | msDiv.id = 'ms' 16 | msDiv.style.cssText = 17 | 'padding:0 0 3px 3px;text-align:left;background-color:#020;' 18 | container.appendChild(msDiv) 19 | 20 | var msText = document.createElement('div') 21 | msText.id = 'msText' 22 | msText.style.cssText = 23 | 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px' 24 | msText.innerHTML = 'Memory' 25 | msDiv.appendChild(msText) 26 | 27 | var msGraph = document.createElement('div') 28 | msGraph.id = 'msGraph' 29 | msGraph.style.cssText = 30 | 'position:relative;width:74px;height:30px;background-color:#0f0' 31 | msDiv.appendChild(msGraph) 32 | 33 | while (msGraph.children.length < 74) { 34 | var bar = document.createElement('span') 35 | bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131' 36 | msGraph.appendChild(bar) 37 | } 38 | 39 | var updateGraph = function(dom, height, color) { 40 | var child = dom.appendChild(dom.firstChild) 41 | child.style.height = height + 'px' 42 | if (color) child.style.backgroundColor = color 43 | } 44 | 45 | var perf = window.performance || {} 46 | // polyfill usedJSHeapSize 47 | if (!perf && !perf.memory) { 48 | perf.memory = { usedJSHeapSize: 0 } 49 | } 50 | if (perf && !perf.memory) { 51 | perf.memory = { usedJSHeapSize: 0 } 52 | } 53 | 54 | // support of the API? 55 | if (perf.memory.totalJSHeapSize === 0) { 56 | console.warn( 57 | 'totalJSHeapSize === 0... performance.memory is only available in Chrome .' 58 | ) 59 | } 60 | 61 | // TODO, add a sanity check to see if values are bucketed. 62 | // If so, reminde user to adopt the --enable-precise-memory-info flag. 63 | // open -a "/Applications/Google Chrome.app" --args --enable-precise-memory-info 64 | 65 | var self = this 66 | var memory = 0 67 | var lastTime = Date.now() 68 | var lastUsedHeap = perf.memory.usedJSHeapSize 69 | return { 70 | domElement: container, 71 | 72 | update: function() { 73 | // refresh only 30time per second 74 | if (Date.now() - lastTime < 1000 / 30) return 75 | lastTime = Date.now() 76 | 77 | var delta = perf.memory.usedJSHeapSize - lastUsedHeap 78 | lastUsedHeap = perf.memory.usedJSHeapSize 79 | var color = delta < 0 ? '#830' : '#131' 80 | 81 | var ms = perf.memory.usedJSHeapSize 82 | msMin = Math.min(msMin, ms) 83 | msMax = Math.max(msMax, ms) 84 | 85 | self.memory = bytesToSize(ms, 2) 86 | msText.textContent = 'Mem: ' + self.memory 87 | 88 | var normValue = ms / (30 * 1024 * 1024) 89 | var height = Math.min(30, 30 - normValue * 30) 90 | updateGraph(msGraph, height, color) 91 | 92 | function bytesToSize(bytes, nFractDigit) { 93 | var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] 94 | if (bytes == 0) return 'n/a' 95 | nFractDigit = nFractDigit !== undefined ? nFractDigit : 0 96 | var precision = Math.pow(10, nFractDigit) 97 | var i = Math.floor(Math.log(bytes) / Math.log(1024)) 98 | return ( 99 | Math.round(bytes * precision / Math.pow(1024, i)) / precision + 100 | ' ' + 101 | sizes[i] 102 | ) 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /benchmarks/DBMonster/monitor.js: -------------------------------------------------------------------------------- 1 | var Monitoring = 2 | Monitoring || 3 | (function() { 4 | var stats = new MemoryStats() 5 | stats.domElement.style.position = 'fixed' 6 | stats.domElement.style.right = '0px' 7 | stats.domElement.style.bottom = '0px' 8 | document.body.appendChild(stats.domElement) 9 | requestAnimationFrame(function rAFloop() { 10 | stats.update() 11 | requestAnimationFrame(rAFloop) 12 | }) 13 | 14 | var RenderRate = function() { 15 | var container = document.createElement('div') 16 | container.id = 'stats' 17 | container.style.cssText = 18 | 'width:150px;opacity:0.9;cursor:pointer;position:fixed;right:80px;bottom:0px;' 19 | 20 | var msDiv = document.createElement('div') 21 | msDiv.id = 'ms' 22 | msDiv.style.cssText = 23 | 'padding:0 0 3px 3px;text-align:left;background-color:#020;' 24 | container.appendChild(msDiv) 25 | 26 | var msText = document.createElement('div') 27 | msText.id = 'msText' 28 | msText.style.cssText = 29 | 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px' 30 | msText.innerHTML = 'Repaint rate: 0/sec' 31 | msDiv.appendChild(msText) 32 | 33 | var self = this 34 | var rate = 0 35 | var bucketSize = 20 36 | var bucket = [] 37 | var lastTime = Date.now() 38 | return { 39 | domElement: container, 40 | ping: function() { 41 | var start = lastTime 42 | var stop = Date.now() 43 | var rate = 1000 / (stop - start) 44 | if (rate == Infinity) { 45 | return 46 | } 47 | bucket.push(rate) 48 | if (bucket.length > bucketSize) { 49 | bucket.shift() 50 | } 51 | var sum = 0 52 | for (var i = 0; i < bucket.length; i++) { 53 | sum = sum + bucket[i] 54 | } 55 | self.rate = sum / bucket.length 56 | msText.textContent = 'Repaint rate: ' + self.rate.toFixed(2) + '/sec' 57 | lastTime = stop 58 | }, 59 | rate: function() { 60 | return self.rate 61 | } 62 | } 63 | } 64 | 65 | var renderRate = new RenderRate() 66 | document.body.appendChild(renderRate.domElement) 67 | 68 | return { 69 | memoryStats: stats, 70 | renderRate: renderRate 71 | } 72 | })() 73 | -------------------------------------------------------------------------------- /benchmarks/DBMonster/readme.md: -------------------------------------------------------------------------------- 1 | > DB Monster benchmark 2 | 3 | [Online benchmark](https://nervjs.github.io/nerv/benchmarks/DBMonster/) 4 | 5 | Visit [js-repaint-perfs](https://github.com/mathieuancelin/js-repaint-perfs/) for more details and usage. 6 | -------------------------------------------------------------------------------- /benchmarks/DBMonster/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #333; 3 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | font-size: 14px; 5 | line-height: 1.42857143; 6 | margin: 0; 7 | } 8 | 9 | label { 10 | display: inline-block; 11 | font-weight: 700; 12 | margin-bottom: 5px; 13 | } 14 | 15 | input[type=range] { 16 | display: block; 17 | width: 100%; 18 | } 19 | 20 | table { 21 | border-collapse: collapse; 22 | border-spacing: 0; 23 | } 24 | 25 | :before, 26 | :after { 27 | box-sizing: border-box; 28 | } 29 | 30 | .table>thead>tr>th, 31 | .table>tbody>tr>th, 32 | .table>tfoot>tr>th, 33 | .table>thead>tr>td, 34 | .table>tbody>tr>td, 35 | .table>tfoot>tr>td { 36 | border-top: 1px solid #ddd; 37 | line-height: 1.42857143; 38 | padding: 8px; 39 | vertical-align: top; 40 | } 41 | 42 | .table { 43 | width: 100%; 44 | } 45 | 46 | .table-striped>tbody>tr:nth-child(odd)>td, 47 | .table-striped>tbody>tr:nth-child(odd)>th { 48 | background: #f9f9f9; 49 | } 50 | 51 | .label { 52 | border-radius: .25em; 53 | color: #fff; 54 | display: inline; 55 | font-size: 75%; 56 | font-weight: 700; 57 | line-height: 1; 58 | padding: .2em .6em .3em; 59 | text-align: center; 60 | vertical-align: baseline; 61 | white-space: nowrap; 62 | } 63 | 64 | .label-success { 65 | background-color: #5cb85c; 66 | } 67 | 68 | .label-warning { 69 | background-color: #f0ad4e; 70 | } 71 | 72 | .popover { 73 | background-color: #fff; 74 | background-clip: padding-box; 75 | border: 1px solid #ccc; 76 | border: 1px solid rgba(0, 0, 0, .2); 77 | border-radius: 6px; 78 | box-shadow: 0 5px 10px rgba(0, 0, 0, .2); 79 | display: none; 80 | left: 0; 81 | max-width: 276px; 82 | padding: 1px; 83 | position: absolute; 84 | text-align: left; 85 | top: 0; 86 | white-space: normal; 87 | z-index: 1010; 88 | } 89 | 90 | .popover>.arrow:after { 91 | border-width: 10px; 92 | content: ""; 93 | } 94 | 95 | .popover.left { 96 | margin-left: -10px; 97 | } 98 | 99 | .popover.left>.arrow { 100 | border-right-width: 0; 101 | border-left-color: rgba(0, 0, 0, .25); 102 | margin-top: -11px; 103 | right: -11px; 104 | top: 50%; 105 | } 106 | 107 | .popover.left>.arrow:after { 108 | border-left-color: #fff; 109 | border-right-width: 0; 110 | bottom: -10px; 111 | content: " "; 112 | right: 1px; 113 | } 114 | 115 | .popover>.arrow { 116 | border-width: 11px; 117 | } 118 | 119 | .popover>.arrow, 120 | .popover>.arrow:after { 121 | border-color: transparent; 122 | border-style: solid; 123 | display: block; 124 | height: 0; 125 | position: absolute; 126 | width: 0; 127 | } 128 | 129 | .popover-content { 130 | padding: 9px 14px; 131 | } 132 | 133 | .Query { 134 | position: relative; 135 | } 136 | 137 | .Query:hover .popover { 138 | display: block; 139 | left: -100%; 140 | width: 100%; 141 | } 142 | 143 | -------------------------------------------------------------------------------- /benchmarks/DBMonster/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | module.exports = { 5 | resolve: { 6 | alias: { 7 | nervjs: path.join( 8 | __dirname, 9 | '..', 10 | '..', 11 | './packages', 12 | 'nerv', 13 | 'dist', 14 | 'nerv.min.js' 15 | ) 16 | }, 17 | extensions: ['.js', '.jsx'] 18 | }, 19 | entry: { 20 | main: path.join(__dirname, 'app.js') 21 | }, 22 | output: { 23 | path: path.join(__dirname), 24 | filename: 'build.js' 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.js$/, 30 | loader: 'babel-loader', 31 | exclude: /node_modules/, 32 | options: { 33 | presets: [['env', { 34 | 'targets': { 35 | 'browsers': ['last 2 versions', 'safari >= 7'] 36 | } 37 | }], 'stage-0'], 38 | plugins: [ 39 | [ 40 | 'transform-react-jsx', 41 | { 42 | pragma: 'createElement' 43 | } 44 | ] 45 | ] 46 | } 47 | } 48 | ] 49 | }, 50 | plugins: [ 51 | new webpack.DefinePlugin({ 52 | 'process.env': { NODE_ENV: JSON.stringify('production') } 53 | }), 54 | new webpack.optimize.UglifyJsPlugin() 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /benchmarks/isomorphic-ui-benchmarks/README.md: -------------------------------------------------------------------------------- 1 | Please go [here](https://github.com/NervJS/isomorphic-ui-benchmarks), then clone the repo, run `npm install`, `npm start`, `npm run benchmark`. -------------------------------------------------------------------------------- /benchmarks/js-framwork-bench/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "react"], 3 | "plugins": [] 4 | } -------------------------------------------------------------------------------- /benchmarks/js-framwork-bench/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nerv 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /benchmarks/js-framwork-bench/readme.md: -------------------------------------------------------------------------------- 1 | > js-framework-bench 2 | 3 | [Third party Online result](https://rawgit.com/krausest/js-framework-benchmark/master/webdriver-ts-results/table.html) 4 | 5 | Visit [uibench](https://github.com/krausest/js-framework-benchmark) for more details and usage. 6 | -------------------------------------------------------------------------------- /benchmarks/js-framwork-bench/src/Store.es6.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _random(max) { 4 | return Math.round(Math.random()*1000)%max; 5 | } 6 | 7 | export class Store { 8 | constructor() { 9 | this.data = []; 10 | this.selected = undefined; 11 | this.id = 1; 12 | } 13 | buildData(count = 1000) { 14 | var adjectives = ["pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain", "quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd", "unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"]; 15 | var colours = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"]; 16 | var nouns = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"]; 17 | var data = []; 18 | for (var i = 0; i < count; i++) 19 | data.push({id: this.id++, label: adjectives[_random(adjectives.length)] + " " + colours[_random(colours.length)] + " " + nouns[_random(nouns.length)] }); 20 | return data; 21 | } 22 | updateData(mod = 10) { 23 | for (let i=0;i d.id === id); 29 | this.data.splice(idx, 1); 30 | } 31 | run() { 32 | this.data = this.buildData(); 33 | this.selected = undefined; 34 | } 35 | add() { 36 | this.data = this.data.concat(this.buildData(1000)); 37 | } 38 | update() { 39 | this.updateData(); 40 | } 41 | select(id) { 42 | this.selected = id; 43 | } 44 | runLots() { 45 | this.data = this.buildData(10000); 46 | this.selected = undefined; 47 | } 48 | clear() { 49 | this.data = []; 50 | this.selected = undefined; 51 | } 52 | swapRows() { 53 | if(this.data.length > 10) { 54 | var a = this.data[4]; 55 | this.data[4] = this.data[9]; 56 | this.data[9] = a; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /benchmarks/js-framwork-bench/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require("babel-plugin-syntax-jsx") 3 | 4 | var cache = {}; 5 | var loaders = [ 6 | { 7 | test: /\.jsx$/, 8 | loader: 'babel-loader' 9 | }, 10 | { 11 | test: /\.es6\.js$/, 12 | loader: 'babel-loader' 13 | }, 14 | { 15 | test: /\.css$/, 16 | loader: 'style-loader!css-loader' 17 | } 18 | ]; 19 | var extensions = [ 20 | '', '.js', '.jsx', '.es6.js', '.msx' 21 | ]; 22 | 23 | module.exports = [{ 24 | cache: cache, 25 | module: { 26 | loaders: loaders 27 | }, 28 | entry: { 29 | main: './src/Main.jsx', 30 | }, 31 | output: { 32 | path: './dist', 33 | filename: '[name].js', 34 | sourceMapFilename: "[file].map", 35 | }, 36 | resolve: { 37 | extensions: extensions, 38 | root: [ 39 | __dirname, 40 | __dirname + '/src' 41 | ] 42 | } 43 | }]; -------------------------------------------------------------------------------- /benchmarks/uibench/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var Nerv = require('nervjs') 4 | 5 | function TableCell (_ref) { 6 | var text = _ref.text 7 | 8 | return Nerv.createElement( 9 | 'td', 10 | { className: 'TableCell', onClick: onClick(text) }, 11 | text 12 | ) 13 | } 14 | 15 | function onClick (text) { 16 | return function (e) { 17 | console.log('Clicked' + text) 18 | e.stopPropagation() 19 | } 20 | } 21 | 22 | function shouldCellUpdate (p, c) { 23 | return p.text !== c.text 24 | } 25 | 26 | function TableRow (_ref2) { 27 | var data = _ref2.data 28 | 29 | var classes = data.active ? 'TableRow active' : 'TableRow' 30 | 31 | var cells = data.props 32 | 33 | var children = [] 34 | for (var i = 0; i < cells.length; i++) { 35 | // Key is used because Nerv prints warnings that there should be a key, libraries that can detect that children 36 | // shape isn't changing should render cells without keys. 37 | children.push( 38 | Nerv.createElement(TableCell, { 39 | key: i, 40 | text: cells[i], 41 | onShouldComponentUpdate: shouldCellUpdate 42 | }) 43 | ) 44 | } 45 | 46 | return Nerv.createElement( 47 | 'tr', 48 | { className: classes, 'data-id': data.id }, 49 | Nerv.createElement(TableCell, { 50 | text: '#' + data.id, 51 | onShouldComponentUpdate: isDataChanged 52 | }), 53 | children 54 | ) 55 | } 56 | 57 | function isDataChanged (p, c) { 58 | return p.data !== c.data 59 | } 60 | 61 | function Table (_ref3) { 62 | var data = _ref3.data 63 | 64 | var items = data.items 65 | 66 | var children = [] 67 | for (var i = 0; i < items.length; i++) { 68 | var item = items[i] 69 | children.push( 70 | Nerv.createElement(TableRow, { 71 | key: item.id, 72 | data: item, 73 | onShouldComponentUpdate: isDataChanged 74 | }) 75 | ) 76 | } 77 | 78 | return Nerv.createElement( 79 | 'table', 80 | { className: 'Table' }, 81 | Nerv.createElement('tbody', null, children) 82 | ) 83 | } 84 | 85 | function AnimBox (_ref4) { 86 | var data = _ref4.data 87 | 88 | var time = data.time 89 | var style = { 90 | borderRadius: (time % 10).toString() + 'px', 91 | background: 'rgba(0,0,0,' + (0.5 + (time % 10) / 10).toString() + ')' 92 | } 93 | 94 | return Nerv.createElement('div', { 95 | className: 'AnimBox', 96 | 'data-id': data.id, 97 | style: style 98 | }) 99 | } 100 | 101 | function Anim (_ref5) { 102 | var data = _ref5.data 103 | 104 | var items = data.items 105 | 106 | var children = [] 107 | for (var i = 0; i < items.length; i++) { 108 | var item = items[i] 109 | children.push( 110 | Nerv.createElement(AnimBox, { 111 | key: item.id, 112 | data: item, 113 | onShouldComponentUpdate: isDataChanged 114 | }) 115 | ) 116 | } 117 | 118 | return Nerv.createElement('div', { className: 'Anim' }, children) 119 | } 120 | 121 | function TreeLeaf (_ref6) { 122 | var data = _ref6.data 123 | 124 | return Nerv.createElement('li', { className: 'TreeLeaf' }, data.id) 125 | } 126 | 127 | function TreeNode (_ref7) { 128 | var data = _ref7.data 129 | 130 | var children = [] 131 | 132 | for (var i = 0; i < data.children.length; i++) { 133 | var n = data.children[i] 134 | if (n.container) { 135 | children.push( 136 | Nerv.createElement(TreeNode, { 137 | key: n.id, 138 | data: n, 139 | onShouldComponentUpdate: isDataChanged 140 | }) 141 | ) 142 | } else { 143 | children.push( 144 | Nerv.createElement(TreeLeaf, { 145 | key: n.id, 146 | data: n, 147 | onShouldComponentUpdate: isDataChanged 148 | }) 149 | ) 150 | } 151 | } 152 | 153 | return Nerv.createElement('ul', { className: 'TreeNode' }, children) 154 | } 155 | 156 | function Tree (_ref8) { 157 | var data = _ref8.data 158 | 159 | return Nerv.createElement( 160 | 'div', 161 | { className: 'Tree' }, 162 | Nerv.createElement(TreeNode, { 163 | data: data.root, 164 | onShouldComponentUpdate: isDataChanged 165 | }) 166 | ) 167 | } 168 | 169 | class Main extends Nerv.Component { 170 | shouldComponentUpdate (nextProps, nextState) { 171 | return this.props.data !== nextProps.data 172 | } 173 | 174 | render () { 175 | const { data } = this.props 176 | const location = data.location 177 | 178 | var section 179 | if (location === 'table') { 180 | section = Nerv.createElement(Table, { data: data.table }) 181 | } else if (location === 'anim') { 182 | section = Nerv.createElement(Anim, { data: data.anim }) 183 | } else if (location === 'tree') { 184 | section = Nerv.createElement(Tree, { data: data.tree }) 185 | } 186 | 187 | return Nerv.createElement('div', { className: 'Main' }, section) 188 | } 189 | } 190 | 191 | uibench.init('Nerv', '1.2.4') 192 | 193 | document.addEventListener('DOMContentLoaded', function (e) { 194 | var container = document.querySelector('#App') 195 | 196 | uibench.run( 197 | function (state) { 198 | Nerv.render( 199 | Nerv.createElement(Main, { 200 | data: state 201 | }), 202 | container 203 | ) 204 | }, 205 | function (samples) { 206 | Nerv.render( 207 | Nerv.createElement('pre', null, JSON.stringify(samples, null, ' ')), 208 | container 209 | ) 210 | } 211 | ) 212 | }) 213 | -------------------------------------------------------------------------------- /benchmarks/uibench/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | UI Benchmark: Nerv 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /benchmarks/uibench/readme.md: -------------------------------------------------------------------------------- 1 | > UI Bench benchmark 2 | 3 | [Online benchmark](https://nervjs.github.io/uibench/build/) 4 | 5 | Visit [uibench](https://github.com/localvoid/uibench) for more details and usage. 6 | -------------------------------------------------------------------------------- /benchmarks/uibench/rollup.config.js: -------------------------------------------------------------------------------- 1 | const buble = require('rollup-plugin-buble') 2 | const uglify = require('rollup-plugin-uglify') 3 | const path = require('path') 4 | const cjs = require('rollup-plugin-commonjs') 5 | const resolve = require('rollup-plugin-node-resolve') 6 | const alias = require('rollup-plugin-alias') 7 | const babel = require('rollup-plugin-babel') 8 | 9 | module.exports = { 10 | dest: path.join(__dirname, 'build.js'), 11 | input: path.join(__dirname, 'app.js'), 12 | format: 'iife', 13 | name: 'Nerv', 14 | plugins: [ 15 | alias({ 16 | nervjs: path.join( 17 | __dirname, 18 | '..', 19 | '..', 20 | './packages', 21 | 'nerv', 22 | 'dist', 23 | 'index.esm.js' 24 | ) 25 | }), 26 | resolve(), 27 | cjs(), 28 | buble(), 29 | uglify() 30 | ], 31 | sourceMap: false, 32 | browser: true 33 | } 34 | -------------------------------------------------------------------------------- /browsers/ie8.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | ;(function () { 4 | var ua = navigator.userAgent.match(/MSIE (\d+)/) 5 | if (ua === null) { 6 | return 7 | } 8 | var ieVersion = ua[1] 9 | if (ieVersion >= 9) { 10 | return 11 | } 12 | 13 | var innerText = Object.getOwnPropertyDescriptor( 14 | Element.prototype, 15 | 'innerText' 16 | ) 17 | var nodeName = Object.getOwnPropertyDescriptor(Element.prototype, 'nodeName') 18 | 19 | Object.defineProperties(Element.prototype, { 20 | textContent: { 21 | get: function get () { 22 | return innerText.get.call(this) 23 | }, 24 | set: function set (x) { 25 | return innerText.set.call(this, x) 26 | } 27 | }, 28 | nodeName: { 29 | get: function get () { 30 | return nodeName.get.call(this).toUpperCase() 31 | } 32 | } 33 | }) 34 | 35 | // Overwrites native 'firstElementChild' prototype. 36 | // Adds Document & DocumentFragment support for IE9 & Safari. 37 | // Returns array instead of HTMLCollection. 38 | ;(function (constructor) { 39 | if ( 40 | constructor && 41 | constructor.prototype && 42 | constructor.prototype.firstElementChild == null 43 | ) { 44 | Object.defineProperty(constructor.prototype, 'firstElementChild', { 45 | get: function get () { 46 | // eslint-disable-next-line 47 | var node, 48 | nodes = this.childNodes, 49 | i = 0 50 | // eslint-disable-next-line 51 | while ((node = nodes[i++])) { 52 | if (node.nodeType === 1) { 53 | return node 54 | } 55 | } 56 | return null 57 | } 58 | }) 59 | } 60 | })(window.Node || window.Element) 61 | })() 62 | -------------------------------------------------------------------------------- /browsers/karma.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | window.it.skip = xit; 3 | window.it.skipKarma = xit; 4 | window.describe.skip = xdescribe; 5 | window.describe.skipKarma = xdescribe; 6 | // disable the test suites in IE8 7 | window.describe.ie = document.all && !document.addEventListener 8 | ? xdescribe 9 | : describe; 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var Nerv = require('./dist/nerv.js') 4 | 5 | module.exports = Nerv 6 | module.exports.default = module.exports 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coverageDirectory: 'coverage', 3 | coverageReporters: ['html', 'lcov', 'text'], 4 | collectCoverageFrom: [ 5 | 'packages/nerv/src/**/*.ts' 6 | ], 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], 8 | moduleNameMapper: { 9 | nervjs: '/packages/nerv/src', 10 | 'nerv-create-class': '/packages/nerv-create-class', 11 | 'nerv-devtools': '/packages/nerv-devtools', 12 | 'nerv-redux': '/packages/nerv-redux', 13 | 'nerv-shared': '/packages/nerv-shared', 14 | 'nerv-test-utils': '/packages/nerv-test-utils', 15 | 'nerv-utils': '/packages/nerv-utils' 16 | }, 17 | globals: { 18 | 'ts-jest': { 19 | tsConfig: { 20 | target: 'es5', 21 | removeComments: false, 22 | preserveConstEnums: true, 23 | moduleResolution: 'node', 24 | experimentalDecorators: true, 25 | noImplicitAny: false, 26 | allowSyntheticDefaultImports: true, 27 | strictNullChecks: true, 28 | noImplicitThis: true, 29 | sourceMap: true 30 | }, 31 | diagnostics: false 32 | } 33 | }, 34 | rootDir: __dirname, 35 | testMatch: [ 36 | // '/packages/nerv/__tests__/cloneElement.spec.js', 37 | // '/packages/nerv/__tests__/component.spec.js', 38 | // '/packages/nerv/__tests__/render.spec.js', 39 | // '/packages/nerv/__tests__/lifecycle.spec.js', 40 | // '/packages/nerv/__tests__/svg.spec.js' 41 | // '/packages/nerv/__tests__/event.spec.js' 42 | // '/packages/**/__tests__/**/*spec.js' 43 | '/packages/**/__tests__/**/*spec.js' 44 | ], 45 | transform: { 46 | '^.+\\.jsx?$': 'babel-jest', 47 | '^.+\\.tsx?$': 'ts-jest' 48 | }, 49 | transformIgnorePatterns: ['/node_modules/(?!lodash-es)'] 50 | } 51 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.4.0", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "command": { 7 | "publish": { 8 | "message": "chore(release): publish %s" 9 | } 10 | }, 11 | "version": "1.5.7" 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-build", 3 | "private": true, 4 | "version": "1.0.0", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "description": "A react-like framework based on virtual-dom", 9 | "scripts": { 10 | "precommit": "npm run lint && npm run lint-staged", 11 | "prepush": "npm run test", 12 | "postinstall": "lerna bootstrap", 13 | "clean": "lerna exec -- rimraf dist", 14 | "lint": "tslint -p tsconfig.json --type-check", 15 | "lint-staged": "lint-staged", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "bench:uibench": "rollup --config ./benchmarks/uibench/rollup.config.js", 19 | "bench:dbmon": "webpack --config ./benchmarks/DBMonster/webpack.config.js", 20 | "test:coverage": "jest --coverage", 21 | "test:karma": "karma start karma.conf.js --single-run", 22 | "test:karma:watch": "npm run test:karma -- no-single-run", 23 | "build": "npm run clean && lerna exec -- rollup -c && npm run build:typing && node afterbuild.js && npm run size", 24 | "build:typing": "rimraf lib && tsc && lerna exec -- node ../../typing.js", 25 | "size": "gzip-size ./packages/nerv/dist/nerv.min.js", 26 | "build:esm": "lerna exec -- rollup -c --environment TARGET:esm", 27 | "build:umd": "lerna exec -- rollup -c --environment TARGET:umd", 28 | "release": "lerna publish --exact --conventional-commits", 29 | "release:beta": "lerna publish --exact --conventional-commits --cd-version=prepatch --preid=beta --npm-tag=beta" 30 | }, 31 | "lint-staged": { 32 | "*.{js,jsx}": [ 33 | "eslint --fix", 34 | "git add" 35 | ], 36 | "*.{ts,tsx}": [ 37 | "tslint --fix", 38 | "git add" 39 | ] 40 | }, 41 | "keywords": [ 42 | "react-like" 43 | ], 44 | "repository": { 45 | "type": "git", 46 | "url": "git+https://github.com/NervJS/nerv.git" 47 | }, 48 | "author": "luckyadam", 49 | "license": "MIT", 50 | "bugs": { 51 | "url": "https://github.com/NervJS/nerv/issues" 52 | }, 53 | "homepage": "https://github.com/NervJS/nerv.git#readme", 54 | "devDependencies": { 55 | "@types/jasmine": "^2.6.0", 56 | "@types/node": "^8.0.26", 57 | "@types/sinon": "^2.3.3", 58 | "babel-core": "^6.25.0", 59 | "babel-eslint": "^7.2.3", 60 | "babel-jest": "^23.6.0", 61 | "babel-loader": "^7.1.2", 62 | "babel-plugin-external-helpers": "^6.22.0", 63 | "babel-plugin-transform-react-jsx": "^6.23.0", 64 | "babel-plugin-transform-runtime": "^6.23.0", 65 | "babel-preset-env": "^1.6.0", 66 | "babel-preset-es3": "^1.0.1", 67 | "babel-preset-stage-0": "^6.22.0", 68 | "coveralls": "^2.13.1", 69 | "dts-bundle": "^0.7.3", 70 | "es3ify-webpack-plugin": "^0.0.1", 71 | "es5-polyfill": "0.0.5", 72 | "es6-object-assign": "^1.1.0", 73 | "es6-promise": "^4.1.1", 74 | "eslint": "^3.19.0", 75 | "eslint-config-standard": "^10.2.1", 76 | "eslint-config-standard-jsx": "^4.0.2", 77 | "eslint-plugin-import": "^2.7.0", 78 | "eslint-plugin-node": "^5.1.1", 79 | "eslint-plugin-promise": "^3.5.0", 80 | "eslint-plugin-react": "^6.10.3", 81 | "eslint-plugin-standard": "^3.0.1", 82 | "gzip-size-cli": "^2.1.0", 83 | "husky": "^0.14.3", 84 | "is-ci": "^1.0.10", 85 | "jasmine-core": "^2.8.0", 86 | "jest": "^23.6.0", 87 | "karma": "^4.1.0", 88 | "karma-chrome-launcher": "^2.2.0", 89 | "karma-jasmine": "^1.1.0", 90 | "karma-jasmine-diff-reporter": "^1.1.1", 91 | "karma-jasmine-matchers": "^3.7.0", 92 | "karma-sauce-launcher": "^1.2.0", 93 | "karma-sourcemap-loader": "^0.3.7", 94 | "karma-spec-reporter": "^0.0.31", 95 | "karma-typescript": "^3.0.7", 96 | "karma-webpack": "^2.0.4", 97 | "lerna": "2.4.0", 98 | "lint-staged": "^4.0.4", 99 | "npm-run-all": "^4.0.2", 100 | "object-assign": "^4.1.1", 101 | "object.assign": "^4.1.0", 102 | "optimize-js": "^1.0.3", 103 | "prompts": "^0.1.4", 104 | "redux": "^4.0.2", 105 | "rimraf": "^2.6.1", 106 | "rollup": "^0.50.0", 107 | "rollup-plugin-alias": "^1.4.0", 108 | "rollup-plugin-babel": "^3.0.2", 109 | "rollup-plugin-buble": "^0.15.0", 110 | "rollup-plugin-commonjs": "^8.2.1", 111 | "rollup-plugin-es3": "^1.1.0", 112 | "rollup-plugin-memory": "^2.0.0", 113 | "rollup-plugin-node-resolve": "^3.0.0", 114 | "rollup-plugin-replace": "^1.1.1", 115 | "rollup-plugin-typescript2": "^0.5.2", 116 | "rollup-plugin-uglify": "^2.0.1", 117 | "simulant": "^0.2.2", 118 | "sinon": "^2.3.8", 119 | "ts-jest": "^23.10.5", 120 | "ts-loader": "^3.2.0", 121 | "tslint": "^5.7.0", 122 | "typescript": "^3.3.3333", 123 | "webpack": "^3.7.1" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /packages/nerv-create-class/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | *.swp 4 | *.yml 5 | 6 | .rpt2_cache 7 | 8 | __tests__ 9 | src 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /packages/nerv-create-class/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js') 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv-create-class/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-create-class", 3 | "version": "1.5.7", 4 | "description": "Create Nerv component in ES5.", 5 | "main": "index.js", 6 | "module": "dist/index.esm.js", 7 | "jsnext:main": "dist/index.esm.js", 8 | "typings": "dist/index.d.ts", 9 | "unpkg": "dist/nerv-create-class.js", 10 | "jsdelivr": "dist/nerv-create-class.js", 11 | "types": "dist/index.d.ts", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/NervJS/nerv.git" 15 | }, 16 | "author": "yuche", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/NervJS/nerv/issues" 20 | }, 21 | "homepage": "https://github.com/NervJS/nerv", 22 | "dependencies": { 23 | "nerv-shared": "1.4.0", 24 | "nerv-utils": "1.4.5", 25 | "nervjs": "1.5.7" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/nerv-create-class/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2') 2 | const resolve = require('rollup-plugin-node-resolve') 3 | const buble = require('rollup-plugin-buble') 4 | const uglify = require('rollup-plugin-uglify') 5 | const optimizeJs = require('optimize-js') 6 | const babel = require('rollup-plugin-babel') 7 | const es3 = require('rollup-plugin-es3') 8 | const alias = require('rollup-plugin-alias') 9 | const { join } = require('path') 10 | const cwd = __dirname 11 | 12 | function resolver (path) { 13 | return join(__dirname, path) 14 | } 15 | 16 | const optJSPlugin = { 17 | name: 'optimizeJs', 18 | transformBundle (code) { 19 | return optimizeJs(code, { 20 | sourceMap: false, 21 | sourceType: 'module' 22 | }) 23 | } 24 | } 25 | const babelPlugin = babel({ 26 | babelrc: false, 27 | presets: ['es3'] 28 | }) 29 | const uglifyPlugin = uglify({ 30 | compress: { 31 | // compress options 32 | booleans: true, 33 | dead_code: true, 34 | drop_debugger: true, 35 | unused: true 36 | }, 37 | ie8: true, 38 | parse: { 39 | // parse options 40 | html5_comments: false, 41 | shebang: false 42 | }, 43 | sourceMap: false, 44 | toplevel: false, 45 | warnings: false 46 | }) 47 | const baseConfig = { 48 | input: join(cwd, 'src/index.ts'), 49 | output: [ 50 | { 51 | file: join(cwd, 'dist/index.js'), 52 | format: 'cjs', 53 | sourcemap: true 54 | }, 55 | { 56 | file: join(cwd, 'dist/nerv-create-class.js'), 57 | format: 'umd', 58 | name: 'NervCreateClass', 59 | sourcemap: true 60 | } 61 | ], 62 | external: ['nervjs'], 63 | plugins: [ 64 | alias({ 65 | 'nerv-shared': join(cwd, '../nerv-shared/dist/index'), 66 | 'nerv-utils': join(cwd, '../nerv-utils/dist/index') 67 | }), 68 | resolve(), 69 | typescript({ 70 | tsconfig: resolver('../../tsconfig.json'), 71 | typescript: require('typescript') 72 | }), 73 | buble(), 74 | babelPlugin, 75 | es3(['defineProperty', 'freeze']) 76 | ] 77 | } 78 | const esmConfig = Object.assign({}, baseConfig, { 79 | output: Object.assign({}, baseConfig.output, { 80 | sourcemap: true, 81 | format: 'es', 82 | file: join(cwd, 'dist/index.esm.js') 83 | }) 84 | }) 85 | const productionConfig = Object.assign({}, baseConfig, { 86 | output: { 87 | format: 'umd', 88 | file: join(cwd, 'dist/nerv-create-class.min.js'), 89 | name: 'NervCreateClass', 90 | sourcemap: false 91 | }, 92 | plugins: baseConfig.plugins.concat([uglifyPlugin, optJSPlugin]) 93 | }) 94 | 95 | function rollup () { 96 | const target = process.env.TARGET 97 | 98 | if (target === 'umd') { 99 | return baseConfig 100 | } else if (target === 'esm') { 101 | return esmConfig 102 | } else { 103 | return [baseConfig, esmConfig, productionConfig] 104 | } 105 | } 106 | module.exports = rollup() 107 | -------------------------------------------------------------------------------- /packages/nerv-create-class/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Component } from 'nervjs' 2 | import { isNullOrUndef, ComponentLifecycle } from 'nerv-shared' 3 | import { isFunction, isUndefined } from 'nerv-utils' 4 | 5 | export interface Mixin extends ComponentLifecycle { 6 | statics?: { 7 | [key: string]: any 8 | } 9 | mixins?: any 10 | 11 | displayName?: string 12 | propTypes?: { [index: string]: Function } 13 | 14 | getDefaultProps? (): P 15 | getInitialState? (): S 16 | } 17 | 18 | export interface ComponentClass extends Mixin { 19 | propTypes?: {} 20 | contextTypes?: {} 21 | childContextTypes?: {} 22 | defaultProps?: P 23 | displayName?: string 24 | new (props?: P, context?: any): Component 25 | } 26 | 27 | export interface ComponentSpec extends Mixin { 28 | [propertyName: string]: any 29 | render (props?, context?): any 30 | } 31 | 32 | export interface ClassicComponent extends Component { 33 | replaceState (nextState: S, callback?: () => any): void 34 | isMounted (): boolean 35 | getInitialState? (): S 36 | } 37 | 38 | export interface ClassicComponentClass extends ComponentClass { 39 | new (props?: P, context?: any): ClassicComponent 40 | getDefaultProps? (): P 41 | } 42 | 43 | // don't autobind these methods since they already have guaranteed context. 44 | const AUTOBIND_BLACKLIST = { 45 | constructor: 1, 46 | render: 1, 47 | shouldComponentUpdate: 1, 48 | // tslint:disable-next-line:object-literal-sort-keys 49 | componentWillUpdate: 1, 50 | componentWillReceiveProps: 1, 51 | componentDidUpdate: 1, 52 | componentWillMount: 1, 53 | componentDidMount: 1, 54 | componentWillUnmount: 1, 55 | componentDidUnmount: 1, 56 | getDerivedStateFromProps: 1 57 | } 58 | 59 | function extend (base, props) { 60 | for (const key in props) { 61 | if (!isNullOrUndef(props[key])) { 62 | base[key] = props[key] 63 | } 64 | } 65 | return base 66 | } 67 | 68 | function bindAll (ctx: Component) { 69 | for (const i in ctx) { 70 | const v = ctx[i] 71 | if (typeof v === 'function' && !v.__bound && AUTOBIND_BLACKLIST[i] !== 1) { 72 | (ctx[i] = v.bind(ctx)).__bound = true 73 | } 74 | } 75 | } 76 | 77 | function collateMixins (mixins: Function[] | any[], keyed = {}): any { 78 | for (let i = 0, len = mixins.length; i < len; i++) { 79 | const mixin = mixins[i] 80 | 81 | if (mixin.mixins) { 82 | // Recursively collate sub-mixins 83 | collateMixins(mixin.mixins, keyed) 84 | } 85 | 86 | for (const key in mixin as Function[]) { 87 | if (mixin.hasOwnProperty(key) && typeof mixin[key] === 'function') { 88 | (keyed[key] || (keyed[key] = [])).push(mixin[key]) 89 | } 90 | } 91 | } 92 | return keyed 93 | } 94 | 95 | function multihook (hooks: Function[], mergeFn?: Function): any { 96 | return function () { 97 | let ret 98 | 99 | for (let i = 0, len = hooks.length; i < len; i++) { 100 | const hook = hooks[i] 101 | const r = hook.apply(this, arguments) 102 | 103 | if (mergeFn) { 104 | ret = mergeFn(ret, r) 105 | } else if (!isUndefined(r)) { 106 | ret = r 107 | } 108 | } 109 | 110 | return ret 111 | } 112 | } 113 | 114 | function mergeNoDupes (previous: any, current: any) { 115 | if (!isUndefined(current)) { 116 | if (!previous) { 117 | previous = {} 118 | } 119 | 120 | for (const key in current) { 121 | if (current.hasOwnProperty(key)) { 122 | if (previous.hasOwnProperty(key)) { 123 | throw new Error( 124 | `Mixins return duplicate key ${key} in their return values` 125 | ) 126 | } 127 | 128 | previous[key] = current[key] 129 | } 130 | } 131 | } 132 | return previous 133 | } 134 | 135 | function applyMixin ( 136 | key: string, 137 | inst: Component, 138 | mixin: Function[] 139 | ): void { 140 | const hooks = isUndefined(inst[key]) ? mixin : mixin.concat(inst[key]) 141 | inst[key] = 142 | key === 'getDefaultProps' || 143 | key === 'getInitialState' || 144 | key === 'getChildContext' 145 | ? multihook(hooks, mergeNoDupes) 146 | : multihook(hooks) 147 | } 148 | 149 | function applyMixins (Cl: any, mixins: Function[] | any[]) { 150 | for (const key in mixins) { 151 | if (mixins.hasOwnProperty(key)) { 152 | const mixin = mixins[key] 153 | 154 | const inst = key === 'getDefaultProps' ? Cl : Cl.prototype 155 | 156 | if (isFunction(mixin[0])) { 157 | applyMixin(key, inst, mixin) 158 | } else { 159 | inst[key] = mixin 160 | } 161 | } 162 | } 163 | } 164 | 165 | export default function createClass ( 166 | obj: ComponentSpec 167 | ): ClassicComponentClass { 168 | class BoundClass extends Component { 169 | static defaultProps 170 | static displayName = obj.displayName || 'Component' 171 | static propTypes = obj.propTypes 172 | static mixins = obj.mixins && collateMixins(obj.mixins) 173 | static getDefaultProps = obj.getDefaultProps 174 | 175 | constructor (props, context) { 176 | super(props, context) 177 | bindAll(this) 178 | if (this.getInitialState) { 179 | this.state = this.getInitialState() 180 | } 181 | } 182 | 183 | getInitialState? (): S 184 | 185 | replaceState (nextState: S, callback?: () => any) { 186 | this.setState(nextState, callback) 187 | } 188 | 189 | isMounted (): boolean { 190 | return !this.dom 191 | } 192 | } 193 | 194 | extend(BoundClass.prototype, obj) 195 | 196 | if (obj.statics) { 197 | extend(BoundClass, obj.statics) 198 | } 199 | 200 | if (obj.mixins) { 201 | applyMixins(BoundClass, collateMixins(obj.mixins)) 202 | } 203 | 204 | BoundClass.defaultProps = isUndefined(BoundClass.getDefaultProps) 205 | ? undefined 206 | : BoundClass.getDefaultProps() 207 | 208 | return BoundClass 209 | } 210 | -------------------------------------------------------------------------------- /packages/nerv-devtools/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | *.swp 4 | *.yml 5 | 6 | .rpt2_cache 7 | 8 | __tests__ 9 | src 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /packages/nerv-devtools/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js').default 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv-devtools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-devtools", 3 | "version": "1.5.7", 4 | "description": "Devtools for Nerv.js", 5 | "main": "dist/index.esm.js", 6 | "module": "dist/index.esm.js", 7 | "typings": "./dist/index.d.ts", 8 | "keywords": [ 9 | "devtools", 10 | "nerv", 11 | "nervjs" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/NervJS/nerv.git" 16 | }, 17 | "author": "yuche", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/NervJS/nerv/issues" 21 | }, 22 | "homepage": "https://github.com/NervJS/nerv", 23 | "dependencies": { 24 | "nerv-shared": "1.4.0", 25 | "nerv-utils": "1.4.5", 26 | "nervjs": "1.5.7" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/nerv-devtools/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2') 2 | const alias = require('rollup-plugin-alias') 3 | const { join } = require('path') 4 | function resolver (path) { 5 | return join(__dirname, path) 6 | } 7 | const cwd = process.cwd() 8 | module.exports = { 9 | input: resolver('src/index.ts'), 10 | output: [ 11 | { 12 | sourcemap: true, 13 | format: 'cjs', 14 | file: resolver('dist/index.js') 15 | }, 16 | { 17 | sourcemap: true, 18 | format: 'es', 19 | file: resolver('dist/index.esm.js') 20 | } 21 | ], 22 | external: ['nervjs'], 23 | globals: { 24 | nervjs: 'Nerv' 25 | }, 26 | plugins: [ 27 | alias({ 28 | 'nerv-shared': join(cwd, '../nerv-shared/dist/index'), 29 | 'nerv-utils': join(cwd, '../nerv-utils/dist/index') 30 | }), 31 | typescript({ 32 | tsconfig: resolver('../../tsconfig.json'), 33 | typescript: require('typescript') 34 | }) 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/nerv-devtools/src/index.ts: -------------------------------------------------------------------------------- 1 | import { options } from 'nervjs' 2 | import { Renderer } from './renderer' 3 | 4 | /** 5 | * Wrap function with generic error logging 6 | * 7 | * @param {*} fn 8 | * @returns 9 | */ 10 | function catchErrors (fn) { 11 | // tslint:disable-next-line: only-arrow-functions 12 | return function (arg?) { 13 | try { 14 | return fn(arg) 15 | } catch (e) { 16 | /* istanbul ignore next */ 17 | console.error('The react devtools encountered an error') 18 | /* istanbul ignore next */ 19 | console.error(e) // eslint-disable-line no-console 20 | } 21 | } 22 | } 23 | 24 | /* istanbul ignore next */ 25 | const noop = () => undefined 26 | 27 | export function initDevTools () { 28 | const hook = (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ 29 | if (hook == null) { 30 | return 31 | } 32 | 33 | let onCommitRoot: any = noop 34 | 35 | let onCommitUnmount: any = noop 36 | 37 | // Initialize our custom renderer 38 | const rid = Math.random() 39 | .toString(16) 40 | .slice(2) 41 | const nervRenderer = new Renderer(hook, rid) 42 | 43 | catchErrors(() => { 44 | let isDev = false 45 | try { 46 | isDev = process.env.NODE_ENV !== 'production' 47 | } catch (e) { 48 | // 49 | } 50 | 51 | // Tell devtools which bundle type we run in 52 | window.parent.postMessage( 53 | { 54 | source: 'react-devtools-detector', 55 | reactBuildType: /* istanbul ignore next */ isDev 56 | ? 'development' 57 | : 'production' 58 | }, 59 | '*' 60 | ) 61 | 62 | const renderer = { 63 | bundleType: /* istanbul ignore next */ isDev ? 1 : 0, 64 | version: '16.5.2', 65 | rendererPackageName: 'nerv', 66 | // We don't need this, but the devtools `attachRenderer` function relys 67 | // it being there. 68 | findHostInstanceByFiber (vnode) { 69 | return vnode.dom 70 | }, 71 | // We don't need this, but the devtools `attachRenderer` function relys 72 | // it being there. 73 | findFiberByHostInstance (instance) { 74 | return nervRenderer.instMap.get(instance) || null 75 | } 76 | } 77 | 78 | hook._renderers[rid] = renderer 79 | 80 | // We can't bring our own `attachRenderer` function therefore we simply 81 | // prevent the devtools from overwriting our custom renderer by creating 82 | // a noop setter. 83 | Object.defineProperty(hook.helpers, rid, { 84 | get: () => nervRenderer, 85 | set: () => { 86 | if (!nervRenderer.connected) { 87 | helpers.markConnected() 88 | } 89 | } 90 | }) 91 | 92 | const helpers = hook.helpers[rid] 93 | 94 | // Tell the devtools that we are ready to start 95 | hook.emit('renderer-attached', { 96 | id: rid, 97 | renderer, 98 | helpers 99 | }) 100 | 101 | onCommitRoot = catchErrors((root) => { 102 | // Empty (root) 103 | // if (root.type === Fragment && root._children.length == 0) return 104 | 105 | const roots = hook.getFiberRoots(rid) 106 | root = helpers.handleCommitFiberRoot(root) 107 | if (!roots.has(root)) { 108 | roots.add(root) 109 | } 110 | }) 111 | 112 | onCommitUnmount = catchErrors((vnode) => { 113 | hook.onCommitFiberUnmount(rid, vnode) 114 | }) 115 | })() 116 | 117 | // Store (possible) previous hooks so that we don't overwrite them 118 | // const prevVNodeHook = options.vnode 119 | // const prevBeforeDiff = options.diff 120 | // const prevAfterDiff = options.diffed 121 | const prevAfterMount = options.afterMount 122 | const prevBeforeUnmount = options.beforeUnmount 123 | const prevAfterUpdate = options.afterUpdate 124 | const prevBeforeMount = options.beforeMount 125 | const prevBeforeUpdate = options.beforeUpdate 126 | const prevAfterCreate = options.afterCreate 127 | 128 | options.afterCreate = (vnode: any) => { 129 | // Tiny performance improvement by initializing fields as doubles 130 | // from the start. `performance.now()` will always return a double. 131 | // See https://github.com/facebook/react/issues/14365 132 | // and https://slidr.io/bmeurer/javascript-engine-fundamentals-the-good-the-bad-and-the-ugly 133 | vnode.startTime = NaN 134 | vnode.endTime = NaN 135 | 136 | vnode.startTime = 0 137 | vnode.endTime = -1 138 | prevAfterCreate(vnode) 139 | } 140 | 141 | options.beforeMount = (vnode: any) => { 142 | vnode.startTime = now() 143 | prevBeforeMount(vnode) 144 | } 145 | 146 | options.beforeUpdate = (vnode: any) => { 147 | vnode.startTime = now() 148 | prevBeforeUpdate(vnode) 149 | } 150 | 151 | options.afterMount = catchErrors((vnode) => { 152 | prevAfterMount(vnode) 153 | 154 | // These cases are already handled by `unmount` 155 | if (vnode == null) { 156 | return 157 | } 158 | onCommitRoot(vnode) 159 | }) 160 | 161 | options.afterUpdate = catchErrors((vnode) => { 162 | prevAfterUpdate(vnode) 163 | 164 | // These cases are already handled by `unmount` 165 | if (vnode == null) { 166 | return 167 | } 168 | vnode.endTime = now() 169 | onCommitRoot(vnode) 170 | }) 171 | 172 | options.beforeUnmount = catchErrors((vnode) => { 173 | // Call previously defined hook 174 | if (prevBeforeUnmount != null) { 175 | prevBeforeUnmount(vnode) 176 | } 177 | onCommitUnmount(vnode) 178 | }) 179 | 180 | // Inject tracking into setState 181 | // const setState = Component.prototype.setState 182 | // Component.prototype.setState = function (update, callback) { 183 | // // Duplicated in setState() but doesn't matter due to the guard. 184 | // const s = 185 | // (this._nextState !== this.state && this._nextState) || 186 | // (this._nextState = {...this.state}) 187 | 188 | // // Needed in order to check if state has changed after the tree has been committed: 189 | // this._prevState = {...s} 190 | 191 | // return setState.call(this, update, callback) 192 | // } 193 | } 194 | 195 | /** 196 | * Get current timestamp in ms. Used for profiling. 197 | * @returns {number} 198 | */ 199 | export let now = Date.now 200 | 201 | try { 202 | /* istanbul ignore else */ 203 | now = performance.now.bind(performance) 204 | } catch (e) { 205 | // 206 | } 207 | 208 | initDevTools() 209 | -------------------------------------------------------------------------------- /packages/nerv-devtools/src/renderer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getData, 3 | getChildren, 4 | getInstance, 5 | hasDataChanged, 6 | isRoot 7 | } from './utils' 8 | 9 | export class Renderer { 10 | rid: any 11 | pending: any[] 12 | connected: boolean 13 | instMap: WeakMap 14 | hook: any 15 | 16 | constructor (hook, rid) { 17 | this.rid = rid 18 | this.hook = hook 19 | 20 | this.pending = [] 21 | 22 | this.instMap = new WeakMap() 23 | this.connected = false 24 | } 25 | 26 | markConnected () { 27 | this.connected = true 28 | this.flushPendingEvents() 29 | } 30 | 31 | flushPendingEvents () { 32 | if (!this.connected) { 33 | return 34 | } 35 | 36 | const events = this.pending 37 | this.pending = [] 38 | for (let i = 0; i < events.length; i++) { 39 | const event = events[i] 40 | this.hook.emit(event.type, event) 41 | } 42 | } 43 | 44 | mount (vnode) { 45 | this.instMap.set(getInstance(vnode), vnode) 46 | const data = getData(vnode) 47 | 48 | const work = [ 49 | { 50 | internalInstance: vnode, 51 | data, 52 | renderer: this.rid, 53 | type: 'mount' 54 | } 55 | ] 56 | 57 | // Children must be mounted first 58 | if (Array.isArray(data.children)) { 59 | const stack = data.children.slice() 60 | let item 61 | while ((item = stack.pop()) != null) { 62 | const children = getChildren(item) 63 | stack.push(...children) 64 | 65 | this.instMap.set(getInstance(item), item) 66 | 67 | const itemData = getData(item) 68 | 69 | work.push({ 70 | internalInstance: item, 71 | data: itemData, 72 | renderer: this.rid, 73 | type: 'mount' 74 | }) 75 | } 76 | } 77 | 78 | for (let i = work.length; --i >= 0;) { 79 | this.pending.push(work[i]) 80 | } 81 | 82 | // Special event if we have a root 83 | if (isRoot(vnode)) { 84 | this.pending.push({ 85 | internalInstance: vnode, 86 | data, 87 | renderer: this.rid, 88 | type: 'root' 89 | }) 90 | } 91 | } 92 | 93 | update (vnode) { 94 | const data = getData(vnode) 95 | 96 | // Children must be updated first 97 | if (Array.isArray(data.children)) { 98 | for (let i = 0; i < data.children.length; i++) { 99 | const child = data.children[i] 100 | const inst = getInstance(child) 101 | 102 | const prevChild = this.instMap.get(inst) 103 | if (prevChild == null) { 104 | this.mount(child) 105 | } else { 106 | this.update(child) 107 | } 108 | 109 | // Mutate child to keep referential equality intact 110 | data.children[i] = this.instMap.get(inst) 111 | } 112 | } 113 | 114 | const prev = this.instMap.get(data.publicInstance) 115 | 116 | // The `updateProfileTimes` event is a faster version of `updated` and 117 | // is processed much quicker inside the devtools extension. 118 | if (!hasDataChanged(prev, vnode)) { 119 | // Always assume profiling data has changed. When we skip an event here 120 | // the devtools element picker will somehow break. 121 | this.pending.push({ 122 | internalInstance: prev, 123 | data, 124 | renderer: this.rid, 125 | type: 'updateProfileTimes' 126 | }) 127 | return 128 | } 129 | 130 | this.pending.push({ 131 | internalInstance: prev, 132 | data, 133 | renderer: this.rid, 134 | type: 'update' 135 | }) 136 | } 137 | 138 | handleCommitFiberRoot (vnode) { 139 | const inst = getInstance(vnode) 140 | 141 | if (this.instMap.has(inst)) { 142 | this.update(vnode) 143 | } else { 144 | this.mount(vnode) 145 | } 146 | 147 | let root: any = null 148 | if (isRoot(vnode)) { 149 | vnode.treeBaseDuration = 0 150 | root = vnode 151 | } else { 152 | // "rootCommitted" always needs the actual root node for the profiler 153 | // to be able to collect timings. The `_ancestorComponent` property will 154 | // point to a vnode for a root node. 155 | root = vnode.component 156 | while (root._parentComponent != null) { 157 | root = root._parentComponent 158 | } 159 | } 160 | 161 | this.pending.push({ 162 | internalInstance: root, 163 | renderer: this.rid, 164 | data: getData(root), 165 | type: 'rootCommitted' 166 | }) 167 | 168 | this.flushPendingEvents() 169 | return vnode 170 | } 171 | 172 | handleCommitFiberUnmount (vnode) { 173 | const inst = getInstance(vnode) 174 | this.instMap.delete(inst) 175 | 176 | // Special case when unmounting a root (most prominently caused by webpack's 177 | // `hot-module-reloading`). If this happens we need to unmount the virtual 178 | // `Fragment` we're wrapping around each root just for the devtools. 179 | 180 | this.pending.push({ 181 | internalInstance: vnode, 182 | renderer: this.rid, 183 | type: 'unmount' 184 | }) 185 | } 186 | 187 | getNativeFromReactElement (vnode) { 188 | return vnode.dom 189 | } 190 | 191 | getReactElementFromNative (dom) { 192 | return this.instMap.get(dom) || null 193 | } 194 | 195 | // Unused, but devtools expects it to be there 196 | /* istanbul ignore next */ 197 | // tslint:disable-next-line: no-empty 198 | walkTree () {} 199 | 200 | // Unused, but devtools expects it to be there 201 | /* istanbul ignore next */ 202 | // tslint:disable-next-line: no-empty 203 | cleanup () {} 204 | } 205 | -------------------------------------------------------------------------------- /packages/nerv-devtools/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Component, options } from 'nervjs' 2 | import { isFunction, isString, isArray } from 'nerv-utils' 3 | 4 | export function getNodeType (vnode) { 5 | if (isFunction(vnode.type)) { 6 | return 'Composite' 7 | } else if (isString(vnode.type)) { 8 | return 'Native' 9 | } 10 | return 'Text' 11 | } 12 | 13 | export function getDisplayName (vnode): string { 14 | if (isFunction(vnode.type)) { 15 | return vnode.type.displayName || vnode.type.name 16 | } else if (isString(vnode.type)) { 17 | return vnode.type 18 | } 19 | return '#text' 20 | } 21 | 22 | export function setIn (obj, path, value) { 23 | const last = path.pop() 24 | const parent = path.reduce((acc, attr) => (acc ? acc[attr] : null), obj) 25 | if (parent) { 26 | parent[last] = value 27 | } 28 | } 29 | 30 | function isEqual (a) { 31 | return (b) => a === b 32 | } 33 | 34 | export function isRoot (vnode) { 35 | return options.roots.some(isEqual(vnode)) 36 | } 37 | 38 | export function getInstance (vnode) { 39 | if (vnode.component) { 40 | return vnode.component 41 | } 42 | return vnode.dom 43 | } 44 | 45 | export function shallowEqual (a, b, isProps?) { 46 | if (a == null || b == null) { 47 | return false 48 | } 49 | 50 | for (const key in a) { 51 | if (isProps && key === 'children' && b[key] != null) { 52 | continue 53 | } 54 | if (a[key] !== b[key]) { 55 | return false 56 | } 57 | } 58 | 59 | if (Object.keys(a).length !== Object.keys(b).length) { 60 | return false 61 | } 62 | return true 63 | } 64 | 65 | export function hasDataChanged (prev, next) { 66 | return ( 67 | (prev.props !== next.props && 68 | !shallowEqual(prev.props, next.props, true)) || 69 | (prev.component != null && 70 | !shallowEqual(next.component.prevState, next.component.state)) || 71 | // prev.vnode.dom !== next.vnode.dom || @FIXME 72 | prev.ref !== next.ref 73 | ) 74 | } 75 | 76 | export function getChildren (vnode) { 77 | const c = vnode.component 78 | 79 | if (c == null) { 80 | if (vnode.children) { 81 | if (isArray(vnode.children)) { 82 | return vnode.children.slice() 83 | } 84 | return [vnode.children] 85 | } 86 | return [] 87 | } 88 | 89 | return !Array.isArray(c._rendered) && c._rendered != null 90 | ? [c._rendered] 91 | : null 92 | } 93 | 94 | export function getData (_vnode) { 95 | const vnode = _vnode instanceof Component ? _vnode.vnode : _vnode 96 | const component = vnode.component 97 | let updater: any = null 98 | 99 | if (component && component instanceof Component) { 100 | updater = { 101 | setState: component.setState.bind(component), 102 | forceUpdate: component.forceUpdate.bind(component), 103 | setInState (path, value) { 104 | component.setState((prev) => { 105 | setIn(prev, path, value) 106 | return prev 107 | }) 108 | }, 109 | setInProps (path, value) { 110 | setIn(vnode.props, path, value) 111 | component.setState({}) 112 | }, 113 | setInContext (path, value) { 114 | setIn(component.context, path, value) 115 | component.setState({}) 116 | } 117 | } 118 | } 119 | 120 | const duration = vnode.endTime - vnode.startTime 121 | const children = getChildren(vnode) 122 | 123 | return { 124 | nodeType: getNodeType(vnode), 125 | type: vnode.type, 126 | name: getDisplayName(vnode), 127 | ref: vnode.ref || null, 128 | key: vnode.key || null, 129 | updater, 130 | text: vnode.text, 131 | state: 132 | component != null && component instanceof Component 133 | ? component.state 134 | : null, 135 | props: vnode.props, 136 | // The devtools inline text children if they are the only child 137 | children: 138 | vnode.text == null 139 | ? children != null && children.length === 1 && children[0].text != null 140 | ? children[0].text 141 | : children 142 | : null, 143 | publicInstance: getInstance(vnode), 144 | memoizedInteractions: [], 145 | 146 | // Profiler data 147 | actualDuration: duration, 148 | actualStartTime: vnode.startTime, 149 | treeBaseDuration: duration 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /packages/nerv-is/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js') 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv-is/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-is", 3 | "version": "1.4.3", 4 | "description": "Same as react-is", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "yuche", 10 | "license": "MIT", 11 | "dependencies": { 12 | "nerv-shared": "^1.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/nerv-is/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2') 2 | const { join } = require('path') 3 | const alias = require('rollup-plugin-alias') 4 | function resolver (path) { 5 | return join(__dirname, path) 6 | } 7 | module.exports = { 8 | input: resolver('src/index.ts'), 9 | output: { 10 | sourcemap: true, 11 | format: 'es', 12 | file: resolver('dist/index.js') 13 | }, 14 | plugins: [ 15 | alias({ 16 | 'nerv-shared': join(process.cwd(), '../nerv-shared/dist/index') 17 | }), 18 | typescript({ 19 | tsconfig: resolver('../../tsconfig.json'), 20 | typescript: require('typescript') 21 | }) 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/nerv-is/src/index.ts: -------------------------------------------------------------------------------- 1 | import { VType, isNullOrUndef, isValidElement } from 'nerv-shared' 2 | 3 | export function isAsyncMode (obj) { 4 | return false 5 | } 6 | 7 | export function isConcurrentMode (obj) { 8 | return false 9 | } 10 | 11 | export function isValidElementType (obj) { 12 | // TODO 13 | return true 14 | } 15 | 16 | export function isContextConsumer (obj) { 17 | return isValidElement(obj) && (obj.constructor as any).isContextConsumer 18 | } 19 | 20 | export function isContextProvider (obj) { 21 | return isValidElement(obj) && (obj.constructor as any).isContextProvider 22 | } 23 | 24 | export function isFowardRef (obj) { 25 | return isValidElement(obj) && (obj.constructor as any)._forwarded === true && (obj.constructor as any).isMemo == null 26 | } 27 | 28 | export function isMemo (obj) { 29 | return isValidElement(obj) && (obj.constructor as any)._forwarded === true && (obj.constructor as any).isMemo === true 30 | } 31 | 32 | export function isFragment (object) { 33 | return false 34 | } 35 | 36 | export function isLazy (object) { 37 | return false 38 | } 39 | 40 | export function isPortal (object) { 41 | return !isNullOrUndef(object) && (object.vtype & VType.Portal) > 0 42 | } 43 | 44 | export function isProfiler (object) { 45 | return false 46 | } 47 | 48 | export function isStrictMode (object) { 49 | return false 50 | } 51 | 52 | export function isSuspense (object) { 53 | return false 54 | } 55 | 56 | export function isElement (object) { 57 | return isValidElement(object) 58 | } 59 | -------------------------------------------------------------------------------- /packages/nerv-redux/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | *.swp 4 | *.yml 5 | 6 | .rpt2_cache 7 | 8 | __tests__ 9 | src 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /packages/nerv-redux/__tests__/provider.spec.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import sinon from 'sinon' 3 | import { connect, Provider } from '../dist/index.esm' 4 | import { createElement, Component, render } from 'nervjs' 5 | import { createStore } from 'redux' 6 | import { renderIntoDocument } from './util' 7 | 8 | const Empty = () => null 9 | 10 | describe('nerv-redux', () => { 11 | const Child =
12 | let container 13 | function renderToContainer (vnode) { 14 | return render(vnode, container) 15 | } 16 | 17 | beforeEach(() => { 18 | container = document.createElement('div') 19 | const c = container.firstElementChild 20 | if (c) { 21 | render(, container) 22 | } 23 | }) 24 | 25 | it('Provider and connect should be exported', () => { 26 | expect(Provider).toBeDefined() 27 | expect(connect).toBeDefined() 28 | }) 29 | 30 | it('should enforce only one child', () => { 31 | const store = createStore(() => ({})) 32 | 33 | expect(() => 34 | render( 35 | 36 | 37 | , 38 | container 39 | ) 40 | ).not.toThrow() 41 | 42 | // expect(() => 43 | // renderToContainer( 44 | // 45 | // 46 | // 47 | // 48 | // ) 49 | // ).toThrowError(/Provider expects only one child/) 50 | 51 | // expect(() => renderToContainer()).toThrowError( 52 | // /Provider expects only one child/ 53 | // ) 54 | }) 55 | 56 | it('should have store in the context', () => { 57 | const store = createStore(() => ({})) 58 | let child 59 | const getRef = (ref) => { 60 | child = ref 61 | } 62 | class ProviderChild extends Component { 63 | render () { return null } 64 | } 65 | const instance = renderToContainer( 66 | 67 | 68 | 69 | ) 70 | 71 | expect(child.context[Object.keys(child.context)[0]].value.store).toEqual(store) 72 | 73 | // shouldn't modify Provider.context 74 | expect(instance.context).toEqual({}) 75 | }) 76 | 77 | it('should pass state consistently to mapState', (done) => { 78 | const store = createStore((state = 0, action) => { 79 | return action.type === '+' ? state + 1 : state - 1 80 | }) 81 | const mapState = sinon.spy(state => ({ count: state })) 82 | class Outer extends Component { 83 | render () { 84 | return
{this.props.count}
85 | } 86 | } 87 | const App = connect(mapState)(Outer) 88 | renderIntoDocument( 89 | 90 | 91 | 92 | ) 93 | expect(mapState.called).toBeTruthy() 94 | store.dispatch({ type: '+' }) 95 | setTimeout(() => { 96 | expect(mapState.callCount).toBe(2) 97 | expect(store.getState()).toEqual(0) 98 | done() 99 | }, 1) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /packages/nerv-redux/__tests__/util.js: -------------------------------------------------------------------------------- 1 | import { Component, render } from 'nervjs' 2 | 3 | export class Wrapper extends Component { 4 | render () { 5 | return this.props.children 6 | } 7 | 8 | repaint () { 9 | return new Promise(resolve => this.setState({}, resolve)) 10 | } 11 | } 12 | 13 | export function renderIntoDocument (input) { 14 | const parent = document.createElement('div') 15 | document.body.appendChild(parent) 16 | return render(input, parent) 17 | } 18 | -------------------------------------------------------------------------------- /packages/nerv-redux/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js').default 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv-redux/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "@babel/runtime": { 6 | "version": "7.4.5", 7 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", 8 | "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", 9 | "requires": { 10 | "regenerator-runtime": "^0.13.2" 11 | } 12 | }, 13 | "hoist-non-react-statics": { 14 | "version": "3.3.0", 15 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", 16 | "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", 17 | "requires": { 18 | "react-is": "^16.7.0" 19 | } 20 | }, 21 | "invariant": { 22 | "version": "2.2.4", 23 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 24 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 25 | "requires": { 26 | "loose-envify": "^1.0.0" 27 | } 28 | }, 29 | "js-tokens": { 30 | "version": "4.0.0", 31 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 32 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 33 | }, 34 | "loose-envify": { 35 | "version": "1.4.0", 36 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 37 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 38 | "requires": { 39 | "js-tokens": "^3.0.0 || ^4.0.0" 40 | } 41 | }, 42 | "object-assign": { 43 | "version": "4.1.1", 44 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 45 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 46 | }, 47 | "prop-types": { 48 | "version": "15.7.2", 49 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 50 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 51 | "requires": { 52 | "loose-envify": "^1.4.0", 53 | "object-assign": "^4.1.1", 54 | "react-is": "^16.8.1" 55 | } 56 | }, 57 | "react-is": { 58 | "version": "16.8.6", 59 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", 60 | "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" 61 | }, 62 | "react-redux": { 63 | "version": "7.1.0", 64 | "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.0.tgz", 65 | "integrity": "sha512-hyu/PoFK3vZgdLTg9ozbt7WF3GgX5+Yn3pZm5/96/o4UueXA+zj08aiSC9Mfj2WtD1bvpIb3C5yvskzZySzzaw==", 66 | "requires": { 67 | "@babel/runtime": "^7.4.5", 68 | "hoist-non-react-statics": "^3.3.0", 69 | "invariant": "^2.2.4", 70 | "loose-envify": "^1.4.0", 71 | "prop-types": "^15.7.2", 72 | "react-is": "^16.8.6" 73 | } 74 | }, 75 | "redux": { 76 | "version": "4.0.1", 77 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", 78 | "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", 79 | "requires": { 80 | "loose-envify": "^1.4.0", 81 | "symbol-observable": "^1.2.0" 82 | } 83 | }, 84 | "regenerator-runtime": { 85 | "version": "0.13.2", 86 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", 87 | "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" 88 | }, 89 | "symbol-observable": { 90 | "version": "1.2.0", 91 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", 92 | "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/nerv-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-redux", 3 | "version": "1.5.7", 4 | "description": "Redux for Nerv.js", 5 | "main": "index.js", 6 | "module": "dist/index.esm.js", 7 | "repository": "https://github.com/NervJS/nerv", 8 | "author": "yuche ", 9 | "license": "MIT", 10 | "keywords": [ 11 | "nerv", 12 | "react", 13 | "redux" 14 | ], 15 | "devDependencies": { 16 | "nervjs": "1.5.7" 17 | }, 18 | "peerDependencies": { 19 | "nervjs": "1.5.7", 20 | "redux": ">=2" 21 | }, 22 | "dependencies": { 23 | "react-redux": "^7.1.0", 24 | "redux": "^4.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/nerv-redux/rollup.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import alias from 'rollup-plugin-alias' 3 | import memory from 'rollup-plugin-memory' 4 | import babel from 'rollup-plugin-babel' 5 | import nodeResolve from 'rollup-plugin-node-resolve' 6 | import commonjs from 'rollup-plugin-commonjs' 7 | import replace from 'rollup-plugin-replace' 8 | import es3 from 'rollup-plugin-es3' 9 | import { join } from 'path' 10 | 11 | function resolver (path) { 12 | return join(__dirname, path) 13 | } 14 | 15 | const babelrc = JSON.parse(fs.readFileSync('../../.babelrc')) 16 | 17 | const modules = { 18 | input: resolver('src/index.js'), 19 | output: [ 20 | { 21 | sourcemap: true, 22 | name: 'NervRedux', 23 | format: 'es', 24 | file: resolver('dist/index.esm.js') 25 | }, 26 | { 27 | sourcemap: true, 28 | format: 'cjs', 29 | file: resolver('dist/index.js') 30 | } 31 | ], 32 | exports: 'default', 33 | external: ['nervjs', 'redux'], 34 | useStrict: false, 35 | globals: { 36 | nervjs: 'Nerv', 37 | redux: 'Redux' 38 | }, 39 | plugins: [ 40 | { 41 | // This insane thing transforms Lodash CommonJS modules to ESModules. Doing so shaves 500b (20%) off the library size. 42 | load: function (id) { 43 | if (id.match(/\blodash\b/)) { 44 | return fs 45 | .readFileSync(id, 'utf8') 46 | .replace( 47 | /\b(?:var\s+)?([\w$]+)\s*=\s*require\((['"])(.*?)\2\)\s*[,;]/g, 48 | 'import $1 from $2$3$2;' 49 | ) 50 | .replace(/\bmodule\.exports\s*=\s*/, 'export default ') 51 | } 52 | } 53 | }, 54 | alias({ 55 | 'react-redux': join(__dirname, 'node_modules/react-redux/es/index.js'), 56 | react: 'nervjs', 57 | 'react-dom': 'nervjs', 58 | invariant: join(__dirname, '/src/invariant.js'), 59 | 'prop-types': join(__dirname, '/src/prop-types.js'), 60 | 'react-is': join(process.cwd(), '../nerv-is/dist/index') 61 | }), 62 | babel({ 63 | babelrc: false, 64 | presets: [ 65 | [ 66 | 'env', 67 | { 68 | // spec: true, 69 | modules: false, 70 | useBuiltIns: false, 71 | loose: true 72 | } 73 | ], 74 | ['stage-0'] 75 | ], 76 | plugins: babelrc.plugins.concat(['external-helpers']) 77 | }), 78 | nodeResolve({ 79 | jsnext: true, 80 | main: true, 81 | preferBuiltins: false 82 | }), 83 | commonjs({ 84 | include: ['node_modules/**'], 85 | exclude: ['node_modules/react-redux/**'] 86 | }), 87 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), 88 | es3() 89 | ] 90 | } 91 | 92 | const umd = Object.assign({}, modules) 93 | 94 | umd.output = { 95 | format: 'umd', 96 | sourcemap: true, 97 | file: resolver('dist/nerv-redux.js'), 98 | name: 'NervRedux' 99 | } 100 | umd.plugins = [ 101 | memory({ 102 | path: 'src/index.js', 103 | contents: "export { default } from './index';" 104 | }) 105 | ].concat(modules.plugins) 106 | 107 | export default [modules, umd] 108 | -------------------------------------------------------------------------------- /packages/nerv-redux/src/index.js: -------------------------------------------------------------------------------- 1 | import { Provider, connect, connectAdvanced, useDispatch, useSelector, useStore, batch, ReactReduxContext } from 'react-redux' 2 | export { Provider, connect, connectAdvanced, useDispatch, useSelector, useStore, batch, ReactReduxContext } 3 | export default { Provider, connect, connectAdvanced, useDispatch, useSelector, useStore, batch, ReactReduxContext } 4 | -------------------------------------------------------------------------------- /packages/nerv-redux/src/invariant.js: -------------------------------------------------------------------------------- 1 | export default function () {} 2 | -------------------------------------------------------------------------------- /packages/nerv-redux/src/prop-types.js: -------------------------------------------------------------------------------- 1 | function proptype () { } 2 | proptype.isRequired = proptype 3 | 4 | const getProptype = () => proptype 5 | 6 | const PropTypes = { 7 | element: getProptype, 8 | func: getProptype, 9 | shape: getProptype, 10 | instanceOf: getProptype 11 | } 12 | 13 | export default PropTypes 14 | -------------------------------------------------------------------------------- /packages/nerv-server/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | *.swp 4 | *.yml 5 | 6 | .rpt2_cache 7 | 8 | __tests__ 9 | src 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /packages/nerv-server/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js') 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-server", 3 | "version": "1.5.7", 4 | "description": "Server-side rendering for Nerv.js", 5 | "author": "yuche", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "module": "dist/nerv.ssr.js", 9 | "jsnext:main": "dist/nerv.ssr.js", 10 | "types": "dist/index.d.ts", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/NervJS/nerv.git" 14 | }, 15 | "dependencies": { 16 | "nerv-shared": "1.4.0", 17 | "nerv-utils": "1.4.5", 18 | "nervjs": "1.5.7" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/NervJS/nerv/issues" 22 | }, 23 | "homepage": "https://github.com/NervJS/nerv" 24 | } 25 | -------------------------------------------------------------------------------- /packages/nerv-server/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2') 2 | const pkg = require('./package.json') 3 | const alias = require('rollup-plugin-alias') 4 | const { join } = require('path') 5 | const cwd = __dirname 6 | function resolver (path) { 7 | return join(__dirname, path) 8 | } 9 | module.exports = { 10 | input: 'src/index.ts', 11 | external: ['nervjs'], 12 | plugins: [ 13 | alias({ 14 | 'nerv-shared': join(cwd, '../nerv-shared/dist/index'), 15 | 'nerv-utils': join(cwd, '../nerv-utils/dist/index'), 16 | nervjs: join(cwd, '../nerv/dist/index.esm') 17 | }), 18 | typescript({ 19 | tsconfig: resolver('../../tsconfig.json') 20 | }) 21 | ], 22 | output: [ 23 | { 24 | format: 'cjs', 25 | sourcemap: true, 26 | file: join(__dirname, 'dist/index.js') 27 | }, 28 | { 29 | format: 'es', 30 | sourcemap: true, 31 | file: join(__dirname, pkg.module) 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /packages/nerv-server/src/index.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:max-line-length 2 | import { 3 | isVNode, 4 | isVText, 5 | isNullOrUndef, 6 | isInvalid, 7 | isComposite 8 | } from 'nerv-shared' 9 | import { isString, isNumber, isFunction, isArray, clone, extend } from 'nerv-utils' 10 | import { Component, renderComponent } from 'nervjs' 11 | import { 12 | encodeEntities, 13 | isVoidElements, 14 | getCssPropertyName, 15 | isUnitlessNumber 16 | } from './utils' 17 | 18 | const skipAttributes = { 19 | ref: true, 20 | key: true, 21 | children: true, 22 | owner: true 23 | } 24 | 25 | function hashToClassName (obj) { 26 | const arr: string[] = [] 27 | for (const i in obj) { 28 | if (obj[i]) { 29 | arr.push(i) 30 | } 31 | } 32 | return arr.join(' ') 33 | } 34 | 35 | function renderStylesToString (styles: string | object): string { 36 | if (isString(styles)) { 37 | return styles 38 | } else { 39 | let renderedString = '' 40 | for (const styleName in styles) { 41 | const value = styles[styleName] 42 | 43 | if (isString(value)) { 44 | renderedString += `${getCssPropertyName(styleName)}${value};` 45 | } else if (isNumber(value)) { 46 | renderedString += `${getCssPropertyName( 47 | styleName 48 | )}${value}${isUnitlessNumber[styleName] ? '' : 'px'};` 49 | } 50 | } 51 | return renderedString 52 | } 53 | } 54 | 55 | function renderVNodeToString (vnode, parent, context, isSvg?: boolean) { 56 | if (isInvalid(vnode)) { 57 | return '' 58 | } 59 | const { type, props, children } = vnode 60 | if (isVText(vnode)) { 61 | return encodeEntities(vnode.text) 62 | } else if (isVNode(vnode)) { 63 | let renderedString = `<${type}` 64 | let html 65 | if (!isNullOrUndef(props)) { 66 | for (let prop in props) { 67 | const value = props[prop] 68 | if (skipAttributes[prop]) { 69 | continue 70 | } 71 | if (prop === 'dangerouslySetInnerHTML') { 72 | html = value.__html 73 | } else if (prop === 'style') { 74 | const styleStr = renderStylesToString(value) 75 | renderedString += styleStr ? ` style="${renderStylesToString(value)}"` : '' 76 | } else if (prop === 'class' || prop === 'className') { 77 | renderedString += ` class="${isString(value) 78 | ? value 79 | : hashToClassName(value)}"` 80 | } else if (prop === 'defaultValue') { 81 | if (!props.value) { 82 | renderedString += ` value="${encodeEntities(value)}"` 83 | } 84 | } else if (prop === 'defaultChecked') { 85 | if (!props.checked) { 86 | renderedString += ` checked="${value}"` 87 | } 88 | } else if (isSvg && prop.match(/^xlink\:?(.+)/)) { 89 | prop = prop.toLowerCase().replace(/^xlink\:?(.+)/, 'xlink:$1') 90 | renderedString += ` ${prop}="${encodeEntities(value)}"` 91 | } else { 92 | if (isString(value)) { 93 | renderedString += ` ${prop}="${encodeEntities(value)}"` 94 | } else if (isNumber(value)) { 95 | renderedString += ` ${prop}="${value}"` 96 | } else if (value === true) { 97 | renderedString += ` ${prop}` 98 | } 99 | } 100 | } 101 | } 102 | if (isVoidElements[type]) { 103 | renderedString += `/>` 104 | } else { 105 | renderedString += `>` 106 | if (html) { 107 | renderedString += html 108 | } else if (!isInvalid(children)) { 109 | if (isString(children)) { 110 | renderedString += children === '' ? ' ' : encodeEntities(children) 111 | } else if (isNumber(children)) { 112 | renderedString += children + '' 113 | } else if (isArray(children)) { 114 | for (let i = 0, len = children.length; i < len; i++) { 115 | const child = children[i] 116 | if (isString(child)) { 117 | renderedString += child === '' ? ' ' : encodeEntities(child) 118 | } else if (isNumber(child)) { 119 | renderedString += child 120 | } else if (!isInvalid(child)) { 121 | isSvg = type === 'svg' ? true : type === 'foreignObject' ? false : isSvg 122 | renderedString += renderVNodeToString( 123 | child, 124 | vnode, 125 | context, 126 | isSvg 127 | ) 128 | } 129 | } 130 | } else { 131 | isSvg = type === 'svg' ? true : type === 'foreignObject' ? false : isSvg 132 | renderedString += renderVNodeToString(children, vnode, context, isSvg) 133 | } 134 | } 135 | if (!isVoidElements[type]) { 136 | renderedString += `` 137 | } 138 | } 139 | return renderedString 140 | } else if (isComposite(vnode)) { 141 | let instance 142 | if (vnode.type.prototype && vnode.type.prototype.render) { 143 | instance = new type(props, context) 144 | } else { 145 | instance = new Component(props, context) 146 | instance.render = () => type.call(instance, instance.props, instance.context) 147 | } 148 | instance._disable = true 149 | instance.props = props 150 | instance.context = context 151 | if (isFunction(instance.componentWillMount)) { 152 | instance.componentWillMount() 153 | instance.state = instance.getState() 154 | } 155 | const rendered = renderComponent(instance) 156 | if (isFunction(instance.getChildContext)) { 157 | context = extend(clone(context), instance.getChildContext()) 158 | } 159 | return renderVNodeToString(rendered, vnode, context, isSvg) 160 | } 161 | } 162 | 163 | export function renderToString (input: any): string { 164 | return renderVNodeToString(input, {}, {}) as string 165 | } 166 | 167 | export function renderToStaticMarkup (input: any): string { 168 | return renderVNodeToString(input, {}, {}) as string 169 | } 170 | -------------------------------------------------------------------------------- /packages/nerv-server/src/is.ts: -------------------------------------------------------------------------------- 1 | export * from 'nerv-shared' 2 | 3 | export function isBoolean (arg): arg is true | false { 4 | return arg === true || arg === false 5 | } 6 | 7 | export function isUndefined (o: any): o is undefined { 8 | return o === void 0 9 | } 10 | 11 | export function isNull (o: any): o is null { 12 | return o === null 13 | } 14 | -------------------------------------------------------------------------------- /packages/nerv-server/src/utils.ts: -------------------------------------------------------------------------------- 1 | const uppercasePattern = /[A-Z]/g 2 | 3 | const CssPropCache = {} 4 | 5 | export function getCssPropertyName (str): string { 6 | if (CssPropCache.hasOwnProperty(str)) { 7 | return CssPropCache[str] 8 | } 9 | return (CssPropCache[str] = 10 | str.replace(uppercasePattern, '-$&').toLowerCase() + ':') 11 | } 12 | 13 | export const isVoidElements = { 14 | 'area': true, 15 | 'base': true, 16 | 'br': true, 17 | 'col': true, 18 | 'command': true, 19 | 'embed': true, 20 | 'hr': true, 21 | 'img': true, 22 | 'input': true, 23 | 'keygen': true, 24 | 'link': true, 25 | 'meta': true, 26 | 'param': true, 27 | 'source': true, 28 | 'track': true, 29 | 'wbr': true 30 | } 31 | 32 | /** 33 | * CSS properties which accept numbers but are not in units of "px". 34 | */ 35 | export const isUnitlessNumber = { 36 | animationIterationCount: true, 37 | borderImageOutset: true, 38 | borderImageSlice: true, 39 | borderImageWidth: true, 40 | boxFlex: true, 41 | boxFlexGroup: true, 42 | boxOrdinalGroup: true, 43 | columnCount: true, 44 | columns: true, 45 | flex: true, 46 | flexGrow: true, 47 | flexPositive: true, 48 | flexShrink: true, 49 | flexNegative: true, 50 | flexOrder: true, 51 | gridArea: true, 52 | gridRow: true, 53 | gridRowEnd: true, 54 | gridRowSpan: true, 55 | gridRowStart: true, 56 | gridColumn: true, 57 | gridColumnEnd: true, 58 | gridColumnSpan: true, 59 | gridColumnStart: true, 60 | fontWeight: true, 61 | lineClamp: true, 62 | lineHeight: true, 63 | opacity: true, 64 | order: true, 65 | orphans: true, 66 | tabSize: true, 67 | widows: true, 68 | zIndex: true, 69 | zoom: true, 70 | 71 | // SVG-related properties 72 | fillOpacity: true, 73 | floodOpacity: true, 74 | stopOpacity: true, 75 | strokeDasharray: true, 76 | strokeDashoffset: true, 77 | strokeMiterlimit: true, 78 | strokeOpacity: true, 79 | strokeWidth: true 80 | } 81 | 82 | const entities = { 83 | '<': '<', 84 | '>': '>', 85 | '&': '&', 86 | '"': '"', 87 | '\\': ''' 88 | } 89 | 90 | export function encodeEntities (text): string { 91 | if (typeof text !== 'string') { 92 | text = String(text) 93 | } 94 | return text.replace(/[<>\"\&\\]/g, (m) => entities[m]) 95 | } 96 | 97 | export { extend } from 'nerv-utils' 98 | -------------------------------------------------------------------------------- /packages/nerv-shared/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | *.swp 4 | *.yml 5 | 6 | .rpt2_cache 7 | 8 | __tests__ 9 | src 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /packages/nerv-shared/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js') 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv-shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-shared", 3 | "version": "1.4.0", 4 | "description": "Internal utilities for Nerv.js", 5 | "main": "index.js", 6 | "types": "dist/index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/NervJS/nerv.git" 10 | }, 11 | "author": "yuche", 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/NervJS/nerv/issues" 15 | }, 16 | "homepage": "https://github.com/NervJS/nerv" 17 | } 18 | -------------------------------------------------------------------------------- /packages/nerv-shared/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2') 2 | const { join } = require('path') 3 | function resolver (path) { 4 | return join(__dirname, path) 5 | } 6 | module.exports = { 7 | input: resolver('src/index.ts'), 8 | output: { 9 | sourcemap: true, 10 | format: 'es', 11 | file: resolver('dist/index.js') 12 | }, 13 | plugins: [ 14 | typescript({ 15 | tsconfig: resolver('../../tsconfig.json'), 16 | typescript: require('typescript') 17 | }) 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/nerv-shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export interface Widget { 2 | vtype: VType 3 | name: string 4 | _owner: any 5 | props: any 6 | _rendered: any 7 | context: any 8 | init (parentContext, parentComponent): Element | null 9 | update ( 10 | previous: ComponentInstance, 11 | current: ComponentInstance, 12 | context: any, 13 | dom?: Element 14 | ): Element | null 15 | destroy (dom?: Element) 16 | } 17 | 18 | export interface Portal { 19 | type: Element 20 | vtype: VType 21 | children: VirtualNode 22 | dom: null | Element 23 | } 24 | 25 | export type ComponentInstance = CompositeComponent | StatelessComponent 26 | 27 | export interface CompositeComponent extends Widget { 28 | type: any 29 | component: Component 30 | ref?: Ref 31 | dom: Element | null 32 | } 33 | 34 | export interface StatelessComponent extends Widget { 35 | type: Function 36 | dom: Element | null 37 | } 38 | 39 | export const EMPTY_CHILDREN: any[] = [] 40 | 41 | export const EMPTY_OBJ = {} 42 | 43 | export interface VText { 44 | vtype: VType 45 | text: string | number 46 | dom: Text | null 47 | } 48 | 49 | export interface VVoid { 50 | dom: Text | null 51 | vtype: VType 52 | } 53 | 54 | export interface VNode { 55 | vtype: VType 56 | type: string 57 | props: Props 58 | children: VirtualChildren 59 | key: string | number | undefined 60 | namespace: string | null 61 | _owner: Component // TODO: this is a component 62 | isSvg?: boolean 63 | parentContext?: any 64 | dom: Element | null 65 | ref: Function | string | null 66 | } 67 | 68 | export type VirtualNode = 69 | | VNode 70 | | VText 71 | | CompositeComponent 72 | | StatelessComponent 73 | | VVoid 74 | | Portal 75 | 76 | export type VirtualChildren = Array | VirtualNode 77 | 78 | export type Ref = (node?: Element | null) => void | null | string 79 | 80 | export interface Props { 81 | children?: VirtualChildren 82 | ref?: Ref 83 | key?: any 84 | className?: string | object 85 | [k: string]: any 86 | } 87 | 88 | export interface ComponentLifecycle { 89 | componentWillMount? (): void 90 | componentDidMount? (): void 91 | componentWillReceiveProps? (nextProps: Readonly

, nextContext: any): void 92 | shouldComponentUpdate? ( 93 | nextProps: Readonly

, 94 | nextState: Readonly, 95 | nextContext: any 96 | ): boolean 97 | componentWillUpdate? ( 98 | nextProps: Readonly

, 99 | nextState: Readonly, 100 | nextContext: any 101 | ): void 102 | componentDidUpdate? ( 103 | prevProps: Readonly

, 104 | prevState: Readonly, 105 | prevContext: any 106 | ): void 107 | componentWillUnmount? (): void 108 | componentDidCatch? (error?): void 109 | getDerivedStateFromProps? (nextProps: Readonly

, prevState: Readonly): object | null 110 | getDerivedStateFromError? (error?): object | null 111 | getSnapshotBeforeUpdate? (prevProps: Readonly

, prevState: Readonly): object | null 112 | } 113 | 114 | export interface Refs { 115 | [k: string]: any 116 | } 117 | 118 | export interface Component extends ComponentLifecycle { 119 | state: Readonly 120 | props: Readonly

& Readonly 121 | context: any 122 | _dirty: boolean 123 | _disable: boolean 124 | _rendered: any 125 | _parentComponent: Component 126 | prevProps: P 127 | prevState: S 128 | prevContext: object 129 | isReactComponent: object 130 | dom: any 131 | vnode: CompositeComponent 132 | clearCallBacks: () => void 133 | getState (): S 134 | // tslint:disable-next-line:member-ordering 135 | refs: Refs 136 | render (props?, context?): VirtualNode 137 | } 138 | 139 | export function isNullOrUndef (o: any): o is undefined | null { 140 | return o === undefined || o === null 141 | } 142 | 143 | export function isInvalid (o: any): o is undefined | null | true | false { 144 | return isNullOrUndef(o) || o === true || o === false 145 | } 146 | 147 | export function isVNode (node): node is VNode { 148 | return !isNullOrUndef(node) && node.vtype === VType.Node 149 | } 150 | 151 | export function isVText (node): node is VText { 152 | return !isNullOrUndef(node) && node.vtype === VType.Text 153 | } 154 | 155 | export function isComponent (instance): instance is Component { 156 | return !isInvalid(instance) && instance.isReactComponent === EMPTY_OBJ 157 | } 158 | 159 | export function isWidget ( 160 | node 161 | ): node is CompositeComponent | StatelessComponent { 162 | return ( 163 | !isNullOrUndef(node) && 164 | (node.vtype & (VType.Composite)) > 0 165 | ) 166 | } 167 | 168 | export function isPortal (vtype: VType, node): node is Portal { 169 | return (vtype & VType.Portal) > 0 170 | } 171 | 172 | export function isComposite (node): node is CompositeComponent { 173 | return !isNullOrUndef(node) && node.vtype === VType.Composite 174 | } 175 | 176 | export function isValidElement (node): node is VirtualNode { 177 | return !isNullOrUndef(node) && node.vtype 178 | } 179 | 180 | export function isHook (arg) { 181 | return !isNullOrUndef(arg) && typeof arg.vhook === 'number' 182 | } 183 | 184 | // tslint:disable-next-line:no-empty 185 | export function noop () {} 186 | 187 | // typescript will compile the enum's value for us. 188 | // eg. 189 | // Composite = 1 << 2 => Composite = 4 190 | export const enum VType { 191 | Text = 1, 192 | Node = 1 << 1, 193 | Composite = 1 << 2, 194 | Void = 1 << 4, 195 | Portal = 1 << 5 196 | } 197 | -------------------------------------------------------------------------------- /packages/nerv-test-utils/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | *.swp 4 | *.yml 5 | 6 | .rpt2_cache 7 | 8 | __tests__ 9 | src 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /packages/nerv-test-utils/__tests__/__snapshots__/test.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ReactTestUtils Simulate should have locally attached media events 1`] = ` 4 | Array [ 5 | "animationEnd", 6 | "animationIteration", 7 | "animationStart", 8 | "blur", 9 | "change", 10 | "click", 11 | "contextMenu", 12 | "doubleClick", 13 | "drag", 14 | "dragEnd", 15 | "dragEnter", 16 | "dragExit", 17 | "dragLeave", 18 | "dragOver", 19 | "dragStart", 20 | "drop", 21 | "error", 22 | "focus", 23 | "input", 24 | "keyDown", 25 | "keyPress", 26 | "keyUp", 27 | "load", 28 | "mouseDown", 29 | "mouseEnter", 30 | "mouseLeave", 31 | "mouseMove", 32 | "mouseOut", 33 | "mouseOver", 34 | "mouseUp", 35 | "submit", 36 | "touchCancel", 37 | "touchEnd", 38 | "touchMove", 39 | "touchStart", 40 | "transitionEnd", 41 | ] 42 | `; 43 | -------------------------------------------------------------------------------- /packages/nerv-test-utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js').default 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv-test-utils/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "nerv-server": { 6 | "version": "1.2.3", 7 | "resolved": "http://registry.npm.taobao.org/nerv-server/download/nerv-server-1.2.3.tgz", 8 | "integrity": "sha1-wkzWG9FK8zthxC3ufrWDEZosOYE=", 9 | "requires": { 10 | "nerv-shared": "1.2.3", 11 | "nerv-utils": "1.2.0" 12 | } 13 | }, 14 | "nerv-shared": { 15 | "version": "1.2.3", 16 | "resolved": "http://registry.npm.taobao.org/nerv-shared/download/nerv-shared-1.2.3.tgz", 17 | "integrity": "sha1-P1fi+R6n3auHU9ctfq2UMOAdgng=" 18 | }, 19 | "nerv-utils": { 20 | "version": "1.2.0", 21 | "resolved": "http://registry.npm.taobao.org/nerv-utils/download/nerv-utils-1.2.0.tgz", 22 | "integrity": "sha1-h4TfqcpTmub7dJYhDAxoghhVa4k=" 23 | }, 24 | "nervjs": { 25 | "version": "1.2.4", 26 | "resolved": "http://registry.npm.taobao.org/nervjs/download/nervjs-1.2.4.tgz", 27 | "integrity": "sha1-0bsM54sLS/xB7RNErMLSy3wlCIA=", 28 | "requires": { 29 | "nerv-shared": "1.2.3", 30 | "nerv-utils": "1.2.4-beta.1" 31 | }, 32 | "dependencies": { 33 | "nerv-utils": { 34 | "version": "1.2.4-beta.1", 35 | "resolved": "http://registry.npm.taobao.org/nerv-utils/download/nerv-utils-1.2.4-beta.1.tgz", 36 | "integrity": "sha1-tV5hTAhrm0Rt/K6TVf21t9Rdpt8=" 37 | } 38 | } 39 | }, 40 | "simulant": { 41 | "version": "0.2.2", 42 | "resolved": "https://registry.npmjs.org/simulant/-/simulant-0.2.2.tgz", 43 | "integrity": "sha1-8bzlJxK2p6DaON392n6DsgsdoB4=" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/nerv-test-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-test-utils", 3 | "version": "1.5.7", 4 | "description": "Nerv test utils", 5 | "main": "index.js", 6 | "module": "dist/index.esm.js", 7 | "author": "yuche", 8 | "license": "MIT", 9 | "dependencies": { 10 | "nerv-server": "1.4.6", 11 | "nerv-shared": "1.4.0", 12 | "nerv-utils": "1.4.5", 13 | "nervjs": "1.5.7", 14 | "simulant": "^0.2.2" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/NervJS/nerv.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/NervJS/nerv/issues" 22 | }, 23 | "homepage": "https://github.com/NervJS/nerv" 24 | } 25 | -------------------------------------------------------------------------------- /packages/nerv-test-utils/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2') 2 | const alias = require('rollup-plugin-alias') 3 | const { join } = require('path') 4 | function resolver (path) { 5 | return join(__dirname, path) 6 | } 7 | const cwd = process.cwd() 8 | module.exports = { 9 | input: resolver('src/index.ts'), 10 | output: [ 11 | { 12 | sourcemap: true, 13 | format: 'cjs', 14 | file: resolver('dist/index.js') 15 | }, 16 | { 17 | sourcemap: true, 18 | format: 'es', 19 | file: resolver('dist/index.esm.js') 20 | } 21 | ], 22 | external: ['nervjs', 'simulant'], 23 | globals: { 24 | nervjs: 'Nerv' 25 | }, 26 | plugins: [ 27 | alias({ 28 | nervjs: join(cwd, '../nerv/dist/index'), 29 | 'nerv-shared': join(cwd, '../nerv-shared/dist/index'), 30 | 'nerv-utils': join(cwd, '../nerv-utils/dist/index') 31 | }), 32 | typescript({ 33 | tsconfig: resolver('../../tsconfig.json'), 34 | typescript: require('typescript') 35 | }) 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /packages/nerv-test-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'nervjs' 2 | import { 3 | isValidElement, 4 | isComposite, 5 | VirtualNode, 6 | isWidget, 7 | isVNode, 8 | isComponent 9 | } from 'nerv-shared' 10 | import { isString, isArray } from 'nerv-utils' 11 | import simulant from 'simulant' 12 | 13 | function renderIntoDocument (instance) { 14 | const dom = document.createElement('div') 15 | document.body.appendChild(dom) 16 | return React.render(instance, dom) 17 | } 18 | 19 | const Simulate = {} 20 | 21 | const EVENTS = [ 22 | 'keyDown', 23 | 'keyPress', 24 | 'keyUp', 25 | 'focus', 26 | 'blur', 27 | 'click', 28 | 'contextMenu', 29 | 'doubleClick', 30 | 'drag', 31 | 'dragEnd', 32 | 'dragEnter', 33 | 'dragExit', 34 | 'dragLeave', 35 | 'dragOver', 36 | 'dragStart', 37 | 'drop', 38 | 'mouseDown', 39 | 'mouseEnter', 40 | 'mouseLeave', 41 | 'mouseMove', 42 | 'mouseOut', 43 | 'mouseOver', 44 | 'mouseUp', 45 | 'change', 46 | 'input', 47 | 'submit', 48 | 'touchCancel', 49 | 'touchEnd', 50 | 'touchMove', 51 | 'touchStart', 52 | 'load', 53 | 'error', 54 | 'animationStart', 55 | 'animationEnd', 56 | 'animationIteration', 57 | 'transitionEnd' 58 | ] 59 | 60 | EVENTS.forEach((event) => { 61 | Simulate[event] = (node, mock) => 62 | simulant.fire(node, event.toLowerCase(), mock) 63 | }) 64 | 65 | function isElement (instance) { 66 | return isValidElement(instance) 67 | } 68 | 69 | function isElementOfType (instance, convenienceConstructor) { 70 | return isElement(instance) && convenienceConstructor === instance.type 71 | } 72 | 73 | function isDOMComponent (inst): inst is Element { 74 | return !!(inst && inst.nodeType === 1 && inst.tagName) 75 | } 76 | 77 | function isDOMComponentOfType (instance: any, tagName: string): boolean { 78 | return ( 79 | isDOMComponent(instance) && 80 | isString(tagName) && 81 | instance.tagName === tagName.toUpperCase() 82 | ) 83 | } 84 | 85 | function isCompositeComponent (instance) { 86 | return isComposite(instance) 87 | } 88 | 89 | function isCompositeComponentWithType (instance, type) { 90 | if (!isCompositeComponent(instance)) { 91 | return false 92 | } 93 | return type === instance.type 94 | } 95 | 96 | function findAllInRenderedTree ( 97 | tree: VirtualNode, 98 | test: (vnode: VirtualNode) => boolean 99 | ) { 100 | if (isValidElement(tree) || isComponent(tree) || isDOMComponent) { 101 | const node = isVNode(tree) ? tree.dom : tree 102 | let result = test(node as any) ? [node as any] : [] 103 | let children 104 | if (isWidget(tree) || isComponent(tree)) { 105 | children = tree._rendered 106 | } else if (isVNode(tree)) { 107 | children = tree.children 108 | } else { 109 | children = tree 110 | } 111 | if (isWidget(children)) { 112 | result = result.concat(findAllInRenderedTree(children, test) as any) 113 | } else if (isVNode(children)) { 114 | result = result.concat(findAllInRenderedTree(children, test) as any) 115 | } else if (isArray(children)) { 116 | children.forEach((child) => { 117 | result = result.concat(findAllInRenderedTree(child, test) as any) 118 | }) 119 | } else if (isDOMComponent(children)) { 120 | console.log(children) 121 | } 122 | return result 123 | } else { 124 | throw new Error('Tree must be a valid elment!') 125 | } 126 | } 127 | 128 | function parseClass (filter) { 129 | if (isArray(filter)) { 130 | return filter 131 | } else if (isString(filter)) { 132 | return filter.trim().split(/\s+/) 133 | } else { 134 | return [] 135 | } 136 | } 137 | 138 | function scryRenderedDOMComponentsWithClass ( 139 | tree, 140 | classNames: string | string[] 141 | ) { 142 | return findAllInRenderedTree(tree, (instance) => { 143 | if (isDOMComponent(instance)) { 144 | const classList = parseClass(instance.getAttribute('class')) 145 | return parseClass(classNames).every( 146 | (className) => classList.indexOf(className) !== -1 147 | ) 148 | } 149 | return false 150 | }) 151 | } 152 | 153 | function findRenderedDOMComponentWithClass ( 154 | tree, 155 | classNames: string | string[] 156 | ) { 157 | const result = scryRenderedDOMComponentsWithClass(tree, classNames) 158 | if (result.length === 1) { 159 | return result[0] 160 | } 161 | throw new Error(`Did not find exactly one result: ${result}.`) 162 | } 163 | 164 | function scryRenderedDOMComponentsWithTag (tree, tag: string) { 165 | return findAllInRenderedTree(tree, (instance) => { 166 | return isDOMComponentOfType(instance, tag) 167 | }) 168 | } 169 | 170 | function findRenderedDOMComponentWithTag (tree, tag: string) { 171 | const result = scryRenderedDOMComponentsWithTag(tree, tag) 172 | if (result.length === 1) { 173 | return result[0] 174 | } 175 | throw new Error(`Did not find exactly one result: ${result}.`) 176 | } 177 | 178 | function scryRenderedComponentsWithType (tree, type) { 179 | return findAllInRenderedTree(tree, (instance) => { 180 | return isCompositeComponentWithType(instance, type) 181 | }) 182 | } 183 | 184 | function findRenderedComponentWithType (tree, type: string) { 185 | const result = scryRenderedComponentsWithType(tree, type) 186 | if (result.length === 1) { 187 | return result[0] 188 | } 189 | throw new Error(`Did not find exactly one result: ${result}.`) 190 | } 191 | 192 | function mockComponent (module, mockTagName) { 193 | mockTagName = mockTagName || module.mockTagName || 'div' 194 | module.prototype.render.mockImplementation(function () { 195 | return React.createElement(mockTagName, null, this.props.children) 196 | }) 197 | } 198 | 199 | export { 200 | Simulate, 201 | renderIntoDocument, 202 | mockComponent, 203 | isElement, 204 | isElementOfType, 205 | isDOMComponent, 206 | isDOMComponentOfType, 207 | isCompositeComponent, 208 | isCompositeComponentWithType, 209 | findAllInRenderedTree, 210 | scryRenderedDOMComponentsWithClass, 211 | scryRenderedComponentsWithType, 212 | scryRenderedDOMComponentsWithTag, 213 | findRenderedComponentWithType, 214 | findRenderedDOMComponentWithClass, 215 | findRenderedDOMComponentWithTag 216 | } 217 | 218 | export default { 219 | Simulate, 220 | renderIntoDocument, 221 | mockComponent, 222 | isElement, 223 | isElementOfType, 224 | isDOMComponent, 225 | isDOMComponentOfType, 226 | isCompositeComponent, 227 | isCompositeComponentWithType, 228 | findAllInRenderedTree, 229 | scryRenderedDOMComponentsWithClass, 230 | scryRenderedComponentsWithType, 231 | scryRenderedDOMComponentsWithTag, 232 | findRenderedComponentWithType, 233 | findRenderedDOMComponentWithClass, 234 | findRenderedDOMComponentWithTag 235 | } 236 | -------------------------------------------------------------------------------- /packages/nerv-utils/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | *.swp 4 | *.yml 5 | 6 | .rpt2_cache 7 | 8 | __tests__ 9 | src 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /packages/nerv-utils/__tests__/next-tick.spec.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from '../src' 2 | import sinon from 'sinon' 3 | 4 | describe('nextTick', () => { 5 | // const canUsePromise = (() => { 6 | // return 'Promise' in window && isNative(Promise) 7 | // })() 8 | // function isNative (Ctor) { 9 | // return typeof ctor === 'function' && /native code/.test(Ctor.toString()) 10 | // } 11 | it('accepts a callback', done => { 12 | nextTick(done) 13 | }) 14 | 15 | // TODO: fix this in IE X 16 | // it('returns a Promise when provided no callback', done => { 17 | // const ua = navigator.userAgent.match(/MSIE (\d+)/) 18 | // if (!canUsePromise || ua !== null) { 19 | // done() 20 | // } 21 | // nextTick().then(done) 22 | // })X 23 | 24 | it.skip('throw error in callback can carry on', async () => { 25 | const consoleErr = console.error 26 | console.error = function () {} 27 | const spy = sinon.spy(console, 'error') 28 | nextTick(() => { 29 | throw new Error('e') 30 | }) 31 | await nextTick() 32 | expect(spy.called).toBeTruthy() 33 | console.error = consoleErr 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /packages/nerv-utils/__tests__/shallow-equal.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowEqual } from '../src' 2 | describe('shallowEqual', () => { 3 | it('shallowEqual', () => { 4 | const a = { a: 1 } 5 | const b = { a: 2 } 6 | const c = { a: 1 } 7 | const d = { a: 1, v: [] } 8 | const e = { a: 1, v: [] } 9 | const arr1 = [] 10 | const f = { a: 1, v: arr1 } 11 | const g = { a: 1, v: arr1 } 12 | expect(shallowEqual(a, b)).not.toBeTruthy() 13 | expect(shallowEqual(null, 110)).toBeFalsy() 14 | expect(shallowEqual(+0, -0)).toBeTruthy() 15 | expect(shallowEqual([], [1])).toBeFalsy() 16 | expect(shallowEqual(a, c)).toBeTruthy() 17 | expect(shallowEqual(d, e)).not.toBeTruthy() 18 | expect(shallowEqual(f, g)).toBeTruthy() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/nerv-utils/__tests__/simple-map.spec.js: -------------------------------------------------------------------------------- 1 | import { SimpleMap } from '../src' 2 | 3 | describe('simpleMap', () => { 4 | const map = new SimpleMap() 5 | 6 | it('get and set', () => { 7 | expect(map.clear()).toBeUndefined() 8 | expect(map.get('a')).toBeUndefined() 9 | expect(map.has('a')).toBeFalsy() 10 | map.set('a', 1) 11 | expect(map.size).toBe(1) 12 | expect(map.has('b')).toBeFalsy() 13 | map.set('a', 1) 14 | expect(map.size).toBe(1) 15 | expect(map.get('a')).toBe(1) 16 | map.set('b', 2) 17 | expect(map.size).toBe(2) 18 | expect(map.has('b')).toBeTruthy() 19 | expect(map.get('b')).toBe(2) 20 | expect(map.delete('c')).toBeFalsy() 21 | expect(map.delete('b')).toBeTruthy() 22 | expect(map.size).toBe(1) 23 | map.clear() 24 | expect(map.size).toEqual(0) 25 | // expect(map).to.haveOwnProperty('get') 26 | // expect(map).to.haveOwnProperty('has') 27 | // expect(map).to.haveOwnProperty('delete') 28 | // expect(map).to.haveOwnProperty('clear') 29 | // expect(map).to.haveOwnProperty('size') 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /packages/nerv-utils/__tests__/util.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | isNative, 3 | isArray, 4 | isNumber, 5 | isBoolean, 6 | isFunction, 7 | isObject, 8 | isString, 9 | extend, 10 | clone 11 | } from '../src' 12 | 13 | describe('Util', () => { 14 | it('types', () => { 15 | const a = 1 16 | const b = 'b' 17 | const c = () => {} 18 | const d = {} 19 | const f = [1, 2, 3] 20 | const h = false 21 | 22 | expect(isNumber(a)).toBeTruthy() 23 | expect(isString(b)).toBeTruthy() 24 | expect(isFunction(c)).toBeTruthy() 25 | expect(isObject(d)).toBeTruthy() 26 | expect(isArray(f)).toBeTruthy() 27 | expect(isBoolean(h)).toBeTruthy() 28 | }) 29 | 30 | it('isNative', () => { 31 | function a () {} 32 | expect(isNative(a)).not.toBeTruthy() 33 | expect(isNative(Array)).toBeTruthy() 34 | }) 35 | 36 | it('extend && clone', () => { 37 | const a = { a: 1, b: 2 } 38 | extend(a, { c: 1 }) 39 | expect(a.a).toBe(1) 40 | expect(a.b).toBe(2) 41 | expect(a.c).toBe(1) 42 | expect(a.d).not.toBeDefined() 43 | 44 | extend(a, { a: 2 }) 45 | expect(a).toEqual({a: 2, b: 2, c: 1}) 46 | 47 | extend(a, { a: { aa: 1, bb: 2 } }) 48 | expect(a).toEqual({a: { aa: 1, bb: 2 }, b: 2, c: 1}) 49 | 50 | const b = clone(a) 51 | expect(b).toEqual({a: { aa: 1, bb: 2 }, b: 2, c: 1}) 52 | b.b = 10 53 | expect(b.b).toBe(10) 54 | expect(a.b).toBe(2) 55 | // const Proto = function () { 56 | // this.a = 'a' 57 | // } 58 | // Proto.prototype.b = 'b' 59 | // const f = new Proto() 60 | // extend({}, f) 61 | // expect(f.b).not.toBeDefined() 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /packages/nerv-utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js') 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerv-utils", 3 | "version": "1.4.5", 4 | "description": "Internal utilities for Nerv.js", 5 | "main": "index.js", 6 | "types": "dist/index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/NervJS/nerv.git" 10 | }, 11 | "author": "yuche", 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/NervJS/nerv/issues" 15 | }, 16 | "homepage": "https://github.com/NervJS/nerv" 17 | } 18 | -------------------------------------------------------------------------------- /packages/nerv-utils/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2') 2 | const { join } = require('path') 3 | function resolver (path) { 4 | return join(__dirname, path) 5 | } 6 | module.exports = { 7 | input: resolver('src/index.ts'), 8 | output: { 9 | sourcemap: true, 10 | name: 'nerv-devtools', 11 | format: 'es', 12 | file: resolver('dist/index.js') 13 | }, 14 | external: ['nerv-shared'], 15 | plugins: [ 16 | typescript({ 17 | tsconfig: resolver('../../tsconfig.json'), 18 | typescript: require('typescript') 19 | }) 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/nerv-utils/src/env.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line 2 | export var global = (function() { 3 | let local 4 | 5 | if (typeof global !== 'undefined') { 6 | local = global 7 | } else if (typeof self !== 'undefined') { 8 | local = self 9 | } else { 10 | try { 11 | // tslint:disable-next-line:function-constructor 12 | local = Function('return this')() 13 | } catch (e) { 14 | throw new Error('global object is unavailable in this environment') 15 | } 16 | } 17 | return local 18 | })() 19 | 20 | export const isBrowser = typeof window !== 'undefined' 21 | 22 | // tslint:disable-next-line:no-empty 23 | function noop () {} 24 | 25 | const fakeDoc: any = { 26 | createElement: noop, 27 | createElementNS: noop, 28 | createTextNode: noop 29 | } 30 | 31 | export const doc: Document = isBrowser ? document : fakeDoc 32 | 33 | export const UA = isBrowser && window.navigator.userAgent.toLowerCase() 34 | 35 | export const isMacSafari = isBrowser && UA && window.navigator.platform && 36 | /mac/i.test(window.navigator.platform) && /^((?!chrome|android).)*safari/i.test(UA) 37 | 38 | export const isTaro = isBrowser && !document.scripts 39 | 40 | export const isIE9 = UA && UA.indexOf('msie 9.0') > 0 41 | 42 | export const isiOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) 43 | -------------------------------------------------------------------------------- /packages/nerv-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as nextTick } from './next-tick' 2 | export { default as shallowEqual } from './shallow-equal' 3 | export { SimpleMap, MapClass } from './simple-map' 4 | export * from './is' 5 | export * from './env' 6 | 7 | export function getPrototype (obj) { 8 | /* istanbul ignore next */ 9 | if (Object.getPrototypeOf) { 10 | return Object.getPrototypeOf(obj) 11 | } else if (obj.__proto__) { 12 | return obj.__proto__ 13 | } 14 | /* istanbul ignore next */ 15 | return obj.constructor.prototype 16 | } 17 | 18 | export function isAttrAnEvent (attr: string): boolean { 19 | return attr[0] === 'o' && attr[1] === 'n' 20 | } 21 | 22 | const extend = ((): ((source: S, from: F) => S | F & S) => { 23 | if ('assign' in Object) { 24 | return (source: S, from: F): S | F & S => { 25 | if (!from) { 26 | return source 27 | } 28 | Object.assign(source, from) 29 | return source 30 | } 31 | } else { 32 | return (source: S, from: F): S | F & S => { 33 | if (!from) { 34 | return source 35 | } 36 | for (const key in from) { 37 | if (from.hasOwnProperty(key)) { 38 | (source as any)[key] = from[key] 39 | } 40 | } 41 | return source 42 | } 43 | } 44 | })() 45 | 46 | export { extend } 47 | 48 | export function clone (obj: T): T | {} { 49 | return extend({}, obj) 50 | } 51 | -------------------------------------------------------------------------------- /packages/nerv-utils/src/is.ts: -------------------------------------------------------------------------------- 1 | import { doc } from './env' 2 | 3 | export function isNumber (arg): arg is number { 4 | return typeof arg === 'number' 5 | } 6 | 7 | export const isSupportSVG = isFunction(doc.createAttributeNS) 8 | 9 | export function isString (arg): arg is string { 10 | return typeof arg === 'string' 11 | } 12 | 13 | export function isFunction (arg): arg is Function { 14 | return typeof arg === 'function' 15 | } 16 | 17 | export function isBoolean (arg): arg is true | false { 18 | return arg === true || arg === false 19 | } 20 | 21 | export const isArray = Array.isArray 22 | 23 | export function isObject (arg): arg is Object { 24 | return arg === Object(arg) && !isFunction(arg) 25 | } 26 | export function isNative (Ctor) { 27 | return isFunction(Ctor) && /native code/.test(Ctor.toString()) 28 | } 29 | 30 | export function isUndefined (o): o is undefined { 31 | return o === undefined 32 | } 33 | 34 | // Object.is polyfill 35 | // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is 36 | export function objectIs (x: any, y: any) { 37 | if (x === y) { // Steps 1-5, 7-10 38 | // Steps 6.b-6.e: +0 != -0 39 | return x !== 0 || 1 / x === 1 / y 40 | } 41 | // eslint-disable-next-line no-self-compare 42 | return x !== x && y !== y 43 | } 44 | -------------------------------------------------------------------------------- /packages/nerv-utils/src/next-tick.ts: -------------------------------------------------------------------------------- 1 | import { global, isMacSafari } from './env' 2 | import { isFunction } from './is' 3 | 4 | const canUsePromise = 'Promise' in global && !isMacSafari 5 | 6 | let resolved 7 | if (canUsePromise) { 8 | resolved = Promise.resolve() 9 | } 10 | 11 | const nextTick: (fn, ...args) => void = (fn, ...args) => { 12 | fn = isFunction(fn) ? fn.bind(null, ...args) : fn 13 | if (canUsePromise) { 14 | return resolved.then(fn) 15 | } 16 | const timerFunc = 'requestAnimationFrame' in global && !isMacSafari ? requestAnimationFrame : setTimeout 17 | timerFunc(fn) 18 | } 19 | 20 | export default nextTick 21 | -------------------------------------------------------------------------------- /packages/nerv-utils/src/shallow-equal.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | // tslint:disable-next-line 3 | Object.is = Object.is || function (x, y) { 4 | if (x === y) { 5 | return x !== 0 || 1 / x === 1 / y 6 | } 7 | return x !== x && y !== y 8 | } 9 | 10 | export default function shallowEqual (obj1, obj2) { 11 | if (obj1 === null || obj2 === null) { 12 | return false 13 | } 14 | if (Object.is(obj1, obj2)) { 15 | return true 16 | } 17 | const obj1Keys = obj1 ? Object.keys(obj1) : [] 18 | const obj2Keys = obj2 ? Object.keys(obj2) : [] 19 | if (obj1Keys.length !== obj2Keys.length) { 20 | return false 21 | } 22 | 23 | for (let i = 0; i < obj1Keys.length; i++) { 24 | const obj1KeyItem = obj1Keys[i] 25 | if (!obj2.hasOwnProperty(obj1KeyItem) || !Object.is(obj1[obj1KeyItem], obj2[obj1KeyItem])) { 26 | return false 27 | } 28 | } 29 | 30 | return true 31 | } 32 | -------------------------------------------------------------------------------- /packages/nerv-utils/src/simple-map.ts: -------------------------------------------------------------------------------- 1 | import { global } from './env' 2 | export interface Cache { 3 | k: Key 4 | v: Value 5 | } 6 | 7 | export class SimpleMap { 8 | cache: Array> 9 | size: number 10 | constructor () { 11 | this.cache = [] 12 | this.size = 0 13 | } 14 | set (k, v) { 15 | const len = this.cache.length 16 | if (!len) { 17 | this.cache.push({ k, v }) 18 | this.size += 1 19 | return 20 | } 21 | for (let i = 0; i < len; i++) { 22 | const item = this.cache[i] 23 | if (item.k === k) { 24 | item.v = v 25 | return 26 | } 27 | } 28 | this.cache.push({ k, v }) 29 | this.size += 1 30 | } 31 | 32 | get (k) { 33 | const len = this.cache.length 34 | if (!len) { 35 | return 36 | } 37 | for (let i = 0; i < len; i++) { 38 | const item = this.cache[i] 39 | if (item.k === k) { 40 | return item.v 41 | } 42 | } 43 | } 44 | 45 | has (k) { 46 | const len = this.cache.length 47 | if (!len) { 48 | return false 49 | } 50 | for (let i = 0; i < len; i++) { 51 | const item = this.cache[i] 52 | if (item.k === k) { 53 | return true 54 | } 55 | } 56 | return false 57 | } 58 | 59 | delete (k) { 60 | const len = this.cache.length 61 | for (let i = 0; i < len; i++) { 62 | const item = this.cache[i] 63 | if (item.k === k) { 64 | this.cache.splice(i, 1) 65 | this.size -= 1 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | 72 | clear () { 73 | let len = this.cache.length 74 | this.size = 0 75 | if (!len) { 76 | return 77 | } 78 | while (len) { 79 | this.cache.pop() 80 | len-- 81 | } 82 | } 83 | } 84 | 85 | export const MapClass: MapConstructor = 86 | 'Map' in global ? Map : (SimpleMap as any) 87 | -------------------------------------------------------------------------------- /packages/nerv/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | *.swp 4 | *.yml 5 | 6 | .rpt2_cache 7 | 8 | __tests__ 9 | src 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/children.spec.js: -------------------------------------------------------------------------------- 1 | import { Children } from '../src/children' 2 | 3 | describe('Children', () => { 4 | describe('map', () => { 5 | it('should return itself when is undefined', () => { 6 | expect(Children.map()).toBe(undefined) 7 | }) 8 | it('should bind the ctx', () => { 9 | const children = ['1', '2', '3'] 10 | function times2 (n) { 11 | return n * 2 12 | } 13 | expect(Children.map(children, times2, Number)).toEqual([2, 4, 6]) 14 | }) 15 | it('should handle array of arrays', () => { 16 | const children = ['1', '2', '3', ['4']] 17 | function times2 (n) { 18 | return n * 2 19 | } 20 | expect(Children.map(children, times2, Number)).toEqual([2, 4, 6, 8]) 21 | }) 22 | it('should exec map with every element', () => { 23 | const children = [1, 2, 3] 24 | function times2 (n) { 25 | return n * 2 26 | } 27 | expect(Children.map(children, times2)).toEqual([2, 4, 6]) 28 | }) 29 | }) 30 | 31 | describe('forEach', () => { 32 | it('should return itself when is undefined', () => { 33 | expect(Children.forEach()).toBe(undefined) 34 | }) 35 | 36 | it('should exec with every element', () => { 37 | const children = [1, 2, 3] 38 | function times2 (n, i) { 39 | children[i] = n * 2 40 | } 41 | Children.forEach(children, times2) 42 | expect(children).toEqual([2, 4, 6]) 43 | }) 44 | 45 | it('should bind the ctx', () => { 46 | const children = ['1', '2', '3'] 47 | function times2 (n, i) { 48 | children[i] = n * 2 49 | } 50 | Children.forEach(children, times2, Number) 51 | expect(children).toEqual([2, 4, 6]) 52 | }) 53 | }) 54 | 55 | it('should return the only one children in a array', () => { 56 | expect(Children.only(['wallace'])).toBe('wallace') 57 | }) 58 | 59 | it('should throw error when input childrens', () => { 60 | expect(() => { 61 | Children.only([1, 2]) 62 | }).toThrowError(`Children.only() expects only one child.`) 63 | }) 64 | 65 | it('count should work', () => { 66 | expect(Children.count([])).toBe(0) 67 | }) 68 | 69 | describe('toArray', () => { 70 | it('should return empty array when undefined', () => { 71 | expect(Children.toArray().length).toBe(0) 72 | }) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/cloneElement.spec.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import { Component, createElement, cloneElement, render } from '../src' 3 | import createVText from '../src/vdom/create-vtext' 4 | import { normalizeHTML } from './util' 5 | 6 | describe('cloneElement()', () => { 7 | let scratch 8 | 9 | beforeEach(() => { 10 | scratch = document.createElement('div') 11 | }) 12 | 13 | it('can clone vtext', () => { 14 | const t = cloneElement(createVText('test')) 15 | expect(t.text).toEqual('test') 16 | }) 17 | 18 | it('can clone string and number', () => { 19 | const t = cloneElement('test') 20 | const t1 = cloneElement(12) 21 | expect(t.text).toEqual('test') 22 | expect(t1.text).toEqual(12) 23 | }) 24 | 25 | it('can clone svg', () => { 26 | if (document.documentMode === 8) { 27 | return 28 | } 29 | const t1 = createElement('svg') 30 | render(t1, scratch) 31 | const t2 = cloneElement(t1) 32 | expect(t2.namespace).toBeTruthy() 33 | }) 34 | 35 | it('can clone fragment', () => { 36 | const f1 = [

1
, 2] 37 | const f2 = cloneElement(f1) 38 | expect(f2[0].children.text).toBe('1') 39 | expect(f2[1].children.text).toBe('2') 40 | }) 41 | 42 | it('can clone a vnode with props', () => { 43 | const vnode =
44 | const cloneVNode = cloneElement(vnode) 45 | expect(cloneVNode.type).toEqual('div') 46 | expect(cloneVNode.hasOwnProperty('props')).toBeTruthy() 47 | const { style } = cloneVNode.props 48 | expect(style.width).toBe('800px') 49 | expect(cloneVNode.props.className).toBe('hh') 50 | expect(cloneVNode.children.length).toBe(0) 51 | }) 52 | 53 | it('can clone node with children', () => { 54 | const vnode = ( 55 |
56 |
1
57 | 2 58 | ssd 59 |
60 | ) 61 | const cloneVNode = cloneElement(vnode) 62 | render(cloneVNode, scratch) 63 | expect(scratch.innerHTML).toEqual( 64 | normalizeHTML('
1
2ssd
') 65 | ) 66 | }) 67 | it('can clone node with children contains Components', () => { 68 | class C extends Component { 69 | render () { 70 | return
71 | } 72 | } 73 | 74 | const vnode = ( 75 |
76 |
1
77 | 78 | sd 79 |
80 | ) 81 | const cloneVNode = cloneElement(vnode) 82 | render(cloneVNode, scratch) 83 | expect(scratch.innerHTML).toEqual( 84 | normalizeHTML( 85 | '
1
sd
' 86 | ) 87 | ) 88 | }) 89 | it('can clone node by new props', () => { 90 | const vnode = ( 91 |
92 |
1
93 | sd 94 |
95 | ) 96 | const cloneVNode = cloneElement(vnode, { 97 | style: { 98 | width: '800px' 99 | }, 100 | className: 'hh' 101 | }) 102 | render(cloneVNode, scratch) 103 | const dom = scratch.firstChild 104 | expect(dom.className).toBe('hh') 105 | expect(dom.style.width).toBe('800px') 106 | // expect(scratch.firstChild).to.have.property('className', 'hh') 107 | // expect(scratch.firstChild.style).to.have.property('width', '800px') 108 | }) 109 | 110 | it('can clone node by new children', () => { 111 | const vnode = ( 112 |
113 |
1
114 | sd 115 |
116 | ) 117 | const cloneVNode = cloneElement(vnode, null, 1) 118 | render(cloneVNode, scratch) 119 | expect(scratch.innerHTML).toEqual( 120 | normalizeHTML('
1
') 121 | ) 122 | }) 123 | 124 | it('can preserve empty children', () => { 125 | const Foo = props =>
{props.children}
126 | const Bar = props => {'b'} 127 | const C = cloneElement(, {}) 128 | render(C, scratch) 129 | expect(scratch.innerHTML).toEqual(normalizeHTML('
b
')) 130 | }) 131 | 132 | it('can clone false/null element', () => { 133 | const C = cloneElement(false) 134 | render(C, scratch) 135 | expect(scratch.innerHTML).toEqual(normalizeHTML('')) 136 | const C1 = cloneElement(null) 137 | render(C1, scratch) 138 | expect(scratch.innerHTML).toEqual(normalizeHTML('')) 139 | }) 140 | }) 141 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/context.spec.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import { Component, createElement, render } from '../src/index.ts' 3 | import createVText from '../src/vdom/create-vtext' 4 | import { rerender } from '../src/render-queue' 5 | import sinon from 'sinon' 6 | import { CHILDREN_MATCHER, normalizeHTML } from './util' 7 | 8 | describe('context', () => { 9 | let scratch 10 | 11 | beforeEach(() => { 12 | scratch = document.createElement('div') 13 | }) 14 | 15 | it('should pass context to grandchildren', () => { 16 | const CONTEXT = { a: 'a' } 17 | const PROPS = { b: 'b' } 18 | let doRender = null 19 | 20 | class Outer extends Component { 21 | constructor () { 22 | super(...arguments) 23 | this.state = {} 24 | } 25 | componentDidMount () { 26 | doRender = () => { 27 | this.setState(PROPS) 28 | } 29 | } 30 | getChildContext () { 31 | return CONTEXT 32 | } 33 | render () { 34 | return ( 35 |
36 | 37 |
38 | ) 39 | } 40 | } 41 | const getChildContextSpy = sinon.spy(Outer.prototype, 'getChildContext') 42 | 43 | class Inner extends Component { 44 | shouldComponentUpdate () { 45 | return true 46 | } 47 | componentWillReceiveProps () {} 48 | componentWillUpdate () {} 49 | componentDidUpdate () {} 50 | render () { 51 | return
{this.context && this.context.a}
52 | } 53 | } 54 | const scu = sinon.spy(Inner.prototype, 'shouldComponentUpdate') 55 | const cwrp = sinon.spy(Inner.prototype, 'componentWillReceiveProps') 56 | const cwu = sinon.spy(Inner.prototype, 'componentWillUpdate') 57 | const cdu = sinon.spy(Inner.prototype, 'componentDidUpdate') 58 | 59 | render(, scratch) 60 | 61 | expect(getChildContextSpy.callCount).toBe(1) 62 | 63 | CONTEXT.foo = 'bar' 64 | doRender() 65 | rerender() 66 | 67 | expect(getChildContextSpy.callCount).toBe(2) 68 | 69 | const props = { children: CHILDREN_MATCHER, ...PROPS } 70 | expect(scu.calledWith(props, {}, CONTEXT)).toBeTruthy() 71 | expect(cwrp.calledWith(props, CONTEXT)).toBeTruthy() 72 | expect(cwu.calledWith(props, {})).toBeTruthy() 73 | expect(cdu.calledWith({ children: CHILDREN_MATCHER }, {})).toBeTruthy() 74 | }) 75 | 76 | it('should pass context to direct children', () => { 77 | const CONTEXT = { a: 'a' } 78 | const PROPS = { b: 'b' } 79 | 80 | let doRender = null 81 | 82 | class Outer extends Component { 83 | constructor () { 84 | super(...arguments) 85 | this.state = {} 86 | } 87 | componentDidMount () { 88 | doRender = () => { 89 | this.setState(PROPS) 90 | } 91 | } 92 | getChildContext () { 93 | return CONTEXT 94 | } 95 | render () { 96 | return 97 | } 98 | } 99 | const getChildContextSpy = sinon.spy(Outer.prototype, 'getChildContext') 100 | 101 | class Inner extends Component { 102 | shouldComponentUpdate () { 103 | return true 104 | } 105 | componentWillReceiveProps () {} 106 | componentWillUpdate () {} 107 | componentDidUpdate () {} 108 | render () { 109 | return
{this.context && this.context.a}
110 | } 111 | } 112 | const scu = sinon.spy(Inner.prototype, 'shouldComponentUpdate') 113 | const cwrp = sinon.spy(Inner.prototype, 'componentWillReceiveProps') 114 | const cwu = sinon.spy(Inner.prototype, 'componentWillUpdate') 115 | const cdu = sinon.spy(Inner.prototype, 'componentDidUpdate') 116 | const innerRender = sinon.spy(Inner.prototype, 'render') 117 | 118 | render(, scratch) 119 | 120 | expect(getChildContextSpy.callCount).toBe(1) 121 | 122 | CONTEXT.foo = 'bar' 123 | doRender() 124 | rerender() 125 | 126 | expect(getChildContextSpy.callCount).toBe(2) 127 | 128 | const props = { children: CHILDREN_MATCHER, ...PROPS } 129 | expect(scu.calledWith(props, {}, CONTEXT)).toBeTruthy() 130 | expect(cwrp.calledWith(props, CONTEXT)).toBeTruthy() 131 | expect(cwu.calledWith(props, {})).toBeTruthy() 132 | expect(cdu.calledWith({ children: CHILDREN_MATCHER }, {})).toBeTruthy() 133 | expect(innerRender.returned(sinon.match({ children: [createVText('a')] }))) 134 | }) 135 | 136 | it('should preserve existing context properties when creating child contexts', () => { 137 | const outerContext = { outer: 1 } 138 | const innerContext = { inner: 2 } 139 | class Outer extends Component { 140 | getChildContext () { 141 | return { ...outerContext } 142 | } 143 | render () { 144 | return ( 145 |
146 | 147 |
148 | ) 149 | } 150 | } 151 | 152 | class Inner extends Component { 153 | getChildContext () { 154 | return { ...innerContext } 155 | } 156 | render () { 157 | return 158 | } 159 | } 160 | 161 | class InnerMost extends Component { 162 | render () { 163 | const { outer, inner } = this.context 164 | return ( 165 | 166 | {outer} 167 | {inner} 168 | 169 | ) 170 | } 171 | } 172 | 173 | render(, scratch) 174 | expect(scratch.innerHTML).toEqual( 175 | normalizeHTML('
12
') 176 | ) 177 | }) 178 | 179 | it('Should child component constructor access context', () => { 180 | const randomNumber = Math.random() 181 | const CONTEXT = { info: randomNumber } 182 | class Outer extends Component { 183 | getChildContext () { 184 | return CONTEXT 185 | } 186 | render () { 187 | return ( 188 |
189 | 190 |
191 | ) 192 | } 193 | } 194 | 195 | class Inner extends Component { 196 | constructor (props, context) { 197 | super(props, context) 198 | expect(context.info).toEqual(CONTEXT.info) 199 | this.state = { 200 | s: null 201 | } 202 | } 203 | render () { 204 | return null 205 | } 206 | } 207 | 208 | render(, scratch) 209 | }) 210 | }) 211 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/createElement.spec.js: -------------------------------------------------------------------------------- 1 | import h from '../src/vdom/h' 2 | import createElement from '../src/vdom/create-element' 3 | 4 | describe('test create real dom tree from virtual dom tree', () => { 5 | it('test dom node', () => { 6 | const tree = h( 7 | 'div', 8 | { 9 | id: 'test', 10 | key: '0', 11 | style: { width: '10px', height: '200px' }, 12 | attributes: { prop: 'cc' } 13 | }, 14 | [ 15 | h('p', { key: '1', className: 'test_p' }, '1'), 16 | h('p', { key: '3', className: 'test_p' }, '3'), 17 | h('p', { key: '2', className: 'test_p' }, '2'), 18 | h('p', { key: '4', className: 'test_p' }, '4') 19 | ] 20 | ) 21 | const dom = createElement(tree) 22 | expect(dom.tagName).toBe('DIV') 23 | expect(dom.childNodes.length).toBe(4) 24 | expect(dom.style.width).toBe('10px') 25 | }) 26 | 27 | it('should throw error when a type can not be created', () => { 28 | expect(() => { 29 | createElement({}) 30 | }).toThrowError('Unsupported VNode.') 31 | }) 32 | 33 | it('should create document Fragment', () => { 34 | const dom = createElement([1, 2, undefined]) 35 | expect(dom.innerHTML).toBe(undefined) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/export.spec.js: -------------------------------------------------------------------------------- 1 | import * as Nerv from '../src' 2 | 3 | describe('Should export correct module', () => { 4 | it('Should export `default` module', () => { 5 | expect(Nerv['default']).not.toBe(undefined) 6 | }) 7 | 8 | it('should export correct module', () => { 9 | expect(Nerv.Children).not.toBe(undefined) 10 | expect(Nerv.Component).not.toBe(undefined) 11 | expect(Nerv.PureComponent).not.toBe(undefined) 12 | expect(Nerv.createElement).not.toBe(undefined) 13 | expect(Nerv.cloneElement).not.toBe(undefined) 14 | expect(Nerv.options).not.toBe(undefined) 15 | expect(Nerv.findDOMNode).not.toBe(undefined) 16 | expect(Nerv.isValidElement).not.toBe(undefined) 17 | expect(Nerv.unmountComponentAtNode).not.toBe(undefined) 18 | expect(Nerv.createPortal).not.toBe(undefined) 19 | expect(Nerv.version).toBe('15.4.2') 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/h.spec.js: -------------------------------------------------------------------------------- 1 | import h from '../src/vdom/h' 2 | import { VType } from 'nerv-shared' 3 | 4 | describe('test generate virtual dom tree', () => { 5 | it('test make virtual dom tree simply', () => { 6 | const tree = h('div', { id: 'test', key: '0' }, 'test') 7 | expect(tree.type).toBe('div') 8 | expect(tree.children.text).toBe('test') 9 | // expect(tree.children[0].text).toBe('test') 10 | }) 11 | 12 | it('test make virtual dom tree with children', () => { 13 | const tree = h('ul', { key: '0', id: 'test', className: 'list' }, [ 14 | h( 15 | 'li', 16 | { key: '1', className: 'list_item' }, 17 | h('span', { key: '2', className: 'list_item_text' }, '1') 18 | ), 19 | h( 20 | 'li', 21 | { key: '3', className: 'list_item' }, 22 | h('span', { key: '4', className: 'list_item_text' }, '2') 23 | ), 24 | h( 25 | 'li', 26 | { key: '5', className: 'list_item' }, 27 | h('span', { key: '6', className: 'list_item_text' }, '3') 28 | ) 29 | ]) 30 | expect(tree.type).toBe('ul') 31 | expect(tree.key).toBe('0') 32 | expect(tree.props.id).toBe('test') 33 | expect(tree.props.className).toBe('list') 34 | expect(tree.children.length).toBe(3) 35 | expect(tree.children[0].key).toBe('1') 36 | expect(tree.children[0].children.children.vtype).toBe(VType.Text) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/keys.spec.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import { Component, createElement, render } from '../src/index' 3 | import { normalizeHTML } from './util' 4 | describe('keys', () => { 5 | let scratch 6 | 7 | beforeAll(() => { 8 | scratch = document.createElement('div') 9 | document.body.appendChild(scratch) 10 | }) 11 | 12 | beforeEach(() => { 13 | scratch.innerHTML = '' 14 | }) 15 | 16 | afterAll(() => { 17 | scratch.parentNode.removeChild(scratch) 18 | scratch = null 19 | }) 20 | 21 | it('should remove orphaned keyed nodes', () => { 22 | let inst 23 | class App extends Component { 24 | constructor () { 25 | super(...arguments) 26 | this.state = { 27 | type: ( 28 |
29 |
1
30 |
  • a
  • 31 |
  • b
  • 32 |
    33 | ) 34 | } 35 | inst = this 36 | } 37 | 38 | render () { 39 | return this.state.type 40 | } 41 | } 42 | render(, scratch) 43 | inst.setState({ 44 | type: ( 45 |
    46 |
    2
    47 |
  • b
  • 48 |
  • c
  • 49 |
    50 | ) 51 | }) 52 | inst.forceUpdate() 53 | expect(scratch.innerHTML).toEqual( 54 | normalizeHTML('
    2
  • b
  • c
  • ') 55 | ) 56 | }) 57 | 58 | it('should patch keyed children properly', () => { 59 | const container = document.createElement('container') 60 | let arr = new Array(100) 61 | for (let i = 0; i < arr.length; i++) { 62 | arr[i] = i 63 | } 64 | arr = arr.map((n) => ({ n })) 65 | const List = ({ n }) =>
  • {n + ','}
  • 66 | const App = ({ list }) => { 67 | return
      {list.map(List)}
    68 | } 69 | render(, container) 70 | const arr2 = arr.filter(({ n }) => n !== 50) 71 | render(, container) 72 | expect(container.textContent.split(',').indexOf('50') !== -1).toBe(false) 73 | const arr3 = arr 74 | .slice(0, 50) 75 | .concat([{ n: 101 }]) 76 | .concat(arr.slice(50, 100)) 77 | render(, container) 78 | expect(container.textContent.split(',').indexOf('101') !== -1).toBe(true) 79 | const arr4 = arr.filter(({ n }) => n % 2) 80 | render(, container) 81 | expect(container.textContent.split(',')).toEqual( 82 | arr4.map(({ n }) => String(n)).concat(['']) 83 | ) 84 | const arr5 = arr 85 | .slice(0, 30) 86 | .concat(arr.slice(40, 50)) 87 | .concat(arr.slice(30, 40)) 88 | .concat(arr.slice(50, 100)) 89 | render(, container) 90 | expect(container.textContent.split(',')).toEqual( 91 | arr5.map(({ n }) => String(n)).concat(['']) 92 | ) 93 | const arr6 = [].concat(arr.slice(2, 5)).concat(arr.slice(0, 2)) 94 | render(, container) 95 | expect(container.textContent.split(',')).toEqual( 96 | arr6.map(({ n }) => String(n)).concat(['']) 97 | ) 98 | const arr7 = arr.slice(0, 5) 99 | render(, container) 100 | expect(container.textContent.split(',')).toEqual( 101 | arr7.map(({ n }) => String(n)).concat(['']) 102 | ) 103 | const arr8 = arr.slice(0, 3) 104 | render(, container) 105 | expect(container.textContent.split(',')).toEqual( 106 | arr8.map(({ n }) => String(n)).concat(['']) 107 | ) 108 | const arr9 = arr.slice(0, 3).reverse() 109 | render(, container) 110 | expect(container.textContent.split(',')).toEqual( 111 | arr9.map(({ n }) => String(n)).concat(['']) 112 | ) 113 | // 114 | render(, container) 115 | render(, container) 116 | expect(container.textContent.split(',')).toEqual( 117 | arr 118 | .slice(10, 20) 119 | .map(({ n }) => String(n)) 120 | .concat(['']) 121 | ) 122 | render(, container) 123 | render(, container) 124 | expect(container.textContent.split(',')).toEqual( 125 | arr4 126 | .concat([{ n: 101 }]) 127 | .map(({ n }) => String(n)) 128 | .concat(['']) 129 | ) 130 | }) 131 | }) 132 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/patch.spec.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import { Component, createElement, render } from '../src/index' 3 | import sinon from 'sinon' 4 | import { normalizeHTML } from './util' 5 | 6 | describe('patch', () => { 7 | let scratch 8 | 9 | beforeEach(() => { 10 | scratch = document.createElement('div') 11 | }) 12 | 13 | it('should not patch when stateless component scu was set false', () => { 14 | const App = ({ text }) =>
    {text}
    15 | render( false} />, scratch) 16 | render( { 17 | expect(prev.text).toBe('test') 18 | expect(current.text).toBe('qweqe') 19 | return false 20 | }} />, scratch) 21 | expect(scratch.firstChild.textContent).toBe('test') 22 | }) 23 | 24 | it('should handle float', () => { 25 | const App = () =>
    26 | render(, scratch) 27 | expect(scratch.innerHTML).toContain('left') 28 | // console.log(scratch.innerHTML) 29 | }) 30 | 31 | it('should ignore last style when it does not exist', () => { 32 | render(
    , scratch) 33 | render(
    , scratch) 34 | render(
    , scratch) 35 | render(
    , scratch) 36 | expect(scratch.innerHTML).toContain('10px') 37 | // console.log(scratch.innerHTML) 38 | }) 39 | 40 | it('should hanlde style', () => { 41 | let inst 42 | class App extends Component { 43 | constructor () { 44 | super(...arguments) 45 | this.state = { 46 | type: ( 47 |
    48 |
    54 | 1 55 |
    56 |
  • a
  • 57 |
  • b
  • 58 |
    59 | ) 60 | } 61 | inst = this 62 | } 63 | 64 | render () { 65 | return this.state.type 66 | } 67 | } 68 | render(, scratch) 69 | inst.setState({ 70 | type: ( 71 |
    72 |
    77 | 1 78 |
    79 |
  • a
  • 80 |
  • b
  • 81 |
    82 | ) 83 | }) 84 | inst.forceUpdate() 85 | const style = getComputedStyle(scratch.firstChild.firstChild) 86 | expect(style.color.indexOf('123')).not.toBe('-1') 87 | }) 88 | 89 | it('should handle classNames', () => { 90 | const key = Math.random() 91 | render(
    , scratch) 92 | render(
    , scratch) 93 | expect(scratch.firstChild.className).toBe('') 94 | }) 95 | 96 | it('should handle order', () => { 97 | let inst 98 | class App extends Component { 99 | constructor () { 100 | super(...arguments) 101 | this.state = { 102 | type: ( 103 |
    104 |
    1
    105 |
  • a
  • 106 |
  • b
  • 107 |
    108 | ) 109 | } 110 | inst = this 111 | } 112 | 113 | render () { 114 | return this.state.type 115 | } 116 | } 117 | render(, scratch) 118 | inst.setState({ 119 | type: ( 120 |
    121 |
    1
    122 |
  • b
  • 123 |
  • a
  • 124 |
    125 | ) 126 | }) 127 | inst.forceUpdate() 128 | expect(scratch.innerHTML).toEqual( 129 | normalizeHTML('
    1
  • b
  • a
  • ') 130 | ) 131 | }) 132 | 133 | it('unkeyed children diffing error', () => { 134 | class A extends Component { 135 | render () { 136 | return
    this is a component
    137 | } 138 | componentWillUnmount () { 139 | // console.log('unmount') 140 | } 141 | componentDidMount () { 142 | // console.log('didMount') 143 | } 144 | componentDidUpdate () { 145 | // console.log('didUpdate') 146 | } 147 | } 148 | 149 | const cwu = sinon.spy(A.prototype, 'componentWillUnmount') 150 | const cdm = sinon.spy(A.prototype, 'componentDidMount') 151 | const cdu = sinon.spy(A.prototype, 'componentDidUpdate') 152 | 153 | class B extends Component { 154 | render () { 155 | const visible = this.props.visible 156 | return ( 157 |
    158 | {visible ?
    this is a plain div
    : null} 159 |
    160 | 161 |
    162 |
    163 | ) 164 | } 165 | } 166 | 167 | let inst 168 | 169 | class App extends Component { 170 | state = { visible: false } 171 | 172 | setVisible (visible) { 173 | this.setState({ 174 | visible 175 | }) 176 | } 177 | 178 | constructor () { 179 | super(...arguments) 180 | inst = this 181 | } 182 | 183 | render () { 184 | return ( 185 |
    186 | 187 |
    188 | ) 189 | } 190 | } 191 | 192 | render(, scratch) 193 | expect(cdm.callCount).toBe(1) 194 | inst.setVisible(true) 195 | inst.forceUpdate() 196 | expect(scratch.innerHTML).toEqual( 197 | normalizeHTML('
    this is a plain div
    this is a component
    ') 198 | ) 199 | expect(cwu.called).toBe(false) 200 | expect(cdm.callCount).toBe(1) 201 | expect(cdu.callCount).toBe(1) 202 | inst.setVisible(false) 203 | inst.forceUpdate() 204 | expect(scratch.innerHTML).toEqual( 205 | normalizeHTML('
    this is a component
    ') 206 | ) 207 | expect(cwu.called).toBe(false) 208 | expect(cdm.callCount).toBe(1) 209 | expect(cdu.callCount).toBe(2) 210 | inst.setVisible(true) 211 | inst.forceUpdate() 212 | expect(scratch.innerHTML).toEqual( 213 | normalizeHTML('
    this is a plain div
    this is a component
    ') 214 | ) 215 | expect(cwu.called).toBe(false) 216 | expect(cdm.callCount).toBe(1) 217 | expect(cdu.callCount).toBe(3) 218 | }) 219 | }) 220 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/portal.spec.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import { 3 | Component, 4 | createElement, 5 | render, 6 | createPortal, 7 | findDOMNode 8 | } from '../src' 9 | import { normalizeHTML } from './util' 10 | 11 | function ownerDocument (node) { 12 | return (node && node.ownerDocument) || document 13 | } 14 | 15 | function getContainer (container, defaultContainer) { 16 | container = typeof container === 'function' ? container() : container 17 | return findDOMNode(container) || defaultContainer 18 | } 19 | 20 | function getOwnerDocument (element) { 21 | return ownerDocument(findDOMNode(element)) 22 | } 23 | 24 | describe('createPortal', () => { 25 | class Portal extends Component { 26 | componentDidMount () { 27 | this.setContainer(this.props.container) 28 | this.forceUpdate(this.props.onRendered) 29 | } 30 | 31 | componentWillReceiveProps (nextProps) { 32 | if (nextProps.container !== this.props.container) { 33 | this.setContainer(nextProps.container) 34 | } 35 | } 36 | 37 | componentWillUnmount () { 38 | this.mountNode = null 39 | } 40 | 41 | setContainer (container) { 42 | this.mountNode = getContainer(container, getOwnerDocument(this).body) 43 | } 44 | 45 | /** 46 | * @public 47 | */ 48 | getMountNode = () => { 49 | return this.mountNode 50 | } 51 | 52 | render () { 53 | const { children } = this.props 54 | 55 | return this.mountNode ? createPortal(children, this.mountNode) : null 56 | } 57 | } 58 | it('should create and remove portal', () => { 59 | let app 60 | class App extends Component { 61 | state = { 62 | show: false 63 | } 64 | 65 | constructor () { 66 | super(...arguments) 67 | app = this 68 | } 69 | 70 | handleClick = () => { 71 | this.setState({ show: !this.state.show }) 72 | } 73 | 74 | container = null 75 | static = null 76 | 77 | render () { 78 | const { show } = this.state 79 | return ( 80 |
    81 | 84 |
    (this.static = node)}> 85 |

    It looks like I will render here.

    86 | {show ? ( 87 | 88 |

    But I actually render here!

    89 |
    90 | ) : null} 91 |
    92 |
    { 94 | this.container = node 95 | }} 96 | /> 97 |
    98 | ) 99 | } 100 | } 101 | const div = document.createElement('div') 102 | document.body.appendChild(div) 103 | render(, div) 104 | expect(app.static.innerHTML).toBe( 105 | normalizeHTML('

    It looks like I will render here.

    ') 106 | ) 107 | expect(app.container.innerHTML).toBe('') 108 | app.handleClick() 109 | app.forceUpdate() 110 | expect(app.static.innerHTML).toBe( 111 | normalizeHTML('

    It looks like I will render here.

    ') 112 | ) 113 | expect(app.container.innerHTML).toBe( 114 | normalizeHTML('

    But I actually render here!

    ') 115 | ) 116 | app.handleClick() 117 | app.forceUpdate() 118 | expect(app.static.innerHTML).toBe( 119 | normalizeHTML('

    It looks like I will render here.

    ') 120 | ) 121 | expect(app.container.innerHTML).toBe('') 122 | }) 123 | }) 124 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/util/dom.js: -------------------------------------------------------------------------------- 1 | // https://github.com/preactjs/preact/blob/master/test/_util/dom.js 2 | 3 | /** 4 | * A helper to generate innerHTML validation strings containing spans 5 | * @param {string | number} contents The contents of the span, as a string 6 | */ 7 | export const span = contents => `${contents}` 8 | 9 | /** 10 | * A helper to generate innerHTML validation strings containing divs 11 | * @param {string | number} contents The contents of the div, as a string 12 | */ 13 | export const div = contents => `
    ${contents}
    ` 14 | 15 | /** 16 | * A helper to generate innerHTML validation strings containing p 17 | * @param {string | number} contents The contents of the p, as a string 18 | */ 19 | export const p = contents => `

    ${contents}

    ` 20 | 21 | /** 22 | * A helper to generate innerHTML validation strings containing sections 23 | * @param {string | number} contents The contents of the section, as a string 24 | */ 25 | export const section = contents => `
    ${contents}
    ` 26 | 27 | /** 28 | * A helper to generate innerHTML validation strings containing uls 29 | * @param {string | number} contents The contents of the ul, as a string 30 | */ 31 | export const ul = contents => `
      ${contents}
    ` 32 | 33 | /** 34 | * A helper to generate innerHTML validation strings containing ols 35 | * @param {string | number} contents The contents of the ol, as a string 36 | */ 37 | export const ol = contents => `
      ${contents}
    ` 38 | 39 | /** 40 | * A helper to generate innerHTML validation strings containing lis 41 | * @param {string | number} contents The contents of the li, as a string 42 | */ 43 | export const li = contents => `
  • ${contents}
  • ` 44 | 45 | /** 46 | * A helper to generate innerHTML validation strings containing inputs 47 | */ 48 | export const input = () => `` 49 | 50 | /** 51 | * A helper to generate innerHTML validation strings containing inputs 52 | * @param {string | number} contents The contents of the h1 53 | */ 54 | export const h1 = contents => `

    ${contents}

    ` 55 | 56 | /** 57 | * A helper to generate innerHTML validation strings containing inputs 58 | * @param {string | number} contents The contents of the h2 59 | */ 60 | export const h2 = contents => `

    ${contents}

    ` 61 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/util/index.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon' 2 | export const EMPTY_CHILDREN = [] 3 | 4 | export const CHILDREN_MATCHER = sinon.match((v) => v === null || (Array.isArray(v) && !v.length), '[empty children]') 5 | 6 | export function normalizeHTML (html) { 7 | const div = document.createElement('div') 8 | div.innerHTML = html 9 | return div.innerHTML 10 | } 11 | 12 | export function getAttributes (node) { 13 | const attrs = {} 14 | if (node.attributes) { 15 | for (let i = node.attributes.length; i--;) { 16 | attrs[node.attributes[i].name] = node.attributes[i].value 17 | } 18 | } 19 | return attrs 20 | } 21 | 22 | export function sortAttributes (html) { 23 | return html.replace(/<([a-z0-9-]+)((?:\s[a-z0-9:_.-]+=".*?")+)((?:\s*\/)?>)/gi, (s, pre, attrs, after) => { 24 | const list = attrs.match(/\s[a-z0-9:_.-]+=".*?"/gi).sort((a, b) => a > b ? 1 : -1) 25 | if (~after.indexOf('/')) { 26 | after = `>` 27 | } 28 | return ('<' + pre + list.join('') + after).toLowerCase() 29 | }) 30 | } 31 | const comparer = document.createElement('div') 32 | 33 | export function innerHTML (HTML) { 34 | comparer.innerHTML = HTML 35 | return sortAttributes(comparer.innerHTML) 36 | } 37 | 38 | export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) 39 | -------------------------------------------------------------------------------- /packages/nerv/__tests__/vtypes.spec.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import { createElement, Component } from '../src/index' 3 | import createVText from '../src/vdom/create-vtext' 4 | import { VType } from 'nerv-shared' 5 | 6 | describe('vtypes', () => { 7 | let scratch 8 | 9 | beforeAll(() => { 10 | scratch = document.createElement('div') 11 | document.body.appendChild(scratch) 12 | }) 13 | 14 | beforeEach(() => { 15 | scratch.innerHTML = '' 16 | }) 17 | 18 | afterAll(() => { 19 | scratch.parentNode.removeChild(scratch) 20 | scratch = null 21 | }) 22 | 23 | it('vnode type', () => { 24 | const div =
    25 | expect(div.vtype).toBe(VType.Node) 26 | }) 27 | 28 | it('vtext type', () => { 29 | const div = createVText('hhh') 30 | expect(div.vtype).toBe(VType.Text) 31 | }) 32 | 33 | it('widget type', () => { 34 | class T extends Component { 35 | render () { 36 | return null 37 | } 38 | } 39 | const t = 40 | expect(t.vtype).toBe(VType.Composite) 41 | }) 42 | 43 | it.skip('stateless type', () => { 44 | const T = () =>
    45 | const t = 46 | expect(t.vtype).toBe(VType.Stateless) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /packages/nerv/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js').default 2 | module.exports.default = module.exports 3 | -------------------------------------------------------------------------------- /packages/nerv/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nervjs", 3 | "version": "1.5.7", 4 | "description": "A react-like framework based on virtual-dom", 5 | "main": "index.js", 6 | "module": "dist/index.esm.js", 7 | "jsnext:main": "dist/index.esm.js", 8 | "typings": "index.d.ts", 9 | "unpkg": "dist/nerv.js", 10 | "jsdelivr": "dist/nerv.js", 11 | "files": [ 12 | "dist", 13 | "index.js", 14 | "index.d.ts", 15 | "package.json", 16 | "CHANGELOG.md", 17 | "README.md", 18 | "../LICENSE" 19 | ], 20 | "keywords": [ 21 | "nerv", 22 | "react", 23 | "preact", 24 | "inferno", 25 | "jsx", 26 | "nervjs", 27 | "reactjs", 28 | "javascript" 29 | ], 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/NervJS/nerv.git" 33 | }, 34 | "author": "luckyadam", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/NervJS/nerv/issues" 38 | }, 39 | "homepage": "https://github.com/NervJS/nerv.git", 40 | "dependencies": { 41 | "nerv-shared": "1.4.0", 42 | "nerv-utils": "1.4.5" 43 | }, 44 | "devDependencies": { 45 | "raf": "^3.4.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/nerv/rollup.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2') 2 | const resolve = require('rollup-plugin-node-resolve') 3 | const buble = require('rollup-plugin-buble') 4 | const uglify = require('rollup-plugin-uglify') 5 | const optimizeJs = require('optimize-js') 6 | const babel = require('rollup-plugin-babel') 7 | const es3 = require('rollup-plugin-es3') 8 | const alias = require('rollup-plugin-alias') 9 | const { join } = require('path') 10 | const cwd = __dirname 11 | 12 | function resolver (path) { 13 | return join(__dirname, path) 14 | } 15 | 16 | const optJSPlugin = { 17 | name: 'optimizeJs', 18 | transformBundle (code) { 19 | return optimizeJs(code, { 20 | sourceMap: false, 21 | sourceType: 'module' 22 | }) 23 | } 24 | } 25 | const babelPlugin = babel({ 26 | babelrc: false, 27 | presets: ['es3'], 28 | sourceMap: true 29 | }) 30 | const uglifyPlugin = uglify({ 31 | compress: { 32 | // compress options 33 | booleans: true, 34 | dead_code: true, 35 | drop_debugger: true, 36 | unused: true 37 | }, 38 | ie8: true, 39 | parse: { 40 | // parse options 41 | html5_comments: false, 42 | shebang: false 43 | }, 44 | sourceMap: false, 45 | toplevel: false, 46 | warnings: false 47 | }) 48 | const baseConfig = { 49 | input: join(cwd, 'src/index.ts'), 50 | sourcemap: true, 51 | output: [ 52 | { 53 | file: join(cwd, 'dist/index.js'), 54 | format: 'cjs', 55 | sourcemap: true 56 | }, 57 | { 58 | file: join(cwd, 'dist/nerv.js'), 59 | format: 'umd', 60 | name: 'Nerv', 61 | sourcemap: true 62 | } 63 | ], 64 | plugins: [ 65 | alias({ 66 | 'nerv-shared': join(cwd, '../nerv-shared/dist/index'), 67 | 'nerv-utils': join(cwd, '../nerv-utils/dist/index') 68 | }), 69 | resolve(), 70 | typescript({ 71 | tsconfig: resolver('../../tsconfig.json'), 72 | typescript: require('typescript') 73 | }), 74 | buble(), 75 | babelPlugin, 76 | es3({ remove: ['defineProperty', 'freeze'] }) 77 | ] 78 | } 79 | const esmConfig = Object.assign({}, baseConfig, { 80 | output: Object.assign({}, baseConfig.output, { 81 | sourcemap: true, 82 | format: 'es', 83 | file: join(cwd, 'dist/index.esm.js') 84 | }) 85 | }) 86 | const productionConfig = Object.assign({}, baseConfig, { 87 | output: [ 88 | { 89 | format: 'umd', 90 | file: join(cwd, 'dist/nerv.min.js'), 91 | name: 'Nerv', 92 | sourcemap: false 93 | }, 94 | { 95 | file: join(cwd, 'dist/index.prod.js'), 96 | format: 'cjs', 97 | sourcemap: false 98 | } 99 | ], 100 | plugins: baseConfig.plugins.concat([uglifyPlugin, optJSPlugin]) 101 | }) 102 | 103 | function rollup () { 104 | const target = process.env.TARGET 105 | 106 | if (target === 'umd') { 107 | return baseConfig 108 | } else if (target === 'esm') { 109 | return esmConfig 110 | } else { 111 | return [baseConfig, esmConfig, productionConfig] 112 | } 113 | } 114 | module.exports = rollup() 115 | -------------------------------------------------------------------------------- /packages/nerv/src/children.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from 'nerv-utils' 2 | import { 3 | isNullOrUndef, 4 | VirtualChildren, 5 | EMPTY_CHILDREN, 6 | isInvalid 7 | } from 'nerv-shared' 8 | 9 | export type IterateFn = ( 10 | value: VirtualChildren | any, 11 | index: number, 12 | array: Array 13 | ) => any 14 | 15 | export const Children = { 16 | map (children: Array, fn: IterateFn, ctx: any): any[] { 17 | if (isNullOrUndef(children)) { 18 | return children 19 | } 20 | children = Children.toArray(children) 21 | if (ctx && ctx !== children) { 22 | fn = fn.bind(ctx) 23 | } 24 | return children.map(fn) 25 | }, 26 | forEach ( 27 | children: Array, 28 | fn: IterateFn, 29 | ctx: any 30 | ): void { 31 | if (isNullOrUndef(children)) { 32 | return 33 | } 34 | children = Children.toArray(children) 35 | if (ctx && ctx !== children) { 36 | fn = fn.bind(ctx) 37 | } 38 | for (let i = 0, len = children.length; i < len; i++) { 39 | const child = isInvalid(children[i]) ? null : children[i] 40 | 41 | fn(child, i, children) 42 | } 43 | }, 44 | count (children: Array): number { 45 | children = Children.toArray(children) 46 | return children.length 47 | }, 48 | only (children: Array): VirtualChildren | any { 49 | children = Children.toArray(children) 50 | if (children.length !== 1) { 51 | throw new Error('Children.only() expects only one child.') 52 | } 53 | return children[0] 54 | }, 55 | toArray ( 56 | children: Array 57 | ): Array { 58 | if (isNullOrUndef(children)) { 59 | return [] 60 | } 61 | if (isArray(children)) { 62 | const result = [] 63 | 64 | flatten(children, result) 65 | 66 | return result 67 | } 68 | return EMPTY_CHILDREN.concat(children) 69 | } 70 | } 71 | 72 | function flatten (arr, result) { 73 | for (let i = 0, len = arr.length; i < len; i++) { 74 | const value = arr[i] 75 | if (isArray(value)) { 76 | flatten(value, result) 77 | } else { 78 | result.push(value) 79 | } 80 | } 81 | return result 82 | } 83 | -------------------------------------------------------------------------------- /packages/nerv/src/clone-element.ts: -------------------------------------------------------------------------------- 1 | import createElement from './create-element' 2 | import createVText from './vdom/create-vtext' 3 | import { extend, clone, isArray, isString, isNumber } from 'nerv-utils' 4 | import { isVText, isVNode, EMPTY_CHILDREN, VType, isNullOrUndef, isPortal, isInvalid } from 'nerv-shared' 5 | import { createVoid } from './vdom/create-void' 6 | 7 | export default function cloneElement (vnode, props?: object, ...children): any { 8 | if (isVText(vnode)) { 9 | return createVText(vnode.text) 10 | } 11 | if (isString(vnode) || isNumber(vnode)) { 12 | return createVText(vnode) 13 | } 14 | if (isInvalid(vnode) 15 | || (!isInvalid(vnode) && isPortal(vnode.vtype, vnode)) 16 | || (vnode && (vnode.vtype & VType.Void))) { 17 | return createVoid() 18 | } 19 | const properties = clone(extend(clone(vnode.props), props)) 20 | if (vnode.namespace) { 21 | properties.namespace = vnode.namespace 22 | } 23 | if ((vnode.vtype & VType.Composite) && !isNullOrUndef(vnode.ref)) { 24 | properties.ref = vnode.ref 25 | } 26 | let childrenTmp = 27 | (arguments.length > 2 ? 28 | [].slice.call(arguments, 2) : 29 | vnode.children || properties.children) || [] 30 | if (childrenTmp.length) { 31 | if (childrenTmp.length === 1) { 32 | childrenTmp = children[0] 33 | } 34 | } 35 | if (isArray(vnode)) { 36 | return vnode.map((item) => { 37 | return cloneElement(item) 38 | }) 39 | } 40 | const newVNode = createElement(vnode.type, properties) 41 | if (isArray(childrenTmp)) { 42 | let _children = childrenTmp.map((child) => { 43 | return cloneElement(child, child.props) 44 | }) 45 | if (_children.length === 0) { 46 | _children = EMPTY_CHILDREN 47 | } 48 | if (isVNode(newVNode)) { 49 | newVNode.children = _children 50 | } 51 | newVNode.props.children = _children 52 | } else if (childrenTmp) { 53 | if (isVNode(newVNode)) { 54 | newVNode.children = cloneElement(childrenTmp) 55 | } 56 | newVNode.props.children = cloneElement(childrenTmp, childrenTmp.props) 57 | } 58 | return newVNode 59 | } 60 | -------------------------------------------------------------------------------- /packages/nerv/src/component.ts: -------------------------------------------------------------------------------- 1 | import { isFunction, extend, clone } from 'nerv-utils' 2 | import { enqueueRender } from './render-queue' 3 | import { updateComponent } from './lifecycle' 4 | import { 5 | Props, 6 | ComponentLifecycle, 7 | Refs, 8 | EMPTY_OBJ, 9 | Component as ComponentInst, 10 | CompositeComponent, 11 | EMPTY_CHILDREN 12 | } from 'nerv-shared' 13 | import { Hook, HookEffect } from './hooks' 14 | 15 | interface Component

    extends ComponentLifecycle { 16 | _rendered: any 17 | dom: any 18 | } 19 | 20 | class Component implements ComponentInst { 21 | public static defaultProps: {} 22 | static getDerivedStateFromError? (error?): object | null 23 | state: Readonly 24 | props: Readonly

    & Readonly 25 | prevProps: P 26 | prevState: S 27 | prevContext: object 28 | _parentComponent: Component 29 | vnode: CompositeComponent 30 | context: any 31 | _dirty = true 32 | _disable = true 33 | _pendingStates: any[] = [] 34 | _pendingCallbacks: Function[] = [] 35 | refs: Refs 36 | isReactComponent: Object 37 | _afterScheduleEffect = false 38 | hooks: Hook[] = [] 39 | effects: HookEffect[] = EMPTY_CHILDREN 40 | layoutEffects: HookEffect[] = EMPTY_CHILDREN 41 | 42 | constructor (props?: P, context?: any) { 43 | if (!this.state) { 44 | this.state = {} as S 45 | } 46 | this.props = props || ({} as P) 47 | this.context = context || EMPTY_OBJ 48 | this.refs = {} 49 | } 50 | 51 | setState ( 52 | state: 53 | | ((prevState: Readonly, props: P) => Pick | S) 54 | | (Pick | S), 55 | callback?: () => void 56 | ): void { 57 | if (state) { 58 | this._pendingStates.push(state) 59 | } 60 | if (isFunction(callback)) { 61 | this._pendingCallbacks.push(callback) 62 | } 63 | if (!this._disable) { 64 | enqueueRender(this) 65 | } 66 | } 67 | 68 | getState () { 69 | // tslint:disable-next-line:no-this-assignment 70 | const { _pendingStates, state, props } = this 71 | if (!_pendingStates.length) { 72 | return state 73 | } 74 | const stateClone = clone(state) 75 | const queue = _pendingStates.concat() 76 | this._pendingStates.length = 0 77 | queue.forEach((nextState) => { 78 | if (isFunction(nextState)) { 79 | nextState = nextState.call(this, state, props) 80 | } 81 | extend(stateClone, nextState) 82 | }) 83 | 84 | return stateClone as S 85 | } 86 | 87 | clearCallBacks () { 88 | // cached the length of callbacks, or callbacks may increase by calling them in the same loop 89 | let len = this._pendingCallbacks.length 90 | while (this._dirty ? this._pendingCallbacks.length : len) { 91 | const cb = this._pendingCallbacks.shift()! 92 | cb.call(this) 93 | len-- 94 | } 95 | } 96 | 97 | forceUpdate (callback?: Function) { 98 | if (isFunction(callback)) { 99 | this._pendingCallbacks.push(callback) 100 | } 101 | updateComponent(this, true) 102 | } 103 | 104 | // tslint:disable-next-line 105 | public render(nextProps?: P, nextState?, nextContext?): any {} 106 | } 107 | 108 | Component.prototype.isReactComponent = EMPTY_OBJ 109 | 110 | export default Component 111 | -------------------------------------------------------------------------------- /packages/nerv/src/create-context.ts: -------------------------------------------------------------------------------- 1 | import { Emiter } from './emiter' 2 | import Component from './component' 3 | import { isUndefined, objectIs } from 'nerv-utils' 4 | 5 | export let uid = 0 6 | 7 | function onlyChild (children) { 8 | return Array.isArray(children) ? children[0] : children 9 | } 10 | 11 | export interface ProviderProps { 12 | value: T 13 | } 14 | 15 | export interface ConsumerProps { 16 | children: Function 17 | } 18 | 19 | export interface ConsumerState { 20 | value: T 21 | } 22 | 23 | export interface Context { 24 | Provider: Component> 25 | Consumer: Component> 26 | _id: string, 27 | _defaultValue: T 28 | } 29 | 30 | export function createContext (defaultValue: T): Context { 31 | const contextProp = '__context_' + uid++ + '__' 32 | 33 | class Provider extends Component> { 34 | static isContextProvider = true 35 | 36 | emiter = new Emiter(this.props.value) 37 | 38 | getChildContext () { 39 | return { 40 | [contextProp]: this.emiter 41 | } 42 | } 43 | 44 | componentWillReceiveProps (nextProps: ProviderProps) { 45 | if (!objectIs(this.props.value, nextProps.value)) { 46 | this.emiter.set(nextProps.value) 47 | } 48 | } 49 | 50 | render () { 51 | return this.props.children 52 | } 53 | } 54 | 55 | // tslint:disable-next-line: max-classes-per-file 56 | class Consumer extends Component> { 57 | static isContextConsumer = true 58 | state = { 59 | value: this.getContextValue() 60 | } 61 | 62 | context: { 63 | [contextProp: string]: Emiter | undefined 64 | } 65 | 66 | componentWillMount () { 67 | const emiter = this.context[contextProp] 68 | if (emiter) { 69 | emiter.off(this.onUpdate) 70 | } 71 | } 72 | 73 | componentDidMount () { 74 | const emiter = this.context[contextProp] 75 | if (emiter) { 76 | emiter.on(this.onUpdate) 77 | } 78 | } 79 | 80 | onUpdate = (value: T) => { 81 | if (!objectIs(value, this.state.value)) { 82 | this.setState({ 83 | value: this.getContextValue() 84 | }) 85 | } 86 | } 87 | 88 | getContextValue (): T { 89 | const emiter = this.context[contextProp] 90 | return isUndefined(emiter) ? defaultValue : emiter.value 91 | } 92 | 93 | render () { 94 | return onlyChild(this.props.children)(this.state.value) 95 | } 96 | } 97 | 98 | return { 99 | Provider, 100 | Consumer, 101 | _id: contextProp, 102 | _defaultValue: defaultValue 103 | } as any 104 | } 105 | -------------------------------------------------------------------------------- /packages/nerv/src/create-element.ts: -------------------------------------------------------------------------------- 1 | import h from './vdom/h' 2 | import { isFunction, isString, isUndefined } from 'nerv-utils' 3 | import FullComponent from './full-component' 4 | // import StatelessComponent from './stateless-component' 5 | import CurrentOwner from './current-owner' 6 | import { 7 | Props, 8 | VNode, 9 | VirtualChildren, 10 | EMPTY_CHILDREN 11 | } from 'nerv-shared' 12 | import SVGPropertyConfig from './vdom/svg-property-config' 13 | import Component from './component' 14 | 15 | function transformPropsForRealTag (type: string, props: Props) { 16 | const newProps: Props = {} 17 | for (const propName in props) { 18 | const propValue = props[propName] 19 | if (propName === 'defaultValue') { 20 | newProps.value = props.value || props.defaultValue 21 | continue 22 | } 23 | const svgPropName = SVGPropertyConfig.DOMAttributeNames[propName] 24 | if (svgPropName && svgPropName !== propName) { 25 | newProps[svgPropName] = propValue 26 | continue 27 | } 28 | newProps[propName] = propValue 29 | } 30 | return newProps 31 | } 32 | 33 | /** 34 | * 35 | * @param props 36 | * @param defaultProps 37 | * defaultProps should respect null but ignore undefined 38 | * @see: https://facebook.github.io/react/docs/react-component.html#defaultprops 39 | */ 40 | function transformPropsForComponent (props: Props, defaultProps?: Props) { 41 | const newProps: any = {} 42 | for (const propName in props) { 43 | const propValue = props[propName] 44 | newProps[propName] = propValue 45 | } 46 | if (defaultProps) { 47 | for (const propName in defaultProps) { 48 | if (isUndefined(newProps[propName])) { 49 | newProps[propName] = defaultProps[propName] 50 | } 51 | } 52 | } 53 | return newProps 54 | } 55 | 56 | function createElement ( 57 | type: string | Function | Component, 58 | properties?: T & Props | null, 59 | ..._children: Array 60 | ) { 61 | let children: any = _children 62 | if (_children) { 63 | if (_children.length === 1) { 64 | children = _children[0] 65 | } else if (_children.length === 0) { 66 | children = undefined 67 | } 68 | } 69 | let props 70 | if (isString(type)) { 71 | props = transformPropsForRealTag(type, properties as Props) 72 | props.owner = CurrentOwner.current 73 | return h(type, props, children as any) as VNode 74 | } else if (isFunction(type)) { 75 | props = transformPropsForComponent( 76 | properties as any, 77 | (type as any).defaultProps 78 | ) 79 | if (!props.children || props.children === EMPTY_CHILDREN) { 80 | props.children = children || children === 0 ? children : EMPTY_CHILDREN 81 | } 82 | props.owner = CurrentOwner.current 83 | return new FullComponent(type, props) 84 | } 85 | return type 86 | } 87 | 88 | export default createElement 89 | -------------------------------------------------------------------------------- /packages/nerv/src/create-ref.ts: -------------------------------------------------------------------------------- 1 | export interface RefObject { 2 | current?: T 3 | } 4 | 5 | export function createRef (): RefObject { 6 | return {} 7 | } 8 | 9 | export function forwardRef (cb: Function): Function { 10 | const fn = (props) => { 11 | const ref = props.ref 12 | delete props.ref 13 | return cb(props, ref) 14 | } 15 | fn._forwarded = true 16 | return fn 17 | } 18 | -------------------------------------------------------------------------------- /packages/nerv/src/current-owner.ts: -------------------------------------------------------------------------------- 1 | import Component from './component' 2 | 3 | const Current: { 4 | current: null | Component, 5 | index: number 6 | } = { 7 | current: null, 8 | index: 0 9 | } 10 | 11 | export default Current 12 | -------------------------------------------------------------------------------- /packages/nerv/src/dom.ts: -------------------------------------------------------------------------------- 1 | import { isValidElement as isValidNervElement, VType, isComponent, isInvalid } from 'nerv-shared' 2 | import { nextTick } from 'nerv-utils' 3 | import { getChildContext } from './lifecycle' 4 | import { render } from './render' 5 | import { unmount } from './vdom/unmount' 6 | import createElement from './create-element' 7 | import Component from './component' 8 | 9 | export function unmountComponentAtNode (dom) { 10 | const component = dom._component 11 | if (isValidNervElement(component)) { 12 | unmount(component, dom) 13 | delete dom._component 14 | return true 15 | } 16 | return false 17 | } 18 | 19 | export function findDOMNode (component) { 20 | if (isInvalid(component)) { 21 | return null 22 | } 23 | return isComponent(component) 24 | ? component.vnode.dom 25 | : isValidNervElement(component) 26 | ? component.dom : component 27 | } 28 | 29 | export function createFactory (type) { 30 | return createElement.bind(null, type) 31 | } 32 | 33 | class WrapperComponent extends Component { 34 | getChildContext () { 35 | // tslint:disable-next-line 36 | return this.props.context 37 | } 38 | 39 | render () { 40 | return this.props.children 41 | } 42 | } 43 | 44 | export function unstable_renderSubtreeIntoContainer ( 45 | parentComponent, 46 | vnode, 47 | container, 48 | callback 49 | ) { 50 | // @TODO: should handle props.context? 51 | const wrapper = createElement( 52 | WrapperComponent, 53 | { context: getChildContext(parentComponent, parentComponent.context) }, 54 | vnode 55 | ) 56 | const rendered = render(wrapper as any, container) 57 | if (callback) { 58 | callback.call(rendered) 59 | } 60 | return rendered 61 | } 62 | 63 | export function isValidElement (element) { 64 | return ( 65 | isValidNervElement(element) && (element.vtype & (VType.Composite | VType.Node)) > 0 66 | ) 67 | } 68 | 69 | export const unstable_batchedUpdates = nextTick 70 | -------------------------------------------------------------------------------- /packages/nerv/src/emiter.ts: -------------------------------------------------------------------------------- 1 | class Emiter { 2 | public value: T 3 | private handlers: Function[] = [] 4 | 5 | constructor (value: T) { 6 | this.value = value 7 | } 8 | 9 | on (handler: Function) { 10 | this.handlers.push(handler) 11 | } 12 | 13 | off (handler: Function) { 14 | this.handlers = this.handlers.filter((h) => h !== handler) 15 | } 16 | 17 | set (value: T) { 18 | this.value = value 19 | this.handlers.forEach((h) => h(this.value)) 20 | } 21 | } 22 | 23 | export { 24 | Emiter 25 | } 26 | -------------------------------------------------------------------------------- /packages/nerv/src/fragment.ts: -------------------------------------------------------------------------------- 1 | export function Fragment (props) { 2 | return props.children 3 | } 4 | -------------------------------------------------------------------------------- /packages/nerv/src/full-component.ts: -------------------------------------------------------------------------------- 1 | import { VType, CompositeComponent, Ref } from 'nerv-shared' 2 | import { 3 | mountComponent, 4 | reRenderComponent, 5 | unmountComponent, 6 | getContextByContextType 7 | } from './lifecycle' 8 | import Component from './component' 9 | import { isUndefined, isArray } from 'nerv-utils' 10 | import options from './options' 11 | 12 | class ComponentWrapper implements CompositeComponent { 13 | vtype = VType.Composite 14 | type: any 15 | name: string 16 | _owner: any 17 | props: any 18 | component: Component 19 | context: any 20 | key: any 21 | dom: Element | null 22 | _rendered: any 23 | ref: Ref 24 | 25 | constructor (type, props) { 26 | this.type = type 27 | this.name = type.name 28 | if (isUndefined(this.name)) { 29 | const names = type.toString().match(/^function\s*([^\s(]+)/) 30 | this.name = isArray(names) ? names[0] : 'Component' 31 | } 32 | type.displayName = this.name 33 | this._owner = props.owner 34 | delete props.owner 35 | if ((this.ref = props.ref)) { 36 | delete props.ref 37 | } 38 | if (type._forwarded) { 39 | if (!isUndefined(this.ref)) { 40 | props.ref = this.ref 41 | } 42 | delete this.ref 43 | } 44 | this.props = props 45 | this.key = props.key || null 46 | this.dom = null 47 | options.afterCreate(this) 48 | } 49 | 50 | init (parentContext, parentComponent) { 51 | options.beforeMount(this) 52 | const dom = mountComponent(this, parentContext, parentComponent) 53 | options.afterMount(this) 54 | return dom 55 | } 56 | 57 | update (previous, current, parentContext, domNode?) { 58 | this.context = getContextByContextType(this, parentContext) 59 | options.beforeUpdate(this) 60 | const dom = reRenderComponent(previous, this) 61 | options.afterUpdate(this) 62 | return dom 63 | } 64 | 65 | destroy () { 66 | options.beforeUnmount(this) 67 | unmountComponent(this) 68 | } 69 | } 70 | 71 | export default ComponentWrapper 72 | -------------------------------------------------------------------------------- /packages/nerv/src/hydrate.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-conditional-assignment 2 | import { render } from './render' 3 | 4 | export function hydrate (vnode, container: Element, callback?: Function) { 5 | if (container !== null) { 6 | // lastChild causes less reflow than firstChild 7 | let dom = container.lastChild as Element 8 | // there should be only a single entry for the root 9 | while (dom) { 10 | const next = dom.previousSibling 11 | container.removeChild(dom) 12 | dom = next as Element 13 | } 14 | return render(vnode, container, callback) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/nerv/src/index.ts: -------------------------------------------------------------------------------- 1 | import Component from './component' 2 | import PureComponent from './pure-component' 3 | import { render } from './render' 4 | import createElement from './create-element' 5 | import cloneElement from './clone-element' 6 | import { nextTick } from 'nerv-utils' 7 | import { Children } from './children' 8 | import { hydrate } from './hydrate' 9 | import options from './options' 10 | import { createPortal } from './vdom/create-portal' 11 | import { version } from './version' 12 | import { 13 | unmountComponentAtNode, 14 | findDOMNode, 15 | unstable_renderSubtreeIntoContainer, 16 | createFactory, 17 | unstable_batchedUpdates, 18 | isValidElement 19 | } from './dom' 20 | import { PropTypes } from './prop-types' // for React 15- compat 21 | // tslint:disable-next-line: max-line-length 22 | import { getHooks, useEffect, useLayoutEffect, useReducer, useState, useRef, useCallback, useMemo, useImperativeHandle, useContext } from './hooks' 23 | import { createRef, forwardRef } from './create-ref' 24 | import { memo } from './memo' 25 | import { createContext } from './create-context' 26 | import { renderComponent } from './lifecycle' 27 | import Current from './current-owner' 28 | import { Fragment } from './fragment' 29 | 30 | export { 31 | Children, 32 | Component, 33 | PureComponent, 34 | createElement, 35 | cloneElement, 36 | render, 37 | nextTick, 38 | options, 39 | findDOMNode, 40 | isValidElement, 41 | unmountComponentAtNode, 42 | createPortal, 43 | unstable_renderSubtreeIntoContainer, 44 | hydrate, 45 | createFactory, 46 | unstable_batchedUpdates, 47 | version, 48 | PropTypes, 49 | createRef, 50 | forwardRef, 51 | memo, 52 | createContext, 53 | renderComponent, 54 | getHooks, 55 | Current, 56 | Fragment, 57 | useEffect, useLayoutEffect, useReducer, useState, useRef, useCallback, useMemo, useImperativeHandle, useContext 58 | } 59 | 60 | export default { 61 | Children, 62 | Component, 63 | PureComponent, 64 | createElement, 65 | cloneElement, 66 | render, 67 | nextTick, 68 | options, 69 | findDOMNode, 70 | isValidElement, 71 | unmountComponentAtNode, 72 | createPortal, 73 | unstable_renderSubtreeIntoContainer, 74 | hydrate, 75 | createFactory, 76 | unstable_batchedUpdates, 77 | version, 78 | PropTypes, 79 | createRef, 80 | forwardRef, 81 | memo, 82 | createContext, 83 | renderComponent, 84 | getHooks, 85 | Current, 86 | useEffect, useLayoutEffect, useReducer, useState, useRef, useCallback, useMemo, useImperativeHandle, useContext, 87 | Fragment 88 | } 89 | -------------------------------------------------------------------------------- /packages/nerv/src/memo.ts: -------------------------------------------------------------------------------- 1 | import { shallowEqual, isFunction } from 'nerv-utils' 2 | import Ref from './vdom/ref' 3 | import createElement from './create-element' 4 | import Component from './component' 5 | 6 | export function memo (component: Function, propsAreEqual?: Function) { 7 | function shouldComponentUpdate (this: Component, nextProps): boolean { 8 | const prevRef = this.props.ref 9 | const nextRef = nextProps.ref 10 | if (prevRef !== nextRef) { 11 | Ref.detach(this.vnode, prevRef, this.dom) 12 | Ref.attach(this.vnode, nextRef, this.dom) 13 | return true 14 | } 15 | return isFunction(propsAreEqual) ? !propsAreEqual(this.props, nextProps) : !shallowEqual(this.props, nextProps) 16 | } 17 | 18 | function Memoed (this: Component, props) { 19 | this.shouldComponentUpdate = shouldComponentUpdate 20 | return createElement(component, { ...props }) 21 | } 22 | 23 | Memoed._forwarded = true 24 | 25 | Memoed.isMemo = true 26 | 27 | return Memoed 28 | } 29 | -------------------------------------------------------------------------------- /packages/nerv/src/options.ts: -------------------------------------------------------------------------------- 1 | import { noop, CompositeComponent, StatelessComponent, VirtualNode, Component } from 'nerv-shared' 2 | 3 | export type optionsHook = (vnode: CompositeComponent | StatelessComponent) => void 4 | 5 | const options: { 6 | afterMount: optionsHook 7 | afterUpdate: optionsHook 8 | beforeUpdate: optionsHook 9 | beforeUnmount: optionsHook 10 | beforeMount: optionsHook 11 | afterCreate: optionsHook 12 | beforeRender: (component: Component) => void 13 | roots: VirtualNode[], 14 | debug: boolean 15 | } = { 16 | afterMount: noop, 17 | afterUpdate: noop, 18 | beforeUpdate: noop, 19 | beforeUnmount: noop, 20 | beforeRender: noop, 21 | beforeMount: noop, 22 | afterCreate: noop, 23 | roots: [], 24 | debug: false 25 | } 26 | 27 | export default options 28 | -------------------------------------------------------------------------------- /packages/nerv/src/passive-event.ts: -------------------------------------------------------------------------------- 1 | const defaultOptions = { 2 | passive: false, 3 | capture: false 4 | } 5 | 6 | const eventListenerOptionsSupported = () => { 7 | let supported = false 8 | 9 | try { 10 | const opts = Object.defineProperty({}, 'passive', { 11 | get () { 12 | supported = true 13 | } 14 | }) 15 | window.addEventListener('test', null as any, opts) 16 | window.removeEventListener('test', null as any, opts) 17 | } catch (e) { 18 | supported = false 19 | } 20 | 21 | return supported 22 | } 23 | 24 | function getDefaultPassiveOption () { 25 | const passiveOption = !eventListenerOptionsSupported() ? false : defaultOptions 26 | return () => { 27 | return passiveOption 28 | } 29 | } 30 | 31 | const getPassiveOption = getDefaultPassiveOption() 32 | 33 | export const supportedPassiveEventMap = { 34 | scroll: getPassiveOption(), 35 | wheel: getPassiveOption(), 36 | touchstart: getPassiveOption(), 37 | touchmove: getPassiveOption(), 38 | touchenter: getPassiveOption(), 39 | touchend: getPassiveOption(), 40 | touchleave: getPassiveOption(), 41 | mouseout: getPassiveOption(), 42 | mouseleave: getPassiveOption(), 43 | mouseup: getPassiveOption(), 44 | mousedown: getPassiveOption(), 45 | mousemove: getPassiveOption(), 46 | mouseenter: getPassiveOption(), 47 | mousewheel: getPassiveOption(), 48 | mouseover: getPassiveOption() 49 | } 50 | -------------------------------------------------------------------------------- /packages/nerv/src/prop-types.ts: -------------------------------------------------------------------------------- 1 | import { noop } from 'nerv-shared' 2 | const shim = noop as any 3 | shim.isRequired = noop 4 | 5 | function getShim () { 6 | return shim 7 | } 8 | 9 | const PropTypes = { 10 | array: shim, 11 | bool: shim, 12 | func: shim, 13 | number: shim, 14 | object: shim, 15 | string: shim, 16 | 17 | any: shim, 18 | arrayOf: getShim, 19 | element: shim, 20 | instanceOf: getShim, 21 | node: shim, 22 | objectOf: getShim, 23 | oneOf: getShim, 24 | oneOfType: getShim, 25 | shape: getShim, 26 | exact: getShim, 27 | PropTypes: {}, 28 | checkPropTypes: noop 29 | } 30 | 31 | PropTypes.PropTypes = PropTypes 32 | 33 | export { PropTypes } 34 | -------------------------------------------------------------------------------- /packages/nerv/src/pure-component.ts: -------------------------------------------------------------------------------- 1 | import Component from './component' 2 | import { shallowEqual } from 'nerv-utils' 3 | 4 | class PureComponent extends Component { 5 | isPureComponent = true 6 | 7 | shouldComponentUpdate (nextProps: P, nextState: S) { 8 | return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState) 9 | } 10 | } 11 | 12 | export default PureComponent 13 | -------------------------------------------------------------------------------- /packages/nerv/src/render-queue.ts: -------------------------------------------------------------------------------- 1 | import { nextTick } from 'nerv-utils' 2 | import { updateComponent } from './lifecycle' 3 | 4 | let items: any[] = [] 5 | 6 | export function enqueueRender (component) { 7 | // tslint:disable-next-line:no-conditional-assignment 8 | if (!component._dirty && (component._dirty = true) && items.push(component) === 1) { 9 | nextTick(rerender) 10 | } 11 | } 12 | 13 | export function rerender (isForce = false) { 14 | let p 15 | const list = items 16 | items = [] 17 | // tslint:disable-next-line:no-conditional-assignment 18 | while ((p = list.pop())) { 19 | if (p._dirty) { 20 | updateComponent(p, isForce) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/nerv/src/render.ts: -------------------------------------------------------------------------------- 1 | import { mountVNode, flushMount } from './lifecycle' 2 | import { VirtualNode, isComposite } from 'nerv-shared' 3 | import { patch } from './vdom/patch' 4 | import options from './options' 5 | import { mountElement } from './vdom/create-element' 6 | 7 | export function render ( 8 | vnode: VirtualNode, 9 | container: Element, 10 | callback?: Function 11 | ) { 12 | if (!container) { 13 | throw new Error(`${container} should be a DOM Element`) 14 | } 15 | const lastVnode = (container as any)._component 16 | let dom 17 | options.roots.push(vnode) 18 | if (lastVnode !== undefined) { 19 | options.roots = options.roots.filter((item) => item !== lastVnode) 20 | dom = patch(lastVnode, vnode, container, {}) 21 | } else { 22 | dom = mountVNode(vnode, {}) 23 | mountElement(dom, container) 24 | } 25 | 26 | if (container) { 27 | (container as any)._component = vnode 28 | } 29 | flushMount() 30 | if (callback) { 31 | callback() 32 | } 33 | 34 | return isComposite(vnode) ? vnode.component : dom 35 | } 36 | -------------------------------------------------------------------------------- /packages/nerv/src/vdom/create-element.ts: -------------------------------------------------------------------------------- 1 | import { isSupportSVG, isArray, isString, isNumber, doc, isBoolean } from 'nerv-utils' 2 | import { 3 | isNullOrUndef, 4 | VirtualNode, 5 | VType, 6 | VNode, 7 | isValidElement, 8 | EMPTY_OBJ, 9 | CompositeComponent, 10 | isPortal 11 | } from 'nerv-shared' 12 | import { patchProp } from './patch' 13 | import Ref from './ref' 14 | const SVG_NAMESPACE = 'http://www.w3.org/2000/svg' 15 | 16 | function createElement ( 17 | vnode: VirtualNode | VirtualNode[], 18 | isSvg?: boolean, 19 | parentContext?, 20 | parentComponent? 21 | ): Element | Text | Comment | Array { 22 | let domNode 23 | if (isValidElement(vnode)) { 24 | const vtype = vnode.vtype 25 | if (vtype & (VType.Composite)) { 26 | domNode = (vnode as CompositeComponent).init(parentContext, parentComponent) 27 | } else if (vtype & VType.Text) { 28 | domNode = doc.createTextNode((vnode as any).text); 29 | (vnode as any).dom = domNode 30 | } else if (vtype & VType.Node) { 31 | domNode = mountVNode(vnode as any, isSvg, parentContext, parentComponent) 32 | } else if (vtype & VType.Void) { 33 | domNode = (vnode as any).dom = doc.createTextNode('') 34 | } else if (isPortal(vtype, vnode)) { 35 | vnode.type.appendChild( 36 | createElement(vnode.children, isSvg, parentContext, parentComponent) as Element 37 | ) 38 | domNode = doc.createTextNode('') 39 | } 40 | } else if (isString(vnode) || isNumber(vnode)) { 41 | domNode = doc.createTextNode(vnode as string) 42 | } else if (isNullOrUndef(vnode) || isBoolean(vnode)) { 43 | domNode = doc.createTextNode('') 44 | } else if (isArray(vnode)) { 45 | domNode = vnode.map((child) => createElement(child, isSvg, parentContext, parentComponent)) 46 | } else { 47 | throw new Error('Unsupported VNode.') 48 | } 49 | return domNode 50 | } 51 | 52 | export function mountVNode (vnode: VNode, isSvg?: boolean, parentContext?, parentComponent?) { 53 | if (vnode.isSvg) { 54 | isSvg = true 55 | } else if (vnode.type === 'svg') { 56 | isSvg = true 57 | /* istanbul ignore next */ 58 | } else if (!isSupportSVG) { 59 | isSvg = false 60 | } 61 | if (isSvg) { 62 | vnode.namespace = SVG_NAMESPACE 63 | vnode.isSvg = isSvg 64 | } 65 | const domNode = !isSvg 66 | ? doc.createElement(vnode.type) 67 | : doc.createElementNS(vnode.namespace, vnode.type) 68 | setProps(domNode, vnode, isSvg as boolean) 69 | if (vnode.type === 'foreignObject') { 70 | isSvg = false 71 | } 72 | const children = vnode.children 73 | if (isArray(children)) { 74 | for (let i = 0, len = children.length; i < len; i++) { 75 | mountChild(children[i] as VNode, domNode, parentContext, isSvg, parentComponent) 76 | } 77 | } else { 78 | mountChild(children as VNode, domNode, parentContext, isSvg, parentComponent) 79 | } 80 | vnode.dom = domNode 81 | if (vnode.ref !== null) { 82 | Ref.attach(vnode, vnode.ref, domNode) 83 | } 84 | return domNode 85 | } 86 | 87 | export function mountChild ( 88 | child: VNode, 89 | domNode: Element, 90 | parentContext: Object, 91 | isSvg?: boolean, 92 | parentComponent? 93 | ) { 94 | child.parentContext = parentContext || EMPTY_OBJ 95 | const childNode = createElement(child as VNode, isSvg, parentContext, parentComponent) 96 | mountElement(childNode, domNode) 97 | } 98 | 99 | export function mountElement ( 100 | element: Element | Text | Comment | Array, 101 | parentNode: Element, 102 | refChild?: Node | null 103 | ) { 104 | if (isArray(element)) { 105 | for (let i = 0; i < element.length; i++) { 106 | const el = element[i] 107 | mountElement(el, parentNode) 108 | } 109 | } else { 110 | refChild != null ? parentNode.insertBefore(element, refChild) : parentNode.appendChild(element) 111 | } 112 | } 113 | 114 | export function insertElement ( 115 | element: Element | Text | Comment | Array, 116 | parentNode: Element, 117 | lastDom: Element 118 | ) { 119 | if (isArray(element)) { 120 | for (let i = 0; i < element.length; i++) { 121 | const el = element[i] 122 | insertElement(el, parentNode, lastDom) 123 | } 124 | } else { 125 | parentNode.insertBefore(element, lastDom) 126 | } 127 | } 128 | 129 | function setProps (domNode: Element, vnode: VNode, isSvg: boolean) { 130 | const props = vnode.props 131 | for (const p in props) { 132 | patchProp(domNode, p, null, props[p], null, isSvg) 133 | } 134 | } 135 | 136 | export default createElement 137 | -------------------------------------------------------------------------------- /packages/nerv/src/vdom/create-portal.ts: -------------------------------------------------------------------------------- 1 | import { VirtualNode, VType, Portal } from 'nerv-shared' 2 | 3 | export function createPortal (children: VirtualNode, container: Element): Portal { 4 | return { 5 | type: container, 6 | vtype: VType.Portal, 7 | children, 8 | dom: null 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/nerv/src/vdom/create-vnode.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Props, 3 | VType, 4 | VNode, 5 | VirtualChildren, 6 | Component, 7 | EMPTY_OBJ 8 | } from 'nerv-shared' 9 | 10 | function createVNode ( 11 | type: string, 12 | props: Props, 13 | children: VirtualChildren, 14 | key, 15 | namespace: string, 16 | owner: Component, 17 | ref: Function | string | null | undefined 18 | ): VNode { 19 | return { 20 | type, 21 | key: key || null, 22 | vtype: VType.Node, 23 | props: props || EMPTY_OBJ, 24 | children, 25 | namespace: namespace || null, 26 | _owner: owner, 27 | dom: null, 28 | ref: ref || null 29 | } 30 | } 31 | 32 | export default createVNode 33 | -------------------------------------------------------------------------------- /packages/nerv/src/vdom/create-void.ts: -------------------------------------------------------------------------------- 1 | import { VType } from 'nerv-shared' 2 | export function createVoid () { 3 | return { 4 | dom: null, 5 | vtype: VType.Void 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/nerv/src/vdom/create-vtext.ts: -------------------------------------------------------------------------------- 1 | import { VType, VText } from 'nerv-shared' 2 | 3 | export default function createVText (text: string | number): VText { 4 | return { 5 | text, 6 | vtype: VType.Text, 7 | dom: null 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/nerv/src/vdom/h.ts: -------------------------------------------------------------------------------- 1 | import createVNode from './create-vnode' 2 | import createVText from './create-vtext' 3 | import { createVoid } from './create-void' 4 | import { 5 | Props, 6 | VirtualChildren, 7 | VirtualNode, 8 | isValidElement, 9 | EMPTY_CHILDREN, 10 | VNode 11 | } from 'nerv-shared' 12 | import { isString, isArray, isNumber } from 'nerv-utils' 13 | 14 | function h (type: string, props: Props, children?: VirtualChildren) { 15 | let childNodes 16 | if (props.children) { 17 | if (!children) { 18 | children = props.children 19 | } 20 | } 21 | if (isArray(children)) { 22 | childNodes = [] 23 | addChildren(childNodes, children as any, type) 24 | } else if (isString(children) || isNumber(children)) { 25 | children = createVText(String(children)) 26 | } else if (!isValidElement(children)) { 27 | children = EMPTY_CHILDREN 28 | } 29 | props.children = childNodes !== undefined ? childNodes : children 30 | return createVNode( 31 | type, 32 | props, 33 | props.children as any[], 34 | props.key, 35 | props.namespace, 36 | props.owner, 37 | props.ref 38 | ) as VNode 39 | } 40 | 41 | function addChildren ( 42 | childNodes: VirtualNode[], 43 | children: VirtualNode | VirtualNode[], 44 | type: string 45 | ) { 46 | if (isString(children) || isNumber(children)) { 47 | childNodes.push(createVText(String(children))) 48 | } else if (isValidElement(children)) { 49 | childNodes.push(children) 50 | } else if (isArray(children)) { 51 | for (let i = 0; i < children.length; i++) { 52 | addChildren(childNodes, children[i], type) 53 | } 54 | } else { 55 | childNodes.push(createVoid()) 56 | } 57 | } 58 | 59 | export default h 60 | -------------------------------------------------------------------------------- /packages/nerv/src/vdom/ref.ts: -------------------------------------------------------------------------------- 1 | import { isFunction, isString, isObject } from 'nerv-utils' 2 | import { isComposite } from 'nerv-shared' 3 | import { errorCatcher } from '../lifecycle' 4 | export default { 5 | update (lastVnode, nextVnode, domNode?) { 6 | const prevRef = lastVnode != null && lastVnode.ref 7 | const nextRef = nextVnode != null && nextVnode.ref 8 | 9 | if (prevRef !== nextRef) { 10 | this.detach(lastVnode, prevRef, lastVnode.dom) 11 | this.attach(nextVnode, nextRef, domNode) 12 | } 13 | }, 14 | attach (vnode, ref, domNode: Element) { 15 | const node = isComposite(vnode) ? vnode.component : domNode 16 | if (isFunction(ref)) { 17 | const componentForCatcher = isComposite(vnode) ? vnode.component : vnode 18 | errorCatcher(() => { 19 | ref(node) 20 | }, componentForCatcher) 21 | } else if (isString(ref)) { 22 | const inst = vnode._owner 23 | if (inst && isFunction(inst.render)) { 24 | inst.refs[ref] = node 25 | } 26 | } else if (isObject(ref)) { 27 | ref.current = node 28 | } 29 | }, 30 | detach (vnode, ref, domNode: Element) { 31 | const node = isComposite(vnode) ? vnode.component : domNode 32 | if (isFunction(ref)) { 33 | const componentForCatcher = isComposite(vnode) ? vnode.component : vnode 34 | errorCatcher(() => { 35 | ref(null) 36 | }, componentForCatcher) 37 | } else if (isString(ref)) { 38 | const inst = vnode._owner 39 | if (inst.refs[ref] === node && isFunction(inst.render)) { 40 | delete inst.refs[ref] 41 | } 42 | } else if (isObject(ref)) { 43 | ref.current = null 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/nerv/src/vdom/unmount.ts: -------------------------------------------------------------------------------- 1 | import { isNullOrUndef, isInvalid, VType, VirtualChildren } from 'nerv-shared' 2 | import { isAttrAnEvent, isArray } from 'nerv-utils' 3 | import Ref from './ref' 4 | import { detachEvent } from '../event' 5 | 6 | export function unmountChildren ( 7 | children: VirtualChildren, 8 | parentDom?: Element 9 | ) { 10 | if (isArray(children)) { 11 | for (let i = 0, len = children.length; i < len; i++) { 12 | unmount(children[i], parentDom) 13 | } 14 | } else { 15 | unmount(children, parentDom) 16 | } 17 | } 18 | 19 | export function unmount (vnode, parentDom?) { 20 | if (isInvalid(vnode)) { 21 | return 22 | } 23 | const vtype = vnode.vtype 24 | // Bitwise operators for better performance 25 | // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators 26 | const dom = vnode.dom 27 | 28 | if ((vtype & (VType.Composite)) > 0) { 29 | vnode.destroy() 30 | } else if ((vtype & VType.Node) > 0) { 31 | const { props, children, ref } = vnode 32 | unmountChildren(children) 33 | for (const propName in props) { 34 | if (isAttrAnEvent(propName)) { 35 | detachEvent(dom, propName, props[propName]) 36 | } 37 | } 38 | if (ref !== null) { 39 | Ref.detach(vnode, ref, dom) 40 | } 41 | } else if (vtype & VType.Portal) { 42 | unmountChildren(vnode.children, vnode.type) 43 | } 44 | 45 | if (!isNullOrUndef(parentDom) && !isNullOrUndef(dom)) { 46 | if (isArray(dom)) { 47 | for (let i = 0; i < dom.length; i++) { 48 | parentDom.removeChild(dom[i]) 49 | } 50 | } else { 51 | parentDom.removeChild(dom) 52 | } 53 | } 54 | // vnode.dom = null 55 | } 56 | -------------------------------------------------------------------------------- /packages/nerv/src/version.ts: -------------------------------------------------------------------------------- 1 | // some library check React version 2 | // see: https://github.com/NervJS/nerv/issues/46 3 | export const version = '15.4.2' 4 | -------------------------------------------------------------------------------- /release.js: -------------------------------------------------------------------------------- 1 | const prompts = require('prompts') 2 | const cp = require('child_process') 3 | 4 | async function f () { 5 | const response = await prompts({ 6 | type: 'select', 7 | name: 'version', 8 | message: `What's the release version?`, 9 | choices: [ 10 | { title: 'auto (by semver version)', value: 'auto' }, 11 | { title: 'beta', value: 'beta' }, 12 | { title: 'manual', value: 'manual' } 13 | ], 14 | initial: 1 15 | }) 16 | 17 | const { version } = response 18 | const command = 'npm run build && lerna publish --exact --conventional-commits' 19 | switch (version) { 20 | case 'auto': 21 | cp.execSync(command) 22 | break 23 | case 'beta': 24 | cp.execSync(command + '--cd-version=prepatch --preid=beta --npm-tag=beta') 25 | break 26 | case 'manual': 27 | const manual = await prompts({ 28 | type: 'text', 29 | name: 'version', 30 | message: `What's the EXACT version that you want to publish?` 31 | }) 32 | cp.execFileSync(`${command} --repo-version ${manual.version}`) 33 | break 34 | default: 35 | break 36 | } 37 | } 38 | 39 | f() 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "lib", 12 | "noUnusedLocals": true, 13 | "strictNullChecks": true, 14 | "sourceMap": true, 15 | "declaration": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "nervjs": ["packages/nerv/src"], 19 | "nerv-devtools": ["packages/nerv-devtools/src"], 20 | "nerv-shared": ["packages/nerv-shared/src"], 21 | "nerv-utils": ["packages/nerv-utils/src"] 22 | }, 23 | "rootDir": "." 24 | }, 25 | "include": [ 26 | "packages/*/src", 27 | "packages/*/__tests__" 28 | ], 29 | "exclude": [ 30 | "node_modules", 31 | "dist", 32 | "tests", 33 | "jest", 34 | "lib", 35 | "__tests__", 36 | "**/*.test.ts" 37 | ], 38 | "compileOnSave": false 39 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "defaultSeverity": "warning", 6 | "rules": { 7 | "interface-name": [ 8 | false 9 | ], 10 | "no-conditional-assignment": false, 11 | "prefer-for-of": false, 12 | "one-variable-per-declaration": [false], 13 | "forin": false, 14 | "ordered-imports": [ 15 | "false" 16 | ], 17 | "no-console": [ 18 | false, 19 | "error" 20 | ], 21 | "ban-types": false, 22 | "no-bitwise": false, 23 | "no-object-literal-type-assertion": false, 24 | "member-access": false, 25 | "object-literal-sort-keys": false, 26 | "jsx-no-multiline-js": false, 27 | "quotemark": [ 28 | true, 29 | "single", 30 | "avoid-escape" 31 | ], 32 | "semicolon": [ 33 | true, 34 | "never" 35 | ], 36 | "no-string-literal": false, 37 | "jsx-boolean-value": false, 38 | "jsx-wrap-multiline": false, 39 | "variable-name": [ 40 | true, 41 | "allow-leading-underscore", 42 | "ban-keywords" 43 | ], 44 | "space-before-function-paren": true, 45 | "object-literal-key-quotes": [ 46 | false, 47 | "as-needed" 48 | ], 49 | "trailing-comma": [ 50 | true, 51 | { 52 | "multiline": "never", 53 | "singleline": "never" 54 | } 55 | ] 56 | } 57 | } -------------------------------------------------------------------------------- /typing.js: -------------------------------------------------------------------------------- 1 | const dts = require('dts-bundle') 2 | const { join } = require('path') 3 | 4 | const cwd = process.cwd() 5 | const pkgJSON = require(join(cwd, 'package.json')) 6 | 7 | if (pkgJSON.name === 'nerv-redux') { 8 | return 9 | } 10 | 11 | try { 12 | const name = pkgJSON.name === 'nervjs' ? 'nerv' : pkgJSON.name 13 | dts.bundle({ 14 | main: join(__dirname, 'lib', 'packages', name, 'src/index.d.ts'), 15 | name: pkgJSON.name, 16 | out: join(cwd, 'dist/index.d.ts') 17 | }) 18 | console.log(`${pkgJSON.name} in typings is DONE`) 19 | } catch (e) { 20 | throw Error(e) 21 | } 22 | --------------------------------------------------------------------------------