/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 += `${type}>`
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('')
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 |
80 | )
81 | const cloneVNode = cloneElement(vnode)
82 | render(cloneVNode, scratch)
83 | expect(scratch.innerHTML).toEqual(
84 | normalizeHTML(
85 | ''
86 | )
87 | )
88 | })
89 | it('can clone node by new props', () => {
90 | const vnode = (
91 |
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 |
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('')
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
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('')
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 |
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('')
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('')
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('')
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 => `
`
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 => `
`
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 = `>${pre}>`
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 |
--------------------------------------------------------------------------------