Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HMR to the dev server #641

Merged
merged 14 commits into from
Aug 15, 2017
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

- Move dev-server config options under defaults so it's transparently available in all environments

- Add new `HMR` option for hot-module-replacement


### Removed

Expand Down
25 changes: 10 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,12 @@ Similary you can also control and configure `webpack-dev-server` settings from `
# config/webpacker.yml
development:
dev_server:
host: 0.0.0.0
host: localhost
port: 8080
https: false
```

If you have `hmr` turned to true, then the `stylesheet_pack_tag` generates no output, as you will want to configure your styles to be inlined in your JavaScript for hot reloading. During production and testing, the `stylesheet_pack_tag` will create the appropriate HTML tags.

#### Resolved Paths

If you are adding webpacker to an existing app that has most of the assets inside
Expand Down Expand Up @@ -398,24 +399,18 @@ plugins:
Webpacker out-of-the-box provides CDN support using your Rails app `config.action_controller.asset_host` setting. If you already have [CDN](http://guides.rubyonrails.org/asset_pipeline.html#cdns) added in your rails app
you don't need to do anything extra for webpacker, it just works.

### HTTPS in development

If you're using the `webpack-dev-server` in development, you can serve views over HTTPS
by setting the `https` option for `webpack-dev-server` to `true` in `config/webpacker.yml`,
then start the dev server as usual with `./bin/webpack-dev-server`.

Please note that the `webpack-dev-server` will use a self-signed certificate,
so your web browser will display a warning upon accessing the page.


### Hot module replacement

Webpacker out-of-the-box doesn't ship with HMR just yet. You will need to
install additional plugins for Webpack if you want to add HMR support.
Webpacker out-of-the-box supports HMR with `webpack-dev-server` and
you can toggle it by setting `dev_server/hmr` inside webpacker.yml.

You can checkout these links on this subject:
Checkout these guide for more information:

- https://webpack.js.org/configuration/dev-server/#devserver-hot

To support HMR with React you would need to add `react-hot-loader`. Checkout this guide for
more detailed information:

- https://webpack.js.org/guides/hmr-react/


Expand Down
35 changes: 24 additions & 11 deletions lib/install/config/loaders/core/sass.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const path = require('path')
const { env } = require('../configuration.js')
const { env, settings } = require('../configuration.js')

const postcssConfigPath = path.resolve(process.cwd(), '.postcssrc.yml')
const isProduction = env.NODE_ENV === 'production'
const extractCSS = !settings.dev_server.hmr

module.exports = {
const extractOptions = {
fallback: 'style-loader',
use: [
{ loader: 'css-loader', options: { minimize: isProduction } },
{ loader: 'postcss-loader', options: { sourceMap: true, config: { path: postcssConfigPath } } },
'resolve-url-loader',
{ loader: 'sass-loader', options: { sourceMap: true, indentedSyntax: true } }
]
}

// For production extract styles to a separate bundle
const extractCSSLoader = {
test: /\.(scss|sass|css)$/i,
use: ExtractTextPlugin.extract(extractOptions)
}

// For hot-reloading use regular loaders
const inlineCSSLoader = {
test: /\.(scss|sass|css)$/i,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{ loader: 'css-loader', options: { minimize: env.NODE_ENV === 'production' } },
{ loader: 'postcss-loader', options: { sourceMap: true, config: { path: postcssConfigPath } } },
'resolve-url-loader',
{ loader: 'sass-loader', options: { sourceMap: true } }
]
})
use: ['style-loader'].concat(extractOptions.use)
}

module.exports = isProduction || extractCSS ? extractCSSLoader : inlineCSSLoader
8 changes: 7 additions & 1 deletion lib/install/config/loaders/installers/react.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
const { join } = require('path')
const { settings } = require('../configuration.js')

module.exports = {
test: /\.(js|jsx)?(\.erb)?$/,
exclude: /node_modules/,
loader: 'babel-loader'
loader: 'babel-loader',
options: {
cacheDirectory: join(settings.cache_path, 'babel-loader')
}
}
37 changes: 4 additions & 33 deletions lib/install/config/loaders/installers/vue.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,12 @@
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const { env } = require('../configuration.js')
const { env, settings } = require('../configuration.js')

// Change it to false if you prefer Vue styles to be inlined by javascript in runtime
const extractStyles = false

const cssLoader = [
{ loader: 'css-loader', options: { minimize: env.NODE_ENV === 'production' } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
'resolve-url-loader'
]
const sassLoader = cssLoader.concat([
{ loader: 'sass-loader', options: { sourceMap: true, indentedSyntax: true } }
])
const scssLoader = cssLoader.concat([
{ loader: 'sass-loader', options: { sourceMap: true } }
])

function vueStyleLoader(loader) {
if (extractStyles) {
return ExtractTextPlugin.extract({
fallback: 'vue-style-loader',
use: loader
})
}
return ['vue-style-loader'].concat(loader)
}
const isProduction = env.NODE_ENV === 'production'
const extractCSS = !settings.dev_server.hmr

module.exports = {
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
js: 'babel-loader',
file: 'file-loader',
css: vueStyleLoader(cssLoader),
scss: vueStyleLoader(scssLoader),
sass: vueStyleLoader(sassLoader)
}
extractCSS: isProduction || extractCSS
}
}
9 changes: 8 additions & 1 deletion lib/install/config/webpack/development.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Note: You must restart bin/webpack-dev-server for changes to take effect

const webpack = require('webpack')
const merge = require('webpack-merge')
const sharedConfig = require('./shared.js')
const { settings, output } = require('./configuration.js')
Expand All @@ -15,6 +16,7 @@ module.exports = merge(sharedConfig, {
clientLogLevel: 'none',
host: settings.dev_server.host,
port: settings.dev_server.port,
hot: settings.dev_server.hmr,
contentBase: output.path,
publicPath: output.publicPath,
compress: true,
Expand All @@ -26,5 +28,10 @@ module.exports = merge(sharedConfig, {
stats: {
errorDetails: true
}
}
},

plugins: settings.dev_server.hmr ? [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
] : []
})
1 change: 1 addition & 0 deletions lib/install/config/webpacker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ development:
dev_server:
host: localhost
port: 3035
hmr: false

test:
<<: *default
Expand Down
4 changes: 4 additions & 0 deletions lib/webpacker/dev_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def running?
false
end

def hot_module_replacing?
fetch(:hmr)
end

def host
fetch(:host)
end
Expand Down
7 changes: 6 additions & 1 deletion lib/webpacker/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def javascript_pack_tag(*names, **options)
# in config/webpack/shared.js. By default, this list is auto-generated to match everything in
# app/javascript/packs/*.js. In production mode, the digested reference is automatically looked up.
#
# Note: If the development server is running and hot module replacement is active, this will return nothing.
# In that setup you need to configure your styles to be inlined in your JavaScript for hot reloading.
#
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@justin808 You mean this doc?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe adjust this line to indicate more than "development mode":

  #   # In development mode:
  #   <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
  #   <link rel="stylesheet" media="screen" href="/packs/calendar.css" data-turbolinks-track="reload" />

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

# Examples:
#
# # In development mode:
Expand All @@ -42,7 +45,9 @@ def javascript_pack_tag(*names, **options)
# <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
# <link rel="stylesheet" media="screen" href="/packs/calendar-1016838bab065ae1e122.css" data-turbolinks-track="reload" />
def stylesheet_pack_tag(*names, **options)
stylesheet_link_tag(*sources_from_pack_manifest(names, type: :stylesheet), **options)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to update the above docs to indicate what happens in HMR, as well as for the possibility that the webpack-dev-server is not running for development mode.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added some notes in the README. Anything missing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc for the method should be updated (or removed? with a reference to the readme.)

unless Webpacker.dev_server.running? && Webpacker.dev_server.hot_module_replacing?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct in that React on Rails users will build static assets for test runs while the dev server is possibly running for development. Thus, we'll skip this tag when we need it for tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@justin808 What are the implications? I guess dev server can be used too for serving assets in test environment and don't think that will affect any behaviour. Obviously, if someone wants to statically build and test they can turn off the dev-server and use watcher or on-demand compilation. With dev-server proxy usage is more transparent and consistent so the output will be same in views regardless of compiler used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed it such that Webpacker.dev_server.running? will return false in testing by moving the dev_server configuration to the development env only in webpacker.yml. So now the tag will appear in test mode.

Copy link
Contributor

@justin808 justin808 Aug 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gauravtiwari wrote:

What are the implications?

Since we have a different test setup in webpack for test compared to dev (we want more logging in dev), I'd feel uncomfortable that some of the setup is coming from a different webpack config.

stylesheet_link_tag(*sources_from_pack_manifest(names, type: :stylesheet), **options)
end
end

private
Expand Down