feat(build): webpack@5 and webpack-dev-server@4 (#7826)

SwaggerUI is now built using `webpack@5`, with dev support for `webpack-dev-server@4`
- ES Module output bundle path now points to `swagger-ui-es-bundle-core`, which does not include dependencies
- No change to CommonJS output bundle or path
- Now uses Asset Modules, which replaces `file-loader`, `raw-loader`, and `url-loader`
- Removed unused rules/loaders for `.woff | .woff2 | .ttf | .eot` fonts and html
- Node polyfills are no longer bundled with `webpack@5`, and must be loaded separately and/or use `resolve.fallback`. 
As an example, SwaggerUI loads `process`, `buffer`, and `stream-browserify` as `devDependencies` in order to build development and production bundles.

SwaggerUI-React
- Now imports `swagger-ui-es-bundle-core`, and similarly outputs `swagger-ui-es-bundle-core` to its `dist` directory

Dev notes:
- Order of execution matters for the production npm build scripts. `build-stylesheets` needs to get built first, 
then cleanup of any empty artifacts, before building the various production bundles
- `Dev-helpers` now relies on `HTMLWebpackPlugin` to inject css and bundle files
This commit is contained in:
Tim Lai
2022-03-01 12:08:50 -08:00
committed by GitHub
parent 6534f10206
commit 07d346b516
18 changed files with 27076 additions and 4615 deletions

View File

@@ -5,9 +5,6 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Swagger UI</title> <title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style> <style>
html html
{ {
@@ -34,8 +31,6 @@
<body> <body>
<div id="swagger-ui"></div> <div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script> <script>
window.onload = function() { window.onload = function() {
window["SwaggerUIBundle"] = window["swagger-ui-bundle"] window["SwaggerUIBundle"] = window["swagger-ui-bundle"]

2
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import swaggerUIConstructor, {presets} from "./swagger-ui" import swaggerUIConstructor, {presets} from "./swagger-ui-es-bundle-core"
export default class SwaggerUI extends React.Component { export default class SwaggerUI extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)

View File

@@ -9,10 +9,8 @@ cd "${0%/*}"
mkdir -p ../dist mkdir -p ../dist
# Copy UI's dist files to our directory # Copy UI's dist files to our directory
cp ../../../dist/swagger-ui.js ../dist cp ../../../dist/swagger-ui-es-bundle-core.js ../dist
cp ../../../dist/swagger-ui.js.map ../dist cp ../../../dist/swagger-ui-es-bundle-core.js.map ../dist
cp ../../../dist/swagger-ui-es-bundle.js ../dist
cp ../../../dist/swagger-ui-es-bundle.js.map ../dist
cp ../../../dist/swagger-ui.css ../dist cp ../../../dist/swagger-ui.css ../dist
cp ../../../dist/swagger-ui.css.map ../dist cp ../../../dist/swagger-ui.css.map ../dist

View File

@@ -28,6 +28,13 @@
"name": "swagger-ui-react", "name": "swagger-ui-react",
"main": "commonjs.js", "main": "commonjs.js",
"module": "index.js", "module": "index.js",
"exports": {
"./swagger-ui.css": "./swagger-ui.css",
".": {
"import": "./index.js",
"require": "./commonjs.js"
}
},
"repository": "git@github.com:swagger-api/swagger-ui.git", "repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [ "contributors": [
"(in alphabetical order)", "(in alphabetical order)",

31371
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,15 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "4.5.2", "version": "4.5.2",
"main": "dist/swagger-ui.js", "main": "./dist/swagger-ui.js",
"module": "dist/swagger-ui-es-bundle.js", "module": "./dist/swagger-ui-es-bundle-core.js",
"exports": {
"./swagger-ui.css": "./swagger-ui.css",
".": {
"import": "./dist/swagger-ui-es-bundle-core.js",
"require": "./dist/swagger-ui.js"
}
},
"homepage": "https://github.com/swagger-api/swagger-ui", "homepage": "https://github.com/swagger-api/swagger-ui",
"repository": "git@github.com:swagger-api/swagger-ui.git", "repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [ "contributors": [
@@ -17,13 +24,15 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"automated-release": "release-it -VV --config ./release/.release-it.json", "automated-release": "release-it -VV --config ./release/.release-it.json",
"build": "run-p --aggregate-output build-core build-bundle build-standalone build-stylesheets build:es:bundle build:es:bundle:core", "build": "npm run build-stylesheets && rimraf ./dist/swagger-ui.js ./dist/swagger-ui.js.map && npm run build-all-bundles",
"build-bundle": "webpack --color --config webpack/bundle.babel.js", "build-all-bundles": "run-p --aggregate-output build:core build:bundle build:standalone build:es:bundle build:es:bundle:core",
"build-core": "webpack --color --config webpack/core.babel.js",
"build-standalone": "webpack --color --config webpack/standalone.babel.js",
"build-stylesheets": "webpack --color --config webpack/stylesheets.babel.js", "build-stylesheets": "webpack --color --config webpack/stylesheets.babel.js",
"build:bundle": "webpack --color --config webpack/bundle.babel.js",
"build:core": "webpack --color --config webpack/core.babel.js",
"build:standalone": "webpack --color --config webpack/standalone.babel.js",
"build:es:bundle": "webpack --color --config webpack/es-bundle.babel.js", "build:es:bundle": "webpack --color --config webpack/es-bundle.babel.js",
"build:es:bundle:core": "webpack --color --config webpack/es-bundle-core.babel.js", "build:es:bundle:core": "webpack --color --config webpack/es-bundle-core.babel.js",
"clean": "rimraf ./dist",
"predev": "npm install", "predev": "npm install",
"dev": "webpack serve --config webpack/dev.babel.js", "dev": "webpack serve --config webpack/dev.babel.js",
"deps-license": "license-checker --production --csv --out $npm_package_config_deps_check_dir/licenses.csv && license-checker --development --csv --out $npm_package_config_deps_check_dir/licenses-dev.csv", "deps-license": "license-checker --production --csv --out $npm_package_config_deps_check_dir/licenses.csv && license-checker --development --csv --out $npm_package_config_deps_check_dir/licenses-dev.csv",
@@ -38,7 +47,7 @@
"test:artifact": "jest --config ./config/jest/jest.artifact.config.js", "test:artifact": "jest --config ./config/jest/jest.artifact.config.js",
"test:unit-jest": "cross-env BABEL_ENV=test jest --config ./config/jest/jest.unit.config.js", "test:unit-jest": "cross-env BABEL_ENV=test jest --config ./config/jest/jest.unit.config.js",
"cy:mock-api": "json-server --watch test/e2e-selenium/db.json --port 3204", "cy:mock-api": "json-server --watch test/e2e-selenium/db.json --port 3204",
"cy:server": "webpack serve --config webpack/dev-e2e.babel.js --content-base test/e2e-cypress/static", "cy:server": "webpack serve --config webpack/dev-e2e.babel.js",
"cy:start": "run-p -r cy:server cy:mock-api", "cy:start": "run-p -r cy:server cy:mock-api",
"cy:open": "cypress open", "cy:open": "cypress open",
"cy:run": "cypress run", "cy:run": "cypress run",
@@ -103,6 +112,7 @@
"@commitlint/cli": "^15.0.0", "@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^16.2.1", "@commitlint/config-conventional": "^16.2.1",
"@jest/globals": "=27.0.6", "@jest/globals": "=27.0.6",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@release-it/conventional-changelog": "=3.0.1", "@release-it/conventional-changelog": "=3.0.1",
"@wojtekmaj/enzyme-adapter-react-17": "=0.6.5", "@wojtekmaj/enzyme-adapter-react-17": "=0.6.5",
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
@@ -111,6 +121,7 @@
"babel-plugin-module-resolver": "=4.1.0", "babel-plugin-module-resolver": "=4.1.0",
"babel-plugin-transform-react-remove-prop-types": "=0.4.24", "babel-plugin-transform-react-remove-prop-types": "=0.4.24",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"buffer": "^6.0.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-env": "=7.0.3", "cross-env": "=7.0.3",
"css-loader": "=5.2.7", "css-loader": "=5.2.7",
@@ -127,10 +138,10 @@
"esm": "=3.2.25", "esm": "=3.2.25",
"expect": "=27.5.1", "expect": "=27.5.1",
"express": "^4.17.1", "express": "^4.17.1",
"file-loader": "^6.0.0",
"git-describe": "^4.1.0", "git-describe": "^4.1.0",
"html-webpack-plugin": "^5.5.0",
"html-webpack-skip-assets-plugin": "^1.0.3",
"husky": "=7.0.4", "husky": "=7.0.4",
"ignore-assets-webpack-plugin": "^2.0.1",
"inspectpack": "=4.7.1", "inspectpack": "=4.7.1",
"jest": "=27.4.7", "jest": "=27.4.7",
"jsdom": "=16.6.0", "jsdom": "=16.6.0",
@@ -141,17 +152,18 @@
"license-checker": "^25.0.0", "license-checker": "^25.0.0",
"lint-staged": "=11.2.6", "lint-staged": "=11.2.6",
"local-web-server": "^4.2.1", "local-web-server": "^4.2.1",
"mini-css-extract-plugin": "^1.6.2", "mini-css-extract-plugin": "^2.4.6",
"mocha": "=8.4.0", "mocha": "=8.4.0",
"npm-audit-ci-wrapper": "^3.0.2", "npm-audit-ci-wrapper": "^3.0.2",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"oauth2-server": "^2.4.1", "oauth2-server": "^2.4.1",
"open": "^8.4.0", "open": "^8.4.0",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"postcss-loader": "^4.3.0", "postcss-loader": "^6.2.1",
"postcss-preset-env": "^7.2.3", "postcss-preset-env": "^7.2.3",
"prettier": "^2.3.0", "prettier": "^2.3.0",
"raw-loader": "^4.0.0", "process": "^0.11.10",
"react-refresh": "^0.11.0",
"react-test-renderer": "=17.0.2", "react-test-renderer": "=17.0.2",
"release-it": "=14.12.5", "release-it": "=14.12.5",
"rimraf": "^3.0.0", "rimraf": "^3.0.0",
@@ -159,14 +171,14 @@
"sass-loader": "^9.0.3", "sass-loader": "^9.0.3",
"sinon": "=12.0.1", "sinon": "=12.0.1",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"start-server-and-test": "=1.14.0", "start-server-and-test": "^1.14.0",
"stream-browserify": "^3.0.0",
"tachyons-sass": "^4.9.5", "tachyons-sass": "^4.9.5",
"terser-webpack-plugin": "^4.2.3", "terser-webpack-plugin": "^5.3.0",
"url-loader": "^4.1.1", "webpack": "^5.65.0",
"webpack": "^4.46.0",
"webpack-bundle-size-analyzer": "^3.1.0", "webpack-bundle-size-analyzer": "^3.1.0",
"webpack-cli": "^4.7.2", "webpack-cli": "^4.9.2",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^4.7.4",
"webpack-stats-plugin": "=1.0.3" "webpack-stats-plugin": "=1.0.3"
}, },
"config": { "config": {

View File

@@ -5,6 +5,8 @@ const request = require.context(".", true, /\.jsx?$/)
const allPlugins = {} const allPlugins = {}
export default allPlugins
request.keys().forEach( function( key ){ request.keys().forEach( function( key ){
if( key === "./index.js" ) { if( key === "./index.js" ) {
return return
@@ -20,5 +22,3 @@ request.keys().forEach( function( key ){
}) })
allPlugins.SafeRender = SafeRender allPlugins.SafeRender = SafeRender
export default allPlugins

View File

@@ -3,7 +3,6 @@
*/ */
import path from "path" import path from "path"
import os from "os"
import fs from "fs" import fs from "fs"
import deepExtend from "deep-extend" import deepExtend from "deep-extend"
import webpack from "webpack" import webpack from "webpack"
@@ -30,26 +29,13 @@ const baseRules = [
cacheDirectory: true, cacheDirectory: true,
}, },
}, },
{ test: /\.(txt|yaml)$/, loader: "raw-loader" }, { test: /\.(txt|yaml)$/,
type: "asset/source",
},
{ {
test: /\.(png|jpg|jpeg|gif|svg)$/, test: /\.(png|jpg|jpeg|gif|svg)$/,
use: [ type: "asset/inline",
{
loader: "url-loader",
options: {
esModule: false,
},
},
],
}, },
{
test: /\.(woff|woff2)$/,
loader: "url-loader?",
options: {
limit: 10000,
},
},
{ test: /\.(ttf|eot)$/, loader: "file-loader" },
] ]
export default function buildConfig( export default function buildConfig(
@@ -72,6 +58,10 @@ export default function buildConfig(
BUILD_TIME: new Date().toUTCString(), BUILD_TIME: new Date().toUTCString(),
}), }),
}), }),
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: ["buffer", "Buffer"],
}),
] ]
const completeConfig = deepExtend( const completeConfig = deepExtend(
@@ -86,16 +76,16 @@ export default function buildConfig(
publicPath: "/dist", publicPath: "/dist",
filename: "[name].js", filename: "[name].js",
chunkFilename: "[name].js", chunkFilename: "[name].js",
libraryTarget: "umd", globalObject: "this",
libraryExport: "default", // TODO: enable library: {
// when esm, library.name should be unset, so do not define here
// when esm, library.export should be unset, so do not define here
type: "umd",
}
}, },
target: "web", target: "web",
node: {
// yaml-js has a reference to `fs`, this is a workaround
fs: "empty",
},
module: { module: {
rules: baseRules, rules: baseRules,
@@ -132,6 +122,11 @@ export default function buildConfig(
"lodash": path.resolve(__dirname, "..", "node_modules/lodash"), "lodash": path.resolve(__dirname, "..", "node_modules/lodash"),
"isarray": path.resolve(__dirname, "..", "node_modules/stream-browserify/node_modules/isarray"), "isarray": path.resolve(__dirname, "..", "node_modules/stream-browserify/node_modules/isarray"),
"react-is": path.resolve(__dirname, "..", "node_modules/react-redux/node_modules/react-is"), "react-is": path.resolve(__dirname, "..", "node_modules/react-redux/node_modules/react-is"),
"safe-buffer": path.resolve(__dirname, "..", "node_modules/string_decoder/node_modules/safe-buffer"),
},
fallback: {
fs: false,
stream: require.resolve("stream-browserify"),
}, },
}, },
@@ -139,8 +134,8 @@ export default function buildConfig(
// Otherwise, provide heavy souremaps suitable for development // Otherwise, provide heavy souremaps suitable for development
devtool: sourcemaps devtool: sourcemaps
? minimize ? minimize
? "nosource-source-map" ? "nosources-source-map"
: "module-source-map" : "cheap-module-source-map"
: false, : false,
performance: { performance: {
@@ -154,8 +149,6 @@ export default function buildConfig(
minimizer: [ minimizer: [
compiler => compiler =>
new TerserPlugin({ new TerserPlugin({
cache: true,
sourceMap: sourcemaps,
terserOptions: { terserOptions: {
mangle: !!mangle, mangle: !!mangle,
}, },

View File

@@ -30,7 +30,10 @@ const result = configBuilder(
}, },
output: { output: {
globalObject: "this", globalObject: "this",
library: "SwaggerUIBundle", library: {
name: "SwaggerUIBundle",
export: "default",
},
}, },
plugins: [ plugins: [
new DuplicatesPlugin({ new DuplicatesPlugin({
@@ -44,7 +47,7 @@ const result = configBuilder(
// filename: path.join("log.bundle-stats.swagger-ui.json"), // filename: path.join("log.bundle-stats.swagger-ui.json"),
// fields: null, // fields: null,
// }), // }),
] ],
} }
) )

View File

@@ -20,7 +20,10 @@ const result = configBuilder(
output: { output: {
globalObject: "this", globalObject: "this",
library: "SwaggerUICore", library: {
name: "SwaggerUICore",
export: "default",
},
}, },
} }
) )

View File

@@ -2,17 +2,75 @@
* @prettier * @prettier
*/ */
// The standard dev config doesn't allow overriding contentBase via the CLI, import path from "path"
// which we do in the npm scripts for e2e tests.
//
// This variant avoids contentBase in the config, so the CLI values take hold.
import devConfig from "./dev.babel" import configBuilder from "./_config-builder"
import styleConfig from "./stylesheets.babel"
// set the common e2e port 3230 // Pretty much the same as devConfig, but with changes to port and static.directory
devConfig.devServer.port = 3230 const devE2eConfig = configBuilder(
{
minimize: false,
mangle: false,
sourcemaps: true,
includeDependencies: true,
},
{
mode: "development",
entry: {
"swagger-ui-bundle": [
"./src/core/index.js",
],
"swagger-ui-standalone-preset": [
"./src/standalone/index.js",
],
"swagger-ui": "./src/style/main.scss",
},
// unset contentBase performance: {
delete devConfig.devServer.contentBase hints: false
},
export default devConfig output: {
filename: "[name].js",
chunkFilename: "[id].js",
library: {
name: "[name]",
export: "default",
},
publicPath: "/",
},
devServer: {
allowedHosts: "all", // for development within VMs
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Headers": "*",
},
port: 3230,
host: "0.0.0.0",
hot: true,
static: {
directory: path.join(__dirname, "../", "test", "e2e-cypress", "static"),
publicPath: "/",
},
client: {
logging: "info",
progress: true,
},
devMiddleware: {},
},
},
)
// mix in the style config's plugins and loader rules
devE2eConfig.plugins = [...devE2eConfig.plugins, ...styleConfig.plugins]
devE2eConfig.module.rules = [
...devE2eConfig.module.rules,
...styleConfig.module.rules,
]
export default devE2eConfig

View File

@@ -3,10 +3,16 @@
*/ */
import path from "path" import path from "path"
import { HotModuleReplacementPlugin } from "webpack" import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin"
import HtmlWebpackPlugin from "html-webpack-plugin"
import { HtmlWebpackSkipAssetsPlugin } from "html-webpack-skip-assets-plugin"
import configBuilder from "./_config-builder" import configBuilder from "./_config-builder"
import styleConfig from "./stylesheets.babel" import styleConfig from "./stylesheets.babel"
const projectBasePath = path.join(__dirname, "../")
const isDevelopment = process.env.NODE_ENV !== "production"
const devConfig = configBuilder( const devConfig = configBuilder(
{ {
minimize: false, minimize: false,
@@ -24,6 +30,7 @@ const devConfig = configBuilder(
"./src/standalone/index.js", "./src/standalone/index.js",
], ],
"swagger-ui": "./src/style/main.scss", "swagger-ui": "./src/style/main.scss",
vendors: ["react-refresh/runtime"],
}, },
performance: { performance: {
@@ -31,25 +38,75 @@ const devConfig = configBuilder(
}, },
output: { output: {
library: "[name]",
filename: "[name].js", filename: "[name].js",
chunkFilename: "[id].js", chunkFilename: "[id].js",
library: {
name: "[name]",
export: "default",
},
publicPath: "/",
}, },
devServer: { devServer: {
port: 3200, allowedHosts: "all", // for development within VMs
publicPath: "/", headers: {
disableHostCheck: true, // for development within VMs "Access-Control-Allow-Origin": "*",
stats: { "Access-Control-Allow-Methods": "*",
colors: true, "Access-Control-Allow-Headers": "*",
}, },
hot: true, port: 3200,
contentBase: path.join(__dirname, "../", "dev-helpers"),
host: "0.0.0.0", host: "0.0.0.0",
hot: true,
static: {
directory: path.resolve(projectBasePath, "dev-helpers"),
publicPath: "/",
},
client: {
logging: "info",
progress: true,
},
}, },
plugins: [new HotModuleReplacementPlugin()], module: {
} rules: [
{
test: /\.jsx?$/,
include: [
path.join(projectBasePath, "src"),
path.join(projectBasePath, "node_modules", "object-assign-deep"),
],
loader: "babel-loader",
options: {
retainLines: true,
cacheDirectory: true,
plugins: [isDevelopment && require.resolve("react-refresh/babel")].filter(Boolean),
},
},
{
test: /\.(txt|yaml)$/,
type: "asset/source",
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: "asset/inline",
},
],
},
plugins: [
isDevelopment && new ReactRefreshWebpackPlugin({ library: "[name]" }),
new HtmlWebpackPlugin({
template: path.join(projectBasePath, "dev-helpers", "index.html"),
}),
new HtmlWebpackSkipAssetsPlugin({
skipAssets: [/swagger-ui\.js/],
}),
].filter(Boolean),
optimization: {
runtimeChunk: "single", // for multiple entry points using ReactRefreshWebpackPlugin
},
},
) )
// mix in the style config's plugins and loader rules // mix in the style config's plugins and loader rules

View File

@@ -29,9 +29,10 @@ const result = configBuilder(
], ],
}, },
output: { output: {
globalObject: "this", library: {
library: "SwaggerUIBundle", type: "commonjs2",
libraryTarget: "commonjs2", export: "default",
},
}, },
plugins: [ plugins: [
new DuplicatesPlugin({ new DuplicatesPlugin({

View File

@@ -30,8 +30,10 @@ const result = configBuilder(
}, },
output: { output: {
globalObject: "this", globalObject: "this",
library: "SwaggerUIBundle", library: {
libraryTarget: "commonjs2", type: "commonjs2",
export: "default",
},
}, },
plugins: [ plugins: [
new DuplicatesPlugin({ new DuplicatesPlugin({

View File

@@ -17,7 +17,10 @@ const result = configBuilder(
output: { output: {
globalObject: "this", globalObject: "this",
library: "SwaggerUIStandalonePreset", library: {
name: "SwaggerUIStandalonePreset",
export: "default",
},
}, },
} }
) )

View File

@@ -7,7 +7,6 @@
import path from "path" import path from "path"
import MiniCssExtractPlugin from "mini-css-extract-plugin" import MiniCssExtractPlugin from "mini-css-extract-plugin"
import IgnoreAssetsPlugin from "ignore-assets-webpack-plugin"
export default { export default {
mode: "production", mode: "production",
@@ -60,11 +59,6 @@ export default {
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: "[name].css", filename: "[name].css",
}), }),
new IgnoreAssetsPlugin({
// This is a hack to avoid a Webpack/MiniCssExtractPlugin bug, for more
// info see https://github.com/webpack-contrib/mini-css-extract-plugin/issues/151
ignore: ["swagger-ui.js", "swagger-ui.js.map"],
}),
], ],
devtool: "source-map", devtool: "source-map",