1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-17 20:51:20 +02:00

Benchmark consistence && Allow users to select benches to run (#1765)

* Use slate rather than relative path

* Move benchmark to one dir

* Use slate-* instead of relative path

* Before and After Function

* Remove un-necessary cross-env

* Hard fix

* Lint the hard fix

* Reset memory in bench()

* Working on Benchmark Frameworks

* Rename to slate-dev-benchmark

* Add packages

* Fix prettier bug

* Benchmark framework is in working

* Do not log in test

* max times test

* mute logger in test

* add hr time

* Better support for maxTime; add support of split runs to save memory space

* Fix maxTries

* Add global.gc

* Global gc for each bench

* Better test interface

* Test max-time

* Test max-time done

* Add Benchmark among packages

* Starting to get benchmark running

* Pure Node lib

* Change babelrc for pure Node benchmark

* Moving Benchmarks

* Get benchmark and test running

* Get benchmark for slate-html-serializer

* add slate-react

* add slate/changes

* all benchmarks are converted

* Run benchmark by yarn

* Run benchmark with expose-gc

* Annotate Bench.js

* Do not bundle slate-dev-benchmark in rollup

* Add annotation

* Allow config file to enable part benchmark compare

* Add config for compare

* support compare.js

* Do not re-allocate memory; due to a large heap taken influence result

* Render with Decorations

* get active marks at range

* Fix bug in showing percents

* Fix percent showing bug

* chore: add more benches

* Better output of benchmark

* Fix linting

* decoration and normal as different benchmark test

* Fix deserialize benchmark

* README.md

* Fix Readme.md

* README.md

* block-spacing config

* safer user config loading

* use package.json to load package in test

* Consistent linting

* move components to parent directory

* Annotation styling in package

* margin line before multi-line block

* Fix naive bug

* Fix naive bug

* Fix a blank line

* only log user and hr

* Better name

* Better annotation for runBundleTasks

* Fix typo

* Better logger

* Move async to test

* Omit skip

* Only log the user space time

* Single line async sleep

* file name fix

* Fix annotation

* Better output of compare

* Remove get-characters(-at-range) benchmarks

* Restore emoji

* Capitalize types

* Remove compare to another area

* Add grep and config interface

* Linting files

* Linting benchmarks

* Linting benchmarks

* Update yarn.lock
This commit is contained in:
Jinxuan Zhu
2018-07-19 16:01:55 -04:00
committed by Ian Storm Taylor
parent 09c93a6cd4
commit 8f9bfdac2b
79 changed files with 1884 additions and 482 deletions

View File

@@ -23,7 +23,19 @@
}, },
"test": { "test": {
"presets": [ "presets": [
"env", ["env", {
"exclude": ["transform-regenerator"]
}],
"react",
"stage-0"
],
"plugins": ["transform-runtime"]
},
"benchmark": {
"presets": [
["env", {
"exclude": ["transform-regenerator"]
}],
"react", "react",
"stage-0" "stage-0"
], ],

3
.gitignore vendored
View File

@@ -21,3 +21,6 @@ packages/*/yarn.lock
.DS_Store .DS_Store
.idea/ .idea/
.vscode/ .vscode/
# Editor files
.tern-port

View File

@@ -90,6 +90,16 @@ yarn benchmark
There will be some subtle changes in iteration speed always, but the comparison reporter will highlight any changes that seem meaningful. You can run `benchmark` multiple times to ensure the speed up persists. There will be some subtle changes in iteration speed always, but the comparison reporter will highlight any changes that seem meaningful. You can run `benchmark` multiple times to ensure the speed up persists.
### Run Selected Benchmarks
To run selected benchmarks, create `tmp/benchmark-config.js` with `module.exports.include`. For example, to run slate-core benchmarks only with `get-*`, we can create a `tmp/benchmark-config.js` as
```
module.exports.include = {
slate: /^get/
}
```
## Adding Browser Support ## Adding Browser Support
Slate aims to targeted all of the modern browsers, and eventually the modern mobile platforms. Right now browser support is limited to the latest versions of [Chrome](https://www.google.com/chrome/browser/desktop/), [Firefox](https://www.mozilla.org/en-US/firefox/new/), and [Safari](http://www.apple.com/safari/), but if you are interested in adding support for another modern platform, that is welcomed! Slate aims to targeted all of the modern browsers, and eventually the modern mobile platforms. Right now browser support is limited to the latest versions of [Chrome](https://www.google.com/chrome/browser/desktop/), [Firefox](https://www.mozilla.org/en-US/firefox/new/), and [Safari](http://www.apple.com/safari/), but if you are interested in adding support for another modern platform, that is welcomed!

115
benchmark/compare.js Normal file
View File

@@ -0,0 +1,115 @@
/* eslint-disable no-console */
const chalk = require('chalk')
const figures = require('figures')
const emojis = require('emojis')
const { resolve } = require('path')
const baseline = require(resolve(process.cwd(), 'tmp/benchmark-baseline'))
const comparison = require(resolve(process.cwd(), 'tmp/benchmark-comparison'))
const { existsSync } = require('fs')
/**
* Constants.
*/
let THRESHOLD = 0.333
const configPath = '../../tmp/benchmark-config.js'
if (existsSync(configPath)) {
const alternative = require(configPath).THRESHOLD
if (typeof alternative === 'number' && alternative > 0) {
THRESHOLD = alternative
}
}
/**
* Print.
*/
console.log()
console.log(` benchmarks`)
baseline.forEach((suite, i) => {
console.log(` ${suite.name}`)
suite.benchmarks.forEach((base, j) => {
const compared = { user: {}, hr: {} }
for (const key of Object.keys(compared)) {
const comp = comparison[i].benchmarks[j]
if (!comp) return
const b = base.iterations / base[key] * 1000
const c = comp.iterations / comp[key] * 1000
const balancePercent =
b > c ? Math.round(Math.abs(b - c) / c * 100) : (c - b) / b * 100
const output = `${b.toFixed(2)} -> ${c.toFixed(2)} ops/sec`
compared[key].baseOutput = output
compared[key].percentOutput = `${balancePercent.toFixed(2)}% ${
c > b ? 'faster' : 'slower'
}`
compared[key].percentValue = balancePercent
compared[key].b = b
compared[key].c = c
compared[key].isFaster = c > b
if (balancePercent > 1000) {
compared[key].percentOutput += emojis.unicode(' :scream: ')
} else if (balancePercent > 100) {
if (c > b) {
compared[key].percentOutput += emojis.unicode(' :raised_hands: ')
} else {
compared[key].percentOutput += emojis.unicode(' :worried: ')
}
}
}
const { user, hr } = compared
if (
user.percentValue < THRESHOLD * 100 &&
hr.percentValue < THRESHOLD * 100
) {
console.log(
chalk.grey(
` ${figures.tick} ${base.name}: ${user.baseOutput} (${
user.percentOutput
})`
)
)
return
}
if (user.isFaster === hr.isFaster) {
if (user.isFaster) {
console.log(chalk.green(` ${figures.star} ${base.name}:`))
console.log(
` user: ${user.baseOutput} (${user.percentOutput})`
)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
return
}
console.log(chalk.red(` ${figures.cross} ${base.name}:`))
console.log(
` user: ${user.baseOutput} (${user.percentOutput})`
)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
return
}
console.log(chalk.red(` ${figures.questionMarkPrefix} ${base.name}:`))
console.log(` user: ${user.baseOutput} (${user.percentOutput})`)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
})
})
console.log()

39
benchmark/config.js Normal file
View File

@@ -0,0 +1,39 @@
const { resolve } = require('path')
const { existsSync } = require('fs')
const program = require('commander')
program
.option('-g, --grep []', 'Add grep pattern to filter running benchmarks')
.option('-c, --config [file]', 'Add config to filter running benchmarks')
.parse(process.argv)
const { grep } = program
if (grep) {
const pattern = new RegExp(grep)
module.exports.include = {
slate: pattern,
'slate-html-serializer': pattern,
'slate-plain-serializer': pattern,
'slate-react': pattern,
}
} else {
let { config = 'tmp/benchmark-config.js' } = program
config = resolve(config)
const userConfig = existsSync(config) ? require(config) : {}
if (userConfig.include) {
module.exports.include = userConfig.include
} else if (userConfig.default) {
module.exports.inlcude = userConfig.default
} else {
module.exports.include = {
slate: /^/,
'slate-html-serializer': /^/,
'slate-plain-serializer': /^/,
'slate-react': /^/,
}
}
}

View File

@@ -0,0 +1,57 @@
const { writeFileSync } = require('fs')
function convertRepo(report) {
const result = []
for (const name in report) {
const suite = report[name]
result.push({
name,
type: 'suite',
benchmarks: convertSuite(suite),
})
}
return result
}
function convertSuite(suite) {
const result = []
for (const name in suite) {
const bench = suite[name]
const { user, cycles } = bench
result.push({
name,
type: 'bench',
elapsed: user,
iterations: cycles,
ops: 1000 * cycles / user,
...bench,
})
}
return result
}
const IS_COMPARE = process.env.COMPARE
const filePath = IS_COMPARE
? './tmp/benchmark-comparison.json'
: './tmp/benchmark-baseline.json'
function generateReport(repo) {
repo
.run()
.then(report => {
const data = JSON.stringify(convertRepo(report))
writeFileSync(filePath, data)
return report
})
.then(report => {
if (IS_COMPARE) {
require('./compare')
}
})
}
module.exports = { generateReport }

45
benchmark/helpers/h.js Normal file
View File

@@ -0,0 +1,45 @@
/* eslint-disable import/no-extraneous-dependencies */
const { createHyperscript } = require('slate-hyperscript')
/**
* Define a hyperscript.
*
* @type {Function}
*/
const h = createHyperscript({
blocks: {
line: 'line',
paragraph: 'paragraph',
quote: 'quote',
code: 'code',
list: 'list',
item: 'item',
image: {
type: 'image',
isVoid: true,
},
},
inlines: {
link: 'link',
hashtag: 'hashtag',
comment: 'comment',
emoji: {
type: 'emoji',
isVoid: true,
},
},
marks: {
b: 'bold',
i: 'italic',
u: 'underline',
},
})
/**
* Export.
*
* @type {Function}
*/
module.exports = h

20
benchmark/index.js Normal file
View File

@@ -0,0 +1,20 @@
const { repo } = require('slate-dev-benchmark')
const { resolve } = require('path')
const { readdirSync } = require('fs')
const { generateReport } = require('./generate-report')
const { include } = require('./config')
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c.match(/^slate/)
)
categories.forEach(dir => {
if (include && include[dir]) {
const { run } = require(`./${dir}`)
run(include[dir])
}
})
generateReport(repo)

View File

@@ -0,0 +1,58 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const Html = require('slate-html-serializer').default
const { JSDOM } = require('jsdom') // eslint-disable-line import/no-extraneous-dependencies
const html = new Html({
parseHtml: JSDOM.fragment,
rules: [
{
deserialize(el, next) {
switch (el.tagName.toLowerCase()) {
case 'blockquote':
return {
object: 'block',
type: 'quote',
nodes: next(el.childNodes),
}
case 'p': {
return {
object: 'block',
type: 'paragraph',
nodes: next(el.childNodes),
}
}
case 'strong': {
return {
object: 'mark',
type: 'bold',
nodes: next(el.childNodes),
}
}
case 'em': {
return {
object: 'mark',
type: 'italic',
nodes: next(el.childNodes),
}
}
}
},
},
],
})
module.exports.default = function(string) {
html.deserialize(string)
}
module.exports.input = `
<blockquote>
<p>
This is editable <strong>rich</strong> text, <em>much</em> better than a textarea!
</p>
</blockquote>
`
.trim()
.repeat(10)

View File

@@ -1,10 +1,10 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import Html from '../..' const Html = require('slate-html-serializer').default
import React from 'react' const React = require('react')
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { JSDOM } from 'jsdom' // eslint-disable-line import/no-extraneous-dependencies const { JSDOM } = require('jsdom') // eslint-disable-line import/no-extraneous-dependencies
const html = new Html({ const html = new Html({
parseHtml: JSDOM.fragment, parseHtml: JSDOM.fragment,
@@ -34,11 +34,11 @@ const html = new Html({
], ],
}) })
export default function(state) { module.exports.default = function(state) {
html.serialize(state) html.serialize(state)
} }
export const input = ( module.exports.input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (

View File

@@ -0,0 +1,37 @@
const { readdirSync } = require('fs')
const { basename, extname, resolve } = require('path')
const { resetMemoization } = require('slate')
const { Suite, Bench } = require('slate-dev-benchmark')
/**
* Benchmarks.
*/
module.exports.run = function(include) {
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c != 'index.js'
)
categories.forEach(category => {
const suite = new Suite(category, { minTries: 100, minTime: 1000 })
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
if (include && !benchmark.match(include)) return
const bench = new Bench(suite, benchmark)
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
bench.input(() => module.input)
bench.run(input => {
fn(input)
resetMemoization()
})
})
})
}

View File

@@ -0,0 +1,40 @@
const { basename, extname, resolve } = require('path')
const { readdirSync } = require('fs')
const { resetMemoization } = require('slate')
const { Suite, Bench } = require('slate-dev-benchmark')
/**
* Benchmarks.
*/
module.exports.run = function(include) {
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c != 'index.js'
)
categories.forEach(category => {
const suite = new Suite(category, {
minTries: 100,
minTime: 1000,
})
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
if (include && !benchmark.match(include)) return
const bench = new Bench(suite, benchmark)
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
bench.input(() => module.input)
bench.run(input => {
fn(input)
resetMemoization()
})
})
})
}

View File

@@ -1,14 +1,16 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import Plain from '../..' const Plain = require('slate-plain-serializer').default
export default function(string) { const input = `
Plain.deserialize(string)
}
export const input = `
This is editable plain text, just like a text area. This is editable plain text, just like a text area.
` `
.trim() .trim()
.repeat(10) .repeat(10)
module.exports.input = input
module.exports.default = function(string) {
Plain.deserialize(string)
}

View File

@@ -1,14 +1,14 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import Plain from '../..' const Plain = require('slate-plain-serializer').default
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(state) { module.exports.default = function(state) {
Plain.serialize(state) Plain.serialize(state)
} }
export const input = ( module.exports.input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (

View File

@@ -0,0 +1,40 @@
const { basename, extname, resolve } = require('path')
const { readdirSync } = require('fs')
const { resetMemoization } = require('slate')
const { Suite, Bench } = require('slate-dev-benchmark')
/**
* Benchmarks.
*/
module.exports.run = function(include) {
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c != 'index.js'
)
categories.forEach(category => {
const suite = new Suite(category, {
minTries: 100,
minTime: 1000,
})
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
if (include && !benchmark.match(include)) return
const bench = new Bench(suite, benchmark)
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
bench.input(() => module.input)
bench.run(input => {
fn(input)
resetMemoization()
})
})
})
}

View File

@@ -0,0 +1,46 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const React = require('react')
const ReactDOM = require('react-dom/server')
const h = require('../../helpers/h')
const { Editor } = require('slate-react')
module.exports.default = function(value) {
const el = React.createElement(Editor, { value })
ReactDOM.renderToStaticMarkup(el)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const texts = value.document.getTexts()
const decorations = texts.flatMap((t, index) => {
if (index % 4 !== 0) return []
if (t.length === 0) return []
return [
{
anchorKey: t.key,
anchorOffset: 0,
focusKey: t.key,
focusOffset: 1,
marks: [{ type: 'underline' }],
},
]
})
module.exports.input = value.change().setValue({ decorations }).value

View File

@@ -1,17 +1,17 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import React from 'react' const React = require('react')
import ReactDOM from 'react-dom/server' const ReactDOM = require('react-dom/server')
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { Editor } from '../..' const { Editor } = require('slate-react')
export default function(value) { module.exports.default = function(value) {
const el = React.createElement(Editor, { value }) const el = React.createElement(Editor, { value })
ReactDOM.renderToStaticMarkup(el) ReactDOM.renderToStaticMarkup(el)
} }
export const input = ( module.exports.input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (

View File

@@ -1,18 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(change) { module.exports.default = function(change) {
change.deleteBackward() change.deleteBackward()
} }
export function before(value) { const value = (
const change = value.change()
return change
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map((v, i) => ( {Array.from(Array(10)).map((v, i) => (
@@ -29,3 +24,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = () => {
return value.change()
}

View File

@@ -1,18 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(change) { module.exports.default = function(change) {
change.deleteForward() change.deleteForward()
} }
export function before(value) { const value = (
const change = value.change()
return change
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map((v, i) => ( {Array.from(Array(10)).map((v, i) => (
@@ -29,3 +24,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = () => {
return value.change()
}

View File

@@ -0,0 +1,32 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ change, block }) {
change.insertNodeByKey(block.key, 0, <paragraph>Hello world</paragraph>)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
{i == 0 ? <cursor /> : ''}
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const block = value.document.getBlocks().last()
module.exports.input = function() {
const change = value.change()
return { change, block }
}

View File

@@ -1,26 +1,15 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { resetMemoization } from '../..'
export default function({ change, keys }) { module.exports.default = function({ change, keys }) {
for (const key of keys) { for (const key of keys) {
change.insertTextByKey(key, 0, 'a') change.insertTextByKey(key, 0, 'a')
} }
} }
export function before(value) { const value = (
const change = value.change()
const keys = value.document
.getTexts()
.toArray()
.map(t => t.key)
resetMemoization()
return { change, keys }
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map((v, i) => ( {Array.from(Array(10)).map((v, i) => (
@@ -37,3 +26,12 @@ export const input = (
</document> </document>
</value> </value>
) )
const keys = value.document
.getTexts()
.toArray()
.map(t => t.key)
module.exports.input = function() {
const change = value.change()
return { change, keys }
}

View File

@@ -1,21 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { resetMemoization } from '../..'
export default function({ change, text }) { module.exports.default = function({ change, text }) {
change.insertTextByKey(text.key, 0, 'a') change.insertTextByKey(text.key, 0, 'a')
} }
export function before(value) { const value = (
const change = value.change()
const text = value.document.getLastText()
resetMemoization()
return { change, text }
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map((v, i) => ( {Array.from(Array(10)).map((v, i) => (
@@ -32,3 +24,9 @@ export const input = (
</document> </document>
</value> </value>
) )
const text = value.document.getLastText()
module.exports.input = function() {
const change = value.change()
return { change, text }
}

View File

@@ -1,18 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(change) { module.exports.default = function(change) {
change.insertText('a') change.insertText('a')
} }
export function before(value) { const value = (
const change = value.change()
return change
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map((v, i) => ( {Array.from(Array(10)).map((v, i) => (
@@ -29,3 +24,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = function() {
return value.change()
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(change) { module.exports.default = function(change) {
change.normalize() change.normalize()
} }
export function before(value) { const value = (
return value.change()
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map((v, i) => ( {Array.from(Array(10)).map((v, i) => (
@@ -28,3 +24,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = function() {
return value.change()
}

View File

@@ -0,0 +1,32 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ change, text }) {
change.removeNodeByKey(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
{i == 0 ? <cursor /> : ''}
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
const change = value.change()
return { change, text }
}

View File

@@ -1,18 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(change) { module.exports.default = function(change) {
change.splitBlock() change.splitBlock()
} }
export function before(value) { const value = (
const change = value.change()
return change
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map((v, i) => ( {Array.from(Array(10)).map((v, i) => (
@@ -29,3 +24,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = function() {
return value.change()
}

37
benchmark/slate/index.js Normal file
View File

@@ -0,0 +1,37 @@
const { readdirSync } = require('fs')
const { basename, extname, resolve } = require('path')
const { resetMemoization } = require('slate')
const { Suite, Bench } = require('slate-dev-benchmark')
/**
* Benchmarks.
*/
module.exports.run = function(include) {
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c != 'index.js'
)
categories.forEach(category => {
const suite = new Suite(category, { minTries: 100, minTime: 1000 })
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
if (include && !benchmark.match(include)) return
const bench = new Bench(suite, benchmark)
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
bench.input(module.input)
bench.run(input => {
fn(input)
resetMemoization()
})
})
})
}

View File

@@ -1,12 +1,12 @@
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import { Value } from '../..' const { Value } = require('slate')
export default function(json) { module.exports.default = function(json) {
Value.fromJSON(json) Value.fromJSON(json)
} }
export const input = { const input = {
document: { document: {
nodes: Array.from(Array(100)).map(() => ({ nodes: Array.from(Array(100)).map(() => ({
type: 'list', type: 'list',
@@ -64,3 +64,7 @@ export const input = {
})), })),
}, },
} }
module.exports.input = function() {
return input
}

View File

@@ -1,12 +1,12 @@
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import { Value } from '../..' const { Value } = require('slate')
export default function(json) { module.exports.default = function(json) {
Value.fromJSON(json) Value.fromJSON(json)
} }
export const input = { const input = {
document: { document: {
nodes: Array.from(Array(10)).map(() => ({ nodes: Array.from(Array(10)).map(() => ({
object: 'block', object: 'block',
@@ -44,3 +44,7 @@ export const input = {
})), })),
}, },
} }
module.exports.input = function() {
return input
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getCharactersAtRange(value.selection) value.document.getActiveMarksAtRange(value.selection)
} }
export function before(value) { const value = (
return value.change().selectAll().value
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document> </document>
</value> </value>
) )
.change()
.selectAll().value
module.exports.input = function() {
return value
}

View File

@@ -0,0 +1,30 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ value, text }) {
value.document.getAncestors(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
return { value, text }
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getBlocksAtRange(value.selection) value.document.getBlocksAtRange(value.selection)
} }
export function before(value) { const value = (
return value.change().selectAll().value
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document> </document>
</value> </value>
) )
.change()
.selectAll().value
module.exports.input = () => {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getBlocks() value.document.getBlocks()
} }
export const input = ( const value = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = function() {
return value
}

View File

@@ -1,20 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { resetMemoization } from '../..'
export default function(text) { module.exports.default = function({ value, first, last }) {
text.getLeaves() value.document.getCommonAncestor(first.key, last.key)
} }
export function before(value) { const value = (
const text = value.document.getFirstText()
resetMemoization()
return text
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -30,3 +23,10 @@ export const input = (
</document> </document>
</value> </value>
) )
const first = value.document.getFirstText()
const last = value.document.getLastText()
module.exports.input = function() {
return { value, first, last }
}

View File

@@ -0,0 +1,30 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ value, text }) {
value.document.getFurthestAncestor(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
return { value, text }
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getInlinesAtRange(value.selection) value.document.getInlinesAtRange(value.selection)
} }
export function before(value) { const value = (
return value.change().selectAll().value
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document> </document>
</value> </value>
) )
.change()
.selectAll().value
module.exports.input = function() {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getInlines() value.document.getInlines()
} }
export const input = ( const value = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = function() {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(text) {
value.document.getCharacters() text.getLeaves()
} }
export const input = ( const value = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -23,3 +23,8 @@ export const input = (
</document> </document>
</value> </value>
) )
const text = value.document.getFirstText()
module.exports.input = function() {
return text
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getMarksAtRange(value.selection) value.document.getMarksAtRange(value.selection)
} }
export function before(value) { const value = (
return value.change().selectAll().value
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document> </document>
</value> </value>
) )
.change()
.selectAll().value
module.exports.input = function() {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getMarks() value.document.getMarks()
} }
export const input = ( const value = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = function() {
return value
}

View File

@@ -0,0 +1,30 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ value, text }) {
value.document.getParent(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = () => {
return { value, text }
}

View File

@@ -1,20 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { resetMemoization } from '../..'
export default function({ value, text }) { module.exports.default = function({ value, text }) {
value.document.getPath(text.key) value.document.getPath(text.key)
} }
export function before(value) { const value = (
const text = value.document.getLastText()
resetMemoization()
return { value, text }
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -30,3 +23,8 @@ export const input = (
</document> </document>
</value> </value>
) )
const text = value.document.getLastText()
module.exports.input = () => {
return { value, text }
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getTextsAtRange(value.selection) value.document.getTextsAtRange(value.selection)
} }
export function before(value) { const value = (
return value.change().selectAll().value
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document> </document>
</value> </value>
) )
.change()
.selectAll().value
module.exports.input = function() {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.document.getTexts() value.document.getTexts()
} }
export const input = ( const value = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = function() {
return value
}

View File

@@ -0,0 +1,30 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ value, text }) {
value.document.getDescendant(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
return { value, text }
}

View File

@@ -1,25 +1,15 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { resetMemoization } from '../..'
export default function({ value, keys }) { module.exports.default = function({ value, keys }) {
keys.forEach(key => { keys.forEach(key => {
value.document.hasNode(key) value.document.hasNode(key)
}) })
} }
export function before(value) { const value = (
const keys = value.document
.getTexts()
.toArray()
.map(t => t.key)
resetMemoization()
return { value, keys }
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -35,3 +25,11 @@ export const input = (
</document> </document>
</value> </value>
) )
const keys = value.document
.getTexts()
.toArray()
.map(t => t.key)
module.exports.input = function() {
return { value, keys }
}

View File

@@ -1,20 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { resetMemoization } from '../..'
export default function({ value, text }) { module.exports.default = function({ value, text }) {
value.document.hasNode(text.key) value.document.hasNode(text.key)
} }
export function before(value) { const value = (
const text = value.document.getLastText()
resetMemoization()
return { value, text }
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -30,3 +23,8 @@ export const input = (
</document> </document>
</value> </value>
) )
const text = value.document.getLastText()
module.exports.input = function() {
return { value, text }
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
export default function(value) { module.exports.default = function(value) {
value.toJSON() value.toJSON()
} }
export const input = ( const value = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document> </document>
</value> </value>
) )
module.exports.input = function() {
return value
}

View File

@@ -1,23 +1,13 @@
/** @jsx h */ /** @jsx h */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import h from '../../test/helpers/h' const h = require('../../helpers/h')
import { resetMemoization } from '../..'
export default function({ value, next }) { module.exports.default = function({ value, next }) {
value.document.updateNode(next) value.document.updateNode(next)
} }
export function before(value) { const value = (
const texts = value.document.getTexts()
const { size } = texts
const text = texts.get(Math.round(size / 2))
const next = text.insertText(0, 'some text')
resetMemoization()
return { value, next }
}
export const input = (
<value> <value>
<document> <document>
{Array.from(Array(10)).map(() => ( {Array.from(Array(10)).map(() => (
@@ -33,3 +23,12 @@ export const input = (
</document> </document>
</value> </value>
) )
const texts = value.document.getTexts()
const { size } = texts
const text = texts.get(Math.round(size / 2))
const next = text.insertText(0, 'some text')
module.exports.input = function() {
return { value, next }
}

View File

@@ -16,17 +16,20 @@
"babel-preset-stage-0": "^6.24.1", "babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.26.0", "babel-runtime": "^6.26.0",
"chalk": "^1.1.3", "chalk": "^1.1.3",
"commander": "^2.15.1",
"copy-webpack-plugin": "^4.4.1", "copy-webpack-plugin": "^4.4.1",
"cross-env": "^5.1.3", "cross-env": "^5.1.3",
"css-loader": "^0.28.9", "css-loader": "^0.28.9",
"emotion": "^9.2.4", "emotion": "^9.2.4",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"emojis": "^1.0.10",
"eslint-config-prettier": "^2.9.0", "eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.8.0",
"eslint-plugin-prettier": "^2.5.0", "eslint-plugin-prettier": "^2.5.0",
"eslint-plugin-react": "^7.6.0", "eslint-plugin-react": "^7.6.0",
"extract-text-webpack-plugin": "^3.0.2", "extract-text-webpack-plugin": "^3.0.2",
"faker": "^3.1.0", "faker": "^3.1.0",
"figures": "^2.0.0",
"fs-promise": "^1.0.0", "fs-promise": "^1.0.0",
"gh-pages": "^0.11.0", "gh-pages": "^0.11.0",
"html-webpack-plugin": "^2.30.1", "html-webpack-plugin": "^2.30.1",
@@ -62,6 +65,7 @@
"rollup-plugin-sourcemaps": "^0.4.2", "rollup-plugin-sourcemaps": "^0.4.2",
"rollup-plugin-uglify": "^3.0.0", "rollup-plugin-uglify": "^3.0.0",
"slate-collapse-on-escape": "^0.6.0", "slate-collapse-on-escape": "^0.6.0",
"slate-dev-benchmark": "*",
"slate-soft-break": "^0.6.0", "slate-soft-break": "^0.6.0",
"source-map-loader": "^0.2.3", "source-map-loader": "^0.2.3",
"source-map-support": "^0.4.0", "source-map-support": "^0.4.0",
@@ -79,13 +83,15 @@
"slate-schema-violations": "*" "slate-schema-violations": "*"
}, },
"scripts": { "scripts": {
"benchmark": "mkdir -p ./tmp && cross-env BABEL_ENV=test babel-node ./node_modules/.bin/_matcha --reporter ./support/benchmark/reporter ./packages/*/benchmark/index.js > ./tmp/benchmark-comparison.json && cross-env BABEL_ENV=test babel-node ./support/benchmark/compare", "benchmark": "cross-env COMPARE=compare node --expose-gc ./tmp/benchmark/index.js",
"benchmark:save": "mkdir -p ./tmp && cross-env BABEL_ENV=test babel-node ./node_modules/.bin/_matcha --reporter ./support/benchmark/reporter ./packages/*/benchmark/index.js > ./tmp/benchmark-baseline.json", "benchmark:save": " yarn benchmark:prepare && node --expose-gc ./tmp/benchmark/index.js",
"benchmark:prepare": "mkdir -p ./tmp && cross-env BABEL_ENV=benchmark babel benchmark --out-dir tmp/benchmark/",
"bootstrap": "lerna bootstrap && yarn build",
"build": "rollup --config ./support/rollup/config.js", "build": "rollup --config ./support/rollup/config.js",
"build:production": "cross-env NODE_ENV=production rollup --config ./support/rollup/config.js && cross-env NODE_ENV=production webpack --config support/webpack/config.js", "build:production": "cross-env NODE_ENV=production rollup --config ./support/rollup/config.js && cross-env NODE_ENV=production webpack --config support/webpack/config.js",
"clean": "lerna run clean && rm -rf ./node_modules ./dist ./build", "clean": "lerna run clean && rm -rf ./node_modules ./dist ./build",
"gh-pages": "gh-pages --dist ./build", "gh-pages": "gh-pages --dist ./build",
"lint": "eslint packages/*/src packages/*/test examples/*/*.js examples/dev/*/*.js && prettier --list-different '**/*.{js,jsx,md,json,css}'", "lint": "eslint benchmark packages/*/src packages/*/test examples/*/*.js examples/dev/*/*.js && prettier --list-different '**/*.{js,jsx,md,json,css}'",
"open": "open http://localhost:8080", "open": "open http://localhost:8080",
"prettier": "prettier --write '**/*.{js,jsx,md,json,css}'", "prettier": "prettier --write '**/*.{js,jsx,md,json,css}'",
"release": "yarn build:production && yarn test && yarn lint && lerna publish && yarn gh-pages", "release": "yarn build:production && yarn test && yarn lint && lerna publish && yarn gh-pages",

View File

@@ -0,0 +1 @@
This package contains 'Suite' and 'Bench' that Slate uses to compare computation efficiency under nodeJS.

View File

@@ -0,0 +1,15 @@
{
"name": "slate-dev-benchmark",
"description": "A simple, development-only benchmark for Slate",
"version": "0.0.1",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "src/index.js",
"devDependencies": {
"mocha": "^2.5.3"
},
"scripts": {
"clean": "rm -rf ./dist ./lib ./node_modules"
},
"keywords": ["slate"]
}

View File

@@ -0,0 +1,227 @@
/* global Promise */
const { BenchType } = require('./types')
const { makeOptions } = require('./makeOptions')
const { Timer } = require('./Timer')
const { logger } = require('./logger')
const errorReport = {
cycles: NaN,
user: NaN,
system: NaN,
all: NaN,
}
/**
* Run a task and calculate the time consuming of tasks
*/
class Bench {
/**
* Construct a bench and register it to a Suite
* @param {Suite} suite
* @param {string} name
* @param {Object} options
*/
constructor(suite, name, options = {}) {
this.name = name
this.options = makeOptions({ ...suite.options, ...options })
this.isFinished = false
this.inputer = () => undefined
this.runner = () => {}
this.report = { ...errorReport }
suite.addBench(this)
}
/**
* Is a Bench?
* @param {any} obj
* @return {boolean}
*/
isBench(obj) {
return obj && obj[BenchType]
}
/**
* Set the method to generate (different} inputs for each run
* @param {Array|Function|Scalar} inputer
* @return {void}
*/
input(inputer) {
if (Array.isArray(inputer)) {
this.inputer = index => inputer[index % inputer.length]
return
}
if (typeof inputer === 'function') {
this.inputer = inputer
return
}
this.inputer = () => inputer
}
/**
* Set the task runner
* @param {Function} runner
* @return {void}
*/
run(runner) {
this.runner = runner
}
/**
* Tries to run tasks in `times`, if the time consuming excedes the max-time, then stop;
* After run, generate report and return
* If initial is the initial index to run the task, for continueing a task in adaptive mode
* @param {number} times
* @param {number} initial
*/
async compose(times, initial) {
times = Math.floor(times)
const isAsync = this.options.async
const { runner, inputer } = this
const { maxTime } = this.options
let seq = Number.isFinite(this.options.maxTries) ? 1 : NaN
let nextCheckIndex = seq
const hrStart = process.hrtime()
if (global.gc) {
global.gc()
}
const report = { user: 0, system: 0, all: 0, hr: 0, cycles: 0 }
for (
let initialIndex = initial;
initialIndex < times;
initialIndex += this.options.allocationTries
) {
const tries = Math.min(times - initialIndex, this.options.allocationTries)
const thisTryReport = await runBundleTasks.call(this, tries, initialIndex)
if (global.gc) {
global.gc()
}
for (const key in report) {
report[key] += thisTryReport[key]
}
}
return report
/**
* Run a bundle of tasks;
* the Bench estimate the time consuming of every `tries` tasks, then explictly run gc, and caculate the time consuming of next bundle tasks
* @param {number} tries
* @param {number} initialIndex
* @return {Promise< Object , *>}
*/
function runBundleTasks(tries, initialIndex) {
const inputs = Array.from({ length: tries }).map(index =>
inputer(index + initialIndex)
)
const timer = new Timer()
timer.start()
return runFrom(0).then(cycles => {
timer.end()
const { elapsed } = timer
return { ...elapsed, cycles }
})
/**
* Run a single task run; If the task is end, return a Promise with the index when the task ends
* @param {number} index
* @return {Promise<number, *>}
*/
function runFrom(index) {
if (index === tries) return Promise.resolve(tries)
if (index === nextCheckIndex) {
const hrEnd = process.hrtime(hrStart)
const elapsed = hrEnd[0] * 1e3 + hrEnd[1] / 1e6
if (elapsed > maxTime) {
return Promise.resolve(index)
} else {
if (elapsed < maxTime / 20) {
seq *= 2
}
nextCheckIndex = seq + nextCheckIndex
}
}
if (!isAsync) {
const inputVar = inputs[index]
runner(inputVar)
return runFrom(index + 1)
} else {
return Promise.resolve(runner(inputs[index])).then(() =>
runFrom(index + 1)
)
}
}
}
}
/*
* Run the bench
* @return {void}
*/
makeRun() {
if (this.isFinished) return true
logger(this)
const { options } = this
const { minTries, maxTime, maxTries } = options
let { minTime } = options
if (minTime > maxTime) minTime = maxTime
return this.compose(minTries, 0)
.then(report => {
if (this.options.mode === 'static') return report
const { all } = report
if (all > minTime) return report
const times = (minTime / all - 1) * minTries
return this.compose(Math.min(times, maxTries), minTries).then(
newReport => {
return mergeResults(report, newReport)
}
)
})
.then(report => {
this.report = report
this.isFinished = true
logger(this)
return true
})
}
}
Bench.prototype[BenchType] = true
/*
* Merge two different report
* @param {Object} res1
* @param {Object} res2
*/
function mergeResults(res1, res2) {
const result = {}
for (const key in res1) {
result[key] = res1[key] + res2[key]
}
return result
}
module.exports = { Bench }

View File

@@ -0,0 +1,73 @@
/* global Promise */
const { RepositoryType } = require('./types')
const { logger } = require('./logger')
const { compose } = require('./compose')
/**
* Repository Class for holding Suite
*/
class Repository {
/**
* Construct a Repository with a name
* @param {string} name
*/
constructor(name = 'default') {
this.name = name
this.suites = []
this.report = {}
this.isFinished = false
}
/**
* Check whether {obj} is repository
* @param {any} obj
* @return {boolean}
*/
isRepository(obj) {
return obj && obj[RepositoryType]
}
/**
* Register a suite to the repository
* @param {Suite} suite
* @return {void}
*/
addSuite(suite) {
this.isFinished = false
this.suites.push(suite)
}
/**
* Run all suites (and all benches under suites) and generate a report
* @return {Promise<Object, *>}
*/
run() {
if (this.isFinished) return Promise.resolve(this.report)
logger(this)
return compose(this.suites).then(() => {
this.isFinished = true
const report = {}
for (const suite of this.suites) {
report[suite.name] = suite.report
}
this.report = report
return report
})
}
}
Repository.prototype[RepositoryType] = true
/**
* By default, all suites are registers to the following {repo}
*/
const repo = new Repository()
module.exports = { Repository, repo }

View File

@@ -0,0 +1,85 @@
/* global Promise */
const { repo } = require('./Repository.js')
const { SuiteType } = require('./types')
const { logger } = require('./logger')
const { compose } = require('./compose')
const { makeOptions } = require('./makeOptions')
/**
* Suite is for holding Benches
*/
class Suite {
/**
* Construct a Suite and regiester it to repository
* @param {string} name
* @param {Object} options
* @property {void|Repository} repository
* @property {any} ...rest
*/
constructor(name, options = {}) {
const { repository = repo } = options
if (repository[name]) {
throw Error(`The suite name ${name} has benn occupied in repository`)
}
if (typeof name !== 'string') {
throw Error(`The suite name must be a string`)
}
this.name = name
this.options = makeOptions(options)
this.isFinished = false
this.benches = []
this.report = {}
repository.addSuite(this)
}
/**
* Whether it is a Suite
* @param {any} obj
* @return {boolean}
*/
isSuite(obj) {
return obj && obj[SuiteType]
}
/**
* Register an bench to the repository
* @param {Bench} bench
* @return {void}
*/
addBench(bench) {
this.isFinished = false
this.benches.push(bench)
}
/**
* Run all benches, and generate report for consumed time
* @return {Promise<Object, *>
*/
makeRun() {
if (this.isFinished) return Promise.resolve(this.report)
logger(this)
return compose(this.benches).then(() => {
this.isFinished = true
const report = {}
for (const bench of this.benches) {
report[bench.name] = bench.report
}
this.report = report
return report
})
}
}
Suite.prototype[SuiteType] = true
module.exports = { Suite }

View File

@@ -0,0 +1,66 @@
const { TimerType } = require('./types')
class Timer {
constructor() {
this.cpuStartTime = {}
this.hrStartTime = null
this.isStopped = false
this.elapsed = {}
}
/**
* Whether it is a Timer
* @param {any} obj
*/
isTimer(obj) {
return obj && obj[TimerType]
}
/**
* Start the timer
* @return {void}
*/
start() {
this.isStopped = false
this.cpuStartTime = process.cpuUsage()
this.hrStartTime = process.hrtime()
this.elapsed = {}
}
/**
* Stop the timer and store restore in this.elapsed
* @return {Object}
*/
end() {
if (this.isStopped) return this.elapsed
const cpuElapsed = process.cpuUsage(this.cpuStartTime)
const hrElapsed = process.hrtime(this.hrStartTime)
const { user, system } = cpuElapsed
const hr = hrElapsed[0] * 1000 + hrElapsed[1] / 1e6
/**
* user: cpu time consumed in user space
* system: cpu time consumed in system space
* all: user+system
* hr: real world time
* (unit): ms
*/
this.elapsed = {
user: user / 1000,
system: system / 1000,
all: (user + system) / 1000,
hr,
}
this.isStopped = true
return this.elapsed
}
}
Timer.prototype[TimerType] = true
module.exports = { Timer }

View File

@@ -0,0 +1,22 @@
/* global Promise */
const { errorLog } = require('./logger')
/**
* Run all benches/suites with Promise; Ensure an error would not block the whole process
* @param {Array<Suite>|Array<Bench>}
* @param {string} name; where to call the run method
*/
function compose(list, name = 'makeRun') {
return dispatch(0)
function dispatch(index) {
if (index === list.length) return Promise.resolve(true)
const node = list[index]
return new Promise(resolve => resolve(node[name]()))
.catch(err => errorLog(err))
.then(() => dispatch(index + 1))
}
}
module.exports = { compose }

View File

@@ -0,0 +1,5 @@
const { Repository, repo } = require('./Repository')
const { Suite } = require('./Suite')
const { Bench } = require('./Bench')
module.exports = { Repository, Suite, Bench, repo }

View File

@@ -0,0 +1,74 @@
/* eslint-disable no-console */
/**
* IS in test
*/
const IS_TEST =
typeof process !== 'undefined' &&
process.env &&
process.env.BABEL_ENV === 'test'
/**
* Log a `message`
*
* @param {String} message
* @param {Any} ...args
* @retrun {void}
*/
function log(message, ...args) {
if (IS_TEST) return
return console.log(message, ...args)
}
/*
* Log a error `message`
*/
function errorLog(message, ...args) {
console.error(message, ...args)
}
/**
* Logging benchmark result
*/
function logger(obj) {
const prefix = ' '
if (obj.isRepository) {
return log(`Repository ${obj.name} is running`)
}
if (obj.isSuite) {
return log(`${prefix}- Suite ${obj.name} is running`)
}
if (obj.isBench) {
if (!obj.isFinished) {
return log(`${prefix + prefix}- Bench ${obj.name} is running`)
}
const { report } = obj
const { cycles } = report
const header = {
user: 'user:',
hr: 'real:',
}
for (const key of ['user', 'hr']) {
log(
`${prefix + prefix + prefix}${header[key]} * ${cycles} cycles: ${
report[key]
} ms; ( ${cycles * 1000 / report[key]} ops/sec)`
)
}
return log(`${prefix + prefix + prefix}cycles: ${cycles}`)
}
return log(obj)
}
module.exports = { logger, errorLog, log }

View File

@@ -0,0 +1,40 @@
const defaultOptions = {
minTime: 1000,
maxTime: 2000,
minTries: 100,
maxTries: Infinity,
allocationTries: 1000,
async: false,
mode: 'adaptive',
}
/**
* Merge two options for configuring a bench run
* @param {Object} options
* @returns {Object}
* @property {number} minTime
* @property {number} maxTime
* @property {number} minTries
* @property {number} maxTries
* @property {number} allocationTries
* @property {boolean} async
* @property {"static"|"adaptive"} mode
*/
function makeOptions(options) {
const result = { ...defaultOptions, ...options }
for (const key in defaultOptions) {
const shallType = typeof defaultOptions[key]
const inputType = typeof result[key]
if (shallType !== inputType) {
throw TypeError(
`Wrong Input in Config Suite, options[${key}] should be ${shallType}, but the input type is ${inputType}`
)
}
}
return result
}
module.exports = { makeOptions }

View File

@@ -0,0 +1,11 @@
const RepositoryType = '@@__SLATE_REPOSITORY__@@'
const SuiteType = '@@__SLATE_SUITE__@@'
const BenchType = '@@__SLATE_BENCH__@@'
const TimerType = '@@__SLATE_BENCH_TIMER_@@'
module.exports = {
RepositoryType,
SuiteType,
BenchType,
TimerType,
}

View File

@@ -0,0 +1,12 @@
/**
* Dependencies.
*/
/**
* Tests.
*/
describe('slate-dev-benchmark', () => {
require('./tries/')
require('./time/')
})

View File

@@ -0,0 +1,22 @@
import { repo, Suite } from '../..'
import fs from 'fs'
import { resolve } from 'path'
describe('time', async () => {
const suite = new Suite('tries')
const testDir = resolve(__dirname)
const files = fs
.readdirSync(testDir)
.filter(x => x[0] !== '.' && x !== 'index.js')
for (const file of files) {
const module = require(`./${file}`)
it(module.experiment, () => {
module.default(suite)
const { expected } = module
repo.isFinished = false
return repo.run().then(() => expected())
})
}
})

View File

@@ -0,0 +1,31 @@
/* global Promise */
import { Bench } from '../..'
import assert from 'assert'
export const experiment = 'max-time-async'
let index = 0
// A wider range than sync, becuase Promise intialization, babel-node takes time
export function expected() {
assert(
index > 5 && index < 12,
`index should be 10, but is actually ${index}`
)
return true
}
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'adaptive',
minTries: 100,
maxTries: 200,
minTime: 1,
maxTime: 100,
async: true,
})
bench.run(
() => new Promise(resolve => setTimeout(() => resolve(index++), 10))
)
}

View File

@@ -0,0 +1,31 @@
import { Bench } from '../..'
import { syncSleep } from '../utils/sleep'
import assert from 'assert'
export const experiment = 'max-time'
let index = 0
export function expected() {
assert(
index > 85 && index < 115,
`index should be around 100, but is actually ${index}`
)
return true
}
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'adaptive',
minTries: 1000,
maxTries: 2000,
minTime: 1,
maxTime: 1000,
async: false,
})
bench.run(() => {
syncSleep(10)
index++
})
}

View File

@@ -0,0 +1,28 @@
import { Bench } from '../..'
import { syncSleep } from '../utils/sleep'
import assert from 'assert'
export const experiment = 'max-time'
let index = 0
export function expected() {
assert(index === 10, `index should be 10, but is actually ${index}`)
return true
}
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'adaptive',
minTries: 100,
maxTries: 200,
minTime: 1,
maxTime: 100,
async: false,
})
bench.run(() => {
syncSleep(10)
index++
})
}

View File

@@ -0,0 +1,25 @@
import assert from 'assert'
import { repo, Suite } from '../..'
import fs from 'fs'
import { resolve } from 'path'
describe('tries', async () => {
const suite = new Suite('tries')
const testDir = resolve(__dirname)
const files = fs
.readdirSync(testDir)
.filter(x => x[0] !== '.' && x !== 'index.js')
for (const file of files) {
const module = require(`./${file}`)
it(module.experiment, () => {
module.default(suite)
const { actual, expected } = module
repo.isFinished = false
return repo.run().then(() => {
assert.deepEqual(actual, expected)
})
})
}
})

View File

@@ -0,0 +1,19 @@
import { Bench } from '../..'
export const experiment = 'max-tries adaptive mode'
export const actual = { index: 0 }
export const expected = { index: 200 }
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'adaptive',
minTries: 100,
maxTries: 200,
minTime: 100,
maxTime: Infinity,
})
bench.run(() => {
actual.index++
})
}

View File

@@ -0,0 +1,18 @@
import { Bench } from '../..'
export const experiment = 'min-tries static mode'
export const actual = { index: 0 }
export const expected = { index: 100 }
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'static',
minTries: 100,
maxTries: 200,
minTime: 100,
})
bench.run(() => {
actual.index++
})
}

View File

@@ -0,0 +1,10 @@
function syncSleep(ms) {
const start = process.hrtime()
while (true) {
const end = process.hrtime(start)
if (end[0] * 1000 + end[1] / 1e6 > ms) return undefined
}
}
export { syncSleep }

View File

@@ -1,48 +0,0 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import Html from '../..'
import React from 'react'
import { JSDOM } from 'jsdom' // eslint-disable-line import/no-extraneous-dependencies
const html = new Html({
parseHtml: JSDOM.fragment,
rules: [
{
serialize(obj, children) {
switch (obj.object) {
case 'block': {
switch (obj.type) {
case 'paragraph':
return React.createElement('p', {}, children)
case 'quote':
return React.createElement('blockquote', {}, children)
}
}
case 'mark': {
switch (obj.type) {
case 'bold':
return React.createElement('strong', {}, children)
case 'italic':
return React.createElement('em', {}, children)
}
}
}
},
},
],
})
export default function(string) {
html.deserialize(string)
}
export const input = `
<blockquote>
<p>
This is editable <strong>rich</strong> text, <em>much</em> better than a textarea!
</p>
</blockquote>
`
.trim()
.repeat(10)

View File

@@ -1,46 +0,0 @@
/* global suite, set, bench */
import fs from 'fs'
import { basename, extname, resolve } from 'path'
import { resetMemoization } from 'slate'
/**
* Benchmarks.
*/
const categoryDir = resolve(__dirname)
const categories = fs
.readdirSync(categoryDir)
.filter(c => c[0] != '.' && c != 'index.js')
categories.forEach(category => {
suite(category, () => {
set('iterations', 50)
set('mintime', 1000)
if (category == 'models') {
after(() => {
resetMemoization()
})
}
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = fs
.readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
let { input, before, after } = module
if (before) input = before(input)
bench(benchmark, () => {
fn(input)
if (after) after()
})
})
})
})

View File

@@ -1,46 +0,0 @@
/* global suite, set, bench */
import fs from 'fs'
import { basename, extname, resolve } from 'path'
import { resetMemoization } from 'slate'
/**
* Benchmarks.
*/
const categoryDir = resolve(__dirname)
const categories = fs
.readdirSync(categoryDir)
.filter(c => c[0] != '.' && c != 'index.js')
categories.forEach(category => {
suite(category, () => {
set('iterations', 50)
set('mintime', 1000)
if (category == 'models') {
after(() => {
resetMemoization()
})
}
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = fs
.readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
let { input, before, after } = module
if (before) input = before(input)
bench(benchmark, () => {
fn(input)
if (after) after()
})
})
})
})

View File

@@ -1,39 +0,0 @@
/* global suite, set, bench */
import fs from 'fs'
import { basename, extname, resolve } from 'path'
/**
* Benchmarks.
*/
const categoryDir = resolve(__dirname)
const categories = fs
.readdirSync(categoryDir)
.filter(c => c[0] != '.' && c != 'index.js')
categories.forEach(category => {
suite(category, () => {
set('iterations', 50)
set('mintime', 1000)
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = fs
.readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
let { input, before, after } = module
if (before) input = before(input)
bench(benchmark, () => {
fn(input)
if (after) after()
})
})
})
})

View File

@@ -1,46 +0,0 @@
/* global suite, set, bench */
import fs from 'fs'
import { basename, extname, resolve } from 'path'
import { resetMemoization } from '..'
/**
* Benchmarks.
*/
const categoryDir = resolve(__dirname)
const categories = fs
.readdirSync(categoryDir)
.filter(c => c[0] != '.' && c != 'index.js')
categories.forEach(category => {
suite(category, () => {
set('iterations', 100)
set('mintime', 1000)
if (category == 'models') {
after(() => {
resetMemoization()
})
}
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = fs
.readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
let { input, before, after } = module
if (before) input = before(input)
bench(benchmark, () => {
fn(input)
if (after) after()
})
})
})
})

View File

@@ -1,14 +1,24 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import chalk from 'chalk' import chalk from 'chalk'
import figures from 'figures'
import emojis from 'emojis'
import baseline from '../../tmp/benchmark-baseline' import baseline from '../../tmp/benchmark-baseline'
import comparison from '../../tmp/benchmark-comparison' import comparison from '../../tmp/benchmark-comparison'
import { existsSync } from 'fs'
/** /**
* Constants. * Constants.
*/ */
const THRESHOLD = 0.333 let THRESHOLD = 0.333
const configPath = '../../tmp/benchmark-config.js'
if (existsSync(configPath)) {
const alternative = require(configPath).THRESHOLD
if (typeof alternative === 'number' && alternative > 0) {
THRESHOLD = alternative
}
}
/** /**
* Print. * Print.
@@ -21,27 +31,72 @@ baseline.forEach((suite, i) => {
console.log(` ${suite.name}`) console.log(` ${suite.name}`)
suite.benchmarks.forEach((base, j) => { suite.benchmarks.forEach((base, j) => {
const compared = { user: {}, hr: {} }
for (const key of Object.keys(compared)) {
const comp = comparison[i].benchmarks[j] const comp = comparison[i].benchmarks[j]
if (!comp) return if (!comp) return
const b = base.iterations / base[key] * 1000
const c = comp.iterations / comp[key] * 1000
const balancePercent =
b > c ? Math.round(Math.abs(b - c) / c * 100) : (c - b) / b * 100
const b = base.iterations / base.elapsed * 1000 const output = `${b.toFixed(2)} -> ${c.toFixed(2)} ops/sec`
const c = comp.iterations / comp.elapsed * 1000 compared[key].baseOutput = output
const threshold = b * THRESHOLD compared[key].percentOutput = `${balancePercent.toFixed(2)}% ${
const slower = b - c > threshold c > b ? 'faster' : 'slower'
const faster = b - c < 0 - threshold }`
const percent = Math.round(Math.abs(b - c) / b * 100) compared[key].percentValue = balancePercent
compared[key].b = b
compared[key].c = c
compared[key].isFaster = c > b
if (balancePercent > 1000) {
compared[key].percentOutput += emojis.unicode(' :scream: ')
} else if (balancePercent > 100) {
if (c > b) {
compared[key].percentOutput += emojis.unicode(' :raised_hands: ')
} else {
compared[key].percentOutput += emojis.unicode(' :worried: ')
}
}
}
let output = `${b.toFixed(2)}${c.toFixed(2)} ops/sec` const { user, hr } = compared
if (slower) output = chalk.red(`${output} (${percent}% slower)`)
else if (faster) output = chalk.green(`${output} (${percent}% faster)`)
else output = chalk.gray(output)
if (percent > 1000) output += ' 😱' if (
else if (faster && percent > 100) output += ' 🙌' user.percentValue < THRESHOLD * 100 &&
else if (slower && percent > 100) output += ' 😟' hr.percentValue < THRESHOLD * 100
) {
console.log(
chalk.grey(
` ${figures.tick} ${base.name}: ${user.baseOutput} (${
user.percentOutput
})`
)
)
return
}
console.log(` ${base.title}`) if (user.isFaster === hr.isFaster) {
console.log(` ${output}`) if (user.isFaster) {
console.log(chalk.green(` ${figures.star} ${base.name}:`))
console.log(
` user: ${user.baseOutput} (${user.percentOutput})`
)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
return
}
console.log(chalk.red(` ${figures.cross} ${base.name}:`))
console.log(
` user: ${user.baseOutput} (${user.percentOutput})`
)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
return
}
console.log(chalk.red(` ${figures.questionMarkPrefix} ${base.name}:`))
console.log(` user: ${user.baseOutput} (${user.percentOutput})`)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
}) })
}) })

View File

@@ -1,31 +0,0 @@
const { stdout } = process
module.exports = function(runner, utils) {
let hasSuite = false
let hasBench = false
runner.on('start', () => {
stdout.write('[')
})
runner.on('end', () => {
stdout.write(']')
})
runner.on('suite start', suite => {
if (hasSuite) stdout.write(',')
stdout.write(`{"name":"${suite.title}","benchmarks":[`)
hasSuite = true
})
runner.on('suite end', suite => {
hasBench = false
stdout.write(']}')
})
runner.on('bench end', bench => {
if (hasBench) stdout.write(',')
stdout.write(JSON.stringify(bench))
hasBench = true
})
}

View File

@@ -11,6 +11,7 @@ import slatePropTypes from '../../packages/slate-prop-types/package.json'
import slateReact from '../../packages/slate-react/package.json' import slateReact from '../../packages/slate-react/package.json'
import slateSchemaViolations from '../../packages/slate-schema-violations/package.json' import slateSchemaViolations from '../../packages/slate-schema-violations/package.json'
import slateSimulator from '../../packages/slate-simulator/package.json' import slateSimulator from '../../packages/slate-simulator/package.json'
// Do not import slateDevBenchmark here. The benchmark shall be a pure nodeJS program and can be run without babel-node
const configurations = [ const configurations = [
...factory(slate), ...factory(slate),

View File

@@ -1926,6 +1926,10 @@ commander@2.9.0:
dependencies: dependencies:
graceful-readlink ">= 1.0.0" graceful-readlink ">= 1.0.0"
commander@^2.15.1:
version "2.16.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
commander@~2.13.0: commander@~2.13.0:
version "2.13.0" version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
@@ -2796,6 +2800,10 @@ emojis-list@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
emojis@^1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/emojis/-/emojis-1.0.10.tgz#2558133df0dff13313c99531647f693d7adb57da"
emotion@^9.2.4: emotion@^9.2.4:
version "9.2.4" version "9.2.4"
resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.2.4.tgz#0139e7cc154b2845f4b9afaa996dd4de13bb90e3" resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.2.4.tgz#0139e7cc154b2845f4b9afaa996dd4de13bb90e3"