-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding panel hot loaders to reload jade, styl, controller without pag…
…e refresh (#26) * Add css-reload-client * Add eslint to devDependencies package * Add jade loader * comment * module.id as string is not a requirement anymore * Adding jade style controller hot loaders * updateStyle does a passthrough for extract text plugin * Re-export after hot accept * Re-export on hot accept * Adding helpers and cleaning up code for consistency * Fix typo * Fix helpers * We need to go deeper * Rename to template loader * Webpack has really bad side effects sharing noopLoader * Add [HMR Panel] prefix * Make a sub-package called panel-hot * Use update-panel-helpers to consolidate panel update logic * Auto-detect isDevServerHot * use proxy fn so `import template from './index.jade` works as is * 0.1.0-rc.2 Much smarter hot reloading capabilities * Add loader-utils to package.json no panel-hot module * add package-lock.json * rc version for hot loaders * PR feedback #1 * PR Feedback #2 * Use findPanelElemsByName * findPanelElemsByTagName * expand getElemName to handle 'template' and 'styles?' * Some documentation for hot reloaders * customElements.define * 0.14.0 * 13 I mean * package-lock
- Loading branch information
Showing
9 changed files
with
2,942 additions
and
1,344 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# Using panel hot loaders with webpack | ||
|
||
## Sample webpack config | ||
|
||
```js | ||
const {HOT} = process.env; | ||
const isDevServer = process.argv.some(s => s.includes(`webpack-dev-server`)); | ||
|
||
const webpackConfig = { | ||
entry { | ||
app: '.src/index.js' | ||
}, | ||
output: { | ||
filename: '[name].js', | ||
}, | ||
loaders: { | ||
rules :[ | ||
{ test: /\.jade$/, use: [ | ||
{ loader: `panel/hot/template-loader`}, | ||
{ loader: `babel-loader`, options: { | ||
presets: [`es2015`], | ||
}}, | ||
{ loader: `virtual-jade-loader`, options: { | ||
vdom: `snabbdom`, | ||
runtime: `var h = require("panel").h;`, | ||
}}, | ||
]}, | ||
{ test: /\.styl$/, use: [ | ||
{ loader: `panel/hot/style-loader`}, | ||
{ loader: `css-loader`}, | ||
{ loader: `stylus-loader`}, | ||
]}, | ||
{ test: /\.js$/, use: [ | ||
{ loader: `babel-loader`, options: { | ||
presets: [`es2015`], | ||
}}, | ||
]}, | ||
] | ||
}, | ||
plugins: [ | ||
new webpack.NoEmitOnErrorsPlugin(), | ||
] | ||
resolveLoader: { | ||
alias: { | ||
'panel-controller': `panel/hot/controller-loader`, | ||
}, | ||
}, | ||
}; | ||
|
||
if (isDevServer && HOT) { | ||
webpackConfig.devServer.hot = true; | ||
webpackConfig.plugins.push(new webpack.NamedModulesPlugin()); | ||
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); | ||
} | ||
``` | ||
|
||
Panel hot loaders only activate if `webpackConfig.devServer.hot === true` otherwise they do a no-op and behave like they are non-existent | ||
|
||
|
||
## Inside a panel component | ||
|
||
```js | ||
import {Component} from 'panel'; | ||
// Ensure controller uses a `export default class SampleController` | ||
import SampleController from 'panel-controller!./controller'; | ||
import template from './index.jade'; | ||
import './index.styl'; | ||
|
||
customElements.define('sample-component', class SampleComponent extends Component { | ||
get config() { | ||
return { | ||
template, | ||
}; | ||
} | ||
|
||
constructor() { | ||
super(...arguments); | ||
this.controller = new SampleController({store: this}); | ||
} | ||
} | ||
``` | ||
Or using a ControlledComponent | ||
```js | ||
import {ControlledComponent} from 'panel'; | ||
import SampleController from 'panel-controller!./controller'; | ||
import template from './index.jade'; | ||
import './index.styl'; | ||
|
||
customElements.define('sample-component', class SampleComponent extends ControlledComponent { | ||
get config() { | ||
return { | ||
template, | ||
// new instance of class is used after hot mode replacement when element is created | ||
controller: new SampleController(), | ||
}; | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* eslint-env commonjs */ | ||
const loaderUtils = require(`loader-utils`); | ||
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 elemName = helpers.getElemName(this.resourcePath); | ||
|
||
return ` | ||
module.hot.accept(${moduleId}, () => { | ||
const newExports = module.exports = require(${moduleId}); | ||
Object.assign(oldExports, newExports); | ||
const updatePanelElems = require('panel/hot/update-panel-elems'); | ||
updatePanelElems('${elemName}', (elem) => { | ||
Object.setPrototypeOf(elem.controller, newExports.default.prototype); | ||
return true; | ||
}); | ||
}); | ||
const oldExports = module.exports = require(${moduleId}); | ||
`.trim().replace(/^ {4}/gm, ``); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* eslint-env commonjs */ | ||
const path = require(`path`); | ||
|
||
// Retrieve elemName for hot injection from path convention | ||
// | ||
// elem name patterns look like this | ||
// ./src/.../${elemName}/index.<ext> | ||
// ./src/.../${elemName}/template.<ext> | ||
// ./src/.../${elemName}/style.<ext> or styles.<ext> | ||
// ./src/.../${elemName}/controller.<ext> | ||
// OR ./src/.../${elemName}.<ext> | ||
// | ||
// this means multiple element definitions in a single file won't work | ||
|
||
module.exports.getElemName = function(resourcePath) { | ||
const pathInfo = path.parse(resourcePath); | ||
let elemName = pathInfo.name; | ||
if (/^(index|template|styles?|controller)$/.test(pathInfo.name)) { | ||
const pathParts = resourcePath.split(`/`); | ||
elemName = pathParts[pathParts.length - 2]; | ||
} | ||
|
||
return elemName; | ||
}; | ||
|
||
module.exports.isDevServerHot = function(webpackOpts) { | ||
return webpackOpts.devServer && webpackOpts.devServer.hot; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* eslint-env commonjs */ | ||
const loaderUtils = require(`loader-utils`); | ||
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 resourcePath = this.resourcePath; | ||
const elemName = helpers.getElemName(resourcePath); | ||
|
||
return ` | ||
module.hot.accept(${moduleId}, () => { | ||
const newStyle = module.exports = require(${moduleId}); | ||
const updatePanelElems = require('panel/hot/update-panel-elems'); | ||
const updateCount = updatePanelElems('${elemName}', elem => { | ||
if (elem.getConfig('useShadowDom')) { | ||
elem.el.querySelector('style').textContent = newStyle.toString(); | ||
return true; | ||
} | ||
}); | ||
if (!updateCount) { | ||
const updateStyle = require('panel/hot/update-style'); | ||
updateStyle(newStyle.toString(), ${JSON.stringify(resourcePath)}); | ||
} | ||
}); | ||
module.exports = require(${moduleId}); | ||
`.trim().replace(/^ {4}/gm, ``); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* eslint-env commonjs */ | ||
const loaderUtils = require(`loader-utils`); | ||
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 elemName = helpers.getElemName(this.resourcePath); | ||
|
||
return ` | ||
let template = require(${moduleId}); | ||
module.hot.accept(${moduleId}, () => { | ||
template = require(${moduleId}); | ||
const updatePanelElems = require('panel/hot/update-panel-elems'); | ||
updatePanelElems('${elemName}', elem => true); | ||
}); | ||
module.exports = function() {return template.apply(this, arguments)}; | ||
`.trim().replace(/^ {4}/gm, ``); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* eslint-env commonjs */ | ||
|
||
// 'That's not enough, we need to go deeper' | ||
// NOTE: document.querySelectorAll(`body /deep/ ${elemName}`) is deprecated, so we use recursion | ||
function findPanelElemsByTagName(rootElem, elemName) { | ||
const results = []; | ||
|
||
for (const elem of rootElem.querySelectorAll(`*`)) { | ||
if (elem.panelID && elem.tagName.toLowerCase() === elemName) { | ||
results.push(elem); | ||
} | ||
if (elem.shadowRoot) { | ||
for (const shadowElem of findPanelElemsByTagName(elem.shadowRoot, elemName)) { | ||
results.push(shadowElem); | ||
} | ||
} | ||
} | ||
|
||
return results; | ||
} | ||
|
||
module.exports = function updatePanelElems(elemName, updateFn) { | ||
let numUpdated = 0; | ||
const elems = findPanelElemsByTagName(document.body, elemName); | ||
|
||
for (const elem of elems) { | ||
if (updateFn.call(null, elem)) { | ||
const update = elem._update || elem.update; | ||
numUpdated += update.apply(elem) ? 1 : 0; | ||
} | ||
} | ||
|
||
if (numUpdated > 0) { | ||
console.info(`[HMR Panel] Updated ${elems.length} ${elemName} elems`); | ||
} else if (!elems.length) { | ||
console.warn(`[HMR Panel] No ${elemName} elems found`); | ||
console.warn(`[HMR Panel] Exepect file path to be '.../<elemName>/index.js' or '.../<elemName>.js'`); | ||
} | ||
|
||
return numUpdated; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* 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); | ||
} | ||
}; |
Oops, something went wrong.