diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c72f7c5c..3488b0a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 63b134b30..2d56dfe7f 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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/ diff --git a/lib/install/config/loaders/core/sass.js b/lib/install/config/loaders/core/sass.js index 1fb428c4a..380d3f779 100644 --- a/lib/install/config/loaders/core/sass.js +++ b/lib/install/config/loaders/core/sass.js @@ -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 diff --git a/lib/install/config/loaders/installers/react.js b/lib/install/config/loaders/installers/react.js index cfd641774..6c3341c73 100644 --- a/lib/install/config/loaders/installers/react.js +++ b/lib/install/config/loaders/installers/react.js @@ -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') + } } diff --git a/lib/install/config/loaders/installers/vue.js b/lib/install/config/loaders/installers/vue.js index c9d88a9a3..40986af2c 100644 --- a/lib/install/config/loaders/installers/vue.js +++ b/lib/install/config/loaders/installers/vue.js @@ -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 } } diff --git a/lib/install/config/webpack/development.js b/lib/install/config/webpack/development.js index 6dfb717e8..92382357c 100644 --- a/lib/install/config/webpack/development.js +++ b/lib/install/config/webpack/development.js @@ -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') @@ -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, @@ -26,5 +28,10 @@ module.exports = merge(sharedConfig, { stats: { errorDetails: true } - } + }, + + plugins: settings.dev_server.hmr ? [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NamedModulesPlugin() + ] : [] }) diff --git a/lib/install/config/webpacker.yml b/lib/install/config/webpacker.yml index 5e5c3b3c9..fba5e8914 100644 --- a/lib/install/config/webpacker.yml +++ b/lib/install/config/webpacker.yml @@ -33,6 +33,7 @@ development: dev_server: host: localhost port: 3035 + hmr: false test: <<: *default diff --git a/lib/webpacker/configuration.rb b/lib/webpacker/configuration.rb index 37815ba6a..304043eb4 100644 --- a/lib/webpacker/configuration.rb +++ b/lib/webpacker/configuration.rb @@ -47,15 +47,22 @@ def fetch(key) end def data - if env.development? - refresh - else + if env.production? @data ||= load + else + refresh end end def load - YAML.load(config_path.read)[env].deep_symbolize_keys + if config_path.exist? && + (@parsed_mtime.nil? || + ((config_mtime = File.mtime(config.public_manifest_path)) > @parsed_mtime)) + @parsed_mtime = config_mtime + YAML.load(config_path.read)[env].deep_symbolize_keys + else + {} + end end def defaults diff --git a/lib/webpacker/dev_server.rb b/lib/webpacker/dev_server.rb index bac89071f..ad6f5a2a7 100644 --- a/lib/webpacker/dev_server.rb +++ b/lib/webpacker/dev_server.rb @@ -12,6 +12,10 @@ def running? false end + def hot_module_replacing? + fetch(:hmr) + end + def host fetch(:host) end diff --git a/lib/webpacker/helper.rb b/lib/webpacker/helper.rb index 191ad4adc..9bdb216f3 100644 --- a/lib/webpacker/helper.rb +++ b/lib/webpacker/helper.rb @@ -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. + # # Examples: # # # In development mode: @@ -42,7 +45,9 @@ def javascript_pack_tag(*names, **options) # <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => # def stylesheet_pack_tag(*names, **options) - stylesheet_link_tag(*sources_from_pack_manifest(names, type: :stylesheet), **options) + unless Webpacker.dev_server.running? && Webpacker.dev_server.hot_module_replacing? + stylesheet_link_tag(*sources_from_pack_manifest(names, type: :stylesheet), **options) + end end private diff --git a/lib/webpacker/manifest.rb b/lib/webpacker/manifest.rb index 11ef101d7..6fa15dea2 100644 --- a/lib/webpacker/manifest.rb +++ b/lib/webpacker/manifest.rb @@ -42,19 +42,36 @@ def find(name) def handle_missing_entry(name) raise Webpacker::Manifest::MissingEntryError, - "Can't find #{name} in #{config.public_manifest_path}. Is webpack still compiling?" + "Can't find #{name} in #{config.public_manifest_path}. Manifest contains: #{@data}" + end + + def missing_file_from_manifest_error(bundle_name) + msg = <<-MSG +Webpacker can't find #{bundle_name} in #{config.public_manifest_path}. Possible causes: +1. You are hot reloading. +2. You want to set Configuration.compile to true for your environment. +3. Webpack has not re-run to reflect updates. +4. You have misconfigured Webpacker's config/webpacker.yml file. +5. Your Webpack configuration is not creating a manifest. +Your manifest contains: +#{@data.to_json} + MSG + raise(Webpacker::FileLoader::NotFoundError.new(msg)) end def data - if env.development? - refresh - else + if env.production? @data ||= load + else + refresh end end def load - if config.public_manifest_path.exist? + if config.public_manifest_path.exist? && + (@parsed_mtime.nil? || + ((manifest_mtime = File.mtime(config.public_manifest_path)) > @parsed_mtime)) + @parsed_mtime = manifest_mtime JSON.parse config.public_manifest_path.read else {}