From 63b2a9751c13da166f29096a124b2febcf679cf8 Mon Sep 17 00:00:00 2001 From: Owen Conti Date: Sat, 8 Jul 2017 08:29:34 -0600 Subject: [PATCH 1/5] Fixes #3329 - Tweak webpack config to share CSS loaders for production builders. Updated production builds to use `ExtractTextPlugin` so styles are not built into JS. --- make-webpack-config.js | 62 +++++++++++++++---------------- webpack-dist-bundle.config.js | 57 +++++++--------------------- webpack-dist-standalone.config.js | 56 ++++++---------------------- webpack-dist.config.js | 59 +++++------------------------ webpack-hot-dev-server.config.js | 54 +++++++++++++-------------- webpack-watch.config.js | 2 +- webpack.check.js | 9 ++--- webpack.config.js | 2 +- webpack.dist-style.config.js | 45 ++++++++++++++++++++++ 9 files changed, 142 insertions(+), 204 deletions(-) create mode 100644 webpack.dist-style.config.js diff --git a/make-webpack-config.js b/make-webpack-config.js index f2628bc0..3ed81a0f 100644 --- a/make-webpack-config.js +++ b/make-webpack-config.js @@ -1,11 +1,11 @@ -var path = require('path') +var path = require("path") -var webpack = require('webpack') -var ExtractTextPlugin = require('extract-text-webpack-plugin') -var deepExtend = require('deep-extend') -const {gitDescribeSync} = require('git-describe'); +var webpack = require("webpack") +var ExtractTextPlugin = require("extract-text-webpack-plugin") +var deepExtend = require("deep-extend") +const {gitDescribeSync} = require("git-describe") -var pkg = require('./package.json') +var pkg = require("./package.json") let gitInfo @@ -13,7 +13,7 @@ try { gitInfo = gitDescribeSync(__dirname) } catch(e) { gitInfo = { - hash: 'noGit', + hash: "noGit", dirty: false } } @@ -21,21 +21,21 @@ try { var commonRules = [ { test: /\.(js(x)?)(\?.*)?$/, use: [{ - loader: 'babel-loader', + loader: "babel-loader", options: { retainLines: true } }], - include: [ path.join(__dirname, 'src') ] + include: [ path.join(__dirname, "src") ] }, { test: /\.(txt|yaml)(\?.*)?$/, - loader: 'raw-loader' }, + loader: "raw-loader" }, { test: /\.(png|jpg|jpeg|gif|svg)(\?.*)?$/, - loader: 'url-loader?limit=10000' }, + loader: "url-loader?limit=10000" }, { test: /\.(woff|woff2)(\?.*)?$/, - loader: 'url-loader?limit=100000' }, + loader: "url-loader?limit=100000" }, { test: /\.(ttf|eot)(\?.*)?$/, - loader: 'file-loader' } + loader: "file-loader" } ] module.exports = function(rules, options) { @@ -54,13 +54,12 @@ module.exports = function(rules, options) { if( specialOptions.separateStylesheets ) { plugins.push(new ExtractTextPlugin({ - filename: '[name].css' + (specialOptions.longTermCaching ? '?[contenthash]' : ''), + filename: "[name].css" + (specialOptions.longTermCaching ? "?[contenthash]" : ""), allChunks: true })) } if( specialOptions.minimize ) { - plugins.push( new webpack.optimize.UglifyJsPlugin({ sourceMap: true, @@ -78,12 +77,11 @@ module.exports = function(rules, options) { plugins.push( new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: specialOptions.minimize ? JSON.stringify('production') : null, - WEBPACK_INLINE_STYLES: !Boolean(specialOptions.separateStylesheets) - + "process.env": { + NODE_ENV: specialOptions.minimize ? JSON.stringify("production") : null, + WEBPACK_INLINE_STYLES: !specialOptions.separateStylesheets }, - 'buildInfo': JSON.stringify({ + "buildInfo": JSON.stringify({ PACKAGE_VERSION: (pkg.version), GIT_COMMIT: gitInfo.hash, GIT_DIRTY: gitInfo.dirty @@ -92,21 +90,21 @@ module.exports = function(rules, options) { delete options._special - var completeConfig = deepExtend({ + var completeConfig = deepExtend({ entry: {}, output: { - path: path.join(__dirname, 'dist'), - publicPath: '/', - filename: '[name].js', - chunkFilename: '[name].js' + path: path.join(__dirname, "dist"), + publicPath: "/", + filename: "[name].js", + chunkFilename: "[name].js" }, - target: 'web', + target: "web", // yaml-js has a reference to `fs`, this is a workaround node: { - fs: 'empty' + fs: "empty" }, module: { @@ -114,17 +112,17 @@ module.exports = function(rules, options) { }, resolveLoader: { - modules: [path.join(__dirname, 'node_modules')], + modules: [path.join(__dirname, "node_modules")], }, externals: { - 'buffertools': true // json-react-schema/deeper depends on buffertools, which fails. + "buffertools": true // json-react-schema/deeper depends on buffertools, which fails. }, resolve: { modules: [ - path.join(__dirname, './src'), - 'node_modules' + path.join(__dirname, "./src"), + "node_modules" ], extensions: [".web.js", ".js", ".jsx", ".json", ".less"], alias: { @@ -132,7 +130,7 @@ module.exports = function(rules, options) { } }, - devtool: specialOptions.sourcemaps ? 'cheap-module-source-map' : null, + devtool: specialOptions.sourcemaps ? "cheap-module-source-map" : null, plugins, diff --git a/webpack-dist-bundle.config.js b/webpack-dist-bundle.config.js index f805c8bf..f876d0ef 100644 --- a/webpack-dist-bundle.config.js +++ b/webpack-dist-bundle.config.js @@ -1,64 +1,33 @@ -var path = require('path') -var rules = [ +const path = require("path") +const styleRules = require("./webpack.dist-style.config.js") + +let rules = [ { test: /\.(worker\.js)(\?.*)?$/, use: [ { - loader: 'worker-loader', + loader: "worker-loader", options: { inline: true, - name: '[name].js' - } - }, - { loader: 'babel-loader' } - ] - }, - { test: /\.(css)(\?.*)?$/, - use: [ - 'style-loader', - 'css-loader', - 'postcss-loader' - ] - }, - { test: /\.(scss)(\?.*)?$/, - use: [ - 'style-loader', - 'css-loader', - { - loader: 'postcss-loader', - options: { sourceMap: true } - }, - { loader: 'sass-loader', - options: { - outputStyle: 'expanded', - sourceMap: true, - sourceMapContents: 'true' + name: "[name].js" } - } - ] - }, - { test: /\.(less)(\?.*)?$/, - use: [ - 'style-loader', - 'css-loader', - { - loader: 'postcss-loader', }, - 'less-loader' + { loader: "babel-loader" } ] } ] +rules = rules.concat(styleRules) -module.exports = require('./make-webpack-config.js')(rules, { +module.exports = require("./make-webpack-config.js")(rules, { _special: { - separateStylesheets: false, + separateStylesheets: true, minimize: true, sourcemaps: true, }, entry: { - 'swagger-ui-bundle': [ - './src/polyfills', - './src/core/index.js' + "swagger-ui-bundle": [ + "./src/polyfills", + "./src/core/index.js" ] }, diff --git a/webpack-dist-standalone.config.js b/webpack-dist-standalone.config.js index 66469098..65aa1839 100644 --- a/webpack-dist-standalone.config.js +++ b/webpack-dist-standalone.config.js @@ -1,65 +1,33 @@ -var path = require('path') +const path = require("path") +const styleRules = require("./webpack.dist-style.config.js") -var rules = [ +let rules = [ { test: /\.(worker\.js)(\?.*)?$/, use: [ { - loader: 'worker-loader', + loader: "worker-loader", options: { inline: true, - name: '[name].js' + name: "[name].js" } }, - { loader: 'babel-loader' } - ] - }, - { test: /\.(css)(\?.*)?$/, - use: [ - 'style-loader', - 'css-loader', - 'postcss-loader' - ] - }, - { test: /\.(scss)(\?.*)?$/, - use: [ - 'style-loader', - 'css-loader', - { - loader: 'postcss-loader', - options: { sourceMap: true } - }, - { loader: 'sass-loader', - options: { - outputStyle: 'expanded', - sourceMap: true, - sourceMapContents: 'true' - } - } - ] - }, - { test: /\.(less)(\?.*)?$/, - use: [ - 'style-loader', - 'css-loader', - { - loader: 'postcss-loader', - }, - 'less-loader' + { loader: "babel-loader" } ] } ] +rules = rules.concat(styleRules) -module.exports = require('./make-webpack-config.js')(rules, { +module.exports = require("./make-webpack-config.js")(rules, { _special: { - separateStylesheets: false, + separateStylesheets: true, minimize: true, sourcemaps: true, }, entry: { - 'swagger-ui-standalone-preset': [ - './src/polyfills', - './src/standalone/index.js' + "swagger-ui-standalone-preset": [ + "./src/polyfills", + "./src/standalone/index.js" ] }, diff --git a/webpack-dist.config.js b/webpack-dist.config.js index b2e730dc..3aa65ed2 100644 --- a/webpack-dist.config.js +++ b/webpack-dist.config.js @@ -1,66 +1,25 @@ -var path = require('path') -var fs = require('fs') +const path = require("path") +const fs = require("fs") const nodeModules = fs.readdirSync("node_modules").filter(function(x) { return x !== ".bin" }) -var ExtractTextPlugin = require('extract-text-webpack-plugin') +const styleRules = require("./webpack.dist-style.config.js") -var rules = [ +let rules = [ { test: /\.(worker\.js)(\?.*)?$/, use: [ { - loader: 'worker-loader', + loader: "worker-loader", options: { inline: true, - name: '[name].js' + name: "[name].js" } }, - { loader: 'babel-loader' } + { loader: "babel-loader" } ] - }, - { test: /\.(css)(\?.*)?$/, - use: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: [ - 'css-loader', - 'postcss-loader' - ] - }) - }, - { test: /\.(scss)(\?.*)?$/, - use: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: [ - { - loader: 'css-loader', - options: { minimize: true } - }, - { - loader: 'postcss-loader', - options: { sourceMap: true } - }, - { loader: 'sass-loader', - options: { - outputStyle: 'expanded', - sourceMap: true, - sourceMapContents: 'true' - } - } - ] - }) - }, - { test: /\.(less)(\?.*)?$/, - use: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: ['css-loader', - { - loader: 'postcss-loader', - }, - 'less-loader' - ] - }) } ] +rules = rules.concat(styleRules) -module.exports = require('./make-webpack-config.js')(rules, { +module.exports = require("./make-webpack-config.js")(rules, { _special: { separateStylesheets: true, minimize: true, diff --git a/webpack-hot-dev-server.config.js b/webpack-hot-dev-server.config.js index cb8e4b70..3e61628b 100644 --- a/webpack-hot-dev-server.config.js +++ b/webpack-hot-dev-server.config.js @@ -1,55 +1,55 @@ -var path = require('path') +const path = require("path") -var rules = [ +const rules = [ { test: /\.(worker\.js)(\?.*)?$/, use: [ { - loader: 'worker-loader', + loader: "worker-loader", options: { inline: true } }, - { loader: 'babel-loader' } + { loader: "babel-loader" } ] }, { test: /\.(jsx)(\?.*)?$/, use: [ - { loader: 'react-hot-loader' }, - { loader: 'babel-loader' } + { loader: "react-hot-loader" }, + { loader: "babel-loader" } ] }, { test: /\.(css)(\?.*)?$/, use: [ - 'style-loader', - 'css-loader', - 'postcss-loader' + "style-loader", + "css-loader", + "postcss-loader" ] }, { test: /\.(scss)(\?.*)?$/, use: [ - 'style-loader', - 'css-loader', + "style-loader", + "css-loader", { - loader: 'postcss-loader', + loader: "postcss-loader", options: { sourceMap: true } }, - { loader: 'sass-loader', + { loader: "sass-loader", options: { - outputStyle: 'expanded', + outputStyle: "expanded", sourceMap: true, - sourceMapContents: 'true' + sourceMapContents: "true" } } ] }, { test: /\.(less)(\?.*)?$/, use: [ - 'style-loader', - 'css-loader', + "style-loader", + "css-loader", { - loader: 'postcss-loader', + loader: "postcss-loader", }, - 'less-loader' + "less-loader" ] } ] @@ -60,25 +60,25 @@ module.exports = require("./make-webpack-config")(rules, { }, devtool: "eval", entry: { - 'swagger-ui-bundle': [ - './src/polyfills', - './src/core/index.js' + "swagger-ui-bundle": [ + "./src/polyfills", + "./src/core/index.js" ], - 'swagger-ui-standalone-preset': [ - './src/polyfills', - './src/standalone/index.js', + "swagger-ui-standalone-preset": [ + "./src/polyfills", + "./src/standalone/index.js", ] }, output: { pathinfo: true, - filename: '[name].js', + filename: "[name].js", library: "[name]", libraryTarget: "umd", chunkFilename: "[id].js" }, devServer: { port: 3200, - contentBase: path.join(__dirname, 'dev-helpers'), + contentBase: path.join(__dirname, "dev-helpers"), publicPath: "/", noInfo: true, hot: true, diff --git a/webpack-watch.config.js b/webpack-watch.config.js index 4406b0aa..ed5c711c 100644 --- a/webpack-watch.config.js +++ b/webpack-watch.config.js @@ -1,3 +1,3 @@ -var config = require("./webpack-dist.config.js") +const config = require("./webpack-dist.config.js") module.exports = config diff --git a/webpack.check.js b/webpack.check.js index 02a47376..6f10188b 100644 --- a/webpack.check.js +++ b/webpack.check.js @@ -1,8 +1,7 @@ -const webpack = require('webpack') -const path = require('path') -const deepMerge = require('deepmerge') -const webpackConfig = require('./webpack-dist-bundle.config.js') -const DEPS_CHECK_DIR = require('./package.json').config.deps_check_dir +const path = require("path") +const deepMerge = require("deepmerge") +const webpackConfig = require("./webpack-dist-bundle.config.js") +const DEPS_CHECK_DIR = require("./package.json").config.deps_check_dir module.exports = deepMerge( webpackConfig, { diff --git a/webpack.config.js b/webpack.config.js index 0d22493e..f0ca3601 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,3 +1,3 @@ module.exports = require("./make-webpack-config")({ -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/webpack.dist-style.config.js b/webpack.dist-style.config.js new file mode 100644 index 00000000..1a3424fd --- /dev/null +++ b/webpack.dist-style.config.js @@ -0,0 +1,45 @@ +const ExtractTextPlugin = require("extract-text-webpack-plugin") + +module.exports = [{ + test: /\.(css)(\?.*)?$/, + use: ExtractTextPlugin.extract({ + fallback: "style-loader", + use: [ + "css-loader", + "postcss-loader" + ] + }) +}, +{ test: /\.(scss)(\?.*)?$/, + use: ExtractTextPlugin.extract({ + fallback: "style-loader", + use: [ + { + loader: "css-loader", + options: { minimize: true } + }, + { + loader: "postcss-loader", + options: { sourceMap: true } + }, + { loader: "sass-loader", + options: { + outputStyle: "expanded", + sourceMap: true, + sourceMapContents: "true" + } + } + ] + }) +}, +{ test: /\.(less)(\?.*)?$/, + use: ExtractTextPlugin.extract({ + fallback: "style-loader", + use: ["css-loader", + { + loader: "postcss-loader", + }, + "less-loader" + ] + }) +}] \ No newline at end of file From 7000c20765eb1e92039b30aafd64472b56d51bff Mon Sep 17 00:00:00 2001 From: Owen Conti Date: Tue, 11 Jul 2017 20:17:06 -0600 Subject: [PATCH 2/5] Moved split-pane-mode.less file into a .scss file. Removed remaining unused .less files. Removed reference to less file in webpack config. Removed dependency on less-loader. Updated standalone and bundle builds to no longer compile any styles. --- package.json | 1 - .../components/split-pane-mode.jsx | 1 - .../components/split-pane-mode.less | 5 -- src/plugins/topbar/topbar.less | 52 ------------------- src/standalone/index.js | 2 - src/style/_split-pane-mode.scss | 3 ++ src/style/main.scss | 1 + webpack-dist-bundle.config.js | 1 - webpack-dist-standalone.config.js | 1 - webpack-hot-dev-server.config.js | 10 ---- webpack.dist-style.config.js | 11 ---- 11 files changed, 4 insertions(+), 84 deletions(-) delete mode 100644 src/core/plugins/split-pane-mode/components/split-pane-mode.less delete mode 100644 src/plugins/topbar/topbar.less create mode 100644 src/style/_split-pane-mode.scss diff --git a/package.json b/package.json index 895e22dd..5ad9552f 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,6 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "2.0.3", "less": "2.7.2", - "less-loader": "4.0.4", "license-checker": "^11.0.0", "mocha": "^3.4.2", "node-sass": "^4.5.0", diff --git a/src/core/plugins/split-pane-mode/components/split-pane-mode.jsx b/src/core/plugins/split-pane-mode/components/split-pane-mode.jsx index 73f2acbc..c1a8fa9c 100644 --- a/src/core/plugins/split-pane-mode/components/split-pane-mode.jsx +++ b/src/core/plugins/split-pane-mode/components/split-pane-mode.jsx @@ -1,7 +1,6 @@ import React from "react" import PropTypes from "prop-types" import SplitPane from "react-split-pane" -import "./split-pane-mode.less" const MODE_KEY = ["split-pane-mode"] const MODE_LEFT = "left" diff --git a/src/core/plugins/split-pane-mode/components/split-pane-mode.less b/src/core/plugins/split-pane-mode/components/split-pane-mode.less deleted file mode 100644 index d5437b37..00000000 --- a/src/core/plugins/split-pane-mode/components/split-pane-mode.less +++ /dev/null @@ -1,5 +0,0 @@ -.swagger-ui { - .Resizer.vertical.disabled { - display: none; - } -} diff --git a/src/plugins/topbar/topbar.less b/src/plugins/topbar/topbar.less deleted file mode 100644 index 9818940b..00000000 --- a/src/plugins/topbar/topbar.less +++ /dev/null @@ -1,52 +0,0 @@ -.swagger-ui { - .topbar { - background-color: #89bf04; - } - - .topbar-wrapper { - padding: 0.7em; - } - - .topbar-logo__img { - float: left; - } - - .topbar-logo__title { - display: inline-block; - color: #fff; - font-size: 1.5em; - font-weight: bold; - margin: 0.15em 0 0 0.5em; - } - - .download-url-wrapper { - text-align: right; - float: right; - } - - .topbar .download-url__text { - width: 28em; - height: 2em; - margin-right: 0.5em; - } - - .download-url__btn { - background-color: #547f00; - border-color: #547f00; - text-decoration: none; - font-weight: bold; - padding: 0.2em 0.3em; - color: white; - border-radius: 0.1em; - - &:hover { - &:extend(.download-url__btn); - } - } - - .center-700 { - display: block; - margin: 0 auto; - width: 45em; - } -} diff --git a/src/standalone/index.js b/src/standalone/index.js index 6616a744..40e9a50c 100644 --- a/src/standalone/index.js +++ b/src/standalone/index.js @@ -1,6 +1,4 @@ import StandaloneLayout from "./layout" -import "../style/main.scss" - import TopbarPlugin from "plugins/topbar" import ConfigsPlugin from "plugins/configs" diff --git a/src/style/_split-pane-mode.scss b/src/style/_split-pane-mode.scss new file mode 100644 index 00000000..4a53d4c6 --- /dev/null +++ b/src/style/_split-pane-mode.scss @@ -0,0 +1,3 @@ +.Resizer.vertical.disabled { + display: none; +} \ No newline at end of file diff --git a/src/style/main.scss b/src/style/main.scss index f6ff00fb..443cc37c 100644 --- a/src/style/main.scss +++ b/src/style/main.scss @@ -14,4 +14,5 @@ @import 'information'; @import 'authorize'; @import 'errors'; + @import 'split-pane-mode'; } diff --git a/webpack-dist-bundle.config.js b/webpack-dist-bundle.config.js index f876d0ef..27a26046 100644 --- a/webpack-dist-bundle.config.js +++ b/webpack-dist-bundle.config.js @@ -15,7 +15,6 @@ let rules = [ ] } ] -rules = rules.concat(styleRules) module.exports = require("./make-webpack-config.js")(rules, { _special: { diff --git a/webpack-dist-standalone.config.js b/webpack-dist-standalone.config.js index 65aa1839..e19f428b 100644 --- a/webpack-dist-standalone.config.js +++ b/webpack-dist-standalone.config.js @@ -15,7 +15,6 @@ let rules = [ ] } ] -rules = rules.concat(styleRules) module.exports = require("./make-webpack-config.js")(rules, { _special: { diff --git a/webpack-hot-dev-server.config.js b/webpack-hot-dev-server.config.js index 3e61628b..c964de7e 100644 --- a/webpack-hot-dev-server.config.js +++ b/webpack-hot-dev-server.config.js @@ -41,16 +41,6 @@ const rules = [ } } ] - }, - { test: /\.(less)(\?.*)?$/, - use: [ - "style-loader", - "css-loader", - { - loader: "postcss-loader", - }, - "less-loader" - ] } ] diff --git a/webpack.dist-style.config.js b/webpack.dist-style.config.js index 1a3424fd..2b36430a 100644 --- a/webpack.dist-style.config.js +++ b/webpack.dist-style.config.js @@ -31,15 +31,4 @@ module.exports = [{ } ] }) -}, -{ test: /\.(less)(\?.*)?$/, - use: ExtractTextPlugin.extract({ - fallback: "style-loader", - use: ["css-loader", - { - loader: "postcss-loader", - }, - "less-loader" - ] - }) }] \ No newline at end of file From 87bb4420e2d4c6a0b32d227b77b339eb19a64e25 Mon Sep 17 00:00:00 2001 From: Owen Conti Date: Tue, 11 Jul 2017 20:30:52 -0600 Subject: [PATCH 3/5] Add `main.scss` as an entry point for the dev build --- webpack-hot-dev-server.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack-hot-dev-server.config.js b/webpack-hot-dev-server.config.js index c964de7e..e002628f 100644 --- a/webpack-hot-dev-server.config.js +++ b/webpack-hot-dev-server.config.js @@ -55,6 +55,7 @@ module.exports = require("./make-webpack-config")(rules, { "./src/core/index.js" ], "swagger-ui-standalone-preset": [ + "./src/style/main.scss", "./src/polyfills", "./src/standalone/index.js", ] From a8f1f4979a22ddc586532d194acd96e3b19b6383 Mon Sep 17 00:00:00 2001 From: shockey Date: Mon, 17 Jul 2017 20:03:11 -0700 Subject: [PATCH 4/5] Add security contact to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0144744e..f7908130 100644 --- a/README.md +++ b/README.md @@ -235,6 +235,10 @@ Access-Control-Allow-Headers: Content-Type, api_key, Authorization Only headers with these names will be allowed to be sent by Swagger-UI. +## Security contact + +Please disclose any security-related issues or vulnerabilities by emailing [security@swagger.io](mailto:security@swagger.io), instead of using the public issue tracker. + ## License Copyright 2017 SmartBear Software From f7e6cadff3a1a6e9a6e67126828afe668ff399f1 Mon Sep 17 00:00:00 2001 From: shockey Date: Mon, 17 Jul 2017 20:20:46 -0700 Subject: [PATCH 5/5] Add deepLinking to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f7908130..dac53d35 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ displayOperationId | Controls the display of operationId in operations list. The displayRequestDuration | Controls the display of the request duration (in milliseconds) for `Try it out` requests. The default is `false`. maxDisplayedTags | If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations. filter | If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be true/false to enable or disable, or an explicit filter string in which case filtering will be enabled using that string as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag. +deepLinking | If set to `true`, enables dynamic deep linking for tags and operations. [Docs](https://github.com/swagger-api/swagger-ui/blob/master/docs/deep-linking.md) ### Plugins