diff --git a/hot/README.md b/hot/README.md index dbb71322..75e2e2f6 100644 --- a/hot/README.md +++ b/hot/README.md @@ -26,7 +26,16 @@ const webpackConfig = { }}, ]}, { test: /\.styl$/, use: [ - { loader: `panel/hot/style-loader`}, + { + loader: `panel/hot/style-loader`, + options: { + // enables or disables the loader + hot: true, + // transforms a path derived element name into something else + // allows for more flexible naming convention + elementNameTransform: (name, path) => `mp-${name}`, + }, + }, { loader: `css-loader`}, { loader: `stylus-loader`}, ]}, diff --git a/hot/controller-loader.js b/hot/controller-loader.js index 68156b8f..8adae11d 100644 --- a/hot/controller-loader.js +++ b/hot/controller-loader.js @@ -5,13 +5,14 @@ const helpers = require(`./loader-helpers`); // Used in non-HMR mode, do nothing module.exports = source => source; -module.exports.pitch = function(remainingReq) { - if (!helpers.isDevServerHot(this.options)) { - return; - } +module.exports.pitch = function(request) { + const options = loaderUtils.getOptions(this) || {}; + const moduleId = loaderUtils.stringifyRequest(this, `!!${request}`); + const elemName = helpers.getElemName(this.resourcePath, options); - const moduleId = loaderUtils.stringifyRequest(this, `!!${remainingReq}`); - const elemName = helpers.getElemName(this.resourcePath); + if (!options.hot) { + return `module.exports = require(${moduleId});`; + } return ` module.hot.accept(${moduleId}, () => { @@ -24,5 +25,5 @@ module.exports.pitch = function(remainingReq) { }); }); const oldExports = module.exports = require(${moduleId}); - `.trim().replace(/^ {4}/gm, ``); + `; }; diff --git a/hot/loader-helpers.js b/hot/loader-helpers.js index bd959ae0..ceda9e41 100644 --- a/hot/loader-helpers.js +++ b/hot/loader-helpers.js @@ -1,5 +1,16 @@ /* eslint-env commonjs */ +const loaderUtils = require(`loader-utils`); const path = require(`path`); +const validateOptions = require(`schema-utils`); + +const OPTIONS_SCHEMA = { + type: `object`, + properties: { + hot: { + type: `boolean`, + }, + }, +}; // Retrieve elemName for hot injection from path convention // @@ -12,7 +23,7 @@ const path = require(`path`); // // this means multiple element definitions in a single file won't work -module.exports.getElemName = function(resourcePath) { +module.exports.getElemName = function(resourcePath, options) { const pathInfo = path.parse(resourcePath); let elemName = pathInfo.name; if (/^(index|template|styles?|controller)$/.test(pathInfo.name)) { @@ -20,9 +31,18 @@ module.exports.getElemName = function(resourcePath) { elemName = pathParts[pathParts.length - 2]; } + const transform = options.elementNameTransform; + if (typeof transform === `function`) { + elemName = transform(elemName, resourcePath); + } + return elemName; }; -module.exports.isDevServerHot = function(webpackOpts) { - return webpackOpts.devServer && webpackOpts.devServer.hot; +module.exports.getOptions = function(context) { + const options = loaderUtils.getOptions(context) || {}; + + validateOptions(OPTIONS_SCHEMA, options, `Panel HMR`); + + return options; }; diff --git a/hot/style-loader.js b/hot/style-loader.js index 333d93e9..ef7804e5 100644 --- a/hot/style-loader.js +++ b/hot/style-loader.js @@ -5,43 +5,26 @@ const helpers = require(`./loader-helpers`); // Used in non-HMR mode, do nothing module.exports = source => source; -module.exports.pitch = function(remainingReq) { - if (!helpers.isDevServerHot(this.options)) { - return; - } - - const moduleId = loaderUtils.stringifyRequest(this, `!!${remainingReq}`); - const options = loaderUtils.getOptions(this); - const resourcePath = this.resourcePath; - const elemName = helpers.getElemName(resourcePath); +module.exports.pitch = function(request) { + const options = helpers.getOptions(this); + const moduleId = loaderUtils.stringifyRequest(this, `!!${request}`); + const elemName = helpers.getElemName(this.resourcePath, options); - let updateSnippet = ``; - if (typeof options.cssHref === `string`) { - updateSnippet = ` - const updateCssHref = require('panel/hot/update-css-href'); - updateCssHref('${options.cssHref}'); - `; - } else { - updateSnippet = ` - const updateStyle = require('panel/hot/update-style'); - updateStyle(newStyle.toString(), ${JSON.stringify(resourcePath)}); - `; + if (!options.hot) { + return `module.exports = require(${moduleId});`; } return ` - module.hot.accept(${moduleId}, () => { - const newStyle = module.exports = require(${moduleId}); + module.hot.accept(${moduleId}, function() { + const newStyle = require(${moduleId}); const updatePanelElems = require('panel/hot/update-panel-elems'); - const updateCount = updatePanelElems('${elemName}', elem => { + updatePanelElems('${elemName}', elem => { if (elem.getConfig('useShadowDom')) { elem.el.querySelector('style').textContent = newStyle.toString(); return true; } }); - if (!updateCount) { - ${updateSnippet.trim()} - } }); module.exports = require(${moduleId}); - `.trim().replace(/^ {4}/gm, ``); + `; }; diff --git a/hot/template-loader.js b/hot/template-loader.js index e57bf5f7..a046c1ff 100644 --- a/hot/template-loader.js +++ b/hot/template-loader.js @@ -5,13 +5,14 @@ const helpers = require(`./loader-helpers`); // Used in non-HMR mode, do nothing module.exports = source => source; -module.exports.pitch = function(remainingReq) { - if (!helpers.isDevServerHot(this.options)) { - return; - } +module.exports.pitch = function(request) { + const options = helpers.getOptions(this); + const moduleId = loaderUtils.stringifyRequest(this, `!!${request}`); + const elemName = helpers.getElemName(this.resourcePath, options); - const moduleId = loaderUtils.stringifyRequest(this, `!!` + remainingReq); - const elemName = helpers.getElemName(this.resourcePath); + if (!options.hot) { + return `module.exports = require(${moduleId});`; + } return ` let template = require(${moduleId}); @@ -21,5 +22,5 @@ module.exports.pitch = function(remainingReq) { updatePanelElems('${elemName}', elem => true); }); module.exports = function() {return template.apply(this, arguments)}; - `.trim().replace(/^ {4}/gm, ``); + `; }; diff --git a/hot/update-css-href.js b/hot/update-css-href.js deleted file mode 100644 index d58e0a40..00000000 --- a/hot/update-css-href.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-env commonjs */ -module.exports = function updateCssHref(cssLinkHref) { - const cssLink = document.querySelector(`link[href][rel=stylesheet][href*='${cssLinkHref}']`); - if (cssLink) { - // Add hash param to css url to trigger style refresh - // We use #t=.. so if css hasn't changed then webpack-dev-server can reply with a 304 Not modified - // Which does a no-op for style calculation and paint - const cssUrl = cssLink.getAttribute(`href`).split(`#`)[0]; - cssLink.setAttribute(`href`, `${cssUrl}#t=${new Date().getTime()}`); - console.info(`[HMR Panel] Refreshed ${cssUrl}`); - } else { - console.warn(`[HMR Panel] Could not find stylesheet matching '${cssLinkHref}' in document`); - } -}; diff --git a/hot/update-panel-elems.js b/hot/update-panel-elems.js index c71ea521..53363a75 100644 --- a/hot/update-panel-elems.js +++ b/hot/update-panel-elems.js @@ -25,8 +25,8 @@ module.exports = function updatePanelElems(elemName, updateFn) { for (const elem of elems) { if (updateFn.call(null, elem)) { - const update = elem._update || elem.update; - numUpdated += update.apply(elem) ? 1 : 0; + numUpdated++; + (elem._update || elem.update).apply(elem); } } diff --git a/hot/update-style.js b/hot/update-style.js deleted file mode 100644 index acea80af..00000000 --- a/hot/update-style.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-env commonjs */ -module.exports = function updateStyle(newCss, styleId) { - let elem = document.getElementById(styleId); - if (elem) { - elem.textContent = newCss; - console.info(`[HMR Panel] Updated ${styleId}`); - } else { - elem = document.createElement(`style`); - elem.setAttribute(`type`, `text/css`); - elem.id = styleId; - elem.textContent = newCss; - document.head.appendChild(elem); - } -}; diff --git a/package-lock.json b/package-lock.json index b3708c96..cd76d39c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -237,11 +237,15 @@ "json-schema-traverse": "^0.3.0" } }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + }, "ajv-keywords": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", - "dev": true + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" }, "align-text": { "version": "0.1.4", @@ -3071,8 +3075,7 @@ "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", @@ -6180,8 +6183,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "q": { "version": "1.5.1", @@ -6993,6 +6995,39 @@ } } }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "dependencies": { + "ajv": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } + } + }, "selenium-standalone": { "version": "6.15.3", "resolved": "https://registry.npmjs.org/selenium-standalone/-/selenium-standalone-6.15.3.tgz", @@ -8566,7 +8601,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" } diff --git a/package.json b/package.json index 4479d9b2..f722c2e5 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "loader-utils": "1.1.0", "lodash.pick": "4.4.0", "raf": "3.2.0", + "schema-utils": "1.0.0", "snabbdom": "0.6.2", "snabbdom-delayed-class": "0.1.1", "webcomponent": "1.2.1"