forked from renchap/webpacker-react
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request renchap#10 from renchap/hmr-build
HMR + Build + Style
- Loading branch information
Showing
10 changed files
with
370 additions
and
192 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
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 |
---|---|---|
|
@@ -11,8 +11,8 @@ This gem is a work in progress. Final feature list: | |
- [x] uses the new Webpacker way of integrating Javascript with Rails (using packs) | ||
- [x] render React components from views using a `react_component` helper | ||
- [x] render React components from controllers using `render react_component: 'name'` | ||
- [x] support for [hot reloading](https://github.com/gaearon/react-hot-loader) | ||
- [ ] render components server-side | ||
- [ ] support for [hot reloading](https://github.com/gaearon/react-hot-loader) | ||
- [ ] use a Rails generator to create new components | ||
|
||
## Installation | ||
|
@@ -47,10 +47,10 @@ The first step is to register your root components (those you want to load from | |
In your pack file (`app/javascript/packs/*.js`), import your components as well as `webpacker-react` and register them. Considering you have a component in `app/javascript/components/hello.js`: | ||
|
||
```javascript | ||
import Hello from 'components/hello'; | ||
import WebpackerReact from 'webpacker-react'; | ||
import Hello from 'components/hello' | ||
import WebpackerReact from 'webpacker-react' | ||
|
||
WebpackerReact.register(Hello); | ||
WebpackerReact.register(Hello) | ||
``` | ||
|
||
Now you can render React components from your views or your controllers. | ||
|
@@ -77,6 +77,95 @@ You can pass any of the usual arguments to render in this call: `layout`, `statu | |
|
||
*Note: you need to have [Webpack process your code](https://github.com/rails/webpacker#binstubs) before it is available to the browser, either by manually running `./bin/webpack` or having the `./bin/webpack-watcher` process running.* | ||
|
||
### Hot Module Replacement | ||
|
||
[HMR](https://webpack.js.org/guides/hmr-react/) allows to reload / add / remove modules live in the browser without | ||
reloading the page. This allows any change you make to your React components to be applied as soon as you save, | ||
preserving their current state. | ||
|
||
First, install `react-hot-loader`: | ||
|
||
``` | ||
./bin/yarn add [email protected] | ||
``` | ||
|
||
You then need to update your Webpack config. | ||
|
||
We provide a convenience function to add the necessary changes to your config if it's not | ||
significantly different than the standard Webpacker config: | ||
|
||
```js | ||
//development.js | ||
... | ||
|
||
var configureHotModuleReplacement = require('webpacker-react/configure-hot-module-replacement') | ||
|
||
var sharedConfig = require('./shared.js') | ||
sharedConfig = configureHotModuleReplacement(sharedConfig) | ||
|
||
module.exports = merge(sharedConfig, ...) | ||
``` | ||
|
||
If you need to change your configuration manually: | ||
|
||
1. set the public URL used to load `webpack-dev-server` assets | ||
```js | ||
{ | ||
output: { | ||
publicPath: 'http://localhost:8080' | ||
} | ||
} | ||
``` | ||
|
||
2. add `react-hot-loader/babel` to your `babel-loader` rules: | ||
```javascript | ||
{ | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.jsx?(.erb)?$/, | ||
exclude: /node_modules/, | ||
loader: 'babel-loader', | ||
options: { | ||
presets: [ | ||
'react', | ||
[ 'latest', { 'es2015': { 'modules': false } } ] | ||
], | ||
plugins: ['react-hot-loader/babel'] | ||
} | ||
} | ||
} | ||
``` | ||
|
||
3. prepend `react-hot-loader/patch` to your entries: | ||
```javascript | ||
{ | ||
entry: | ||
{ application: [ 'react-hot-loader/patch', '../app/javascript/packs/application.js' ], | ||
... | ||
} | ||
``` | ||
|
||
4. you now need to use `webpack-dev-server` (in place of `webpack` or `webpack-watcher`). Make sure the following line is in your development.rb: | ||
```ruby | ||
config.x.webpacker[:dev_server_host] = 'http://localhost:8080/' | ||
``` | ||
and start `webpack-dev-server` in hot replacement mode: | ||
``` | ||
./bin/webpack-dev-server --hot | ||
``` | ||
|
||
5. finally opt in to HMR from your pack files: | ||
```es6 | ||
import SomeRootReactComponent from 'components/some-root-react-component' | ||
import WebpackerReact from 'webpacker-react/hmr' | ||
WebpackerReact.register(SomeRootReactComponent) | ||
if (module.hot) | ||
module.hot.accept('components/some-root-react-component', () => | ||
WebpackerReact.renderOnHMR(SomeRootReactComponent) ) | ||
``` | ||
|
||
## Development | ||
|
||
To work on this gem locally, you first need to clone and setup [the example application](https://github.com/renchap/webpacker-react-example). | ||
|
@@ -90,7 +179,8 @@ gem 'webpacker-react', path: '~/code/webpacker-react/' | |
Finally, you need to tell Yarn to use your local copy of the NPM module in this application, using [`yarn link`](https://yarnpkg.com/en/docs/cli/link): | ||
|
||
``` | ||
$ cd ~/code/webpacker-react/javascript/webpacker_react-npm-module/ | ||
$ cd ~/code/webpacker-react/javascript/webpacker_react-npm-module/dist/ | ||
$ yarn # builds the code | ||
$ yarn link | ||
success Registered "webpacker-react". | ||
info You can now run `yarn link "webpacker-react"` in the projects where you want to use this module and it will be used instead. | ||
|
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
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,21 @@ | ||
{ | ||
"name": "webpacker-react", | ||
"version": "0.0.5", | ||
"description": "Javascript", | ||
"main": "index.js", | ||
"homepage": "https://github.com/renchap/webpacker-react", | ||
"repository": "renchap/webpacker-react", | ||
"author": { | ||
"name": "Renaud Chaput", | ||
"email": "[email protected]" | ||
}, | ||
"peerDependencies": { | ||
"react": ">= 0.14", | ||
"react-dom": ">= 0.14" | ||
}, | ||
"files": ["index.js", "configure-hot-module-replacement.js", "hmr.js"], | ||
"scripts": { | ||
"prepublish": "cd .. ; yarn run build" | ||
}, | ||
"license": "MIT" | ||
} |
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,4 @@ | ||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. | ||
# yarn lockfile v1 | ||
|
||
|
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 |
---|---|---|
@@ -1,26 +1,11 @@ | ||
{ | ||
"name": "webpacker-react", | ||
"version": "0.0.5", | ||
"description": "Javascript", | ||
"main": "dist/index.js", | ||
"homepage": "https://github.com/renchap/webpacker-react", | ||
"repository": "renchap/webpacker-react", | ||
"peerDependencies": { | ||
"react": ">= 0.14", | ||
"react-dom": ">= 0.14" | ||
}, | ||
"name": "webpacker-react-build", | ||
"private": true, | ||
"scripts": { | ||
"build": "babel src --presets=babel-preset-es2015 --out-dir dist", | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"prepublish": "npm run build" | ||
"build": "babel src --presets=babel-preset-es2015 --out-dir dist" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.18.0", | ||
"babel-preset-es2015": "^6.18.0" | ||
}, | ||
"author": { | ||
"name": "Renaud Chaput", | ||
"email": "[email protected]" | ||
}, | ||
"license": "MIT" | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
javascript/webpacker_react-npm-module/src/configure-hot-module-replacement.js
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,35 @@ | ||
import webpack from 'webpack' | ||
import merge from 'webpack-merge' | ||
|
||
function configureHotModuleReplacement(config) { | ||
let webpackDevServerAddr = process.env['WEBPACK_DEV_SERVER_ADDR'] || 'http://localhost:8080/' | ||
config = merge( | ||
config, | ||
{ | ||
output: { | ||
publicPath: webpackDevServerAddr // needed for HMR to know where to load the hot update chunks | ||
}, | ||
plugins: [ | ||
new webpack.NamedModulesPlugin() | ||
] | ||
} | ||
) | ||
|
||
config.module.rules = config.module.rules.map( rule => { | ||
if (rule.loader === 'babel-loader') { | ||
return merge(rule, {options: {plugins: ['react-hot-loader/babel']}}) | ||
} else { | ||
return rule | ||
} | ||
}) | ||
|
||
for (let key of Object.keys(config.entry)) { | ||
if (!(config.entry[key] instanceof Array)) { | ||
config.entry[key] = [config.entry[key]] | ||
} | ||
config.entry[key].unshift('react-hot-loader/patch') | ||
} | ||
return config | ||
} | ||
|
||
module.exports = configureHotModuleReplacement |
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,9 @@ | ||
import { AppContainer } from 'react-hot-loader' | ||
import WebpackerReact from 'webpacker-react' | ||
import React from 'react' | ||
|
||
WebpackerReact.registerWrapForHMR( (reactElement) => { | ||
return React.createElement(AppContainer, {}, reactElement) | ||
}) | ||
|
||
export default WebpackerReact |
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 |
---|---|---|
@@ -1,55 +1,86 @@ | ||
import React from 'react' | ||
import ReactDOM from 'react-dom' | ||
|
||
const CLASS_ATTRIBUTE_NAME = 'data-react-class' | ||
const PROPS_ATTRIBUTE_NAME = 'data-react-props' | ||
|
||
var WebpackerReact = { | ||
eventsRegistered: false, | ||
registeredComponents : {}, | ||
wrapForHMR: null, | ||
|
||
render: function(node, component) { | ||
var propsJson = node.getAttribute(PROPS_ATTRIBUTE_NAME) | ||
var props = propsJson && JSON.parse(propsJson) | ||
|
||
let reactElement = React.createElement(component, props) | ||
if (this.wrapForHMR) { | ||
reactElement = this.wrapForHMR(reactElement) | ||
} | ||
ReactDOM.render(reactElement, node) | ||
}, | ||
|
||
renderOnHMR: function(component) { | ||
var name = component.name | ||
this.registeredComponents[name] = component | ||
|
||
if (!this.wrapForHMR) { | ||
console.warn('webpack-react: renderOnHMR called but not elements not wrapped for HMR') | ||
} | ||
|
||
var toMount = document.querySelectorAll(`[${CLASS_ATTRIBUTE_NAME}=${name}]`) | ||
for (var i = 0; i < toMount.length; ++i) { | ||
var node = toMount[i] | ||
|
||
this.render(node, component) | ||
} | ||
}, | ||
|
||
registerWrapForHMR(wrapForHMR) { | ||
this.wrapForHMR = wrapForHMR | ||
}, | ||
|
||
register: function(component) { | ||
var name = component.name; | ||
var name = component.name | ||
|
||
if(this.registeredComponents[name]) { | ||
console.warn('webpacker-react: Cant register component, another one with this name is already registered: ' + name); | ||
return false; | ||
if (this.registeredComponents[name]) { | ||
console.warn('webpacker-react: Cant register component, another one with this name is already registered: ' + name) | ||
return false | ||
} | ||
|
||
this.registeredComponents[name] = component; | ||
return true; | ||
this.registeredComponents[name] = component | ||
return true | ||
}, | ||
|
||
addEventEventhandlers: function() { | ||
var registeredComponents = this.registeredComponents; | ||
var registeredComponents = this.registeredComponents | ||
|
||
if(this.eventsRegistered == true) { | ||
console.warn("webpacker-react: events have already been registered"); | ||
return false; | ||
if (this.eventsRegistered == true) { | ||
console.warn("webpacker-react: events have already been registered") | ||
return false | ||
} | ||
|
||
document.addEventListener("DOMContentLoaded", function() { | ||
const CLASS_ATTRIBUTE_NAME = 'data-react-class'; | ||
const PROPS_ATTRIBUTE_NAME = 'data-react-props'; | ||
|
||
document.addEventListener("DOMContentLoaded", () => { | ||
var toMount = document.querySelectorAll('[' + CLASS_ATTRIBUTE_NAME + ']') | ||
|
||
for (var i = 0; i < toMount.length; ++i) { | ||
var node = toMount[i]; | ||
var className = node.getAttribute(CLASS_ATTRIBUTE_NAME); | ||
var propsJson = node.getAttribute(PROPS_ATTRIBUTE_NAME); | ||
var props = propsJson && JSON.parse(propsJson); | ||
|
||
var constructor = registeredComponents[className]; | ||
if(constructor) { | ||
ReactDOM.render(React.createElement(constructor, props), node); | ||
var node = toMount[i] | ||
var className = node.getAttribute(CLASS_ATTRIBUTE_NAME) | ||
var component = registeredComponents[className] | ||
|
||
if (component) { | ||
this.render(node, component) | ||
} else { | ||
console.error('webpacker-react: cant render a component that has not been registered: ' + className) | ||
} | ||
} | ||
}); | ||
|
||
this.eventsRegistered = true; | ||
return true; | ||
this.eventsRegistered = true | ||
return true | ||
} | ||
}; | ||
|
||
WebpackerReact.addEventEventhandlers(); | ||
WebpackerReact.addEventEventhandlers() | ||
|
||
export default WebpackerReact; | ||
export default WebpackerReact |
Oops, something went wrong.