From 4e5d8eae654ef382697722c6406dbc96207594aa Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Wed, 22 Dec 2021 19:01:43 +0300 Subject: [PATCH] fix: droped `url` package (#4132) --- .eslintrc.js | 15 +++ client-src/clients/SockJSClient.js | 10 +- client-src/index.js | 45 +++++++- client-src/overlay.js | 36 +++++- client-src/socket.js | 25 ++++- client-src/utils/createSocketURL.js | 80 ++++++++++++- client-src/utils/getCurrentScriptSource.js | 3 + client-src/utils/parseURL.js | 39 ++++--- client-src/utils/reloadApp.js | 16 ++- client-src/utils/sendMessage.js | 2 +- client-src/webpack.config.js | 4 + package-lock.json | 106 ++++++------------ package.json | 2 +- setupTest.js | 2 +- .../multi-compiler.test.js.snap.webpack5 | 12 +- test/e2e/api.test.js | 4 +- 16 files changed, 276 insertions(+), 125 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 654f4758f1..24127cd2b2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,6 +21,21 @@ module.exports = { "prefer-rest-params": "off", strict: ["error", "safe"], "global-require": "off", + "spaced-comment": [ + "error", + "always", + { + line: { + exceptions: ["-", "+"], + markers: ["=", "!", "/"], + }, + block: { + exceptions: ["-", "+"], + markers: ["=", "!"], + balanced: false, + }, + }, + ], }, overrides: [ { diff --git a/client-src/clients/SockJSClient.js b/client-src/clients/SockJSClient.js index 0f90bb6da1..2958d44eef 100644 --- a/client-src/clients/SockJSClient.js +++ b/client-src/clients/SockJSClient.js @@ -38,8 +38,12 @@ export default class SockJSClient { * @param {(...args: any[]) => void} f */ onMessage(f) { - this.sock.onmessage = (e) => { - f(e.data); - }; + this.sock.onmessage = + /** + * @param {Error & { data: string }} e + */ + (e) => { + f(e.data); + }; } } diff --git a/client-src/index.js b/client-src/index.js index d8f832c987..dc85fdcff0 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -1,5 +1,5 @@ /* global __resourceQuery, __webpack_hash__ */ - +/// import webpackHotLog from "webpack/hot/log.js"; import stripAnsi from "./modules/strip-ansi/index.js"; import parseURL from "./utils/parseURL.js"; @@ -10,6 +10,26 @@ import sendMessage from "./utils/sendMessage.js"; import reloadApp from "./utils/reloadApp.js"; import createSocketURL from "./utils/createSocketURL.js"; +/** + * @typedef {Object} Options + * @property {boolean} hot + * @property {boolean} liveReload + * @property {boolean} progress + * @property {boolean | { warnings?: boolean, errors?: boolean }} overlay + * @property {string} [logging] + * @property {number} [reconnect] + */ + +/** + * @typedef {Object} Status + * @property {boolean} isUnloading + * @property {string} currentHash + * @property {string} [previousHash] + */ + +/** + * @type {Status} + */ const status = { isUnloading: false, // TODO Workaround for webpack v4, `__webpack_hash__` is not replaced without HotModuleReplacement @@ -17,6 +37,7 @@ const status = { currentHash: typeof __webpack_hash__ !== "undefined" ? __webpack_hash__ : "", }; +/** @type {Options} */ const options = { hot: false, liveReload: false, @@ -101,6 +122,9 @@ const onSocketMessage = { status.currentHash = hash; }, logging: setAllLogLevel, + /** + * @param {boolean} value + */ overlay(value) { if (typeof document === "undefined") { return; @@ -108,6 +132,9 @@ const onSocketMessage = { options.overlay = value; }, + /** + * @param {number} value + */ reconnect(value) { if (parsedResourceQuery.reconnect === "false") { return; @@ -115,9 +142,15 @@ const onSocketMessage = { options.reconnect = value; }, - progress(progress) { - options.progress = progress; + /** + * @param {boolean} value + */ + progress(value) { + options.progress = value; }, + /** + * @param {{ pluginName?: string, percent: number, msg: string }} data + */ "progress-update": function progressUpdate(data) { if (options.progress) { log.info( @@ -148,6 +181,9 @@ const onSocketMessage = { reloadApp(options, status); }, // TODO: remove in v5 in favor of 'static-changed' + /** + * @param {string} file + */ "content-changed": function contentChanged(file) { log.info( `${ @@ -157,6 +193,9 @@ const onSocketMessage = { self.location.reload(); }, + /** + * @param {string} file + */ "static-changed": function staticChanged(file) { log.info( `${ diff --git a/client-src/overlay.js b/client-src/overlay.js index 819597ed15..5c074702ff 100644 --- a/client-src/overlay.js +++ b/client-src/overlay.js @@ -17,8 +17,11 @@ const colors = { darkgrey: "6D7891", }; +/** @type {HTMLIFrameElement | null | undefined} */ let iframeContainerElement; +/** @type {HTMLDivElement | null | undefined} */ let containerElement; +/** @type {Array<(element: HTMLDivElement) => void>} */ let onLoadQueue = []; ansiHTML.setColors(colors); @@ -38,7 +41,11 @@ function createContainer() { iframeContainerElement.style.zIndex = 9999999999; iframeContainerElement.onload = () => { containerElement = - iframeContainerElement.contentDocument.createElement("div"); + /** @type {Document} */ + ( + /** @type {HTMLIFrameElement} */ + (iframeContainerElement).contentDocument + ).createElement("div"); containerElement.id = "webpack-dev-server-client-overlay-div"; containerElement.style.position = "fixed"; containerElement.style.boxSizing = "border-box"; @@ -71,6 +78,7 @@ function createContainer() { closeButtonElement.style.color = "white"; closeButtonElement.style.cursor = "pointer"; closeButtonElement.style.cssFloat = "right"; + // @ts-ignore closeButtonElement.style.styleFloat = "right"; closeButtonElement.addEventListener("click", () => { hide(); @@ -81,19 +89,27 @@ function createContainer() { containerElement.appendChild(document.createElement("br")); containerElement.appendChild(document.createElement("br")); - iframeContainerElement.contentDocument.body.appendChild(containerElement); + /** @type {Document} */ + ( + /** @type {HTMLIFrameElement} */ + (iframeContainerElement).contentDocument + ).body.appendChild(containerElement); onLoadQueue.forEach((onLoad) => { - onLoad(containerElement); + onLoad(/** @type {HTMLDivElement} */ (containerElement)); }); onLoadQueue = []; - iframeContainerElement.onload = null; + /** @type {HTMLIFrameElement} */ + (iframeContainerElement).onload = null; }; document.body.appendChild(iframeContainerElement); } +/** + * @param {(element: HTMLDivElement) => void} callback + */ function ensureOverlayExists(callback) { if (containerElement) { // Everything is ready, call the callback right away. @@ -124,6 +140,11 @@ function hide() { containerElement = null; } +/** + * @param {string} type + * @param {string | { file?: string, moduleName?: string, loc?: string, message?: string }} item + * @returns {{ header: string, body: string }} + */ function formatProblem(type, item) { let header = type === "warning" ? "WARNING" : "ERROR"; let body = ""; @@ -154,6 +175,10 @@ function formatProblem(type, item) { } // Compilation with errors (e.g. syntax error or missing modules). +/** + * @param {string} type + * @param {Array} messages + */ function show(type, messages) { ensureOverlayExists(() => { messages.forEach((message) => { @@ -177,7 +202,8 @@ function show(type, messages) { entryElement.appendChild(document.createElement("br")); entryElement.appendChild(document.createElement("br")); - containerElement.appendChild(entryElement); + /** @type {HTMLDivElement} */ + (containerElement).appendChild(entryElement); }); }); } diff --git a/client-src/socket.js b/client-src/socket.js index dc76cd295b..4b913e4c69 100644 --- a/client-src/socket.js +++ b/client-src/socket.js @@ -18,12 +18,20 @@ let retries = 0; let maxRetries = 10; let client = null; +/** + * @param {string} url + * @param {{ [handler: string]: (data?: any, params?: any) => any }} handlers + * @param {number} [reconnect] + */ const socket = function initSocket(url, handlers, reconnect) { client = new Client(url); client.onOpen(() => { retries = 0; - maxRetries = reconnect; + + if (typeof reconnect !== "undefined") { + maxRetries = reconnect; + } }); client.onClose(() => { @@ -51,13 +59,18 @@ const socket = function initSocket(url, handlers, reconnect) { } }); - client.onMessage((data) => { - const message = JSON.parse(data); + client.onMessage( + /** + * @param {any} data + */ + (data) => { + const message = JSON.parse(data); - if (handlers[message.type]) { - handlers[message.type](message.data, message.params); + if (handlers[message.type]) { + handlers[message.type](message.data, message.params); + } } - }); + ); }; export default socket; diff --git a/client-src/utils/createSocketURL.js b/client-src/utils/createSocketURL.js index 168c3b6c9e..5d8a28ddbd 100644 --- a/client-src/utils/createSocketURL.js +++ b/client-src/utils/createSocketURL.js @@ -1,12 +1,82 @@ -import url from "url"; +/** + * @param {{ protocol?: string, auth?: string, hostname?: string, port?: string, pathname?: string, search?: string, hash?: string, slashes?: boolean }} objURL + * @returns {string} + */ +function format(objURL) { + let protocol = objURL.protocol || ""; + + if (protocol && protocol.substr(-1) !== ":") { + protocol += ":"; + } + + let auth = objURL.auth || ""; + + if (auth) { + auth = encodeURIComponent(auth); + auth = auth.replace(/%3A/i, ":"); + auth += "@"; + } + + let host = ""; + + if (objURL.hostname) { + host = + auth + + (objURL.hostname.indexOf(":") === -1 + ? objURL.hostname + : `[${objURL.hostname}]`); + + if (objURL.port) { + host += `:${objURL.port}`; + } + } + + let pathname = objURL.pathname || ""; + + if (objURL.slashes) { + host = `//${host || ""}`; + + if (pathname && pathname.charAt(0) !== "/") { + pathname = `/${pathname}`; + } + } else if (!host) { + host = ""; + } + + let search = objURL.search || ""; + + if (search && search.charAt(0) !== "?") { + search = `?${search}`; + } + + let hash = objURL.hash || ""; + + if (hash && hash.charAt(0) !== "#") { + hash = `#${hash}`; + } + + pathname = pathname.replace( + /[?#]/g, + /** + * @param {string} match + * @returns {string} + */ + (match) => encodeURIComponent(match) + ); + search = search.replace("#", "%23"); + + return `${protocol}${host}${pathname}${search}${hash}`; +} -// We handle legacy API that is Node.js specific, and a newer API that implements the same WHATWG URL Standard used by web browsers -// Please look at https://nodejs.org/api/url.html#url_url_strings_and_url_objects +/** + * @param {URL & { fromCurrentScript?: boolean }} parsedURL + * @returns {string} + */ function createSocketURL(parsedURL) { let { hostname } = parsedURL; // Node.js module parses it as `::` - // `new URL(urlString, [baseURLstring])` parses it as '[::]' + // `new URL(urlString, [baseURLString])` parses it as '[::]' const isInAddrAny = hostname === "0.0.0.0" || hostname === "::" || hostname === "[::]"; @@ -80,7 +150,7 @@ function createSocketURL(parsedURL) { socketURLPathname = parsedURL.pathname; } - return url.format({ + return format({ protocol: socketURLProtocol, auth: socketURLAuth, hostname: socketURLHostname, diff --git a/client-src/utils/getCurrentScriptSource.js b/client-src/utils/getCurrentScriptSource.js index 01b05cb66d..2c16610389 100644 --- a/client-src/utils/getCurrentScriptSource.js +++ b/client-src/utils/getCurrentScriptSource.js @@ -1,3 +1,6 @@ +/** + * @returns {string} + */ function getCurrentScriptSource() { // `document.currentScript` is the most accurate way to find the current script, // but is not supported in all browsers. diff --git a/client-src/utils/parseURL.js b/client-src/utils/parseURL.js index 9be4077d25..3a4f2f3fda 100644 --- a/client-src/utils/parseURL.js +++ b/client-src/utils/parseURL.js @@ -1,7 +1,11 @@ -import url from "url"; import getCurrentScriptSource from "./getCurrentScriptSource.js"; +/** + * @param {string} resourceQuery + * @returns {{ [key: string]: string | boolean }} + */ function parseURL(resourceQuery) { + /** @type {{ [key: string]: string }} */ let options = {}; if (typeof resourceQuery === "string" && resourceQuery !== "") { @@ -16,25 +20,20 @@ function parseURL(resourceQuery) { // Else, get the url from the