feat(mixin): split a class in independant parts (#3415)

This commit is contained in:
Julien Fontanet 2018-09-17 16:33:21 +02:00 committed by Pierre Donias
parent 42591bd4bd
commit cfd6fd722a
10 changed files with 259 additions and 128 deletions

View File

@ -0,0 +1,3 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)

View File

@ -0,0 +1,24 @@
/benchmark/
/benchmarks/
*.bench.js
*.bench.js.map
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/fixture/
/fixtures/
*.fixture.js
*.fixture.js.map
*.fixtures.js
*.fixtures.js.map
/test/
/tests/
*.spec.js
*.spec.js.map
__snapshots__/

View File

@ -0,0 +1,49 @@
# ${pkg.name} [![Build Status](https://travis-ci.org/${pkg.shortGitHubPath}.png?branch=master)](https://travis-ci.org/${pkg.shortGitHubPath})
> ${pkg.description}
## Install
Installation of the [npm package](https://npmjs.org/package/${pkg.name}):
```
> npm install --save ${pkg.name}
```
## Usage
**TODO**
## Development
```
# Install dependencies
> yarn
# Run the tests
> yarn test
# Continuously compile
> yarn dev
# Continuously run the tests
> yarn dev-test
# Build for production (automatically called by npm install)
> yarn build
```
## Contributions
Contributions are *very* welcomed, either on the documentation or on
the code.
You may:
- report any [issue](${pkg.bugs})
you've encountered;
- fork and create a pull request.
## License
${pkg.license} © [${pkg.author.name}](${pkg.author.url})

View File

@ -0,0 +1,49 @@
{
"name": "@xen-orchestra/mixin",
"version": "0.0.0",
"license": "ISC",
"description": "",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/mixin",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"dependencies": {
"bind-property-descriptor": "^1.0.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"babel-plugin-dev": "^1.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
}
}

View File

@ -0,0 +1,130 @@
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
// ===================================================================
const { defineProperties, getOwnPropertyDescriptor } = Object
const isIgnoredProperty = name => name[0] === '_' || name === 'constructor'
const IGNORED_STATIC_PROPERTIES = {
__proto__: null,
arguments: true,
caller: true,
length: true,
name: true,
prototype: true,
}
const isIgnoredStaticProperty = name => name in IGNORED_STATIC_PROPERTIES
const ownKeys =
(typeof Reflect !== 'undefined' && Reflect.ownKeys) ||
(({ getOwnPropertyNames: names, getOwnPropertySymbols: symbols }) =>
symbols !== undefined ? obj => names(obj).concat(symbols(obj)) : names)(
Object
)
// -------------------------------------------------------------------
const mixin = Mixins => Class => {
if (__DEV__ && !Array.isArray(Mixins)) {
throw new TypeError('Mixins should be an array')
}
const { name } = Class
// Copy properties of plain object mix-ins to the prototype.
{
const allMixins = Mixins
Mixins = []
const { prototype } = Class
const descriptors = { __proto__: null }
allMixins.forEach(Mixin => {
if (typeof Mixin === 'function') {
Mixins.push(Mixin)
return
}
for (const prop of ownKeys(Mixin)) {
if (__DEV__ && prop in prototype) {
throw new Error(`${name}#${prop} is already defined`)
}
;(descriptors[prop] = getOwnPropertyDescriptor(
Mixin,
prop
)).enumerable = false // Object methods are enumerable but class methods are not.
}
})
defineProperties(prototype, descriptors)
}
const n = Mixins.length
function DecoratedClass (...args) {
const instance = new Class(...args)
for (let i = 0; i < n; ++i) {
const Mixin = Mixins[i]
const { prototype } = Mixin
const mixinInstance = new Mixin(instance, ...args)
const descriptors = { __proto__: null }
const props = ownKeys(prototype)
for (let j = 0, m = props.length; j < m; ++j) {
const prop = props[j]
if (isIgnoredProperty(prop)) {
continue
}
if (prop in instance) {
throw new Error(`${name}#${prop} is already defined`)
}
descriptors[prop] = getBoundPropertyDescriptor(
prototype,
prop,
mixinInstance
)
}
defineProperties(instance, descriptors)
}
return instance
}
// Copy original and mixed-in static properties on Decorator class.
const descriptors = { __proto__: null }
ownKeys(Class).forEach(prop => {
let descriptor
if (
!(
isIgnoredStaticProperty(prop) &&
// if they already exist...
(descriptor = getOwnPropertyDescriptor(DecoratedClass, prop)) !==
undefined &&
// and are not configurable.
!descriptor.configurable
)
) {
descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
}
})
Mixins.forEach(Mixin => {
ownKeys(Mixin).forEach(prop => {
if (isIgnoredStaticProperty(prop)) {
return
}
if (__DEV__ && prop in descriptors) {
throw new Error(`${name}.${prop} is already defined`)
}
descriptors[prop] = getOwnPropertyDescriptor(Mixin, prop)
})
})
defineProperties(DecoratedClass, descriptors)
return DecoratedClass
}
export { mixin as default }

View File

@ -20,6 +20,7 @@
### Released packages
- @xen-orchestra/mixin v0.0.0
- @xen-orchestra/emit-async v0.0.0
- xo-server v5.27.0
- xo-web v5.27.0

View File

@ -35,12 +35,12 @@
"@xen-orchestra/cron": "^1.0.3",
"@xen-orchestra/emit-async": "^0.0.0",
"@xen-orchestra/fs": "^0.3.0",
"@xen-orchestra/mixin": "^0.0.0",
"ajv": "^6.1.1",
"app-conf": "^0.5.0",
"archiver": "^3.0.0",
"async-iterator-to-stream": "^1.0.1",
"base64url": "^3.0.0",
"bind-property-descriptor": "^1.0.0",
"blocked": "^1.2.1",
"bluebird": "^3.5.1",
"body-parser": "^1.18.2",

View File

@ -1,13 +1,3 @@
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
import { isArray, isFunction } from './utils'
// ===================================================================
const { defineProperties, getOwnPropertyDescriptor } = Object
// ===================================================================
// Debounce decorator for methods.
//
// See: https://github.com/wycats/javascript-decorators
@ -49,118 +39,3 @@ export const debounce = duration => (target, name, descriptor) => {
descriptor.value = debounced
return descriptor
}
// -------------------------------------------------------------------
const _ownKeys =
(typeof Reflect !== 'undefined' && Reflect.ownKeys) ||
(({ getOwnPropertyNames: names, getOwnPropertySymbols: symbols }) =>
symbols ? obj => names(obj).concat(symbols(obj)) : names)(Object)
const _isIgnoredProperty = name => name[0] === '_' || name === 'constructor'
const _IGNORED_STATIC_PROPERTIES = {
__proto__: null,
arguments: true,
caller: true,
length: true,
name: true,
prototype: true,
}
const _isIgnoredStaticProperty = name => _IGNORED_STATIC_PROPERTIES[name]
export const mixin = MixIns => Class => {
if (!isArray(MixIns)) {
MixIns = [MixIns]
}
const { name } = Class
// Copy properties of plain object mix-ins to the prototype.
{
const allMixIns = MixIns
MixIns = []
const { prototype } = Class
const descriptors = { __proto__: null }
for (const MixIn of allMixIns) {
if (isFunction(MixIn)) {
MixIns.push(MixIn)
continue
}
for (const prop of _ownKeys(MixIn)) {
if (prop in prototype) {
throw new Error(`${name}#${prop} is already defined`)
}
;(descriptors[prop] = getOwnPropertyDescriptor(
MixIn,
prop
)).enumerable = false // Object methods are enumerable but class methods are not.
}
}
defineProperties(prototype, descriptors)
}
function Decorator (...args) {
const instance = new Class(...args)
for (const MixIn of MixIns) {
const { prototype } = MixIn
const mixinInstance = new MixIn(instance, ...args)
const descriptors = { __proto__: null }
for (const prop of _ownKeys(prototype)) {
if (_isIgnoredProperty(prop)) {
continue
}
if (prop in instance) {
throw new Error(`${name}#${prop} is already defined`)
}
descriptors[prop] = getBoundPropertyDescriptor(
prototype,
prop,
mixinInstance
)
}
defineProperties(instance, descriptors)
}
return instance
}
// Copy original and mixed-in static properties on Decorator class.
const descriptors = { __proto__: null }
for (const prop of _ownKeys(Class)) {
let descriptor
if (
!(
_isIgnoredStaticProperty(prop) &&
// if they already exist...
(descriptor = getOwnPropertyDescriptor(Decorator, prop)) &&
// and are not configurable.
!descriptor.configurable
)
) {
descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
}
}
for (const MixIn of MixIns) {
for (const prop of _ownKeys(MixIn)) {
if (_isIgnoredStaticProperty(prop)) {
continue
}
if (prop in descriptors) {
throw new Error(`${name}.${prop} is already defined`)
}
descriptors[prop] = getOwnPropertyDescriptor(MixIn, prop)
}
}
defineProperties(Decorator, descriptors)
return Decorator
}

View File

@ -2,6 +2,7 @@
import concurrency from 'limit-concurrency-decorator'
import deferrable from 'golike-defer'
import fatfs from 'fatfs'
import mixin from '@xen-orchestra/mixin'
import synchronized from 'decorator-synchronized'
import tarStream from 'tar-stream'
import vmdkToVhd from 'xo-vmdk-to-vhd'
@ -32,7 +33,6 @@ import { satisfies as versionSatisfies } from 'semver'
import createSizeStream from '../size-stream'
import fatfsBuffer, { init as fatfsBufferInit } from '../fatfs-buffer'
import { mixin } from '../decorators'
import {
asyncMap,
camelToSnakeCase,

View File

@ -1,5 +1,6 @@
import XoCollection from 'xo-collection'
import XoUniqueIndex from 'xo-collection/unique-index'
import mixin from '@xen-orchestra/mixin'
import { createClient as createRedisClient } from 'redis'
import { EventEmitter } from 'events'
import { noSuchObject } from 'xo-common/api-errors'
@ -16,7 +17,6 @@ import {
import mixins from './xo-mixins'
import Connection from './connection'
import { mixin } from './decorators'
import { generateToken, noop } from './utils'
// ===================================================================