chore: modernize build pipeline (#1635)

This commit is contained in:
Roman Hotsiy 2021-06-09 14:57:36 +03:00 committed by GitHub
parent 92387bc653
commit c09cf9dbbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 8261 additions and 13386 deletions

View File

@ -26,6 +26,7 @@
- The widest OpenAPI v2.0 features support (yes, it supports even `discriminator`) <br> - The widest OpenAPI v2.0 features support (yes, it supports even `discriminator`) <br>
![](docs/images/discriminator-demo.gif) ![](docs/images/discriminator-demo.gif)
- OpenAPI 3.0 support - OpenAPI 3.0 support
- Basic OpenAPI 3.1 support
- Neat **interactive** documentation for nested objects <br> - Neat **interactive** documentation for nested objects <br>
![](docs/images/nested-demo.gif) ![](docs/images/nested-demo.gif)
- Code samples support (via vendor extension) <br> - Code samples support (via vendor extension) <br>
@ -43,7 +44,6 @@
- [x] ~~React rewrite~~ - [x] ~~React rewrite~~
- [x] ~~docs pre-rendering (performance and SEO)~~ - [x] ~~docs pre-rendering (performance and SEO)~~
- [ ] ability to simple branding/styling - [ ] ability to simple branding/styling
- [ ] built-in API Console
## Releases ## Releases
**Important:** all the 2.x releases are deployed to npm and can be used via jsdeliver: **Important:** all the 2.x releases are deployed to npm and can be used via jsdeliver:
@ -58,6 +58,7 @@ Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(d
## Version Guidance ## Version Guidance
| ReDoc Release | OpenAPI Specification | | ReDoc Release | OpenAPI Specification |
|:--------------|:----------------------| |:--------------|:----------------------|
| 2.0.0-alpha.54| 3.1, 3.0.x, 2.0 |
| 2.0.0-alpha.x | 3.0, 2.0 | | 2.0.0-alpha.x | 3.0, 2.0 |
| 1.19.x | 2.0 | | 1.19.x | 2.0 |
| 1.18.x | 2.0 | | 1.18.x | 2.0 |

690
cli/npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,8 @@
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"mobx": "^6.3.2", "mobx": "^6.3.2",
"node-libs-browser": "^2.2.1", "node-libs-browser": "^2.2.1",
"react": "^17.0.2", "react": "^16.8.4",
"react-dom": "^17.0.2", "react-dom": "^16.8.4",
"redoc": "2.0.0-rc.53", "redoc": "2.0.0-rc.53",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"tslib": "^2.2.0", "tslib": "^2.2.0",

46
config/webpack-utils.ts Normal file
View File

@ -0,0 +1,46 @@
import * as webpack from 'webpack';
export function getBabelLoader({useBuiltIns, hot}: {useBuiltIns: boolean, hot?: boolean}) {
return {
loader: 'babel-loader',
options: {
babelrc: false,
sourceType: 'unambiguous',
presets: [
[
'@babel/preset-env',
{
useBuiltIns: useBuiltIns ? 'usage' : false,
corejs: 3,
exclude: ['transform-typeof-symbol'],
targets: 'defaults',
modules: false,
},
],
['@babel/preset-react', { development: false, runtime: 'classic' }],
'@babel/preset-typescript',
],
plugins: [
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties', { loose: false }],
[
'@babel/plugin-transform-runtime',
{
corejs: false,
helpers: true,
// eslint-disable-next-line import/no-internal-modules
version: require('@babel/runtime/package.json').version,
regenerator: true,
},
],
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-nullish-coalescing-operator',
hot ? 'react-hot-loader/babel' : undefined,
].filter(Boolean)
},
};
}
export function webpackIgnore(regexp) {
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash/noop.js'));
}

View File

@ -31,7 +31,7 @@ const DropDownList = styled.ul`
list-style: none; list-style: none;
margin: 4px 0 0 0; margin: 4px 0 0 0;
padding: 5px 0; padding: 5px 0;
font-family: 'Lato'; font-family: Roboto,sans-serif;
overflow: hidden; overflow: hidden;
`; `;

View File

@ -16,7 +16,7 @@ const demos = [
label: 'Google Calendar', label: 'Google Calendar',
}, },
{ value: 'https://api.apis.guru/v2/specs/slack.com/1.5.0/openapi.yaml', label: 'Slack' }, { value: 'https://api.apis.guru/v2/specs/slack.com/1.5.0/openapi.yaml', label: 'Slack' },
{ value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/swagger.yaml', label: 'Zoom.us' }, { value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/openapi.yaml', label: 'Zoom.us' },
{ value: 'https://docs.graphhopper.com/openapi.json', label: 'GraphHopper' }, { value: 'https://docs.graphhopper.com/openapi.json', label: 'GraphHopper' },
]; ];
@ -154,7 +154,7 @@ const Heading = styled.nav`
display: flex; display: flex;
align-items: center; align-items: center;
font-family: 'Lato'; font-family: Roboto, sans-serif;
`; `;
const Logo = styled.img` const Logo = styled.img`

View File

@ -1,21 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
// tslint:disable-next-line import type { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
import { AppContainer } from 'react-hot-loader'; import RedocStandalone from './hot';
// import DevTools from 'mobx-react-devtools';
import { Redoc, RedocProps } from '../../src/components/Redoc/Redoc';
import { AppStore } from '../../src/services/AppStore';
import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec';
const renderRoot = (props: RedocProps) =>
render(
<AppContainer>
<Redoc {...props} />
</AppContainer>,
document.getElementById('example'),
);
const big = window.location.search.indexOf('big') > -1; const big = window.location.search.indexOf('big') > -1;
const swagger = window.location.search.indexOf('swagger') > -1; const swagger = window.location.search.indexOf('swagger') > -1;
@ -25,30 +11,6 @@ const userUrl = window.location.search.match(/url=(.*)$/);
const specUrl = const specUrl =
(userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'); (userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml');
let store;
const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 }; const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 };
async function init() { render(<RedocStandalone specUrl={specUrl} options={options} />, document.getElementById('example'));
const spec = await loadAndBundleSpec(specUrl);
store = new AppStore(spec, specUrl, options);
renderRoot({ store });
}
init();
if (module.hot) {
const reload = (reloadStore = false) => async () => {
if (reloadStore) {
// create a new Store
store.dispose();
const state = await store.toJS();
store = AppStore.fromJS(state);
}
renderRoot({ store });
};
module.hot.accept(['../../src/components/Redoc/Redoc'], reload());
module.hot.accept(['../../src/services/AppStore'], reload(true));
}

10
demo/playground/hot.tsx Normal file
View File

@ -0,0 +1,10 @@
import * as React from 'react';
// eslint-disable-next-line import/no-internal-modules
import { hot } from 'react-hot-loader/root';
import { RedocStandalone as RedocStandaloneOrig, RedocStandaloneProps } from '../../src';
const RedocStandalone = function (props: RedocStandaloneProps) {
return <RedocStandaloneOrig {...props} />;
}
export default hot(RedocStandalone);

View File

@ -1,9 +1,9 @@
import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
import * as HtmlWebpackPlugin from 'html-webpack-plugin'; import * as HtmlWebpackPlugin from 'html-webpack-plugin';
import { compact } from 'lodash';
import { resolve } from 'path'; import { resolve } from 'path';
import * as webpack from 'webpack'; import * as webpack from 'webpack';
import { getBabelLoader, webpackIgnore } from '../config/webpack-utils';
const VERSION = JSON.stringify(require('../package.json').version); const VERSION = JSON.stringify(require('../package.json').version);
const REVISION = JSON.stringify( const REVISION = JSON.stringify(
@ -14,38 +14,6 @@ function root(filename) {
return resolve(__dirname + '/' + filename); return resolve(__dirname + '/' + filename);
} }
const tsLoader = (env) => ({
loader: 'ts-loader',
options: {
compilerOptions: {
module: env.bench ? 'esnext' : 'es2015',
declaration: false,
},
},
});
const babelLoader = () => ({
loader: 'babel-loader',
options: {
generatorOpts: {
decoratorsBeforeExport: true,
},
plugins: compact([
['@babel/plugin-syntax-typescript', { isTSX: true }],
['@babel/plugin-syntax-decorators', { legacy: true }],
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-jsx',
]),
},
});
const babelHotLoader = {
loader: 'babel-loader',
options: {
plugins: ['react-hot-loader/babel'],
},
};
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
entry: [ entry: [
root('../src/polyfills.ts'), root('../src/polyfills.ts'),
@ -57,6 +25,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
: 'index.tsx', : 'index.tsx',
), ),
], ],
target: 'web',
output: { output: {
filename: 'redoc-demo.bundle.js', filename: 'redoc-demo.bundle.js',
path: root('dist'), path: root('dist'),
@ -69,22 +38,25 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
port: 9090, port: 9090,
disableHostCheck: true, disableHostCheck: true,
stats: 'minimal', stats: 'minimal',
hot: true,
}, },
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'], extensions: ['.ts', '.tsx', '.js', '.json'],
fallback: {
path: require.resolve('path-browserify'),
http: false,
fs: false,
os: false,
},
alias: alias:
mode !== 'production' mode !== 'production'
? { ? {
'react-dom': '@hot-loader/react-dom', 'react-dom': '@hot-loader/react-dom',
} }
: {}, : {},
}, },
node: {
fs: 'empty',
},
performance: false, performance: false,
externals: { externals: {
@ -101,12 +73,23 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' }, { test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: compact([ use: [getBabelLoader({useBuiltIns: true, hot: true} )],
mode !== 'production' ? babelHotLoader : undefined, exclude: {
tsLoader(env), and: [/node_modules/],
babelLoader(), not: {
]), or: [
exclude: [/node_modules/], /swagger2openapi/,
/reftools/,
/openapi-sampler/,
/mobx/,
/oas-resolver/,
/oas-kit-common/,
/oas-schema-walker/,
/\@redocly\/openapi-core/,
/colorette/,
],
},
},
}, },
{ {
test: /\.css$/, test: /\.css$/,
@ -117,29 +100,18 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
}, },
}, },
}, },
{
test: /node_modules\/(swagger2openapi|reftools|oas-resolver|oas-kit-common|oas-schema-walker)\/.*\.js$/,
use: {
loader: 'ts-loader',
options: {
transpileOnly: true,
instance: 'ts2js-transpiler-only',
compilerOptions: {
allowJs: true,
declaration: false,
},
},
},
},
], ],
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__REDOC_VERSION__: VERSION, __REDOC_VERSION__: VERSION,
__REDOC_REVISION__: REVISION, __REDOC_REVISION__: REVISION,
'process.env': '{}',
'process.platform': '"browser"',
'process.stdout': 'null',
}), }),
new webpack.NamedModulesPlugin(), // new webpack.NamedModulesPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(), // new webpack.optimize.ModuleConcatenationPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: env.playground template: env.playground
? 'demo/playground/index.html' ? 'demo/playground/index.html'
@ -147,16 +119,12 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
? 'benchmark/index.html' ? 'benchmark/index.html'
: 'demo/index.html', : 'demo/index.html',
}), }),
new ForkTsCheckerWebpackPlugin(), new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
ignore(/js-yaml\/dumper\.js$/), webpackIgnore(/js-yaml\/dumper\.js$/),
ignore(/json-schema-ref-parser\/lib\/dereference\.js/), webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
ignore(/^\.\/SearchWorker\.worker$/), webpackIgnore(/^\.\/SearchWorker\.worker$/),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: ['demo/openapi.yaml'], patterns: ['demo/openapi.yaml'],
}), }),
], ],
}); });
function ignore(regexp) {
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash/noop.js'));
}

20334
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,10 @@
"type": "git", "type": "git",
"url": "git://github.com/Redocly/redoc" "url": "git://github.com/Redocly/redoc"
}, },
"browserslist": [
"defaults",
"ie 11"
],
"engines": { "engines": {
"node": ">=6.9", "node": ">=6.9",
"npm": ">=3.0.0" "npm": ">=3.0.0"
@ -25,9 +29,9 @@
"main": "bundles/redoc.lib.js", "main": "bundles/redoc.lib.js",
"types": "typings/index.d.ts", "types": "typings/index.d.ts",
"scripts": { "scripts": {
"start": "webpack-dev-server --mode=development --env.playground --hot --config demo/webpack.config.ts", "start": "webpack serve --mode=development --env playground --hot --config demo/webpack.config.ts",
"start:prod": "webpack-dev-server --env.playground --mode=production --config demo/webpack.config.ts", "start:prod": "webpack serve --env playground --mode=production --config demo/webpack.config.ts",
"start:benchmark": "webpack-dev-server --mode=production --env.bench --config demo/webpack.config.ts", "start:benchmark": "webpack serve --mode=production --env.bench --config demo/webpack.config.ts",
"test": "npm run lint && npm run unit && npm run license-check", "test": "npm run lint && npm run unit && npm run license-check",
"unit": "jest --coverage", "unit": "jest --coverage",
"e2e": "cypress run", "e2e": "cypress run",
@ -36,16 +40,16 @@
"ts-check": "tsc --noEmit --skipLibCheck", "ts-check": "tsc --noEmit --skipLibCheck",
"cy:open": "cypress open", "cy:open": "cypress open",
"bundle:clean": "rimraf bundles", "bundle:clean": "rimraf bundles",
"bundle:standalone": "webpack --env.standalone --mode=production", "bundle:standalone": "webpack --env production --env standalone --mode=production",
"bundle:lib": "webpack --mode=production && npm run declarations", "bundle:lib": "webpack --mode=production && npm run declarations",
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone", "bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone",
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/", "declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json", "stats": "webpack --env production --env standalone --json --profile --mode=production > stats.json",
"prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"", "prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
"lint": "eslint 'src/**/*.{js,ts,tsx}'", "lint": "eslint 'src/**/*.{js,ts,tsx}'",
"benchmark": "node ./benchmark/benchmark.js", "benchmark": "node ./benchmark/benchmark.js",
"start:demo": "webpack-dev-server --hot --config demo/webpack.config.ts --mode=development", "start:demo": "webpack serve --hot --config demo/webpack.config.ts --mode=development",
"compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs", "compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs",
"build:demo": "webpack --mode=production --config demo/webpack.config.ts", "build:demo": "webpack --mode=production --config demo/webpack.config.ts",
"deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read", "deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read",
@ -54,10 +58,17 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.3", "@babel/core": "^7.14.3",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-decorators": "^7.14.2",
"@babel/plugin-proposal-object-rest-spread": "^7.14.4",
"@babel/plugin-syntax-decorators": "^7.12.13", "@babel/plugin-syntax-decorators": "^7.12.13",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.10.4", "@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/plugin-syntax-typescript": "^7.10.4", "@babel/plugin-syntax-typescript": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.14.3",
"@babel/preset-env": "^7.14.4",
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.13.0",
"@cypress/webpack-preprocessor": "^5.9.0", "@cypress/webpack-preprocessor": "^5.9.0",
"@hot-loader/react-dom": "^17.0.1", "@hot-loader/react-dom": "^17.0.1",
"@types/chai": "^4.2.18", "@types/chai": "^4.2.18",
@ -77,18 +88,18 @@
"@types/react-tabs": "^2.3.2", "@types/react-tabs": "^2.3.2",
"@types/styled-components": "^5.1.1", "@types/styled-components": "^5.1.1",
"@types/tapable": "^2.2.2", "@types/tapable": "^2.2.2",
"@types/webpack": "^4.41.21", "@types/webpack": "^5.28.0",
"@types/webpack-env": "^1.15.2", "@types/webpack-env": "^1.16.0",
"@types/yargs": "^17.0.0", "@types/yargs": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0", "@typescript-eslint/parser": "^4.26.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
"babel-loader": "^8.1.0", "babel-loader": "^8.2.2",
"babel-plugin-styled-components": "^1.12.0", "babel-plugin-styled-components": "^1.12.0",
"beautify-benchmark": "^0.2.4", "beautify-benchmark": "^0.2.4",
"bundlesize": "^0.18.1", "bundlesize": "^0.18.1",
"conventional-changelog-cli": "^2.0.34", "conventional-changelog-cli": "^2.0.34",
"copy-webpack-plugin": "^6.0.3", "copy-webpack-plugin": "^9.0.0",
"core-js": "^3.13.1", "core-js": "^3.13.1",
"coveralls": "^3.1.0", "coveralls": "^3.1.0",
"css-loader": "^5.2.6", "css-loader": "^5.2.6",
@ -99,7 +110,7 @@
"eslint-plugin-import": "^2.23.4", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-react": "^7.24.0", "eslint-plugin-react": "^7.24.0",
"fork-ts-checker-webpack-plugin": "^6.2.10", "fork-ts-checker-webpack-plugin": "^6.2.10",
"html-webpack-plugin": "^4.3.0", "html-webpack-plugin": "^5.3.1",
"jest": "^27.0.3", "jest": "^27.0.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
@ -112,21 +123,19 @@
"react-hot-loader": "^4.13.0", "react-hot-loader": "^4.13.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"shelljs": "^0.8.4", "shelljs": "^0.8.4",
"source-map-loader": "^1.0.0",
"style-loader": "^2.0.0", "style-loader": "^2.0.0",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"ts-jest": "^27.0.2", "ts-jest": "^27.0.2",
"ts-loader": "^8.0.1", "ts-loader": "^8.0.1",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"tslib": "^2.2.0",
"typescript": "~4.1.0", "typescript": "~4.1.0",
"unfetch": "^4.2.0", "unfetch": "^4.2.0",
"url-polyfill": "^1.1.12", "url-polyfill": "^1.1.12",
"webpack": "^4.44.0", "webpack": "^5.38.1",
"webpack-cli": "^3.3.12", "webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^3.11.2",
"webpack-node-externals": "^2.5.0", "webpack-node-externals": "^3.0.0",
"workerize-loader": "^1.3.0" "workerize-loader": "github:redocly/workerize-loader#webpack-5-dist"
}, },
"peerDependencies": { "peerDependencies": {
"core-js": "^3.1.4", "core-js": "^3.1.4",
@ -136,7 +145,8 @@
"styled-components": "^4.1.1 || ^5.1.1" "styled-components": "^4.1.1 || ^5.1.1"
}, },
"dependencies": { "dependencies": {
"@redocly/openapi-core": "^1.0.0-beta.48", "@babel/runtime": "^7.14.0",
"@redocly/openapi-core": "^1.0.0-beta.50",
"@redocly/react-dropdown-aria": "^2.0.11", "@redocly/react-dropdown-aria": "^2.0.11",
"@types/node": "^15.6.1", "@types/node": "^15.6.1",
"classnames": "^2.3.1", "classnames": "^2.3.1",
@ -150,21 +160,21 @@
"memoize-one": "^5.2.1", "memoize-one": "^5.2.1",
"mobx-react": "^7.2.0", "mobx-react": "^7.2.0",
"openapi-sampler": "^1.0.1", "openapi-sampler": "^1.0.1",
"path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.5.1", "perfect-scrollbar": "^1.5.1",
"polished": "^4.1.3", "polished": "^4.1.3",
"prismjs": "^1.23.0", "prismjs": "^1.23.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-tabs": "^3.2.2", "react-tabs": "^3.2.2",
"slugify": "^1.5.3", "slugify": "~1.4.7",
"stickyfill": "^1.1.1", "stickyfill": "^1.1.1",
"swagger2openapi": "^7.0.6", "swagger2openapi": "^7.0.6",
"tslib": "^2.2.0",
"url-template": "^2.0.8" "url-template": "^2.0.8"
}, },
"bundlesize": [ "bundlesize": [
{ {
"path": "./bundles/redoc.standalone.js", "path": "./bundles/redoc.standalone.js",
"maxSize": "300 kB" "maxSize": "350 kB"
} }
], ],
"jest": { "jest": {

View File

@ -1,12 +1,12 @@
import * as React from 'react'; import * as React from 'react';
import { StoreConsumer } from '../components/StoreBuilder'; import { StoreContext } from '../components/StoreBuilder';
import styled, { css } from '../styled-components'; import styled, { css } from '../styled-components';
import { HistoryService } from '../services'; import { HistoryService } from '../services';
// tslint:disable-next-line // tslint:disable-next-line
export const linkifyMixin = className => css` export const linkifyMixin = (className) => css`
${className} { ${className} {
cursor: pointer; cursor: pointer;
margin-left: -20px; margin-left: -20px;
@ -33,36 +33,41 @@ export const linkifyMixin = className => css`
} }
`; `;
const isModifiedEvent = event => const isModifiedEvent = (event) =>
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
export class Link extends React.Component<{ to: string; className?: string; children?: any }> { export function Link(props: { to: string; className?: string; children?: any }) {
navigate = (history: HistoryService, event) => { const store = React.useContext(StoreContext);
if ( const clickHandler = React.useCallback(
!event.defaultPrevented && // onClick prevented default (event) => {
event.button === 0 && // ignore everything but left clicks if (!store) return;
!isModifiedEvent(event) // ignore clicks with modifier keys navigate(store.menu.history, event);
) { },
event.preventDefault(); [store],
history.replace(this.props.to); );
}
};
render() { if (!store) return null;
return (
<StoreConsumer> return (
{store => ( <a
<a className={props.className}
className={this.props.className} href={store!.menu.history.linkForId(props.to)}
href={store!.menu.history.linkForId(this.props.to)} onClick={clickHandler}
onClick={this.navigate.bind(this, store!.menu.history)} aria-label={props.to}
aria-label={this.props.to} >
> {props.children}
{this.props.children} </a>
</a> );
)} }
</StoreConsumer>
); function navigate(history: HistoryService, event) {
if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
history.replace(this.props.to);
} }
} }

View File

@ -1,4 +1,3 @@
import * as PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
import { RedocNormalizedOptions, RedocRawOptions } from '../services/RedocNormalizedOptions'; import { RedocNormalizedOptions, RedocRawOptions } from '../services/RedocNormalizedOptions';
@ -14,47 +13,23 @@ export interface RedocStandaloneProps {
onLoaded?: (e?: Error) => any; onLoaded?: (e?: Error) => any;
} }
export class RedocStandalone extends React.PureComponent<RedocStandaloneProps> { export const RedocStandalone = function (props: RedocStandaloneProps) {
static propTypes = { const { spec, specUrl, options = {}, onLoaded } = props;
spec: (props, _, componentName) => { const hideLoading = options.hideLoading !== undefined;
if (!props.spec && !props.specUrl) {
return new Error(
`One of props 'spec' or 'specUrl' was not specified in '${componentName}'.`,
);
}
return null;
},
specUrl: (props, _, componentName) => { const normalizedOpts = new RedocNormalizedOptions(options);
if (!props.spec && !props.specUrl) {
return new Error(
`One of props 'spec' or 'specUrl' was not specified in '${componentName}'.`,
);
}
return null;
},
options: PropTypes.any,
onLoaded: PropTypes.any,
};
render() { return (
const { spec, specUrl, options = {}, onLoaded } = this.props; <ErrorBoundary>
const hideLoading = options.hideLoading !== undefined; <StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}>
{({ loading, store }) =>
const normalizedOpts = new RedocNormalizedOptions(options); !loading ? (
<Redoc store={store!} />
return ( ) : hideLoading ? null : (
<ErrorBoundary> <Loading color={normalizedOpts.theme.colors.primary.main} />
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}> )
{({ loading, store }) => }
!loading ? ( </StoreBuilder>
<Redoc store={store!} /> </ErrorBoundary>
) : hideLoading ? null : ( );
<Loading color={normalizedOpts.theme.colors.primary.main} />
)
}
</StoreBuilder>
</ErrorBoundary>
);
}
} }

View File

@ -1,5 +1,5 @@
import * as memoize from 'memoize-one/dist/memoize-one.cjs'; // fixme: https://github.com/alexreardon/memoize-one/issues/37 import * as React from 'react';
import { Component, createContext } from 'react'; import { createContext } from 'react';
import { AppStore } from '../services/'; import { AppStore } from '../services/';
import { RedocRawOptions } from '../services/RedocNormalizedOptions'; import { RedocRawOptions } from '../services/RedocNormalizedOptions';
@ -14,7 +14,7 @@ export interface StoreBuilderProps {
onLoaded?: (e?: Error) => void; onLoaded?: (e?: Error) => void;
children: (props: { loading: boolean; store?: AppStore }) => any; children: (props: { loading: boolean; store: AppStore | null }) => any;
} }
export interface StoreBuilderState { export interface StoreBuilderState {
@ -25,79 +25,41 @@ export interface StoreBuilderState {
prevSpecUrl?: string; prevSpecUrl?: string;
} }
const { Provider, Consumer } = createContext<AppStore | undefined>(undefined); const StoreContext = createContext<AppStore | undefined>(undefined);
export { Provider as StoreProvider, Consumer as StoreConsumer }; const { Provider, Consumer } = StoreContext;
export { Provider as StoreProvider, Consumer as StoreConsumer, StoreContext };
export class StoreBuilder extends Component<StoreBuilderProps, StoreBuilderState> { export function StoreBuilder(props: StoreBuilderProps) {
static getDerivedStateFromProps(nextProps: StoreBuilderProps, prevState: StoreBuilderState) { const {spec, specUrl, options, onLoaded, children } = props;
if (nextProps.specUrl !== prevState.prevSpecUrl || nextProps.spec !== prevState.prevSpec) {
return { const [resolvedSpec, setResolvedSpec] = React.useState<any>(null);
loading: true,
resolvedSpec: null, React.useEffect(() => {
prevSpec: nextProps.spec, async function load() {
prevSpecUrl: nextProps.specUrl, if (!spec && !specUrl) {
}; return undefined;
}
setResolvedSpec(null);
const resolved = await loadAndBundleSpec(spec || specUrl!);
setResolvedSpec(resolved);
} }
load();
}, [spec, specUrl])
return null; const store = React.useMemo(() => {
} if (!resolvedSpec) return null;
state: StoreBuilderState = {
loading: true,
resolvedSpec: null,
};
@memoize
makeStore(spec, specUrl, options) {
if (!spec) {
return undefined;
}
try { try {
return new AppStore(spec, specUrl, options); return new AppStore(resolvedSpec, specUrl, options);
} catch (e) { } catch (e) {
if (this.props.onLoaded) { if (onLoaded) {
this.props.onLoaded(e); onLoaded(e);
} }
throw e; throw e;
} }
} }, [resolvedSpec, specUrl, options]);
componentDidMount() { return children({
this.load(); loading: !store,
} store,
});
componentDidUpdate() {
if (this.state.resolvedSpec === null) {
this.load();
} else if (!this.state.loading && this.props.onLoaded) {
// may run multiple time
this.props.onLoaded();
}
}
async load() {
const { specUrl, spec } = this.props;
try {
const resolvedSpec = await loadAndBundleSpec(spec || specUrl!);
this.setState({ resolvedSpec, loading: false });
} catch (e) {
if (this.props.onLoaded) {
this.props.onLoaded(e);
}
this.setState({ error: e });
}
}
render() {
if (this.state.error) {
throw this.state.error;
}
const { specUrl, options } = this.props;
const { loading, resolvedSpec } = this.state;
return this.props.children({
loading,
store: this.makeStore(resolvedSpec, specUrl, options),
});
}
} }

View File

@ -6,9 +6,9 @@ export {
Section, Section,
StyledDropdown, StyledDropdown,
SimpleDropdown, SimpleDropdown,
DropdownOption,
} from './common-elements/'; } from './common-elements/';
export { OpenAPIEncoding } from './types'; export type { DropdownOption } from './common-elements';
export type { OpenAPIEncoding } from './types';
export * from './services'; export * from './services';
export * from './utils'; export * from './utils';

View File

@ -1,15 +1,2 @@
import 'core-js/es/promise';
import 'core-js/es/array/find';
import 'core-js/es/array/includes';
import 'core-js/es/object/assign';
import 'core-js/es/object/entries';
import 'core-js/es/object/is';
import 'core-js/es/string/ends-with';
import 'core-js/es/string/starts-with';
import 'core-js/es/map';
import 'core-js/es/symbol';
import 'unfetch/polyfill/index'; import 'unfetch/polyfill/index';
import 'url-polyfill'; import 'core-js/es/symbol';

View File

@ -1,8 +1,8 @@
import * as styledComponents from 'styled-components'; import * as styledComponents from 'styled-components';
import { ResolvedThemeInterface } from './theme'; import type { ResolvedThemeInterface } from './theme';
export { ResolvedThemeInterface }; export type { ResolvedThemeInterface };
const { const {
default: styled, default: styled,

View File

@ -1,4 +1,10 @@
import { Source, Document, bundle, Config } from '@redocly/openapi-core'; import type { Source, Document } from '@redocly/openapi-core';
// eslint-disable-next-line import/no-internal-modules
import { bundle } from '@redocly/openapi-core/lib/bundle';
// eslint-disable-next-line import/no-internal-modules
import { Config } from '@redocly/openapi-core/lib/config/config';
/* tslint:disable-next-line:no-implicit-dependencies */ /* tslint:disable-next-line:no-implicit-dependencies */
import { convertObj } from 'swagger2openapi'; import { convertObj } from 'swagger2openapi';
import { OpenAPISpec } from '../types'; import { OpenAPISpec } from '../types';

View File

@ -2,6 +2,7 @@
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
import * as webpack from 'webpack'; import * as webpack from 'webpack';
import * as path from 'path'; import * as path from 'path';
import { getBabelLoader, webpackIgnore } from './config/webpack-utils';
const nodeExternals = require('webpack-node-externals')({ const nodeExternals = require('webpack-node-externals')({
// bundle in modules that need transpiling + non-js (e.g. css) // bundle in modules that need transpiling + non-js (e.g. css)
@ -31,7 +32,7 @@ const BANNER = `ReDoc - OpenAPI/Swagger-generated API Reference Documentation
Version: ${VERSION} Version: ${VERSION}
Repo: https://github.com/Redocly/redoc`; Repo: https://github.com/Redocly/redoc`;
export default (env: { standalone?: boolean } = {}, { mode }) => ({ export default (env: { standalone?: boolean } = {}) => ({
entry: env.standalone ? ['./src/polyfills.ts', './src/standalone.tsx'] : './src/index.ts', entry: env.standalone ? ['./src/polyfills.ts', './src/standalone.tsx'] : './src/index.ts',
output: { output: {
filename: env.standalone ? 'redoc.standalone.js' : 'redoc.lib.js', filename: env.standalone ? 'redoc.standalone.js' : 'redoc.lib.js',
@ -42,18 +43,20 @@ export default (env: { standalone?: boolean } = {}, { mode }) => ({
}, },
devtool: 'source-map', devtool: 'source-map',
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'], extensions: ['.ts', '.tsx', '.js', '.mjs', '.json'],
}, fallback: {
node: { path: require.resolve('path-browserify'),
fs: 'empty', http: false,
fs: false,
os: false,
}
}, },
performance: false, performance: false,
optimization: { // target: 'node',
minimize: !!env.standalone, externalsPresets: env.standalone ? {} : { node: true },
},
externals: env.standalone externals: env.standalone
? { ? {
esprima: 'esprima', esprima: 'null',
'node-fetch': 'null', 'node-fetch': 'null',
'node-fetch-h2': 'null', 'node-fetch-h2': 'null',
yaml: 'null', yaml: 'null',
@ -61,7 +64,7 @@ export default (env: { standalone?: boolean } = {}, { mode }) => ({
} }
: (context, request, callback) => { : (context, request, callback) => {
// ignore node-fetch dep of swagger2openapi as it is not used // ignore node-fetch dep of swagger2openapi as it is not used
if (/esprima|node-fetch|node-fetch-h2|yaml|safe-json-stringify$/i.test(request)) { if (/esprima|node-fetch|node-fetch-h2|\/yaml|safe-json-stringify$/i.test(request)) {
return callback(null, 'var undefined'); return callback(null, 'var undefined');
} }
return nodeExternals(context, request, callback); return nodeExternals(context, request, callback);
@ -70,51 +73,22 @@ export default (env: { standalone?: boolean } = {}, { mode }) => ({
module: { module: {
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.(tsx?|[cm]?js)$/,
use: [ use: [getBabelLoader({useBuiltIns: !!env.standalone})],
{ exclude: {
loader: 'ts-loader', and: [/node_modules/],
options: { not: {
compilerOptions: { or: [
module: 'es2015', /swagger2openapi/,
declaration: false, /reftools/,
}, /openapi-sampler/,
}, /mobx/,
}, /oas-resolver/,
{ /oas-kit-common/,
loader: 'babel-loader', /oas-schema-walker/,
options: { /\@redocly\/openapi-core/,
generatorOpts: { /colorette/,
decoratorsBeforeExport: true, ],
},
plugins: [
['@babel/plugin-syntax-typescript', { isTSX: true }],
['@babel/plugin-syntax-decorators', { legacy: true }],
'@babel/plugin-syntax-jsx',
[
'babel-plugin-styled-components',
{
minify: true,
displayName: mode !== 'production',
},
],
],
},
},
],
exclude: [/node_modules/],
},
{
test: /node_modules\/(swagger2openapi|reftools|oas-resolver|oas-kit-common|oas-schema-walker)\/.*\.js$/,
use: {
loader: 'ts-loader',
options: {
instance: 'ts2js-transpiler-only',
transpileOnly: true,
compilerOptions: {
allowJs: true,
declaration: false,
},
}, },
}, },
}, },
@ -127,22 +101,19 @@ export default (env: { standalone?: boolean } = {}, { mode }) => ({
}, },
}, },
}, },
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
], ],
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__REDOC_VERSION__: VERSION, __REDOC_VERSION__: VERSION,
__REDOC_REVISION__: REVISION, __REDOC_REVISION__: REVISION,
'process.env': '{}',
'process.platform': '"browser"',
'process.stdout': 'null',
}), }),
new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }), new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
new webpack.BannerPlugin(BANNER), new webpack.BannerPlugin(BANNER),
ignore(/js-yaml\/dumper\.js$/), webpackIgnore(/js-yaml\/dumper\.js$/),
ignore(/json-schema-ref-parser\/lib\/dereference\.js/), env.standalone ? webpackIgnore(/^\.\/SearchWorker\.worker$/) : undefined,
env.standalone ? ignore(/^\.\/SearchWorker\.worker$/) : ignore(/$non-existing^/), ].filter(Boolean),
],
}); });
function ignore(regexp) {
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash/noop.js'));
}