housekeeping: bundle size reductions (#4713)

* set new bundlesize goal
* preserve `GeneratorFunction` instead of all function names
* use js-yaml fork that doesn't require esprima
* set HTML content directly, instead of using React-Markdown
* use remarkable for all Markdown rendering
* add babel-plugin-transform-react-remove-prop-types
* remove SplitPaneMode plugin
* remove react-collapse
* remove AST plugin, and yaml-js
* trim Markdown HTML string output before rendering
* disable obsolete function name preservation
* add `getComponent` to propTypes
This commit is contained in:
kyle
2018-07-19 13:48:39 -07:00
committed by GitHub
parent b9b4ab20af
commit 0359f9c364
19 changed files with 206 additions and 644 deletions

View File

@@ -9,7 +9,7 @@ export default class AuthorizationPopup extends React.Component {
}
render() {
let { authSelectors, authActions, getComponent, errSelectors, specSelectors, fn: { AST } } = this.props
let { authSelectors, authActions, getComponent, errSelectors, specSelectors, fn: { AST = {} } } = this.props
let definitions = authSelectors.shownDefinitions()
const Auths = getComponent("auths")

View File

@@ -1,6 +1,5 @@
import React from "react"
import PropTypes from "prop-types"
import { Collapse } from "react-collapse"
import { presets } from "react-motion"
import ObjectInspector from "react-inspector"
import Perf from "react-addons-perf"
@@ -25,10 +24,12 @@ export default class Debug extends React.Component {
render() {
let { getState } = this.props
let { getState, getComponent } = this.props
window.props = this.props
const Collapse = getComponent("Collapse")
return (
<div className="info">
<h3><a onClick={this.toggleJsonDump}> {this.plusOrMinus(this.state.jsonDumpOpen)} App </a></h3>
@@ -47,6 +48,7 @@ export default class Debug extends React.Component {
}
Debug.propTypes = {
getState: PropTypes.func.isRequired
getState: PropTypes.func.isRequired,
getComponent: PropTypes.func.isRequired,
}

View File

@@ -1,7 +1,6 @@
import React from "react"
import PropTypes from "prop-types"
import { List } from "immutable"
import { Collapse } from "react-collapse"
export default class Errors extends React.Component {
@@ -9,11 +8,14 @@ export default class Errors extends React.Component {
editorActions: PropTypes.object,
errSelectors: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired
layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
}
render() {
let { editorActions, errSelectors, layoutSelectors, layoutActions } = this.props
let { editorActions, errSelectors, layoutSelectors, layoutActions, getComponent } = this.props
const Collapse = getComponent("Collapse")
if(editorActions && editorActions.jumpToLine) {
var jumpToLine = editorActions.jumpToLine

View File

@@ -1,6 +1,5 @@
import React from "react"
import PropTypes from "prop-types"
import { Collapse as OriCollapse } from "react-collapse"
function xclass(...args) {
return args.filter(a => !!a).join(" ").trim()
@@ -243,11 +242,9 @@ export class Collapse extends React.Component {
children = isOpened ? children : null
return (
<OriCollapse isOpened={isOpened}>
<NoMargin>
{children}
</NoMargin>
</OriCollapse>
<NoMargin>
{children}
</NoMargin>
)
}

View File

@@ -1,300 +0,0 @@
import YAML from "yaml-js"
import isArray from "lodash/isArray"
import lodashFind from "lodash/find"
import { memoize } from "core/utils"
let cachedCompose = memoize(YAML.compose) // TODO: build a custom cache based on content
var MAP_TAG = "tag:yaml.org,2002:map"
var SEQ_TAG = "tag:yaml.org,2002:seq"
export function getLineNumberForPath(yaml, path) {
// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (!isArray(path)) {
throw new TypeError("path should be an array of strings")
}
var i = 0
let ast = cachedCompose(yaml)
// simply walks the tree using current path recursively to the point that
// path is empty
return find(ast, path)
function find(current, path, last) {
if(!current) {
// something has gone quite wrong
// return the last start_mark as a best-effort
if(last && last.start_mark)
return last.start_mark.line
return 0
}
if (path.length && current.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) {
var pair = current.value[i]
var key = pair[0]
var value = pair[1]
if (key.value === path[0]) {
return find(value, path.slice(1), current)
}
if (key.value === path[0].replace(/\[.*/, "")) {
// access the array at the index in the path (example: grab the 2 in "tags[2]")
var index = parseInt(path[0].match(/\[(.*)\]/)[1])
if(value.value.length === 1 && index !== 0 && !!index) {
var nextVal = lodashFind(value.value[0], { value: index.toString() })
} else { // eslint-disable-next-line no-redeclare
var nextVal = value.value[index]
}
return find(nextVal, path.slice(1), value.value)
}
}
}
if (path.length && current.tag === SEQ_TAG) {
var item = current.value[path[0]]
if (item && item.tag) {
return find(item, path.slice(1), current.value)
}
}
if (current.tag === MAP_TAG && !Array.isArray(last)) {
return current.start_mark.line
} else {
return current.start_mark.line + 1
}
}
}
/**
* Get a position object with given
* @param {string} yaml
* YAML or JSON string
* @param {array} path
* an array of stings that constructs a
* JSON Path similar to JSON Pointers(RFC 6901). The difference is, each
* component of path is an item of the array instead of being separated with
* slash(/) in a string
*/
export function positionRangeForPath(yaml, path) {
// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (!isArray(path)) {
throw new TypeError("path should be an array of strings")
}
var invalidRange = {
start: {line: -1, column: -1},
end: {line: -1, column: -1}
}
var i = 0
let ast = cachedCompose(yaml)
// simply walks the tree using astValue path recursively to the point that
// path is empty.
return find(ast)
function find(astValue, astKeyValue) {
if (astValue.tag === MAP_TAG) {
for (i = 0; i < astValue.value.length; i++) {
var pair = astValue.value[i]
var key = pair[0]
var value = pair[1]
if (key.value === path[0]) {
path.shift()
return find(value, key)
}
}
}
if (astValue.tag === SEQ_TAG) {
var item = astValue.value[path[0]]
if (item && item.tag) {
path.shift()
return find(item, astKeyValue)
}
}
// if path is still not empty we were not able to find the node
if (path.length) {
return invalidRange
}
const range = {
start: {
line: astValue.start_mark.line,
column: astValue.start_mark.column,
pointer: astValue.start_mark.pointer,
},
end: {
line: astValue.end_mark.line,
column: astValue.end_mark.column,
pointer: astValue.end_mark.pointer,
}
}
if(astKeyValue) {
// eslint-disable-next-line camelcase
range.key_start = {
line: astKeyValue.start_mark.line,
column: astKeyValue.start_mark.column,
pointer: astKeyValue.start_mark.pointer,
}
// eslint-disable-next-line camelcase
range.key_end = {
line: astKeyValue.end_mark.line,
column: astKeyValue.end_mark.column,
pointer: astKeyValue.end_mark.pointer,
}
}
return range
}
}
/**
* Get a JSON Path for position object in the spec
* @param {string} yaml
* YAML or JSON string
* @param {object} position
* position in the YAML or JSON string with `line` and `column` properties.
* `line` and `column` number are zero indexed
*/
export function pathForPosition(yaml, position) {
// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (typeof position !== "object" || typeof position.line !== "number" ||
typeof position.column !== "number") {
throw new TypeError("position should be an object with line and column" +
" properties")
}
try {
var ast = cachedCompose(yaml)
} catch (e) {
console.error("Error composing AST", e)
console.error(`Problem area:\n`, yaml.split("\n").slice(position.line - 5, position.line + 5).join("\n"))
return null
}
var path = []
return find(ast)
/**
* recursive find function that finds the node matching the position
* @param {object} current - AST object to serach into
*/
function find(current) {
// algorythm:
// is current a promitive?
// // finish recursion without modifying the path
// is current a hash?
// // find a key or value that position is in their range
// // if key is in range, terminate recursion with exisiting path
// // if a value is in range push the corresponding key to the path
// // andcontinue recursion
// is current an array
// // find the item that position is in it"s range and push the index
// // of the item to the path and continue recursion with that item.
var i = 0
if (!current || [MAP_TAG, SEQ_TAG].indexOf(current.tag) === -1) {
return path
}
if (current.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) {
var pair = current.value[i]
var key = pair[0]
var value = pair[1]
if (isInRange(key)) {
return path
} else if (isInRange(value)) {
path.push(key.value)
return find(value)
}
}
}
if (current.tag === SEQ_TAG) {
for (i = 0; i < current.value.length; i++) {
var item = current.value[i]
if (isInRange(item)) {
path.push(i.toString())
return find(item)
}
}
}
return path
/**
* Determines if position is in node"s range
* @param {object} node - AST node
* @return {Boolean} true if position is in node"s range
*/
function isInRange(node) {
/* jshint camelcase: false */
// if node is in a single line
if (node.start_mark.line === node.end_mark.line) {
return (position.line === node.start_mark.line) &&
(node.start_mark.column <= position.column) &&
(node.end_mark.column >= position.column)
}
// if position is in the same line as start_mark
if (position.line === node.start_mark.line) {
return position.column >= node.start_mark.column
}
// if position is in the same line as end_mark
if (position.line === node.end_mark.line) {
return position.column <= node.end_mark.column
}
// if position is between start and end lines return true, otherwise
// return false.
return (node.start_mark.line < position.line) &&
(node.end_mark.line > position.line)
}
}
}
// utility fns
export let pathForPositionAsync = promisifySyncFn(pathForPosition)
export let positionRangeForPathAsync = promisifySyncFn(positionRangeForPath)
export let getLineNumberForPathAsync = promisifySyncFn(getLineNumberForPath)
function promisifySyncFn(fn) {
return function(...args) {
return new Promise((resolve) => resolve(fn(...args)))
}
}

View File

@@ -1,9 +0,0 @@
import * as AST from "./ast"
import JumpToPath from "./jump-to-path"
export default function() {
return {
fn: { AST },
components: { JumpToPath }
}
}

View File

@@ -1,4 +1,4 @@
import YAML from "js-yaml"
import YAML from "@kyleshockey/js-yaml"
export const parseYamlConfig = (yaml, system) => {
try {

View File

@@ -1,25 +1,28 @@
import React from "react"
import PropTypes from "prop-types"
import ReactMarkdown from "react-markdown"
import cx from "classnames"
import { Parser, HtmlRenderer } from "commonmark"
import Remarkable from "remarkable"
import { OAS3ComponentWrapFactory } from "../helpers"
import { sanitizer } from "core/components/providers/markdown"
const parser = new Remarkable("commonmark")
export const Markdown = ({ source, className = "" }) => {
if ( source ) {
const parser = new Parser()
const writer = new HtmlRenderer()
const html = writer.render(parser.parse(source || ""))
const html = parser.render(source)
const sanitized = sanitizer(html)
if ( !source || !html || !sanitized ) {
return null
let trimmed
if(typeof sanitized === "string") {
trimmed = sanitized.trim()
}
return (
<ReactMarkdown
source={sanitized}
<div
dangerouslySetInnerHTML={{
__html: trimmed
}}
className={cx(className, "renderedMarkdown")}
/>
)

View File

@@ -1,4 +1,4 @@
import YAML from "js-yaml"
import YAML from "@kyleshockey/js-yaml"
import { Map } from "immutable"
import parseUrl from "url-parse"
import serializeError from "serialize-error"
@@ -80,7 +80,7 @@ export const parseToJson = (str) => ({specActions, specSelectors, errActions}) =
let hasWarnedAboutResolveSpecDeprecation = false
export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }, getConfigs}) => {
export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST = {} }, getConfigs}) => {
if(!hasWarnedAboutResolveSpecDeprecation) {
console.warn(`specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!`)
hasWarnedAboutResolveSpecDeprecation = true
@@ -100,7 +100,7 @@ export const resolveSpec = (json, url) => ({specActions, specSelectors, errActio
url = specSelectors.url()
}
let { getLineNumberForPath } = AST
let getLineNumberForPath = AST.getLineNumberForPath ? AST.getLineNumberForPath : () => undefined
let specStr = specSelectors.specStr()
@@ -149,7 +149,7 @@ const debResolveSubtrees = debounce(async () => {
errSelectors,
fn: {
resolveSubtree,
AST: { getLineNumberForPath }
AST = {}
},
specSelectors,
specActions,
@@ -160,6 +160,8 @@ const debResolveSubtrees = debounce(async () => {
return
}
let getLineNumberForPath = AST.getLineNumberForPath ? AST.getLineNumberForPath : () => undefined
const specStr = specSelectors.specStr()
const {

View File

@@ -1,85 +0,0 @@
import React from "react"
import PropTypes from "prop-types"
import SplitPane from "react-split-pane"
const MODE_KEY = ["split-pane-mode"]
const MODE_LEFT = "left"
const MODE_RIGHT = "right"
const MODE_BOTH = "both" // or anything other than left/right
export default class SplitPaneMode extends React.Component {
static propTypes = {
threshold: PropTypes.number,
children: PropTypes.array,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
};
static defaultProps = {
threshold: 100, // in pixels
children: [],
};
initializeComponent = (c) => {
this.splitPane = c
}
onDragFinished = () => {
let { threshold, layoutActions } = this.props
let { position, draggedSize } = this.splitPane.state
this.draggedSize = draggedSize
let nearLeftEdge = position <= threshold
let nearRightEdge = draggedSize <= threshold
layoutActions
.changeMode(MODE_KEY, (
nearLeftEdge
? MODE_RIGHT : nearRightEdge
? MODE_LEFT : MODE_BOTH
))
}
sizeFromMode = (mode, defaultSize) => {
if(mode === MODE_LEFT) {
this.draggedSize = null
return "0px"
} else if (mode === MODE_RIGHT) {
this.draggedSize = null
return "100%"
}
// mode === "both"
return this.draggedSize || defaultSize
}
render() {
let { children, layoutSelectors } = this.props
const mode = layoutSelectors.whatMode(MODE_KEY)
const left = mode === MODE_RIGHT ? <noscript/> : children[0]
const right = mode === MODE_LEFT ? <noscript/> : children[1]
const size = this.sizeFromMode(mode, "50%")
return (
<SplitPane
disabledClass={""}
ref={this.initializeComponent}
split='vertical'
defaultSize={"50%"}
primary="second"
minSize={0}
size={size}
onDragFinished={this.onDragFinished}
allowResize={mode !== MODE_LEFT && mode !== MODE_RIGHT }
resizerStyle={{"flex": "0 0 auto", "position": "relative"}}
>
{ left }
{ right }
</SplitPane>
)
}
}

View File

@@ -1,15 +0,0 @@
import SplitPaneMode from "./components/split-pane-mode"
export default function SplitPaneModePlugin() {
return {
// statePlugins: {
// layout: {
// actions,
// selectors,
// }
// },
components: {
SplitPaneMode
}
}
}

View File

@@ -4,11 +4,9 @@ import spec from "core/plugins/spec"
import view from "core/plugins/view"
import samples from "core/plugins/samples"
import logs from "core/plugins/logs"
import ast from "core/plugins/ast"
import swaggerJs from "core/plugins/swagger-js"
import auth from "core/plugins/auth"
import util from "core/plugins/util"
import SplitPaneModePlugin from "core/plugins/split-pane-mode"
import downloadUrlPlugin from "core/plugins/download-url"
import configsPlugin from "core/plugins/configs"
import deepLinkingPlugin from "core/plugins/deep-linking"
@@ -55,6 +53,7 @@ import Info, {
InfoBasePath
} from "core/components/info"
import InfoContainer from "core/containers/info"
import JumpToPath from "core/components/jump-to-path"
import Footer from "core/components/footer"
import FilterContainer from "core/containers/filter"
import ParamBody from "core/components/param-body"
@@ -102,6 +101,7 @@ export default function() {
liveResponse: LiveResponse,
info: Info,
InfoContainer,
JumpToPath,
onlineValidatorBadge: OnlineValidatorBadge,
operations: Operations,
operation: Operation,
@@ -174,8 +174,6 @@ export default function() {
swaggerJs,
jsonSchemaComponents,
auth,
ast,
SplitPaneModePlugin,
downloadUrlPlugin,
deepLinkingPlugin,
filter,