From 4625a6aa0bce7813a9db5805594cdfc9b58e0e1f Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 2 Mar 2017 11:13:49 +0000 Subject: [PATCH 01/27] Add optional static asset task, refactor configs and add new manifest plugin Add file and style loaders Use the env options pass and configure file and style loader Setup dev server using env supplied Add node-sass Update production config to use plugins in production mode Refactor compile task as we now use manifest plugin Remove extra whitespace Refactor javascript pack tag and add a new stylesheet pack tag Remove extra logic from source lookup class Use same public path used in shared config Add some checks to make sure valid host and port is passed to dev server Change failure message Remove unwanted env variables Re-order variables Add back NODE_ENV Add the failure check block Just use manifest to lookup files in all environments Remove digesting option Remove digestion option Fix typo Remove reference to digesting in readme Fix error message Remove extra whitespace Remove loader option plugin Fix linter complaints Add example for static assets in readme Remove bash Use file.basename Cleanup webpack configs Fix typo Use env correctly Use hash in production Don't remove .js from babel, instead append jsx to it Export file loader extensions to a variable Redo everything, but first lets add rubocop Copy rubocop.yml from rails repo Change quotes Use package.json and refactor webpack config files Use package.json to load config and get rid of railtie Move templates to separate folder Update webpack binstubs to use package.json as config Lint using rubocop Conditionally require static config Setup a separate install task to setup static assets support Fix typo Add static task Update name and description in package.json Rename config to package_json and update references Add version to separate file and post-install notes Make sure puts write to terminal immediately Rename folder for clarity Update templates to use new path Remove image support from vue installer Update package.json template Update message text Refactor package_json class Add new config class to compile task Block request until digests.json is available Remove .tt Restructure static config location Fix static asset description Preserve whitespace Install static only when needed Use template to insert static support code Cleanup error and warning messages Make sure to check webpacker is installed before running static task Update not found message Update readme with latest changes Refactor classes, variable names and error messages Update post install message Rename static to assets Minor cleanup Fix readme formatting Rename for clarity Wrap it in begin block Minor fixes Wrap everything inside a block Ensure to exit out of the program Add a verify task that gets run before running any dependent task Add the verify task Fix typo Fix missing variable Update postinstall message Fix a typo --- .rubocop.yml | 124 ++++++++++++++++++ .travis.yml | 2 + Gemfile | 2 +- README.md | 89 +++++++++++-- lib/install/bin/webpack-dev-server.tt | 35 +++-- lib/install/bin/webpack.tt | 23 +++- lib/install/config/assets.js | 40 ++++++ lib/install/config/webpack/development.hot.js | 16 +++ .../config/{ => webpack}/development.js | 11 +- .../config/{ => webpack}/production.js | 9 +- lib/install/config/{ => webpack}/shared.js | 31 +++-- lib/install/node/package.json | 34 +++++ lib/install/template.rb | 24 ---- lib/install/templates/assets.rb | 45 +++++++ lib/install/templates/install.rb | 20 +++ lib/tasks/installers/angular.rake | 32 ++--- lib/tasks/installers/react.rake | 26 ++-- lib/tasks/installers/vue.rake | 36 ++--- lib/tasks/webpacker.rake | 15 ++- lib/tasks/webpacker/assets.rake | 14 ++ lib/tasks/webpacker/compile.rake | 28 ++-- lib/tasks/webpacker/install.rake | 2 +- lib/tasks/webpacker/verify.rake | 18 +++ lib/webpacker.rb | 2 +- lib/webpacker/digests.rb | 42 ------ lib/webpacker/helper.rb | 35 ++++- lib/webpacker/manifest.rb | 53 ++++++++ lib/webpacker/package_json.rb | 58 ++++++++ lib/webpacker/railtie.rb | 26 ++-- lib/webpacker/source.rb | 40 +++--- lib/webpacker/version.rb | 3 + webpacker.gemspec | 43 ++++-- 32 files changed, 727 insertions(+), 251 deletions(-) create mode 100644 .rubocop.yml create mode 100644 lib/install/config/assets.js create mode 100644 lib/install/config/webpack/development.hot.js rename lib/install/config/{ => webpack}/development.js (71%) rename lib/install/config/{ => webpack}/production.js (73%) rename lib/install/config/{ => webpack}/shared.js (62%) create mode 100644 lib/install/node/package.json delete mode 100644 lib/install/template.rb create mode 100644 lib/install/templates/assets.rb create mode 100644 lib/install/templates/install.rb create mode 100644 lib/tasks/webpacker/assets.rake create mode 100644 lib/tasks/webpacker/verify.rake delete mode 100644 lib/webpacker/digests.rb create mode 100644 lib/webpacker/manifest.rb create mode 100644 lib/webpacker/package_json.rb create mode 100644 lib/webpacker/version.rb diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..62eae71b7 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,124 @@ +AllCops: + TargetRubyVersion: 2.2 + # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop + # to ignore them, so only the ones explicitly set in this file are enabled. + DisabledByDefault: true + Exclude: + - 'lib/install/templates/**' + - 'vendor/**/*' + - 'node_modules/**/*' + +# Prefer &&/|| over and/or. +Style/AndOr: + Enabled: true + +# Do not use braces for hash literals when they are the last argument of a +# method call. +Style/BracesAroundHashParameters: + Enabled: true + +# Align `when` with `case`. +Style/CaseIndentation: + Enabled: true + +# Align comments with method definitions. +Style/CommentIndentation: + Enabled: true + +# No extra empty lines. +Style/EmptyLines: + Enabled: true + +# In a regular class definition, no empty lines around the body. +Style/EmptyLinesAroundClassBody: + Enabled: true + +# In a regular method definition, no empty lines around the body. +Style/EmptyLinesAroundMethodBody: + Enabled: true + +# In a regular module definition, no empty lines around the body. +Style/EmptyLinesAroundModuleBody: + Enabled: true + +# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. +Style/HashSyntax: + Enabled: true + +# Method definitions after `private` or `protected` isolated calls need one +# extra level of indentation. +Style/IndentationConsistency: + Enabled: true + EnforcedStyle: rails + +# Two spaces, no tabs (for indentation). +Style/IndentationWidth: + Enabled: true + +Style/SpaceAfterColon: + Enabled: true + +Style/SpaceAfterComma: + Enabled: true + +Style/SpaceAroundEqualsInParameterDefault: + Enabled: true + +Style/SpaceAroundKeyword: + Enabled: true + +Style/SpaceAroundOperators: + Enabled: true + +Style/SpaceBeforeFirstArg: + Enabled: true + +# Defining a method with parameters needs parentheses. +Style/MethodDefParentheses: + Enabled: true + +# Use `foo {}` not `foo{}`. +Style/SpaceBeforeBlockBraces: + Enabled: true + +# Use `foo { bar }` not `foo {bar}`. +Style/SpaceInsideBlockBraces: + Enabled: true + +# Use `{ a: 1 }` not `{a:1}`. +Style/SpaceInsideHashLiteralBraces: + Enabled: true + +Style/SpaceInsideParens: + Enabled: true + +# Check quotes usage according to lint rule below. +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +# Detect hard tabs, no hard tabs. +Style/Tab: + Enabled: true + +# Blank lines should not have any spaces. +Style/TrailingBlankLines: + Enabled: true + +# No trailing whitespace. +Style/TrailingWhitespace: + Enabled: true + +# Use quotes for string literals when they are enough. +Style/UnneededPercentQ: + Enabled: true + +# Align `end` with the matching keyword or starting expression except for +# assignments, where it should be aligned with the LHS. +Lint/EndAlignment: + Enabled: true + EnforcedStyleAlignWith: variable + +# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. +Lint/RequireParentheses: + Enabled: true diff --git a/.travis.yml b/.travis.yml index 44f316aac..13211a640 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ cache: yarn: true install: + - gem install rubocop - nvm install node - node -v - npm i -g yarn @@ -20,3 +21,4 @@ install: script: - yarn lint + - rubocop diff --git a/Gemfile b/Gemfile index fa75df156..b4e2a20bb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ -source 'https://rubygems.org' +source "https://rubygems.org" gemspec diff --git a/README.md b/README.md index 6c3f859cc..8413d0c62 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Webpacker is currently compatible with Rails 4.2+, but there's no guarantee it w in the future. You can either make use of Webpacker during setup of a new application with `--webpack` -or you can add the gem and run `bin/rails webpacker:install` in an existing application. +or you can add the gem and run `./bin/rails webpacker:install` in an existing application. As the rubygems version isn't promised to be kept up to date until the release of Rails 5.1, you may want to include the gem directly from GitHub: @@ -23,6 +23,8 @@ As the rubygems version isn't promised to be kept up to date until the release o gem 'webpacker', github: 'rails/webpacker' ``` +You can also see a list of available commands by running `./bin/rails webpacker` + ## Binstubs Webpacker ships with three binstubs: `./bin/webpack`, `./bin/webpack-watcher` and `./bin/webpack-dev-server`. @@ -78,13 +80,62 @@ app/javascript/calendar/models/month.js But it could also look a million other ways. The only convention that Webpacker enforces is the one where entry points are automatically configured by the files in `app/javascript/packs`. +## Advanced Configuration + +By default, webpacker provides simple conventions for where the webpack configs, javascript app files and compiled webpack bundles will go in your rails app, but all these are configurable from package.json that comes with webpacker. You can also configure webpack-dev-server host and port in your development environment + +```json +{ + "config": { + "_comment": "Configure webpacker internals (do not remove)", + "webpacker": { + "srcPath": "app/javascript", + "configPath": "config/webpack", + "nodeModulesPath": "node_modules", + "distDir": "packs", + "distPath": "public/packs", + "digestFileName": "digests.json" + }, + "_comment": "Configure webpack-dev-server", + "devServer": { + "enabled": true, + "host": "localhost", + "port": "8080", + "compress": true + } + } +} +``` + +**For ex:** if you rename `packs` directory inside `app/javascript` from `packs` to `bundles`, make sure you also update your `distDir` and `distPath`. + +```json +"webpacker": { + "distDir": "bundles", + "distPath": "public/bundles", +} +``` + +**Note:** Do not delete this config otherwise your app will break, unless you really know what you're doing. ## Deployment -To compile all the packs during deployment, you can use the `rails webpacker:compile` command. This -will invoke the production configuration, which includes digesting. The `javascript_pack_tag` helper -method will automatically insert the correct digest when run in production mode. Just like the asset -pipeline does it. +Webpacker hooks up a new `webpacker:compile` task to `assets:precompile`, which gets run whenever you run `assets:precompile`. The `javascript_pack_tag` and `stylesheet_pack_tag` helper method will automatically insert the correct HTML tag for compiled pack. Just like the asset pipeline does it. By default the output will look like this in different environments, + +```html + + + + + + + + + +``` + +**Note:** *`stylesheet_pack_tag` helper is not available out-of-the-box. +Check [statics assets](#ready-for-static-assets) support section for more details.* ## Linking to sprockets assets @@ -100,19 +151,40 @@ var railsImagePath = "<%= helpers.image_path('rails.png') %>"; This is enabled by the `rails-erb-loader` loader rule in `config/webpack/shared.js`. +## Ready for Static Assets + +Static assets support isn't enabled out-of-the-box. To enable static assets support in your javascript app you will need to run `rails webpacker:install:assets` after you have installed webpacker. Once installed, you can +link static files like images and styles directly into your javascript app code and +have them properly compiled automatically. + +```js +// React component example +import React from 'react' +import ReactDOM from 'react-dom' +import clockIcon from '../counter/images/clock.png' +import './hello-react.sass' + +const Hello = props => ( +
+ clock +

Hello {props.name}!

+
+) +``` + ## Ready for React -To use Webpacker with React, just create a new app with `rails new myapp --webpack=react` (or run `rails webpacker:install:react` on a Rails 5.1 app already setup with webpack), and all the relevant dependencies +To use Webpacker with React, just create a new app with `rails new myapp --webpack=react` (or run `rails webpacker:install:react` on a Rails app already setup with webpacker), and all the relevant dependencies will be added via yarn and changes to the configuration files made. Now you can create JSX files and have them properly compiled automatically. ## Ready for Angular with TypeScript -To use Webpacker with Angular, just create a new app with `rails new myapp --webpack=angular` (or run `rails webpacker:install:angular` on a Rails 5.1 app already setup with webpack). TypeScript support and the Angular core libraries will be added via yarn and changes to the configuration files made. An example component written in TypeScript is also added to your project in `app/javascript` so that you can experiment Angular right away. +To use Webpacker with Angular, just create a new app with `rails new myapp --webpack=angular` (or run `rails webpacker:install:angular` on a Rails app already setup with webpacker). TypeScript support and the Angular core libraries will be added via yarn and changes to the configuration files made. An example component written in TypeScript is also added to your project in `app/javascript` so that you can experiment Angular right away. ## Ready for Vue -To use Webpacker with Vue, just create a new app with `rails new myapp --webpack=vue` (or run `rails webpacker:install:vue` on a Rails 5.1 app already setup with webpack). Vue and its supported libraries will be added via yarn and changes to the configuration files made. An example component is also added to your project in `app/javascript` so that you can experiment Vue right away. +To use Webpacker with Vue, just create a new app with `rails new myapp --webpack=vue` (or run `rails webpacker:install:vue` on a Rails app already setup with webpacker). Vue and its supported libraries will be added via yarn and changes to the configuration files made. An example component is also added to your project in `app/javascript` so that you can experiment Vue right away. ## Wishlist @@ -120,7 +192,6 @@ To use Webpacker with Vue, just create a new app with `rails new myapp --webpack - Improve process for linking to assets compiled by sprockets - shouldn't need to specify ` <% helpers = ActionController::Base.helpers %>` at the beginning of each file - Consider chunking setup -- Consider on-demand compiling with digests when digesting=true ## License Webpacker is released under the [MIT License](https://opensource.org/licenses/MIT). diff --git a/lib/install/bin/webpack-dev-server.tt b/lib/install/bin/webpack-dev-server.tt index f09a430d8..e5aa45da3 100644 --- a/lib/install/bin/webpack-dev-server.tt +++ b/lib/install/bin/webpack-dev-server.tt @@ -1,27 +1,40 @@ <%= shebang %> +$stdout.sync = true + require 'shellwords' +require 'json' ENV['RAILS_ENV'] ||= 'development' -RAILS_ENV = ENV['RAILS_ENV'] +RAILS_ENV = ENV['RAILS_ENV'] ENV['NODE_ENV'] ||= RAILS_ENV -NODE_ENV = ENV['NODE_ENV'] +NODE_ENV = ENV['NODE_ENV'] APP_PATH = File.expand_path('../', __dir__) ESCAPED_APP_PATH = APP_PATH.shellescape -SET_NODE_PATH = "NODE_PATH=#{ESCAPED_APP_PATH}/node_modules" -WEBPACKER_BIN = "./node_modules/.bin/webpack-dev-server" -WEBPACK_CONFIG = "#{ESCAPED_APP_PATH}/config/webpack/#{NODE_ENV}.js" +begin + package_json = File.read(File.join(APP_PATH, 'package.json')) + config = JSON.parse(package_json) -# Warn the user if the configuration is not set -RAILS_ENV_CONFIG = File.join("config", "environments", "#{RAILS_ENV}.rb") + NODE_MODULES_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['nodeModulesPath'])}" + WEBPACK_CONFIG_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['configPath'])}" + PACKS_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['distPath'])}" + WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack-dev-server" + WEBPACK_CONFIG = "#{WEBPACK_CONFIG_PATH}/development.hot.js" -# Look into the environment file for a non-commented variable declaration -unless File.foreach(File.join(APP_PATH, RAILS_ENV_CONFIG)).detect { |line| line.match(/^\s*[^#]*config\.x\.webpacker\[\:dev_server_host\].*=/) } - puts "Warning: if you want to use webpack-dev-server, you need to tell Webpacker to serve asset packs from it. Please set config.x.webpacker[:dev_server_host] in #{RAILS_ENV_CONFIG}.\n\n" + if config['devServer'] && !config['devServer']['enabled'] + puts "Error: If you want to use webpack-dev-server, you will need to enable " \ + "webpack-dev-server from your package.json { devServer: { enabled: true } }" + exit! + end +rescue Errno::ENOENT, NoMethodError + puts 'Package.json or [webpacker, devServer] config key not found.' + puts 'Please run bundle exec rails webpacker:install to install webpacker' + exit! end Dir.chdir(APP_PATH) do - exec "#{SET_NODE_PATH} #{WEBPACKER_BIN} --config #{WEBPACK_CONFIG} --content-base #{ESCAPED_APP_PATH}/public/packs #{ARGV.join(" ")}" + exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} " \ + "--config #{WEBPACK_CONFIG} --content-base #{PACKS_PATH} #{ARGV.join(" ")}" end diff --git a/lib/install/bin/webpack.tt b/lib/install/bin/webpack.tt index 2835897c8..0e3378f38 100644 --- a/lib/install/bin/webpack.tt +++ b/lib/install/bin/webpack.tt @@ -1,5 +1,8 @@ <%= shebang %> +$stdout.sync = true + require 'shellwords' +require 'json' ENV['RAILS_ENV'] ||= 'development' RAILS_ENV = ENV['RAILS_ENV'] @@ -10,10 +13,22 @@ NODE_ENV = ENV['NODE_ENV'] APP_PATH = File.expand_path('../', __dir__) ESCAPED_APP_PATH = APP_PATH.shellescape -SET_NODE_PATH = "NODE_PATH=#{ESCAPED_APP_PATH}/node_modules" -WEBPACK_BIN = "./node_modules/webpack/bin/webpack.js" -WEBPACK_CONFIG = "#{ESCAPED_APP_PATH}/config/webpack/#{NODE_ENV}.js" +begin + package_json = File.read(File.join(APP_PATH, 'package.json')) + config = JSON.parse(package_json) + + NODE_MODULES_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['nodeModulesPath'])}" + WEBPACK_CONFIG_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['configPath'])}" +rescue Errno::ENOENT, NoMethodError + puts 'Package.json or [webpacker, devServer] config key not found.' + puts 'Please run bundle exec rails webpacker:install to install webpacker' + exit! +end + +WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack" +WEBPACK_CONFIG = "#{WEBPACK_CONFIG_PATH}/#{NODE_ENV}.js" Dir.chdir(APP_PATH) do - exec "#{SET_NODE_PATH} #{WEBPACK_BIN} --config #{WEBPACK_CONFIG} #{ARGV.join(" ")}" + exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} --config #{WEBPACK_CONFIG}" \ + " #{ARGV.join(" ")}" end diff --git a/lib/install/config/assets.js b/lib/install/config/assets.js new file mode 100644 index 000000000..92bc6f6eb --- /dev/null +++ b/lib/install/config/assets.js @@ -0,0 +1,40 @@ +// Handles static asset integration - like sass and images +// Note: You must restart bin/webpack-watcher for changes to take effect + +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const process = require('process') +const sharedConfig = require('./shared.js') +const { devServer } = require('../../package.json') + +const production = process.env.NODE_ENV === 'production' +const hotServerAddr = `http://${devServer.host}:${devServer.port}/` + +module.exports = { + module: { + rules: [ + { + test: /\.(scss|sass|css)$/i, + use: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: ['css-loader', 'sass-loader'] + }) + }, + { + test: /\.(jpeg|png|gif|svg|eot|svg|ttf|woff|woff2)$/i, + use: [{ + loader: 'file-loader', + options: { + publicPath: devServer.enabled ? hotServerAddr : `/${sharedConfig.distDir}/`, + name: production ? '[name]-[hash].[ext]' : '[name].[ext]' + } + }] + } + ] + }, + + plugins: [ + new ExtractTextPlugin( + production ? '[name]-[hash].css' : '[name].css' + ) + ] +} diff --git a/lib/install/config/webpack/development.hot.js b/lib/install/config/webpack/development.hot.js new file mode 100644 index 000000000..7c61ffca2 --- /dev/null +++ b/lib/install/config/webpack/development.hot.js @@ -0,0 +1,16 @@ +// Note: You must restart bin/webpack-dev-server for changes to take effect + +const merge = require('webpack-merge') +const devConfig = require('./development.js') +const sharedConfig = require('./shared.js') +const { devServer } = require('../../package.json') + +module.exports = merge(devConfig, { + devServer: { + host: devServer.host, + port: devServer.port, + compress: devServer.compress, + publicPath: devServer.enabled ? + `http://${devServer.host}:${devServer.port}/` : `/${sharedConfig.distDir}/` + } +}) diff --git a/lib/install/config/development.js b/lib/install/config/webpack/development.js similarity index 71% rename from lib/install/config/development.js rename to lib/install/config/webpack/development.js index 24f9d630e..df93d8ae7 100644 --- a/lib/install/config/development.js +++ b/lib/install/config/webpack/development.js @@ -1,8 +1,7 @@ +/* eslint global-require: 0 */ // Note: You must restart bin/webpack-watcher for changes to take effect -const webpack = require('webpack') const merge = require('webpack-merge') - const sharedConfig = require('./shared.js') module.exports = merge(sharedConfig.config, { @@ -14,11 +13,5 @@ module.exports = merge(sharedConfig.config, { output: { pathinfo: true - }, - - plugins: [ - new webpack.LoaderOptionsPlugin({ - debug: true - }) - ] + } }) diff --git a/lib/install/config/production.js b/lib/install/config/webpack/production.js similarity index 73% rename from lib/install/config/production.js rename to lib/install/config/webpack/production.js index 83b8f2ee3..31f366280 100644 --- a/lib/install/config/production.js +++ b/lib/install/config/webpack/production.js @@ -1,23 +1,20 @@ -// Note: You must restart bin/webpack-watcher for changes to take effect +/* eslint global-require: 0 */ +// Note: You must run bin/webpack for changes to take effect const webpack = require('webpack') const merge = require('webpack-merge') const CompressionPlugin = require('compression-webpack-plugin') - const sharedConfig = require('./shared.js') module.exports = merge(sharedConfig.config, { output: { filename: '[name]-[chunkhash].js' }, plugins: [ - new webpack.LoaderOptionsPlugin({ - minimize: true - }), new webpack.optimize.UglifyJsPlugin(), new CompressionPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', - test: /\.js$/ + test: /\.(js|css|svg|eot|ttf|woff|woff2)$/ }) ] }) diff --git a/lib/install/config/shared.js b/lib/install/config/webpack/shared.js similarity index 62% rename from lib/install/config/shared.js rename to lib/install/config/webpack/shared.js index 50381ff71..dc36c375b 100644 --- a/lib/install/config/shared.js +++ b/lib/install/config/webpack/shared.js @@ -4,16 +4,18 @@ const webpack = require('webpack') const path = require('path') const process = require('process') const glob = require('glob') +const ManifestPlugin = require('webpack-manifest-plugin') const extname = require('path-complete-extname') +const { webpacker } = require('../../package.json') -let distDir = process.env.WEBPACK_DIST_DIR - -if (distDir === undefined) { - distDir = 'packs' -} +const srcPath = webpacker.srcPath +const distDir = webpacker.distDir +const distPath = webpacker.distPath +const nodeModulesPath = webpacker.nodeModulesPath +const digestFileName = webpacker.digestFileName const config = { - entry: glob.sync(path.join('app', 'javascript', 'packs', '*.js*')).reduce( + entry: glob.sync(path.join(srcPath, distDir, '*.js*')).reduce( (map, entry) => { const basename = path.basename(entry, extname(entry)) const localMap = map @@ -22,7 +24,7 @@ const config = { }, {} ), - output: { filename: '[name].js', path: path.resolve('public', distDir) }, + output: { filename: '[name].js', path: path.resolve(distPath) }, module: { rules: [ @@ -50,23 +52,30 @@ const config = { }, plugins: [ - new webpack.EnvironmentPlugin(Object.keys(process.env)) + new webpack.EnvironmentPlugin(Object.keys(process.env)), + new ManifestPlugin({ + fileName: digestFileName, + publicPath: `/${distDir}/` + }) ], resolve: { extensions: ['.js', '.coffee'], modules: [ - path.resolve('app/javascript'), - path.resolve('node_modules') + path.resolve(srcPath), + path.resolve(nodeModulesPath) ] }, resolveLoader: { - modules: [path.resolve('node_modules')] + modules: [path.resolve(nodeModulesPath)] } } module.exports = { + srcPath, distDir, + distPath, + nodeModulesPath, config } diff --git a/lib/install/node/package.json b/lib/install/node/package.json new file mode 100644 index 000000000..8f68fd5cb --- /dev/null +++ b/lib/install/node/package.json @@ -0,0 +1,34 @@ +{ + "name": "webpacker-app", + "version": "1.0.0", + "private": true, + "description": "Integrates Webpack to manage application-like JavaScript in Rails", + "main": "app/javascript/packs/index.js", + + "scripts": { + "lint": "./node_modules/eslint/bin/eslint.js lib/ --fix", + "compile": "./bin/webpack", + "build": "NODE_ENV=production ./bin/webpack", + "server": "./bin/webpack-dev-server", + "watch": "./bin/webpack-watcher" + }, + + "_comment": "Customise as required, but do not remove otherwise your app will break", + "webpacker": { + "srcPath": "app/javascript", + "configPath": "config/webpack", + "nodeModulesPath": "node_modules", + "_comment": "If you rename distDir, please update distPath too", + "distDir": "packs", + "distPath": "public/packs", + "digestFileName": "digests.json" + }, + + "_comment": "Configure webpack-dev-server", + "devServer": { + "enabled": false, + "host": "localhost", + "port": "8080", + "compress": true + } +} diff --git a/lib/install/template.rb b/lib/install/template.rb deleted file mode 100644 index 0f37d6afd..000000000 --- a/lib/install/template.rb +++ /dev/null @@ -1,24 +0,0 @@ -directory "#{__dir__}/javascript", 'app/javascript' - -directory "#{__dir__}/bin", 'bin' -chmod 'bin', 0755 & ~File.umask, verbose: false - -directory "#{__dir__}/config", 'config/webpack' - -append_to_file '.gitignore', <<-EOS -/public/packs -/node_modules -EOS - -run './bin/yarn add webpack webpack-merge path-complete-extname babel-loader babel-core babel-preset-env coffee-loader coffee-script compression-webpack-plugin rails-erb-loader glob' -run './bin/yarn add --dev webpack-dev-server' - -environment \ - "# Make javascript_pack_tag lookup digest hash to enable long-term caching\n" + - " config.x.webpacker[:digesting] = true\n", - env: 'production' - -environment \ - "# Make javascript_pack_tag load assets from webpack-dev-server.\n" + - " # config.x.webpacker[:dev_server_host] = \"http://localhost:8080\"\n", - env: 'development' diff --git a/lib/install/templates/assets.rb b/lib/install/templates/assets.rb new file mode 100644 index 000000000..a7da8a0d5 --- /dev/null +++ b/lib/install/templates/assets.rb @@ -0,0 +1,45 @@ +# Installs modules to enable assets linking, compiling and digesting +# with webpack + +require "json" +require "webpacker/package_json" + +# Use existing package.json +# Add new options for assets +begin + package_json = Webpacker::PackageJson.config + package_json[:scripts][:postinstall] = "npm rebuild node-sass" + package_json[:webpacker][:assets] = true +rescue NoMethodError + puts "Error: Webpacker core config and scripts key not found in package.json." \ + " Make sure webpacker:install is run successfully" + exit! +end + +#Β Write to package.json +File.open(Rails.root.join("package.json"), "w+") do |file| + file.write(JSON.pretty_generate(package_json)) +end + +copy_file "#{File.expand_path("..", __dir__)}/config/assets.js", + "config/webpack/assets.js" + +assets_webpack_config = <<-EOS +const { webpacker } = require('../../package.json') + +if (webpacker.assets) { + const assetsConfig = require('./assets.js') + sharedConfig.config = merge(sharedConfig.config, assetsConfig) +} +EOS + +insert_into_file "config/webpack/development.js", + assets_webpack_config, + after: "const sharedConfig = require('./shared.js')\n" + +insert_into_file "config/webpack/production.js", + assets_webpack_config, + after: "const sharedConfig = require('./shared.js')\n" + +run "./bin/yarn add extract-text-webpack-plugin node-sass file-loader " \ + "sass-loader css-loader style-loader " diff --git a/lib/install/templates/install.rb b/lib/install/templates/install.rb new file mode 100644 index 000000000..07efb8b98 --- /dev/null +++ b/lib/install/templates/install.rb @@ -0,0 +1,20 @@ +# Setup webpacker + +directory "#{File.expand_path("..", __dir__)}/node", "./" +directory "#{File.expand_path("..", __dir__)}/javascript", "app/javascript" + +directory "#{File.expand_path("..", __dir__)}/bin", "bin" +chmod "bin", 0755 & ~File.umask, verbose: false + +directory "#{File.expand_path("..", __dir__)}/config/webpack", "config/webpack" + +append_to_file ".gitignore", <<-EOS +/public/packs +/node_modules +EOS + +run "./bin/yarn add webpack webpack-merge path-complete-extname " \ +"webpack-manifest-plugin babel-loader coffee-loader coffee-script " \ +"babel-core babel-preset-env compression-webpack-plugin rails-erb-loader glob" + +run "./bin/yarn add --dev webpack-dev-server" diff --git a/lib/tasks/installers/angular.rake b/lib/tasks/installers/angular.rake index 3e13d3bb6..9aa5c8dec 100644 --- a/lib/tasks/installers/angular.rake +++ b/lib/tasks/installers/angular.rake @@ -1,18 +1,14 @@ +require "webpacker/package_json" + namespace :webpacker do namespace :install do desc "Install everything needed for Angular" - task :angular do - config_path = Rails.root.join('config/webpack/shared.js') - - config = begin - File.read(config_path) - rescue Errno::ENOENT - puts 'Webpack config not found. Make sure webpacker:install' \ - ' is run successfully before installing angular' - exit! - end + task angular: ["webpacker:install:verify"] do + webpacker_config = Webpacker::PackageJson.webpacker + config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") + config = File.read(config_path) - if config.include?('ts-loader') + if config.include?("ts-loader") puts "The configuration file already has a reference to ts-loader, skipping the test rule..." else puts "Adding a loader rule to include ts-loader for .ts files in #{config_path}..." @@ -29,18 +25,18 @@ namespace :webpacker do File.write config_path, config puts "Copying Angular example to app/javascript/packs/hello_angular.js" - FileUtils.copy File.expand_path('../../install/examples/angular/hello_angular.js', __dir__), - Rails.root.join('app/javascript/packs/hello_angular.js') + FileUtils.copy File.expand_path("../../install/examples/angular/hello_angular.js", __dir__), + Rails.root.join("app/javascript/packs/hello_angular.js") puts "Copying Angular Hello app to app/javascript/hello_angular" - FileUtils.copy_entry File.expand_path('../../install/examples/angular/hello_angular', __dir__), - Rails.root.join('app/javascript/hello_angular') + FileUtils.copy_entry File.expand_path("../../install/examples/angular/hello_angular", __dir__), + Rails.root.join("app/javascript/hello_angular") puts "Copying tsconfig.json to the Rails root directory" - FileUtils.copy File.expand_path('../../install/examples/angular/tsconfig.json', __dir__), - Rails.root.join('tsconfig.json') + FileUtils.copy File.expand_path("../../install/examples/angular/tsconfig.json", __dir__), + Rails.root.join("tsconfig.json") - exec './bin/yarn add typescript ts-loader core-js zone.js rxjs @angular/core @angular/common @angular/compiler @angular/platform-browser @angular/platform-browser-dynamic' + exec "./bin/yarn add typescript ts-loader core-js zone.js rxjs @angular/core @angular/common @angular/compiler @angular/platform-browser @angular/platform-browser-dynamic" end end end diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index 2e10a8899..fa584cf65 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -1,16 +1,12 @@ +require "webpacker/package_json" + namespace :webpacker do namespace :install do desc "Install everything needed for react" - task :react do - config_path = Rails.root.join('config/webpack/shared.js') - - config = begin - File.read(config_path) - rescue Errno::ENOENT - puts 'Webpack config not found. Make sure webpacker:install is' \ - ' run successfully before installing react' - exit! - end + task react: ["webpacker:install:verify"] do + webpacker_config = Webpacker::PackageJson.webpacker + config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") + config = File.read(config_path) if config =~ /presets:\s*\[\s*\[\s*'env'/ puts "Replacing loader presets to include react in #{config_path}" @@ -21,7 +17,7 @@ namespace :webpacker do if config.include?("test: /\\.js(\\.erb)?$/") puts "Replacing loader test to include react in #{config_path}" - config.gsub!("test: /\\.js(\\.erb)?$/", "test: /\\.jsx?(\\.erb)?$/") + config.gsub!("test: /\\.js(\\.erb)?$/", "test: /\\.(js|jsx)?(\\.erb)?$/") else puts "Couldn't automatically update loader test in #{config_path}. Please set test: /\\.jsx?(\\.erb)?$/." end @@ -29,14 +25,14 @@ namespace :webpacker do File.write config_path, config puts "Copying .babelrc to project directory" - FileUtils.copy File.expand_path('../../install/examples/react/.babelrc', __dir__), + FileUtils.copy File.expand_path("../../install/examples/react/.babelrc", __dir__), Rails.root puts "Copying react example to app/javascript/packs/hello_react.jsx" - FileUtils.copy File.expand_path('../../install/examples/react/hello_react.jsx', __dir__), - Rails.root.join('app/javascript/packs/hello_react.jsx') + FileUtils.copy File.expand_path("../../install/examples/react/hello_react.jsx", __dir__), + Rails.root.join("app/javascript/packs/hello_react.jsx") - exec './bin/yarn add react react-dom babel-preset-react' + exec "./bin/yarn add react react-dom babel-preset-react" end end end diff --git a/lib/tasks/installers/vue.rake b/lib/tasks/installers/vue.rake index cdac9ce6b..d2b858ee4 100644 --- a/lib/tasks/installers/vue.rake +++ b/lib/tasks/installers/vue.rake @@ -1,46 +1,36 @@ +require "webpacker/package_json" + namespace :webpacker do namespace :install do desc "Install everything needed for Vue" - task :vue do - config_path = Rails.root.join('config/webpack/shared.js') - - config = begin - File.read(config_path) - rescue Errno::ENOENT - puts 'Webpack config not found. Make sure webpacker:install' \ - ' is run successfully before installing vue' - exit! - end + task vue: ["webpacker:install:verify"] do + webpacker_config = Webpacker::PackageJson.webpacker + config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") + config = File.read(config_path) # Module resolution https://webpack.js.org/concepts/module-resolution/ if config.include?("'vue$':'vue/dist/vue.esm.js'") puts "Couldn't automatically update module resolution in #{config_path}. Please set resolve { alias:{ 'vue$':'vue/dist/vue.esm.js' } }." else - config.gsub!(/resolve:(\s*\{)(\s*)extensions/,"resolve:\\1\\2alias: { 'vue$':'vue/dist/vue.esm.js' },\\2extensions") - end - - if config.include?("loader: 'url-loader?mimetype=image/png'") - puts "Couldn't automatically update url-loader in #{config_path}. Please set { test: /\.png$/, loader: 'url-loader?mimetype=image/png' }." - else - config.gsub!(/module:(\s*\{)(\s*)rules:(\s*)\[/,"module:\\1\\2rules:\\3[\\2 { test: /\.png$/, loader: 'url-loader?mimetype=image/png'},") + config.gsub!(/resolve:(\s*\{)(\s*)extensions/, "resolve:\\1\\2alias: { 'vue$':'vue/dist/vue.esm.js' },\\2extensions") end if config.include?("loader: 'vue-loader',") puts "Couldn't automatically update vue-loader in #{config_path}. Please set { test: /.vue$/, loader: 'vue-loader', options: { loaders: { 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'}}}." else - config.gsub!(/module:(\s*\{)(\s*)rules:(\s*)\[/,"module:\\1\\2rules:\\3[\\2 {\\2 test: /\.vue$/, loader: 'vue-loader',\\2 options: {\\2 loaders: { 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'}\\2 }\\2 },") + config.gsub!(/module:(\s*\{)(\s*)rules:(\s*)\[/, "module:\\1\\2rules:\\3[\\2 {\\2 test: /\.vue$/, loader: 'vue-loader',\\2 options: {\\2 loaders: { 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'}\\2 }\\2 },") end File.write config_path, config puts "Copying the Vue example to app/javascript/packs/vue" - FileUtils.copy File.expand_path('../../install/examples/vue/hello_vue.js', File.dirname(__FILE__)), - Rails.root.join('app/javascript/packs/hello_vue.js') + FileUtils.copy File.expand_path("../../install/examples/vue/hello_vue.js", File.dirname(__FILE__)), + Rails.root.join("app/javascript/packs/hello_vue.js") - FileUtils.copy File.expand_path('../../install/examples/vue/app.vue', File.dirname(__FILE__)), - Rails.root.join('app/javascript/packs/app.vue') + FileUtils.copy File.expand_path("../../install/examples/vue/app.vue", File.dirname(__FILE__)), + Rails.root.join("app/javascript/packs/app.vue") - exec "./bin/yarn add vue vue-loader vue-template-compiler sass-loader node-sass css-loader url-loader axios" + exec "./bin/yarn add vue vue-loader vue-template-compiler sass-loader node-sass css-loader axios" end end end diff --git a/lib/tasks/webpacker.rake b/lib/tasks/webpacker.rake index 722bc9523..44fab4024 100644 --- a/lib/tasks/webpacker.rake +++ b/lib/tasks/webpacker.rake @@ -1,13 +1,14 @@ tasks = { - 'webpacker:install' => 'Installs and setup webpack with yarn', - 'webpacker:compile' => 'Compiles webpack bundles based on environment', - 'webpacker:install:react' => 'Installs and setup example react component', - 'webpacker:install:vue' => 'Installs and setup example vue component', - 'webpacker:install:angular' => 'Installs and setup example angular2 component' + "webpacker:install" => "Installs and setup webpack with yarn", + "webpacker:compile" => "Compiles webpack bundles based on environment", + "webpacker:install:assets" => "Adds static assets(images and styles) support", + "webpacker:install:react" => "Installs and setup example react component", + "webpacker:install:vue" => "Installs and setup example vue component", + "webpacker:install:angular" => "Installs and setup example angular2 component" }.freeze -desc 'Lists available tasks under webpacker' +desc "Lists available tasks under webpacker" task :webpacker do - puts 'Available webpacker tasks are:' + puts "Available webpacker tasks are:" tasks.each { |task, message| puts task.ljust(30) + message } end diff --git a/lib/tasks/webpacker/assets.rake b/lib/tasks/webpacker/assets.rake new file mode 100644 index 000000000..d7a2ea85f --- /dev/null +++ b/lib/tasks/webpacker/assets.rake @@ -0,0 +1,14 @@ +STATIC_APP_TEMPLATE_PATH = File.expand_path("../../install/templates/assets.rb", __dir__) + +namespace :webpacker do + namespace :install do + desc "Add static assets(images and styles) support to webpacker" + task assets: ["webpacker:install:verify"] do + if Rails::VERSION::MAJOR >= 5 + exec "./bin/rails app:template LOCATION=#{STATIC_APP_TEMPLATE_PATH}" + else + exec "./bin/rake rails:template LOCATION=#{STATIC_APP_TEMPLATE_PATH}" + end + end + end +end diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index e1b26c80e..b77300195 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -1,26 +1,20 @@ +require "webpacker/package_json" REGEX_MAP = /\A.*\.map\z/ namespace :webpacker do desc "Compile javascript packs using webpack for production with digests" - task :compile => :environment do - dist_dir = Rails.application.config.x.webpacker[:packs_dist_dir] - result = `WEBPACK_DIST_DIR=#{dist_dir} NODE_ENV=production ./bin/webpack --json` + task compile: ["webpacker:install:verify", :environment] do + webpacker_config = Webpacker::PackageJson.webpacker + result = `WEBPACK_DIST_DIR=#{webpacker_config[:distDir]} NODE_ENV=production ./bin/webpack` unless $?.success? - puts JSON.parse(result)['errors'] + puts JSON.parse(result)["errors"] exit! $?.exitstatus end - webpack_digests = JSON.parse(result)['assetsByChunkName'].each_with_object({}) do |(chunk, file), h| - h[chunk] = file.is_a?(Array) ? file.find {|f| REGEX_MAP !~ f } : file - end.to_json - - digests_path = Rails.application.config.x.webpacker[:digests_path] - packs_path = Rails.root.join('public', dist_dir) || File.dirname(digests_path) - packs_digests_path = digests_path || Rails.root.join(packs_path, 'digests.json') - - FileUtils.mkdir_p(packs_path) - File.open(packs_digests_path, 'w+') { |file| file.write webpack_digests } + packs_path = Rails.root.join("public", webpacker_config[:distDir]) + packs_digests_path = Rails.root.join(webpacker_config[:distPath], webpacker_config[:digestFileName]) + webpack_digests = JSON.parse(File.read(packs_digests_path)) puts "Compiled digests for all packs in #{packs_digests_path}: " puts webpack_digests @@ -28,8 +22,8 @@ namespace :webpacker do end # Compile packs after we've compiled all other assets during precompilation -if Rake::Task.task_defined?('assets:precompile') - Rake::Task['assets:precompile'].enhance do - Rake::Task['webpacker:compile'].invoke +if Rake::Task.task_defined?("assets:precompile") + Rake::Task["assets:precompile"].enhance do + Rake::Task["webpacker:compile"].invoke end end diff --git a/lib/tasks/webpacker/install.rake b/lib/tasks/webpacker/install.rake index 770070a03..658e75bad 100644 --- a/lib/tasks/webpacker/install.rake +++ b/lib/tasks/webpacker/install.rake @@ -1,4 +1,4 @@ -WEBPACKER_APP_TEMPLATE_PATH = File.expand_path('../../install/template.rb', __dir__) +WEBPACKER_APP_TEMPLATE_PATH = File.expand_path("../../install/templates/install.rb", __dir__) namespace :webpacker do desc "Install webpacker in this application" diff --git a/lib/tasks/webpacker/verify.rake b/lib/tasks/webpacker/verify.rake new file mode 100644 index 000000000..87df17784 --- /dev/null +++ b/lib/tasks/webpacker/verify.rake @@ -0,0 +1,18 @@ +require "webpacker/package_json" + +namespace :webpacker do + namespace :install do + desc "Verify if webpacker is installed and package.json is in place" + task :verify do + begin + webpacker_config = Webpacker::PackageJson.webpacker + File.read(Rails.root.join(webpacker_config[:configPath], "shared.js")) + rescue Webpacker::PackageJson::PackageJsonError, NoMethodError, Errno::ENOENT + puts "Webpack core config not found in package.json or package.json is missing. \n"\ + "Make sure webpacker:install is run successfully before " \ + "running dependent tasks" + exit! + end + end + end +end diff --git a/lib/webpacker.rb b/lib/webpacker.rb index 463f2ee7b..7ae894239 100644 --- a/lib/webpacker.rb +++ b/lib/webpacker.rb @@ -1,4 +1,4 @@ module Webpacker end -require 'webpacker/railtie' if defined?(Rails) +require "webpacker/railtie" if defined?(Rails) diff --git a/lib/webpacker/digests.rb b/lib/webpacker/digests.rb deleted file mode 100644 index ccf74b833..000000000 --- a/lib/webpacker/digests.rb +++ /dev/null @@ -1,42 +0,0 @@ -# Singleton registry for accessing the digested filenames computed by Webpack in production mode. -# This allows javascript_pack_tag to take a reference to, say, "calendar.js" and turn it into -# "calendar-1016838bab065ae1e314.js". These digested filenames are what enables you to long-term -# cache things in production. -class Webpacker::Digests - class DigestError < StandardError; end - - class_attribute :instance - - class << self - def load(path) - self.instance = new(path) - end - - def lookup(name) - if instance - instance.lookup(name).presence || raise(DigestError.new("Can't find #{name} in #{instance.inspect}")) - else - raise DigestError.new("Webpacker::Digests.load(path) must be called first") - end - end - end - - def initialize(path) - @path = path - @digests = load - end - - def lookup(name) - @digests[name.to_s] - end - - private - def load - if File.exist?(@path) - JSON.parse(File.read(@path)) - else - Rails.logger.info "Didn't find any digests file at #{@path}. You must first compile the packs via rails webpacker:compile" - {} - end - end -end diff --git a/lib/webpacker/helper.rb b/lib/webpacker/helper.rb index 5875729c0..fa8e37644 100644 --- a/lib/webpacker/helper.rb +++ b/lib/webpacker/helper.rb @@ -1,8 +1,9 @@ -require 'webpacker/source' +require "webpacker/source" +require "webpacker/package_json" module Webpacker::Helper # Creates a script tag that references the named pack file, as compiled by Webpack per the entries list - # in config/webpack/shared.js. By default, this list is auto-generated to match everything in + # 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. # # Examples: @@ -15,6 +16,34 @@ module Webpacker::Helper # <%= javascript_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => # def javascript_pack_tag(name, **options) - javascript_include_tag(Webpacker::Source.new(name).path, **options) + filename = File.basename(name, ".js") + filename += ".js" + javascript_include_tag(Webpacker::Source.new(filename).path, **options) + end + + # Creates a link tag that references the named pack file, as compiled by Webpack per the entries list + # 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. + # + # Examples: + # + # # In development mode: + # <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => + # + # + # # In production mode: + # <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => + # + def stylesheet_pack_tag(name, **options) + webpacker_config = Webpacker::PackageJson.webpacker + unless webpacker_config[:assets] + raise StandardError, + "Stylesheet support is not enabled. Install using " \ + "webpacker:install:assets" + end + + filename = File.basename(name, ".css") + filename += ".css" + stylesheet_link_tag(Webpacker::Source.new(filename).path, **options) end end diff --git a/lib/webpacker/manifest.rb b/lib/webpacker/manifest.rb new file mode 100644 index 000000000..ecd235517 --- /dev/null +++ b/lib/webpacker/manifest.rb @@ -0,0 +1,53 @@ +# Singleton registry for accessing the packs path using generated manifest. +# This allows javascript_pack_tag or stylesheet_pack_tag to take a reference to, +# say, "calendar.js" or "calendar.css" and turn it into "/packs/calendar.js" or +# "/packs/calendar.css" in development. In production mode, it returns digested +# files, # "/packs/calendar-1016838bab065ae1e314.js" and +# "/packs/calendar-1016838bab065ae1e314.css" for long-term caching + +class Webpacker::Manifest + class ManifestError < StandardError; end + + class_attribute :instance + + class << self + def load(path = digests_path) + self.instance = new(path) + end + + def lookup(name) + load if Rails.env.development? + + if instance + instance.lookup(name).presence || raise(ManifestError.new("Can't find #{name} in #{instance.inspect}. Try reloading in case it's still compiling!")) + else + raise ManifestError.new("Webpacker::Manifest.load(path) must be called first") + end + end + + def digests_path + webpacker_config = Webpacker::PackageJson.webpacker + Rails.root.join(webpacker_config[:distPath], webpacker_config[:digestFileName]) + end + end + + def lookup(name) + @digests[name.to_s] + end + + private + + def initialize(path) + @path = path + @digests = load + end + + def load + if File.exist?(@path) + JSON.parse(File.read(@path)) + else + Rails.logger.info "Didn't find any digests file at #{@path}. You must first compile the packs via rails webpacker:compile" + {} + end + end +end diff --git a/lib/webpacker/package_json.rb b/lib/webpacker/package_json.rb new file mode 100644 index 000000000..6d81ea718 --- /dev/null +++ b/lib/webpacker/package_json.rb @@ -0,0 +1,58 @@ +# Loads the package.json file from app root to read the webpacker configuration + +class Webpacker::PackageJson + class PackageJsonError < StandardError; end + class_attribute :instance + attr_accessor :config + + class << self + def load(path = Rails.root.join("package.json")) + self.instance = new(path) + end + + def config + load if Rails.env.development? + instance.config + end + + def scripts + config[:scripts] + end + + def webpacker + begin + config[:webpacker] + rescue NoMethodError + raise PackageJsonError, "Error: Webpacker core config not found in package.json." \ + " Make sure webpacker:install is run successfully" + end + end + + def dev_server + begin + config[:devServer] + rescue NoMethodError + raise PackageJsonError, "Error: webpack-dev-server config not found in package.json." \ + " Make sure webpacker:install is run successfully" + end + end + end + + private + + def initialize(path) + @path = path + @config = load + end + + def load + if File.exist?(@path) + package_json = JSON.parse(File.read(@path)) + HashWithIndifferentAccess.new(package_json) + else + Rails.logger.info "Didn't find any package.json file at #{@path}. " \ + "You must first install webpacker via rails webpacker:install" + {} + end + end +end diff --git a/lib/webpacker/railtie.rb b/lib/webpacker/railtie.rb index 2b096a25b..952d48e47 100644 --- a/lib/webpacker/railtie.rb +++ b/lib/webpacker/railtie.rb @@ -1,7 +1,6 @@ -require 'rails/railtie' +require "rails/railtie" -require 'webpacker/helper' -require 'webpacker/digests' +require "webpacker/helper" class Webpacker::Engine < ::Rails::Engine initializer :webpacker do |app| @@ -9,18 +8,17 @@ class Webpacker::Engine < ::Rails::Engine ActionController::Base.helper Webpacker::Helper end - app.config.x.webpacker[:packs_dist_dir] ||= 'packs' - app.config.x.webpacker[:packs_dist_path] ||= \ - "/#{app.config.x.webpacker[:packs_dist_dir]}" + # Load config from package.json or initialise with defaults + # when running rails webpacker:install + Webpacker::PackageJson.load + webpacker_config = Webpacker::PackageJson.webpacker - if app.config.x.webpacker[:digesting] - app.config.x.webpacker[:digests_path] ||= \ - Rails.root.join('public', - app.config.x.webpacker[:packs_dist_dir], - 'digests.json') - - Webpacker::Digests.load \ - app.config.x.webpacker[:digests_path] + if !(webpacker_config && webpacker_config[:distPath]) + webpacker_config = { distPath: "public/packs", digestFileName: "digests.json" } end + + Webpacker::Manifest.load( + Rails.root.join(webpacker_config[:distPath], webpacker_config[:digestFileName]) + ) end end diff --git a/lib/webpacker/source.rb b/lib/webpacker/source.rb index 18ab4de1c..d71907970 100644 --- a/lib/webpacker/source.rb +++ b/lib/webpacker/source.rb @@ -1,38 +1,32 @@ -# Translates a logical reference for a pack source into the final path needed in the HTML. -# This translation takes into account whether digesting is configured to happen, which it -# is by default in the production environment (as set via -# `Rails.configuration.x.webpacker[:digesting] = true`). +require "webpacker/manifest" +require "webpacker/package_json" + +# Translates a logical reference for a pack source into the final +# path needed in the HTML using generated digests.json manifest. class Webpacker::Source - def initialize(name) - @name = name + class SourceError < StandardError; end + + def initialize(filename) + @filename = filename end def path - if config[:dev_server_host].present? - "#{config[:dev_server_host]}/#{filename}" - elsif config[:digesting] - File.join(dist_path, digested_filename) + if Rails.env.development? && dev_server_enabled? + "http://#{dev_server[:host]}:#{dev_server[:port]}/#{filename}" else - File.join(dist_path, filename) + Webpacker::Manifest.lookup(filename) end end private - attr_accessor :name - def config - Rails.configuration.x.webpacker - end - - def digested_filename - Webpacker::Digests.lookup(name) - end + attr_accessor :filename - def dist_path - config[:packs_dist_path] + def dev_server + Webpacker::PackageJson.dev_server end - def filename - "#{name}.js" + def dev_server_enabled? + ENV["DEV_SERVER_ENABLED"] || dev_server[:enabled] end end diff --git a/lib/webpacker/version.rb b/lib/webpacker/version.rb new file mode 100644 index 000000000..24b5532d5 --- /dev/null +++ b/lib/webpacker/version.rb @@ -0,0 +1,3 @@ +module Webpacker + VERSION = "1.0".freeze +end diff --git a/webpacker.gemspec b/webpacker.gemspec index 66f05570d..d9370ac97 100644 --- a/webpacker.gemspec +++ b/webpacker.gemspec @@ -1,20 +1,39 @@ +$:.push File.expand_path("../lib", __FILE__) +require "webpacker/version" + Gem::Specification.new do |s| - s.name = 'webpacker' - s.version = '1.0' - s.authors = 'David Heinemeier Hansson' - s.email = 'david@basecamp.com' - s.summary = 'Use Webpack to manage app-like JavaScript modules in Rails' - s.homepage = 'https://github.com/rails/webpacker' - s.license = 'MIT' + s.name = "webpacker" + s.version = Webpacker::VERSION + s.authors = "David Heinemeier Hansson" + s.email = "david@basecamp.com" + s.summary = "Use Webpack to manage app-like JavaScript modules in Rails" + s.homepage = "https://github.com/rails/webpacker" + s.license = "MIT" - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = ">= 1.9.3" - s.add_dependency 'activesupport', '>= 4.2' - s.add_dependency 'multi_json', '~> 1.2' - s.add_dependency 'railties', '>= 4.2' + s.add_dependency "activesupport", ">= 4.2" + s.add_dependency "multi_json", "~> 1.2" + s.add_dependency "railties", ">= 4.2" - s.add_development_dependency 'bundler', '~> 1.12' + s.add_development_dependency "bundler", "~> 1.12" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- test/*`.split("\n") + s.post_install_message = %{ + Webpacker installed! You now need to setup webpacker using + following command - bundle exec rails webpacker:install + + After installation, you can link example javascript app pack available in + application/javascript using this helper in your view, + + <%= javascript_pack_tag 'application' %> + + Don't forget to update the name of your app and description in package.json + + To check, list of commands available, run - bundle exec rails webpacker + + Important Notice: Your package.json file contains configuration for webpacker. + Modify as needed, but do not remove otherwise your app will break. + } end From 769cd06d8b388d3cda359fdbf64321b0edb8692e Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Tue, 7 Mar 2017 16:34:34 +0000 Subject: [PATCH 02/27] Add stylesheet_pack_tag helper doc to readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 8413d0c62..201348d46 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ have them properly compiled automatically. ```js // React component example +// app/javascripts/packs/hello_react.jsx import React from 'react' import ReactDOM from 'react-dom' import clockIcon from '../counter/images/clock.png' @@ -172,6 +173,12 @@ const Hello = props => ( ) ``` +and then within your view, include the `stylesheet_pack_tag` with the name of your pack, + +```erb +<%= stylesheet_pack_tag 'hello_react' %> +``` + ## Ready for React To use Webpacker with React, just create a new app with `rails new myapp --webpack=react` (or run `rails webpacker:install:react` on a Rails app already setup with webpacker), and all the relevant dependencies From 57723828ad7df478403e971ceaa8fd6df3585253 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Sun, 12 Mar 2017 16:22:18 +0000 Subject: [PATCH 03/27] Fix aesthetics based on code review Take production into consideration for static assets Just use json.parse to validate the object Enable dev server by default --- README.md | 2 +- lib/install/bin/webpack-dev-server.tt | 19 ++++--------- lib/install/bin/webpack.tt | 9 +++---- lib/install/config/assets.js | 4 +-- ...velopment.hot.js => development.server.js} | 0 lib/install/config/webpack/shared.js | 18 ++++++------- lib/install/node/package.json | 4 +-- lib/install/templates/assets.rb | 27 +++++++++---------- lib/tasks/installers/angular.rake | 4 +-- lib/tasks/installers/react.rake | 4 +-- lib/tasks/installers/vue.rake | 4 +-- lib/tasks/webpacker/compile.rake | 8 +++--- lib/tasks/webpacker/verify.rake | 6 ++--- .../{package_json.rb => configuration.rb} | 13 ++++----- lib/webpacker/helper.rb | 16 ++++------- lib/webpacker/manifest.rb | 5 ++-- lib/webpacker/railtie.rb | 8 +++--- lib/webpacker/source.rb | 7 +++-- webpacker.gemspec | 16 ----------- 19 files changed, 66 insertions(+), 108 deletions(-) rename lib/install/config/webpack/{development.hot.js => development.server.js} (100%) rename lib/webpacker/{package_json.rb => configuration.rb} (63%) diff --git a/README.md b/README.md index 201348d46..a03fc4e8b 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ By default, webpacker provides simple conventions for where the webpack configs, "nodeModulesPath": "node_modules", "distDir": "packs", "distPath": "public/packs", - "digestFileName": "digests.json" + "manifestFileName": "manifest.json" }, "_comment": "Configure webpack-dev-server", "devServer": { diff --git a/lib/install/bin/webpack-dev-server.tt b/lib/install/bin/webpack-dev-server.tt index e5aa45da3..499376592 100644 --- a/lib/install/bin/webpack-dev-server.tt +++ b/lib/install/bin/webpack-dev-server.tt @@ -11,23 +11,14 @@ ENV['NODE_ENV'] ||= RAILS_ENV NODE_ENV = ENV['NODE_ENV'] APP_PATH = File.expand_path('../', __dir__) -ESCAPED_APP_PATH = APP_PATH.shellescape begin - package_json = File.read(File.join(APP_PATH, 'package.json')) - config = JSON.parse(package_json) - - NODE_MODULES_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['nodeModulesPath'])}" - WEBPACK_CONFIG_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['configPath'])}" - PACKS_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['distPath'])}" + config = JSON.parse(File.read(File.join(APP_PATH, 'package.json'))) + NODE_MODULES_PATH = File.join(APP_PATH.shellescape, config['webpacker']['nodeModulesPath']) + WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, config['webpacker']['configPath']) + PACKS_PATH = File.join(APP_PATH.shellescape, config['webpacker']['distPath']) WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack-dev-server" - WEBPACK_CONFIG = "#{WEBPACK_CONFIG_PATH}/development.hot.js" - - if config['devServer'] && !config['devServer']['enabled'] - puts "Error: If you want to use webpack-dev-server, you will need to enable " \ - "webpack-dev-server from your package.json { devServer: { enabled: true } }" - exit! - end + WEBPACK_CONFIG = "#{WEBPACK_CONFIG_PATH}/development.server.js" rescue Errno::ENOENT, NoMethodError puts 'Package.json or [webpacker, devServer] config key not found.' puts 'Please run bundle exec rails webpacker:install to install webpacker' diff --git a/lib/install/bin/webpack.tt b/lib/install/bin/webpack.tt index 0e3378f38..87bdfeb0c 100644 --- a/lib/install/bin/webpack.tt +++ b/lib/install/bin/webpack.tt @@ -11,14 +11,11 @@ ENV['NODE_ENV'] ||= RAILS_ENV NODE_ENV = ENV['NODE_ENV'] APP_PATH = File.expand_path('../', __dir__) -ESCAPED_APP_PATH = APP_PATH.shellescape begin - package_json = File.read(File.join(APP_PATH, 'package.json')) - config = JSON.parse(package_json) - - NODE_MODULES_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['nodeModulesPath'])}" - WEBPACK_CONFIG_PATH = "#{File.join(ESCAPED_APP_PATH, config['webpacker']['configPath'])}" + config = JSON.parse(File.read(File.join(APP_PATH, 'package.json'))) + NODE_MODULES_PATH = File.join(APP_PATH.shellescape, config['webpacker']['nodeModulesPath']) + WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, config['webpacker']['configPath']) rescue Errno::ENOENT, NoMethodError puts 'Package.json or [webpacker, devServer] config key not found.' puts 'Please run bundle exec rails webpacker:install to install webpacker' diff --git a/lib/install/config/assets.js b/lib/install/config/assets.js index 92bc6f6eb..2b0a8c631 100644 --- a/lib/install/config/assets.js +++ b/lib/install/config/assets.js @@ -7,7 +7,6 @@ const sharedConfig = require('./shared.js') const { devServer } = require('../../package.json') const production = process.env.NODE_ENV === 'production' -const hotServerAddr = `http://${devServer.host}:${devServer.port}/` module.exports = { module: { @@ -24,7 +23,8 @@ module.exports = { use: [{ loader: 'file-loader', options: { - publicPath: devServer.enabled ? hotServerAddr : `/${sharedConfig.distDir}/`, + publicPath: !production && devServer.enabled ? + `http://${devServer.host}:${devServer.port}/` : `/${sharedConfig.distDir}/`, name: production ? '[name]-[hash].[ext]' : '[name].[ext]' } }] diff --git a/lib/install/config/webpack/development.hot.js b/lib/install/config/webpack/development.server.js similarity index 100% rename from lib/install/config/webpack/development.hot.js rename to lib/install/config/webpack/development.server.js diff --git a/lib/install/config/webpack/shared.js b/lib/install/config/webpack/shared.js index dc36c375b..ee7e0f753 100644 --- a/lib/install/config/webpack/shared.js +++ b/lib/install/config/webpack/shared.js @@ -6,13 +6,13 @@ const process = require('process') const glob = require('glob') const ManifestPlugin = require('webpack-manifest-plugin') const extname = require('path-complete-extname') -const { webpacker } = require('../../package.json') - -const srcPath = webpacker.srcPath -const distDir = webpacker.distDir -const distPath = webpacker.distPath -const nodeModulesPath = webpacker.nodeModulesPath -const digestFileName = webpacker.digestFileName +const { + srcPath, + distDir, + distPath, + nodeModulesPath, + manifestFileName +} = require('../../package.json').webpacker const config = { entry: glob.sync(path.join(srcPath, distDir, '*.js*')).reduce( @@ -52,9 +52,9 @@ const config = { }, plugins: [ - new webpack.EnvironmentPlugin(Object.keys(process.env)), + new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(process.env))), new ManifestPlugin({ - fileName: digestFileName, + fileName: manifestFileName, publicPath: `/${distDir}/` }) ], diff --git a/lib/install/node/package.json b/lib/install/node/package.json index 8f68fd5cb..d9dc2869a 100644 --- a/lib/install/node/package.json +++ b/lib/install/node/package.json @@ -21,12 +21,12 @@ "_comment": "If you rename distDir, please update distPath too", "distDir": "packs", "distPath": "public/packs", - "digestFileName": "digests.json" + "manifestFileName": "manifest.json" }, "_comment": "Configure webpack-dev-server", "devServer": { - "enabled": false, + "enabled": true, "host": "localhost", "port": "8080", "compress": true diff --git a/lib/install/templates/assets.rb b/lib/install/templates/assets.rb index a7da8a0d5..77f07e1f6 100644 --- a/lib/install/templates/assets.rb +++ b/lib/install/templates/assets.rb @@ -2,27 +2,25 @@ # with webpack require "json" -require "webpacker/package_json" +require "webpacker/configuration" # Use existing package.json # Add new options for assets begin - package_json = Webpacker::PackageJson.config - package_json[:scripts][:postinstall] = "npm rebuild node-sass" - package_json[:webpacker][:assets] = true + config = Webpacker::Configuration.config + config[:scripts][:postinstall] = "npm rebuild node-sass" + config[:webpacker][:assets] = true rescue NoMethodError - puts "Error: Webpacker core config and scripts key not found in package.json." \ - " Make sure webpacker:install is run successfully" + puts "Error: Webpacker core config and scripts key not found in package.json. Make sure webpacker:install is run successfully" exit! end #Β Write to package.json File.open(Rails.root.join("package.json"), "w+") do |file| - file.write(JSON.pretty_generate(package_json)) + file.write(JSON.pretty_generate(config)) end -copy_file "#{File.expand_path("..", __dir__)}/config/assets.js", - "config/webpack/assets.js" +copy_file "#{File.expand_path("..", __dir__)}/config/assets.js", "config/webpack/assets.js" assets_webpack_config = <<-EOS const { webpacker } = require('../../package.json') @@ -34,12 +32,11 @@ EOS insert_into_file "config/webpack/development.js", - assets_webpack_config, - after: "const sharedConfig = require('./shared.js')\n" + assets_webpack_config, + after: "const sharedConfig = require('./shared.js')\n" insert_into_file "config/webpack/production.js", - assets_webpack_config, - after: "const sharedConfig = require('./shared.js')\n" + assets_webpack_config, + after: "const sharedConfig = require('./shared.js')\n" -run "./bin/yarn add extract-text-webpack-plugin node-sass file-loader " \ - "sass-loader css-loader style-loader " +run "./bin/yarn add extract-text-webpack-plugin node-sass file-loader sass-loader css-loader style-loader" diff --git a/lib/tasks/installers/angular.rake b/lib/tasks/installers/angular.rake index 9aa5c8dec..89aee3763 100644 --- a/lib/tasks/installers/angular.rake +++ b/lib/tasks/installers/angular.rake @@ -1,10 +1,10 @@ -require "webpacker/package_json" +require "webpacker/configuration" namespace :webpacker do namespace :install do desc "Install everything needed for Angular" task angular: ["webpacker:install:verify"] do - webpacker_config = Webpacker::PackageJson.webpacker + webpacker_config = Webpacker::Configuration.webpacker config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") config = File.read(config_path) diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index fa584cf65..9c2aecaa8 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -1,10 +1,10 @@ -require "webpacker/package_json" +require "webpacker/configuration" namespace :webpacker do namespace :install do desc "Install everything needed for react" task react: ["webpacker:install:verify"] do - webpacker_config = Webpacker::PackageJson.webpacker + webpacker_config = Webpacker::Configuration.webpacker config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") config = File.read(config_path) diff --git a/lib/tasks/installers/vue.rake b/lib/tasks/installers/vue.rake index d2b858ee4..cb815ac67 100644 --- a/lib/tasks/installers/vue.rake +++ b/lib/tasks/installers/vue.rake @@ -1,10 +1,10 @@ -require "webpacker/package_json" +require "webpacker/configuration" namespace :webpacker do namespace :install do desc "Install everything needed for Vue" task vue: ["webpacker:install:verify"] do - webpacker_config = Webpacker::PackageJson.webpacker + webpacker_config = Webpacker::Configuration.webpacker config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") config = File.read(config_path) diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index b77300195..8199987ba 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -1,11 +1,11 @@ -require "webpacker/package_json" +require "webpacker/configuration" REGEX_MAP = /\A.*\.map\z/ namespace :webpacker do desc "Compile javascript packs using webpack for production with digests" task compile: ["webpacker:install:verify", :environment] do - webpacker_config = Webpacker::PackageJson.webpacker - result = `WEBPACK_DIST_DIR=#{webpacker_config[:distDir]} NODE_ENV=production ./bin/webpack` + webpacker_config = Webpacker::Configuration.webpacker + result = `NODE_ENV=production ./bin/webpack` unless $?.success? puts JSON.parse(result)["errors"] @@ -13,7 +13,7 @@ namespace :webpacker do end packs_path = Rails.root.join("public", webpacker_config[:distDir]) - packs_digests_path = Rails.root.join(webpacker_config[:distPath], webpacker_config[:digestFileName]) + packs_digests_path = Rails.root.join(webpacker_config[:distPath], webpacker_config[:manifestFileName]) webpack_digests = JSON.parse(File.read(packs_digests_path)) puts "Compiled digests for all packs in #{packs_digests_path}: " diff --git a/lib/tasks/webpacker/verify.rake b/lib/tasks/webpacker/verify.rake index 87df17784..a2e35b47e 100644 --- a/lib/tasks/webpacker/verify.rake +++ b/lib/tasks/webpacker/verify.rake @@ -1,13 +1,13 @@ -require "webpacker/package_json" +require "webpacker/configuration" namespace :webpacker do namespace :install do desc "Verify if webpacker is installed and package.json is in place" task :verify do begin - webpacker_config = Webpacker::PackageJson.webpacker + webpacker_config = Webpacker::Configuration.webpacker File.read(Rails.root.join(webpacker_config[:configPath], "shared.js")) - rescue Webpacker::PackageJson::PackageJsonError, NoMethodError, Errno::ENOENT + rescue Webpacker::Configuration::NotFoundError, NoMethodError, Errno::ENOENT puts "Webpack core config not found in package.json or package.json is missing. \n"\ "Make sure webpacker:install is run successfully before " \ "running dependent tasks" diff --git a/lib/webpacker/package_json.rb b/lib/webpacker/configuration.rb similarity index 63% rename from lib/webpacker/package_json.rb rename to lib/webpacker/configuration.rb index 6d81ea718..e7b0c0bdd 100644 --- a/lib/webpacker/package_json.rb +++ b/lib/webpacker/configuration.rb @@ -1,7 +1,7 @@ # Loads the package.json file from app root to read the webpacker configuration -class Webpacker::PackageJson - class PackageJsonError < StandardError; end +class Webpacker::Configuration + class NotFoundError < StandardError; end class_attribute :instance attr_accessor :config @@ -23,8 +23,7 @@ def webpacker begin config[:webpacker] rescue NoMethodError - raise PackageJsonError, "Error: Webpacker core config not found in package.json." \ - " Make sure webpacker:install is run successfully" + raise NotFoundError, "Error: Webpacker core config not found in package.json. Make sure webpacker:install is run successfully" end end @@ -32,8 +31,7 @@ def dev_server begin config[:devServer] rescue NoMethodError - raise PackageJsonError, "Error: webpack-dev-server config not found in package.json." \ - " Make sure webpacker:install is run successfully" + raise NotFoundError, "Error: webpack-dev-server config not found in package.json. Make sure webpacker:install is run successfully" end end end @@ -47,8 +45,7 @@ def initialize(path) def load if File.exist?(@path) - package_json = JSON.parse(File.read(@path)) - HashWithIndifferentAccess.new(package_json) + HashWithIndifferentAccess.new(JSON.parse(File.read(@path))) else Rails.logger.info "Didn't find any package.json file at #{@path}. " \ "You must first install webpacker via rails webpacker:install" diff --git a/lib/webpacker/helper.rb b/lib/webpacker/helper.rb index fa8e37644..1930728ad 100644 --- a/lib/webpacker/helper.rb +++ b/lib/webpacker/helper.rb @@ -1,5 +1,5 @@ require "webpacker/source" -require "webpacker/package_json" +require "webpacker/configuration" module Webpacker::Helper # Creates a script tag that references the named pack file, as compiled by Webpack per the entries list @@ -16,9 +16,7 @@ module Webpacker::Helper # <%= javascript_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => # def javascript_pack_tag(name, **options) - filename = File.basename(name, ".js") - filename += ".js" - javascript_include_tag(Webpacker::Source.new(filename).path, **options) + javascript_include_tag(Webpacker::Source.new("#{File.basename(name, '.js')}.js").path, **options) end # Creates a link tag that references the named pack file, as compiled by Webpack per the entries list @@ -35,15 +33,11 @@ def javascript_pack_tag(name, **options) # <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => # def stylesheet_pack_tag(name, **options) - webpacker_config = Webpacker::PackageJson.webpacker + webpacker_config = Webpacker::Configuration.webpacker unless webpacker_config[:assets] - raise StandardError, - "Stylesheet support is not enabled. Install using " \ - "webpacker:install:assets" + raise StandardError, "Stylesheet support isn't enabled. Install using - webpacker:install:assets" end - filename = File.basename(name, ".css") - filename += ".css" - stylesheet_link_tag(Webpacker::Source.new(filename).path, **options) + stylesheet_link_tag(Webpacker::Source.new("#{File.basename(name, '.css')}.css").path, **options) end end diff --git a/lib/webpacker/manifest.rb b/lib/webpacker/manifest.rb index ecd235517..964a21668 100644 --- a/lib/webpacker/manifest.rb +++ b/lib/webpacker/manifest.rb @@ -26,8 +26,8 @@ def lookup(name) end def digests_path - webpacker_config = Webpacker::PackageJson.webpacker - Rails.root.join(webpacker_config[:distPath], webpacker_config[:digestFileName]) + webpacker_config = Webpacker::Configuration.webpacker + Rails.root.join(webpacker_config[:distPath], webpacker_config[:manifestFileName]) end end @@ -36,7 +36,6 @@ def lookup(name) end private - def initialize(path) @path = path @digests = load diff --git a/lib/webpacker/railtie.rb b/lib/webpacker/railtie.rb index 952d48e47..839269be5 100644 --- a/lib/webpacker/railtie.rb +++ b/lib/webpacker/railtie.rb @@ -10,15 +10,15 @@ class Webpacker::Engine < ::Rails::Engine # Load config from package.json or initialise with defaults # when running rails webpacker:install - Webpacker::PackageJson.load - webpacker_config = Webpacker::PackageJson.webpacker + Webpacker::Configuration.load + webpacker_config = Webpacker::Configuration.webpacker if !(webpacker_config && webpacker_config[:distPath]) - webpacker_config = { distPath: "public/packs", digestFileName: "digests.json" } + webpacker_config = { distPath: "public/packs", manifestFileName: "manifest.json" } end Webpacker::Manifest.load( - Rails.root.join(webpacker_config[:distPath], webpacker_config[:digestFileName]) + Rails.root.join(webpacker_config[:distPath], webpacker_config[:manifestFileName]) ) end end diff --git a/lib/webpacker/source.rb b/lib/webpacker/source.rb index d71907970..404141d81 100644 --- a/lib/webpacker/source.rb +++ b/lib/webpacker/source.rb @@ -1,8 +1,8 @@ require "webpacker/manifest" -require "webpacker/package_json" +require "webpacker/configuration" # Translates a logical reference for a pack source into the final -# path needed in the HTML using generated digests.json manifest. +# path needed in the HTML using generated manifest.json manifest. class Webpacker::Source class SourceError < StandardError; end @@ -19,11 +19,10 @@ def path end private - attr_accessor :filename def dev_server - Webpacker::PackageJson.dev_server + Webpacker::Configuration.dev_server end def dev_server_enabled? diff --git a/webpacker.gemspec b/webpacker.gemspec index d9370ac97..2c6a9b49b 100644 --- a/webpacker.gemspec +++ b/webpacker.gemspec @@ -20,20 +20,4 @@ Gem::Specification.new do |s| s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- test/*`.split("\n") - s.post_install_message = %{ - Webpacker installed! You now need to setup webpacker using - following command - bundle exec rails webpacker:install - - After installation, you can link example javascript app pack available in - application/javascript using this helper in your view, - - <%= javascript_pack_tag 'application' %> - - Don't forget to update the name of your app and description in package.json - - To check, list of commands available, run - bundle exec rails webpacker - - Important Notice: Your package.json file contains configuration for webpacker. - Modify as needed, but do not remove otherwise your app will break. - } end From 443eef4ec507b59defe3a2d80f9a8d7f1aec0930 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Tue, 14 Mar 2017 10:48:25 +0000 Subject: [PATCH 04/27] Refactor configuration and use that to fetch configs Add a dev server class Cleanup redudant options Fix syntax Cleanup paths using configuration and code aesthetics Setup separate configuration using yml and refactor Cleanup new line Cleanup new line Change key name Fix missing paranthesis Pass options to source class as well Extract singleton methods and refactor config paths Fix exception classes Add warning if webpack-dev-server is on Use hash.fetch and default values Remove redundant package.json Organize method name Cleanup comments and readme Revert back to template.rb Rename load to file_loader Fix require name Fix install path Split webpacker config into two files and configure accordingly Add separate config for dev_server and paths Configure dev_server class to load configuration from separate file Fix minor typos Update readme Fix path Just use directory name instead of path Reorganise method names Load dev server config only in development Return false for dev server in other environments except development Use compute_asset_extname from rails helper Remove parsing error Move content-base option from binstub to js side Fix sentence Put back parsing error --- README.md | 149 ++++++++++-------- lib/install/bin/webpack-dev-server.tt | 38 ++--- lib/install/bin/webpack.tt | 35 ++-- lib/install/config/assets.js | 40 ----- lib/install/config/webpack/configuration.js | 20 +++ lib/install/config/webpack/dev_server.yml | 4 + lib/install/config/webpack/development.js | 2 +- .../config/webpack/development.server.js | 17 +- lib/install/config/webpack/paths.yml | 6 + lib/install/config/webpack/production.js | 2 +- lib/install/config/webpack/shared.js | 56 ++++--- lib/install/node/package.json | 34 ---- lib/install/template.rb | 19 +++ lib/install/templates/assets.rb | 42 ----- lib/install/templates/install.rb | 20 --- lib/tasks/installers/angular.rake | 11 +- lib/tasks/installers/react.rake | 15 +- lib/tasks/installers/vue.rake | 11 +- lib/tasks/webpacker.rake | 9 +- lib/tasks/webpacker/assets.rake | 14 -- lib/tasks/webpacker/compile.rake | 9 +- lib/tasks/webpacker/install.rake | 2 +- lib/tasks/webpacker/verify.rake | 9 +- lib/webpacker/configuration.rb | 59 +++---- lib/webpacker/dev_server.rb | 32 ++++ lib/webpacker/file_loader.rb | 24 +++ lib/webpacker/helper.rb | 30 +++- lib/webpacker/manifest.rb | 44 ++---- lib/webpacker/railtie.rb | 14 +- lib/webpacker/source.rb | 27 ++-- 30 files changed, 369 insertions(+), 425 deletions(-) delete mode 100644 lib/install/config/assets.js create mode 100644 lib/install/config/webpack/configuration.js create mode 100644 lib/install/config/webpack/dev_server.yml create mode 100644 lib/install/config/webpack/paths.yml delete mode 100644 lib/install/node/package.json create mode 100644 lib/install/template.rb delete mode 100644 lib/install/templates/assets.rb delete mode 100644 lib/install/templates/install.rb delete mode 100644 lib/tasks/webpacker/assets.rake create mode 100644 lib/webpacker/dev_server.rb create mode 100644 lib/webpacker/file_loader.rb diff --git a/README.md b/README.md index a03fc4e8b..f58a57f4c 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,6 @@ as Webpack calls it). Let's say you're building a calendar. Your structure could look like this: -```erb -<%# app/views/layout/application.html.erb %> -<%= javascript_pack_tag 'calendar' %> -``` - ```js // app/javascript/packs/calendar.js require('calendar') @@ -74,49 +69,98 @@ require('calendar') ``` app/javascript/calendar/index.js // gets loaded by require('calendar') app/javascript/calendar/components/grid.jsx +app/javascript/calendar/styles/grid.sass app/javascript/calendar/models/month.js ``` -But it could also look a million other ways. The only convention that Webpacker enforces is the -one where entry points are automatically configured by the files in `app/javascript/packs`. +```erb +<%# app/views/layout/application.html.erb %> +<%= javascript_pack_tag 'calendar' %> +<%= stylesheet_pack_tag 'calendar' %> +``` + +But it could also look a million other ways. ## Advanced Configuration -By default, webpacker provides simple conventions for where the webpack configs, javascript app files and compiled webpack bundles will go in your rails app, but all these are configurable from package.json that comes with webpacker. You can also configure webpack-dev-server host and port in your development environment - -```json -{ - "config": { - "_comment": "Configure webpacker internals (do not remove)", - "webpacker": { - "srcPath": "app/javascript", - "configPath": "config/webpack", - "nodeModulesPath": "node_modules", - "distDir": "packs", - "distPath": "public/packs", - "manifestFileName": "manifest.json" - }, - "_comment": "Configure webpack-dev-server", - "devServer": { - "enabled": true, - "host": "localhost", - "port": "8080", - "compress": true - } - } -} +By default, webpacker offers simple conventions for where the webpack configs, javascript app files and compiled webpack bundles will go in your rails app, +but all these options are configurable from `config/webpack/paths.yml` file. + +```yml +# config/webpack/paths.yml +paths: + src_path: app/javascript + config_path: config/webpack + node_modules_path: node_modules + dist_dir: packs + dist_path: public/packs +``` + +**Note:** If you rename `packs` directory inside `app/javascript` from `packs` to `bundles`, make sure you also update your `dist_dir` and `dist_path`. + +```yml +paths: + dist_dir: bundles + dist_path: public/bundles +``` + +Similary, you can also control and configure `webpack-dev-server` settings from +`config/webpack/dev_server.yml` file + +```yml +# config/webpack/dev_server.yml +dev_server: + enabled: true + host: localhost + port: 8080 +``` + +By default, `webpack-dev-server` uses `dist_path` option specified in `paths.yml` as `contentBase`. + +**Note:** Don't forget to disable `webpack-dev-server` incase you are using +`./bin/webpack-watcher` to serve assets in development mode otherwise +you will get 404 for assets because the helper tag will use webpack-dev-server url +to serve assets instead of public directory. + +## Linking to static assets + +Static assets like images, fonts and stylesheets support is enabled out-of-box so, you can link them into your javascript app code and have them compiled automatically. + +```js +// React component example +// app/javascripts/packs/hello_react.jsx +import React from 'react' +import ReactDOM from 'react-dom' +import helloIcon from '../hello_react/images/icon.png' +import './hello-react.sass' + +const Hello = props => ( +
+ hello-icon +

Hello {props.name}!

+
+) ``` -**For ex:** if you rename `packs` directory inside `app/javascript` from `packs` to `bundles`, make sure you also update your `distDir` and `distPath`. +under the hood webpack uses [extract-text-webpack-plugin](https://github.com/webpack-contrib/extract-text-webpack-plugin) plugin to extract all the referenced styles and compile it into a separate `[pack_name].css` bundle so that within your view you can use the `stylesheet_pack_tag` helper, -```json -"webpacker": { - "distDir": "bundles", - "distPath": "public/bundles", -} +```erb +<%= stylesheet_pack_tag 'hello_react' %> ``` -**Note:** Do not delete this config otherwise your app will break, unless you really know what you're doing. +## Getting asset path + +Webpacker provides `asset_pack_path` helper to get the path of any given asset that's been compiled by webpack. + +**For ex,** if you want to create a `` or `` +for an asset used in your pack code you can reference them like this in your view, + +```erb +<%= asset_pack_path 'hello_react.css' %> +<% # => "/packs/hello_react.css" %> + +<% # => %> +``` ## Deployment @@ -134,9 +178,6 @@ Webpacker hooks up a new `webpacker:compile` task to `assets:precompile`, which ``` -**Note:** *`stylesheet_pack_tag` helper is not available out-of-the-box. -Check [statics assets](#ready-for-static-assets) support section for more details.* - ## Linking to sprockets assets It's possible to link to assets that have been precompiled by sprockets. Add the `.erb` extension @@ -151,34 +192,6 @@ var railsImagePath = "<%= helpers.image_path('rails.png') %>"; This is enabled by the `rails-erb-loader` loader rule in `config/webpack/shared.js`. -## Ready for Static Assets - -Static assets support isn't enabled out-of-the-box. To enable static assets support in your javascript app you will need to run `rails webpacker:install:assets` after you have installed webpacker. Once installed, you can -link static files like images and styles directly into your javascript app code and -have them properly compiled automatically. - -```js -// React component example -// app/javascripts/packs/hello_react.jsx -import React from 'react' -import ReactDOM from 'react-dom' -import clockIcon from '../counter/images/clock.png' -import './hello-react.sass' - -const Hello = props => ( -
- clock -

Hello {props.name}!

-
-) -``` - -and then within your view, include the `stylesheet_pack_tag` with the name of your pack, - -```erb -<%= stylesheet_pack_tag 'hello_react' %> -``` - ## Ready for React To use Webpacker with React, just create a new app with `rails new myapp --webpack=react` (or run `rails webpacker:install:react` on a Rails app already setup with webpacker), and all the relevant dependencies diff --git a/lib/install/bin/webpack-dev-server.tt b/lib/install/bin/webpack-dev-server.tt index 499376592..2661c68c8 100644 --- a/lib/install/bin/webpack-dev-server.tt +++ b/lib/install/bin/webpack-dev-server.tt @@ -1,31 +1,33 @@ <%= shebang %> $stdout.sync = true -require 'shellwords' -require 'json' +require "shellwords" +require "yaml" -ENV['RAILS_ENV'] ||= 'development' -RAILS_ENV = ENV['RAILS_ENV'] +ENV["RAILS_ENV"] ||= "development" +RAILS_ENV = ENV["RAILS_ENV"] -ENV['NODE_ENV'] ||= RAILS_ENV -NODE_ENV = ENV['NODE_ENV'] +ENV["NODE_ENV"] ||= RAILS_ENV +NODE_ENV = ENV["NODE_ENV"] -APP_PATH = File.expand_path('../', __dir__) +APP_PATH = File.expand_path("../", __dir__) +CONFIG_PATH = File.join(APP_PATH, "config/webpack/paths.yml") begin - config = JSON.parse(File.read(File.join(APP_PATH, 'package.json'))) - NODE_MODULES_PATH = File.join(APP_PATH.shellescape, config['webpacker']['nodeModulesPath']) - WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, config['webpacker']['configPath']) - PACKS_PATH = File.join(APP_PATH.shellescape, config['webpacker']['distPath']) - WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack-dev-server" - WEBPACK_CONFIG = "#{WEBPACK_CONFIG_PATH}/development.server.js" -rescue Errno::ENOENT, NoMethodError - puts 'Package.json or [webpacker, devServer] config key not found.' - puts 'Please run bundle exec rails webpacker:install to install webpacker' + config = YAML.load(File.read(CONFIG_PATH)) + + NODE_MODULES_PATH = File.join(APP_PATH.shellescape, config["paths"]["node_modules_path"]) + WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, config["paths"]["config_path"]) + + WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack-dev-server" + DEV_SERVER_CONFIG = "#{WEBPACK_CONFIG_PATH}/development.server.js" +rescue Errno::ENOENT + puts "config/webpacker/paths.yml file not found." + puts "Please run bundle exec rails webpacker:install to install webpacker" exit! end Dir.chdir(APP_PATH) do - exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} " \ - "--config #{WEBPACK_CONFIG} --content-base #{PACKS_PATH} #{ARGV.join(" ")}" + exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} --progress --color " \ + "--config #{DEV_SERVER_CONFIG}" end diff --git a/lib/install/bin/webpack.tt b/lib/install/bin/webpack.tt index 87bdfeb0c..57698150d 100644 --- a/lib/install/bin/webpack.tt +++ b/lib/install/bin/webpack.tt @@ -1,24 +1,33 @@ <%= shebang %> $stdout.sync = true -require 'shellwords' -require 'json' +require "shellwords" +require "yaml" -ENV['RAILS_ENV'] ||= 'development' -RAILS_ENV = ENV['RAILS_ENV'] +ENV["RAILS_ENV"] ||= "development" +RAILS_ENV = ENV["RAILS_ENV"] -ENV['NODE_ENV'] ||= RAILS_ENV -NODE_ENV = ENV['NODE_ENV'] +ENV["NODE_ENV"] ||= RAILS_ENV +NODE_ENV = ENV["NODE_ENV"] -APP_PATH = File.expand_path('../', __dir__) +APP_PATH = File.expand_path("../", __dir__) +CONFIG_PATH = File.join(APP_PATH, "config/webpack/paths.yml") +DEV_SERVER_CONFIG_PATH = File.join(APP_PATH, "config/webpack/dev_server.yml") begin - config = JSON.parse(File.read(File.join(APP_PATH, 'package.json'))) - NODE_MODULES_PATH = File.join(APP_PATH.shellescape, config['webpacker']['nodeModulesPath']) - WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, config['webpacker']['configPath']) + config = YAML.load(File.read(CONFIG_PATH)) + dev_server_config = YAML.load(File.read(DEV_SERVER_CONFIG_PATH)) + + NODE_MODULES_PATH = File.join(APP_PATH.shellescape, config["paths"]["node_modules_path"]) + WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, config["paths"]["config_path"]) + + if NODE_ENV == "development" && dev_server_config["dev_server"]["enabled"] + puts "Warning: webpack-dev-server is currently enabled in #{DEV_SERVER_CONFIG_PATH}. " \ + "Disable to serve assets directly from packs directory" + end rescue Errno::ENOENT, NoMethodError - puts 'Package.json or [webpacker, devServer] config key not found.' - puts 'Please run bundle exec rails webpacker:install to install webpacker' + puts "config/webpack/paths.yml or config/webpack/dev_server.yml file not found." + puts "Please run bundle exec rails webpacker:install to install webpacker" exit! end @@ -27,5 +36,5 @@ WEBPACK_CONFIG = "#{WEBPACK_CONFIG_PATH}/#{NODE_ENV}.js" Dir.chdir(APP_PATH) do exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} --config #{WEBPACK_CONFIG}" \ - " #{ARGV.join(" ")}" + " #{ARGV.join(" ")}" end diff --git a/lib/install/config/assets.js b/lib/install/config/assets.js deleted file mode 100644 index 2b0a8c631..000000000 --- a/lib/install/config/assets.js +++ /dev/null @@ -1,40 +0,0 @@ -// Handles static asset integration - like sass and images -// Note: You must restart bin/webpack-watcher for changes to take effect - -const ExtractTextPlugin = require('extract-text-webpack-plugin') -const process = require('process') -const sharedConfig = require('./shared.js') -const { devServer } = require('../../package.json') - -const production = process.env.NODE_ENV === 'production' - -module.exports = { - module: { - rules: [ - { - test: /\.(scss|sass|css)$/i, - use: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: ['css-loader', 'sass-loader'] - }) - }, - { - test: /\.(jpeg|png|gif|svg|eot|svg|ttf|woff|woff2)$/i, - use: [{ - loader: 'file-loader', - options: { - publicPath: !production && devServer.enabled ? - `http://${devServer.host}:${devServer.port}/` : `/${sharedConfig.distDir}/`, - name: production ? '[name]-[hash].[ext]' : '[name].[ext]' - } - }] - } - ] - }, - - plugins: [ - new ExtractTextPlugin( - production ? '[name]-[hash].css' : '[name].css' - ) - ] -} diff --git a/lib/install/config/webpack/configuration.js b/lib/install/config/webpack/configuration.js new file mode 100644 index 000000000..993c2c337 --- /dev/null +++ b/lib/install/config/webpack/configuration.js @@ -0,0 +1,20 @@ +// Common configuration for webpacker loaded from config/webpack/paths.yml + +const path = require('path') +const process = require('process') +const yaml = require('js-yaml') +const fs = require('fs') + +const env = process.env +const configPath = path.resolve('config', 'webpack') +const { paths } = yaml.safeLoad(fs.readFileSync(path.join(configPath, 'paths.yml'), 'utf8')) +const { dev_server } = yaml.safeLoad(fs.readFileSync(path.join(configPath, 'dev_server.yml'), 'utf8')) +const publicPath = env.NODE_ENV !== 'production' && dev_server.enabled ? + `http://${dev_server.host}:${dev_server.port}/` : `/${paths.dist_dir}/` + +module.exports = { + dev_server, + env, + paths, + publicPath +} diff --git a/lib/install/config/webpack/dev_server.yml b/lib/install/config/webpack/dev_server.yml new file mode 100644 index 000000000..dddce8547 --- /dev/null +++ b/lib/install/config/webpack/dev_server.yml @@ -0,0 +1,4 @@ +dev_server: + enabled: true + host: localhost + port: 8080 diff --git a/lib/install/config/webpack/development.js b/lib/install/config/webpack/development.js index df93d8ae7..aec3ebec6 100644 --- a/lib/install/config/webpack/development.js +++ b/lib/install/config/webpack/development.js @@ -4,7 +4,7 @@ const merge = require('webpack-merge') const sharedConfig = require('./shared.js') -module.exports = merge(sharedConfig.config, { +module.exports = merge(sharedConfig, { devtool: 'sourcemap', stats: { diff --git a/lib/install/config/webpack/development.server.js b/lib/install/config/webpack/development.server.js index 7c61ffca2..0e2f36863 100644 --- a/lib/install/config/webpack/development.server.js +++ b/lib/install/config/webpack/development.server.js @@ -1,16 +1,19 @@ // Note: You must restart bin/webpack-dev-server for changes to take effect +const path = require('path') const merge = require('webpack-merge') const devConfig = require('./development.js') -const sharedConfig = require('./shared.js') -const { devServer } = require('../../package.json') +const { dev_server, publicPath, paths } = require('./configuration.js') module.exports = merge(devConfig, { devServer: { - host: devServer.host, - port: devServer.port, - compress: devServer.compress, - publicPath: devServer.enabled ? - `http://${devServer.host}:${devServer.port}/` : `/${sharedConfig.distDir}/` + host: dev_server.host, + port: dev_server.port, + compress: true, + historyApiFallback: true, + https: false, + noInfo: true, + contentBase: path.resolve(paths.dist_path), + publicPath } }) diff --git a/lib/install/config/webpack/paths.yml b/lib/install/config/webpack/paths.yml new file mode 100644 index 000000000..62c6735b7 --- /dev/null +++ b/lib/install/config/webpack/paths.yml @@ -0,0 +1,6 @@ +paths: + src_path: app/javascript + config_path: config/webpack + node_modules_path: node_modules + dist_dir: packs + dist_path: public/packs diff --git a/lib/install/config/webpack/production.js b/lib/install/config/webpack/production.js index 31f366280..82e2e9ff6 100644 --- a/lib/install/config/webpack/production.js +++ b/lib/install/config/webpack/production.js @@ -6,7 +6,7 @@ const merge = require('webpack-merge') const CompressionPlugin = require('compression-webpack-plugin') const sharedConfig = require('./shared.js') -module.exports = merge(sharedConfig.config, { +module.exports = merge(sharedConfig, { output: { filename: '[name]-[chunkhash].js' }, plugins: [ diff --git a/lib/install/config/webpack/shared.js b/lib/install/config/webpack/shared.js index ee7e0f753..b2c183ab0 100644 --- a/lib/install/config/webpack/shared.js +++ b/lib/install/config/webpack/shared.js @@ -2,20 +2,14 @@ const webpack = require('webpack') const path = require('path') -const process = require('process') const glob = require('glob') +const ExtractTextPlugin = require('extract-text-webpack-plugin') const ManifestPlugin = require('webpack-manifest-plugin') const extname = require('path-complete-extname') -const { - srcPath, - distDir, - distPath, - nodeModulesPath, - manifestFileName -} = require('../../package.json').webpacker +const { env, paths, publicPath } = require('./configuration.js') -const config = { - entry: glob.sync(path.join(srcPath, distDir, '*.js*')).reduce( +module.exports = { + entry: glob.sync(path.join(paths.src_path, paths.dist_dir, '*.js*')).reduce( (map, entry) => { const basename = path.basename(entry, extname(entry)) const localMap = map @@ -24,7 +18,7 @@ const config = { }, {} ), - output: { filename: '[name].js', path: path.resolve(distPath) }, + output: { filename: '[name].js', path: path.resolve(paths.dist_path) }, module: { rules: [ @@ -47,35 +41,47 @@ const config = { options: { runner: 'DISABLE_SPRING=1 bin/rails runner' } + }, + { + test: /\.(scss|sass|css)$/i, + use: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: ['css-loader', 'sass-loader'] + }) + }, + { + test: /\.(jpeg|png|gif|svg|eot|svg|ttf|woff|woff2)$/i, + use: [{ + loader: 'file-loader', + options: { + publicPath, + name: env.NODE_ENV === 'production' ? '[name]-[hash].[ext]' : '[name].[ext]' + } + }] } ] }, plugins: [ - new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(process.env))), + new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(env))), + new ExtractTextPlugin( + env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css' + ), new ManifestPlugin({ - fileName: manifestFileName, - publicPath: `/${distDir}/` + fileName: 'manifest.json', + publicPath: `/${paths.dist_dir}/` }) ], resolve: { extensions: ['.js', '.coffee'], modules: [ - path.resolve(srcPath), - path.resolve(nodeModulesPath) + path.resolve(paths.src_path), + path.resolve(paths.node_modules_path) ] }, resolveLoader: { - modules: [path.resolve(nodeModulesPath)] + modules: [paths.node_modules_path] } } - -module.exports = { - srcPath, - distDir, - distPath, - nodeModulesPath, - config -} diff --git a/lib/install/node/package.json b/lib/install/node/package.json deleted file mode 100644 index d9dc2869a..000000000 --- a/lib/install/node/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "webpacker-app", - "version": "1.0.0", - "private": true, - "description": "Integrates Webpack to manage application-like JavaScript in Rails", - "main": "app/javascript/packs/index.js", - - "scripts": { - "lint": "./node_modules/eslint/bin/eslint.js lib/ --fix", - "compile": "./bin/webpack", - "build": "NODE_ENV=production ./bin/webpack", - "server": "./bin/webpack-dev-server", - "watch": "./bin/webpack-watcher" - }, - - "_comment": "Customise as required, but do not remove otherwise your app will break", - "webpacker": { - "srcPath": "app/javascript", - "configPath": "config/webpack", - "nodeModulesPath": "node_modules", - "_comment": "If you rename distDir, please update distPath too", - "distDir": "packs", - "distPath": "public/packs", - "manifestFileName": "manifest.json" - }, - - "_comment": "Configure webpack-dev-server", - "devServer": { - "enabled": true, - "host": "localhost", - "port": "8080", - "compress": true - } -} diff --git a/lib/install/template.rb b/lib/install/template.rb new file mode 100644 index 000000000..36ae1eaea --- /dev/null +++ b/lib/install/template.rb @@ -0,0 +1,19 @@ +# Setup webpacker +directory "#{__dir__}/javascript", "app/javascript" + +directory "#{__dir__}/bin", "bin" +chmod "bin", 0755 & ~File.umask, verbose: false + +directory "#{__dir__}/config/webpack", "config/webpack" + +append_to_file ".gitignore", <<-EOS +/public/packs +/node_modules +EOS + +run "./bin/yarn add webpack webpack-merge js-yaml path-complete-extname " \ +"webpack-manifest-plugin babel-loader coffee-loader coffee-script " \ +"babel-core babel-preset-env compression-webpack-plugin rails-erb-loader glob " \ +"extract-text-webpack-plugin node-sass file-loader sass-loader css-loader style-loader" + +run "./bin/yarn add --dev webpack-dev-server" diff --git a/lib/install/templates/assets.rb b/lib/install/templates/assets.rb deleted file mode 100644 index 77f07e1f6..000000000 --- a/lib/install/templates/assets.rb +++ /dev/null @@ -1,42 +0,0 @@ -# Installs modules to enable assets linking, compiling and digesting -# with webpack - -require "json" -require "webpacker/configuration" - -# Use existing package.json -# Add new options for assets -begin - config = Webpacker::Configuration.config - config[:scripts][:postinstall] = "npm rebuild node-sass" - config[:webpacker][:assets] = true -rescue NoMethodError - puts "Error: Webpacker core config and scripts key not found in package.json. Make sure webpacker:install is run successfully" - exit! -end - -#Β Write to package.json -File.open(Rails.root.join("package.json"), "w+") do |file| - file.write(JSON.pretty_generate(config)) -end - -copy_file "#{File.expand_path("..", __dir__)}/config/assets.js", "config/webpack/assets.js" - -assets_webpack_config = <<-EOS -const { webpacker } = require('../../package.json') - -if (webpacker.assets) { - const assetsConfig = require('./assets.js') - sharedConfig.config = merge(sharedConfig.config, assetsConfig) -} -EOS - -insert_into_file "config/webpack/development.js", - assets_webpack_config, - after: "const sharedConfig = require('./shared.js')\n" - -insert_into_file "config/webpack/production.js", - assets_webpack_config, - after: "const sharedConfig = require('./shared.js')\n" - -run "./bin/yarn add extract-text-webpack-plugin node-sass file-loader sass-loader css-loader style-loader" diff --git a/lib/install/templates/install.rb b/lib/install/templates/install.rb deleted file mode 100644 index 07efb8b98..000000000 --- a/lib/install/templates/install.rb +++ /dev/null @@ -1,20 +0,0 @@ -# Setup webpacker - -directory "#{File.expand_path("..", __dir__)}/node", "./" -directory "#{File.expand_path("..", __dir__)}/javascript", "app/javascript" - -directory "#{File.expand_path("..", __dir__)}/bin", "bin" -chmod "bin", 0755 & ~File.umask, verbose: false - -directory "#{File.expand_path("..", __dir__)}/config/webpack", "config/webpack" - -append_to_file ".gitignore", <<-EOS -/public/packs -/node_modules -EOS - -run "./bin/yarn add webpack webpack-merge path-complete-extname " \ -"webpack-manifest-plugin babel-loader coffee-loader coffee-script " \ -"babel-core babel-preset-env compression-webpack-plugin rails-erb-loader glob" - -run "./bin/yarn add --dev webpack-dev-server" diff --git a/lib/tasks/installers/angular.rake b/lib/tasks/installers/angular.rake index 89aee3763..1d8d2e227 100644 --- a/lib/tasks/installers/angular.rake +++ b/lib/tasks/installers/angular.rake @@ -4,25 +4,24 @@ namespace :webpacker do namespace :install do desc "Install everything needed for Angular" task angular: ["webpacker:install:verify"] do - webpacker_config = Webpacker::Configuration.webpacker - config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") - config = File.read(config_path) + shared_config_path = Webpacker::Configuration.shared_config_path + config = File.read(shared_config_path) if config.include?("ts-loader") puts "The configuration file already has a reference to ts-loader, skipping the test rule..." else - puts "Adding a loader rule to include ts-loader for .ts files in #{config_path}..." + puts "Adding a loader rule to include ts-loader for .ts files in #{shared_config_path}..." config.gsub!(/rules:(\s*\[)(\s*\{)/, "rules:\\1\\2 test: /\.ts$/, loader: 'ts-loader' },\\2") end if config =~ /["'].ts["']/ puts "The configuration file already has a reference to .ts extension, skipping the addition of this extension to the list..." else - puts "Adding '.ts' in loader extensions in #{config_path}..." + puts "Adding '.ts' in loader extensions in #{shared_config_path}..." config.gsub!(/extensions:(.*')(\s*\])/, "extensions:\\1, '.ts'\\2") end - File.write config_path, config + File.write shared_config_path, config puts "Copying Angular example to app/javascript/packs/hello_angular.js" FileUtils.copy File.expand_path("../../install/examples/angular/hello_angular.js", __dir__), diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index 9c2aecaa8..ec281ea15 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -4,25 +4,24 @@ namespace :webpacker do namespace :install do desc "Install everything needed for react" task react: ["webpacker:install:verify"] do - webpacker_config = Webpacker::Configuration.webpacker - config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") - config = File.read(config_path) + shared_config_path = Webpacker::Configuration.shared_config_path + config = File.read(shared_config_path) if config =~ /presets:\s*\[\s*\[\s*'env'/ - puts "Replacing loader presets to include react in #{config_path}" + puts "Replacing loader presets to include react in #{shared_config_path}" config.gsub!(/presets:(\s*\[)(\s*)\[(\s)*'env'/, "presets:\\1\\2'react',\\2[\\3'env'") else - puts "Couldn't automatically update loader presets in #{config_path}. Please set presets: [ 'react', [ 'env', { 'modules': false } ] ]." + puts "Couldn't automatically update loader presets in #{shared_config_path}. Please set presets: [ 'react', [ 'env', { 'modules': false } ] ]." end if config.include?("test: /\\.js(\\.erb)?$/") - puts "Replacing loader test to include react in #{config_path}" + puts "Replacing loader test to include react in #{shared_config_path}" config.gsub!("test: /\\.js(\\.erb)?$/", "test: /\\.(js|jsx)?(\\.erb)?$/") else - puts "Couldn't automatically update loader test in #{config_path}. Please set test: /\\.jsx?(\\.erb)?$/." + puts "Couldn't automatically update loader test in #{shared_config_path}. Please set test: /\\.jsx?(\\.erb)?$/." end - File.write config_path, config + File.write shared_config_path, config puts "Copying .babelrc to project directory" FileUtils.copy File.expand_path("../../install/examples/react/.babelrc", __dir__), diff --git a/lib/tasks/installers/vue.rake b/lib/tasks/installers/vue.rake index cb815ac67..361e1d841 100644 --- a/lib/tasks/installers/vue.rake +++ b/lib/tasks/installers/vue.rake @@ -4,24 +4,23 @@ namespace :webpacker do namespace :install do desc "Install everything needed for Vue" task vue: ["webpacker:install:verify"] do - webpacker_config = Webpacker::Configuration.webpacker - config_path = Rails.root.join(webpacker_config[:configPath], "shared.js") - config = File.read(config_path) + shared_config_path = Webpacker::Configuration.shared_config_path + config = File.read(shared_config_path) # Module resolution https://webpack.js.org/concepts/module-resolution/ if config.include?("'vue$':'vue/dist/vue.esm.js'") - puts "Couldn't automatically update module resolution in #{config_path}. Please set resolve { alias:{ 'vue$':'vue/dist/vue.esm.js' } }." + puts "Couldn't automatically update module resolution in #{shared_config_path}. Please set resolve { alias:{ 'vue$':'vue/dist/vue.esm.js' } }." else config.gsub!(/resolve:(\s*\{)(\s*)extensions/, "resolve:\\1\\2alias: { 'vue$':'vue/dist/vue.esm.js' },\\2extensions") end if config.include?("loader: 'vue-loader',") - puts "Couldn't automatically update vue-loader in #{config_path}. Please set { test: /.vue$/, loader: 'vue-loader', options: { loaders: { 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'}}}." + puts "Couldn't automatically update vue-loader in #{shared_config_path}. Please set { test: /.vue$/, loader: 'vue-loader', options: { loaders: { 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'}}}." else config.gsub!(/module:(\s*\{)(\s*)rules:(\s*)\[/, "module:\\1\\2rules:\\3[\\2 {\\2 test: /\.vue$/, loader: 'vue-loader',\\2 options: {\\2 loaders: { 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'}\\2 }\\2 },") end - File.write config_path, config + File.write shared_config_path, config puts "Copying the Vue example to app/javascript/packs/vue" FileUtils.copy File.expand_path("../../install/examples/vue/hello_vue.js", File.dirname(__FILE__)), diff --git a/lib/tasks/webpacker.rake b/lib/tasks/webpacker.rake index 44fab4024..7dcee289f 100644 --- a/lib/tasks/webpacker.rake +++ b/lib/tasks/webpacker.rake @@ -1,9 +1,8 @@ tasks = { - "webpacker:install" => "Installs and setup webpack with yarn", - "webpacker:compile" => "Compiles webpack bundles based on environment", - "webpacker:install:assets" => "Adds static assets(images and styles) support", - "webpacker:install:react" => "Installs and setup example react component", - "webpacker:install:vue" => "Installs and setup example vue component", + "webpacker:install" => "Installs and setup webpack with yarn", + "webpacker:compile" => "Compiles webpack bundles based on environment", + "webpacker:install:react" => "Installs and setup example react component", + "webpacker:install:vue" => "Installs and setup example vue component", "webpacker:install:angular" => "Installs and setup example angular2 component" }.freeze diff --git a/lib/tasks/webpacker/assets.rake b/lib/tasks/webpacker/assets.rake deleted file mode 100644 index d7a2ea85f..000000000 --- a/lib/tasks/webpacker/assets.rake +++ /dev/null @@ -1,14 +0,0 @@ -STATIC_APP_TEMPLATE_PATH = File.expand_path("../../install/templates/assets.rb", __dir__) - -namespace :webpacker do - namespace :install do - desc "Add static assets(images and styles) support to webpacker" - task assets: ["webpacker:install:verify"] do - if Rails::VERSION::MAJOR >= 5 - exec "./bin/rails app:template LOCATION=#{STATIC_APP_TEMPLATE_PATH}" - else - exec "./bin/rake rails:template LOCATION=#{STATIC_APP_TEMPLATE_PATH}" - end - end - end -end diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index 8199987ba..3cd38e991 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -4,7 +4,6 @@ REGEX_MAP = /\A.*\.map\z/ namespace :webpacker do desc "Compile javascript packs using webpack for production with digests" task compile: ["webpacker:install:verify", :environment] do - webpacker_config = Webpacker::Configuration.webpacker result = `NODE_ENV=production ./bin/webpack` unless $?.success? @@ -12,12 +11,8 @@ namespace :webpacker do exit! $?.exitstatus end - packs_path = Rails.root.join("public", webpacker_config[:distDir]) - packs_digests_path = Rails.root.join(webpacker_config[:distPath], webpacker_config[:manifestFileName]) - webpack_digests = JSON.parse(File.read(packs_digests_path)) - - puts "Compiled digests for all packs in #{packs_digests_path}: " - puts webpack_digests + puts "Compiled digests for all packs in #{Webpacker::Configuration.packs_path}: " + puts JSON.parse(File.read(Webpacker::Configuration.manifest_path)) end end diff --git a/lib/tasks/webpacker/install.rake b/lib/tasks/webpacker/install.rake index 658e75bad..e6bd16e8d 100644 --- a/lib/tasks/webpacker/install.rake +++ b/lib/tasks/webpacker/install.rake @@ -1,4 +1,4 @@ -WEBPACKER_APP_TEMPLATE_PATH = File.expand_path("../../install/templates/install.rb", __dir__) +WEBPACKER_APP_TEMPLATE_PATH = File.expand_path("../../install/template.rb", __dir__) namespace :webpacker do desc "Install webpacker in this application" diff --git a/lib/tasks/webpacker/verify.rake b/lib/tasks/webpacker/verify.rake index a2e35b47e..0b23ea9b8 100644 --- a/lib/tasks/webpacker/verify.rake +++ b/lib/tasks/webpacker/verify.rake @@ -2,13 +2,12 @@ require "webpacker/configuration" namespace :webpacker do namespace :install do - desc "Verify if webpacker is installed and package.json is in place" + desc "Verifies if webpacker is installed" task :verify do begin - webpacker_config = Webpacker::Configuration.webpacker - File.read(Rails.root.join(webpacker_config[:configPath], "shared.js")) - rescue Webpacker::Configuration::NotFoundError, NoMethodError, Errno::ENOENT - puts "Webpack core config not found in package.json or package.json is missing. \n"\ + File.read(Webpacker::Configuration.file_path) + rescue Errno::ENOENT + puts "config/webpack/paths.yml configuration file is missing. \n"\ "Make sure webpacker:install is run successfully before " \ "running dependent tasks" exit! diff --git a/lib/webpacker/configuration.rb b/lib/webpacker/configuration.rb index e7b0c0bdd..0b8c3ab3b 100644 --- a/lib/webpacker/configuration.rb +++ b/lib/webpacker/configuration.rb @@ -1,55 +1,38 @@ -# Loads the package.json file from app root to read the webpacker configuration - -class Webpacker::Configuration - class NotFoundError < StandardError; end - class_attribute :instance - attr_accessor :config +# Loads webpacker configuration from config/webpack/paths.yml +require "webpacker/file_loader" +class Webpacker::Configuration < Webpacker::FileLoader class << self - def load(path = Rails.root.join("package.json")) - self.instance = new(path) + def file_path + Rails.root.join("config", "webpack", "paths.yml") end - def config - load if Rails.env.development? - instance.config + def manifest_path + Rails.root.join(packs_path, "manifest.json") end - def scripts - config[:scripts] + def packs_path + Rails.root.join(paths.fetch(:dist_path, "public/packs")) end - def webpacker - begin - config[:webpacker] - rescue NoMethodError - raise NotFoundError, "Error: Webpacker core config not found in package.json. Make sure webpacker:install is run successfully" - end + def paths + load if Rails.env.development? + raise Webpacker::FileLoader::FileLoaderError.new("Webpacker::Configuration.load must be called first") unless instance + instance.data.fetch(:paths, {}) end - def dev_server - begin - config[:devServer] - rescue NoMethodError - raise NotFoundError, "Error: webpack-dev-server config not found in package.json. Make sure webpacker:install is run successfully" - end + def shared_config_path + Rails.root.join(webpack_config_path, "shared.js") end - end - private - - def initialize(path) - @path = path - @config = load + def webpack_config_path + Rails.root.join(paths.fetch(:config_path, "config/webpack")) end + end + private def load - if File.exist?(@path) - HashWithIndifferentAccess.new(JSON.parse(File.read(@path))) - else - Rails.logger.info "Didn't find any package.json file at #{@path}. " \ - "You must first install webpacker via rails webpacker:install" - {} - end + return super unless File.exist?(@path) + HashWithIndifferentAccess.new(YAML.load(File.read(@path))) end end diff --git a/lib/webpacker/dev_server.rb b/lib/webpacker/dev_server.rb new file mode 100644 index 000000000..bb3163044 --- /dev/null +++ b/lib/webpacker/dev_server.rb @@ -0,0 +1,32 @@ +# Loads webpack-dev-server configuration from config/webpack/dev_server.yml +require "webpacker/configuration" +require "webpacker/file_loader" + +class Webpacker::DevServer < Webpacker::FileLoader + class << self + def dev_server + load if Rails.env.development? + raise Webpacker::FileLoader::FileLoaderError.new("Webpacker::DevServer.load must be called first") unless instance + instance.data.fetch(:dev_server, {}) + end + + def file_path + Rails.root.join(Webpacker::Configuration.webpack_config_path, "dev_server.yml") + end + + def resolve(filename) + "http://#{dev_server.fetch(:host, 'localhost')}:#{dev_server.fetch(:port, 8080)}/#{filename}" + end + + def running? + return false unless Rails.env.development? + dev_server.fetch(:enabled, true) + end + end + + private + def load + return super unless File.exist?(@path) + HashWithIndifferentAccess.new(YAML.load(File.read(@path))) + end +end diff --git a/lib/webpacker/file_loader.rb b/lib/webpacker/file_loader.rb new file mode 100644 index 000000000..8d478420b --- /dev/null +++ b/lib/webpacker/file_loader.rb @@ -0,0 +1,24 @@ +# Provides a base singleton-configuration pattern for loading a file, given a path +class Webpacker::FileLoader + class NotFoundError < StandardError; end + class FileLoaderError < StandardError; end + + class_attribute :instance + attr_accessor :data + + class << self + def load(path = file_path) + self.instance = new(path) + end + end + + private + def initialize(path) + @path = path + @data = load + end + + def load + {}.freeze + end +end diff --git a/lib/webpacker/helper.rb b/lib/webpacker/helper.rb index 1930728ad..874ac3c39 100644 --- a/lib/webpacker/helper.rb +++ b/lib/webpacker/helper.rb @@ -1,7 +1,26 @@ require "webpacker/source" -require "webpacker/configuration" module Webpacker::Helper + # Computes the full path for a given webpacker asset. + # Return relative path using manifest.json and passes it to asset_url helper + # This will use asset_path internally, so most of their behaviors will be the same. + # If :type options is set, a file extension will be appended + # If :host options is set, it overwrites global config.action_controller.asset_host setting + # Examples: + # + # In development mode: + # <%= asset_pack_path 'calendar.js' %> # => "/packs/calendar.js" + # In production mode: + # <%= asset_pack_path 'calendar.css' %> # => "/packs/calendar-1016838bab065ae1e122.css" + # + # With type option: + # <%= asset_pack_path 'calendar', type: :javascript %> # => "/packs/calendar.js" + # When asset_host option is given: + # <%= asset_pack_path 'calendar', type: :javascript %> # => + # "http://example.com/packs/calendar.js" + def asset_pack_path(name, **options) + asset_path(Webpacker::Source.new(name, **options).path, **options) + end # Creates a script tag that references the named pack file, as compiled by Webpack per the entries list # 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. @@ -16,7 +35,7 @@ module Webpacker::Helper # <%= javascript_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => # def javascript_pack_tag(name, **options) - javascript_include_tag(Webpacker::Source.new("#{File.basename(name, '.js')}.js").path, **options) + javascript_include_tag(Webpacker::Source.new(name, type: :javascript).path, **options) end # Creates a link tag that references the named pack file, as compiled by Webpack per the entries list @@ -33,11 +52,6 @@ def javascript_pack_tag(name, **options) # <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => # def stylesheet_pack_tag(name, **options) - webpacker_config = Webpacker::Configuration.webpacker - unless webpacker_config[:assets] - raise StandardError, "Stylesheet support isn't enabled. Install using - webpacker:install:assets" - end - - stylesheet_link_tag(Webpacker::Source.new("#{File.basename(name, '.css')}.css").path, **options) + stylesheet_link_tag(Webpacker::Source.new(name, type: :stylesheet).path, **options) end end diff --git a/lib/webpacker/manifest.rb b/lib/webpacker/manifest.rb index 964a21668..2efef6dea 100644 --- a/lib/webpacker/manifest.rb +++ b/lib/webpacker/manifest.rb @@ -1,52 +1,28 @@ # Singleton registry for accessing the packs path using generated manifest. -# This allows javascript_pack_tag or stylesheet_pack_tag to take a reference to, +# This allows javascript_pack_tag, stylesheet_pack_tag, asset_pack_path to take a reference to, # say, "calendar.js" or "calendar.css" and turn it into "/packs/calendar.js" or -# "/packs/calendar.css" in development. In production mode, it returns digested +# "/packs/calendar.css" in development. In production mode, it returns compiles # files, # "/packs/calendar-1016838bab065ae1e314.js" and # "/packs/calendar-1016838bab065ae1e314.css" for long-term caching -class Webpacker::Manifest - class ManifestError < StandardError; end - - class_attribute :instance +require "webpacker/file_loader" +class Webpacker::Manifest < Webpacker::FileLoader class << self - def load(path = digests_path) - self.instance = new(path) + def file_path + Webpacker::Configuration.manifest_path end def lookup(name) load if Rails.env.development? - - if instance - instance.lookup(name).presence || raise(ManifestError.new("Can't find #{name} in #{instance.inspect}. Try reloading in case it's still compiling!")) - else - raise ManifestError.new("Webpacker::Manifest.load(path) must be called first") - end - end - - def digests_path - webpacker_config = Webpacker::Configuration.webpacker - Rails.root.join(webpacker_config[:distPath], webpacker_config[:manifestFileName]) + raise Webpacker::FileLoader::FileLoaderError.new("Webpacker::Manifest.load must be called first") unless instance + instance.data[name.to_s] || raise(Webpacker::FileLoader::NotFoundError.new("Can't find #{name} in #{@data}. Try reloading in case webpack is still compiling!")) end end - def lookup(name) - @digests[name.to_s] - end - private - def initialize(path) - @path = path - @digests = load - end - def load - if File.exist?(@path) - JSON.parse(File.read(@path)) - else - Rails.logger.info "Didn't find any digests file at #{@path}. You must first compile the packs via rails webpacker:compile" - {} - end + return super unless File.exist?(@path) + JSON.parse(File.read(@path)) end end diff --git a/lib/webpacker/railtie.rb b/lib/webpacker/railtie.rb index 839269be5..60615846c 100644 --- a/lib/webpacker/railtie.rb +++ b/lib/webpacker/railtie.rb @@ -8,17 +8,15 @@ class Webpacker::Engine < ::Rails::Engine ActionController::Base.helper Webpacker::Helper end - # Load config from package.json or initialise with defaults - # when running rails webpacker:install + # Loads webpacker config data from config/webpack/paths.yml Webpacker::Configuration.load - webpacker_config = Webpacker::Configuration.webpacker - if !(webpacker_config && webpacker_config[:distPath]) - webpacker_config = { distPath: "public/packs", manifestFileName: "manifest.json" } + # Loads webpack-dev-server config data from config/webpack/dev_server.yml + if Rails.env.development? + Webpacker::DevServer.load end - Webpacker::Manifest.load( - Rails.root.join(webpacker_config[:distPath], webpacker_config[:manifestFileName]) - ) + # Loads manifest data from public/packs/manifest.json + Webpacker::Manifest.load end end diff --git a/lib/webpacker/source.rb b/lib/webpacker/source.rb index 404141d81..a8f9eb532 100644 --- a/lib/webpacker/source.rb +++ b/lib/webpacker/source.rb @@ -1,31 +1,26 @@ require "webpacker/manifest" -require "webpacker/configuration" +require "webpacker/dev_server" # Translates a logical reference for a pack source into the final -# path needed in the HTML using generated manifest.json manifest. +# path needed in the HTML using generated manifest.json file. class Webpacker::Source - class SourceError < StandardError; end - - def initialize(filename) - @filename = filename + def initialize(name, options = {}) + @name = name + @options = options end def path - if Rails.env.development? && dev_server_enabled? - "http://#{dev_server[:host]}:#{dev_server[:port]}/#{filename}" + if Webpacker::DevServer.running? + Webpacker::DevServer.resolve(compute_source_with_extname) else - Webpacker::Manifest.lookup(filename) + Webpacker::Manifest.lookup(compute_source_with_extname) end end private - attr_accessor :filename - - def dev_server - Webpacker::Configuration.dev_server - end + attr_accessor :name, :options - def dev_server_enabled? - ENV["DEV_SERVER_ENABLED"] || dev_server[:enabled] + def compute_source_with_extname + "#{name}#{ActionController::Base.helpers.compute_asset_extname(name, options)}" end end From 90b2462e98376b7ea7d568407af11451626e89ef Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 16 Mar 2017 05:37:16 +0000 Subject: [PATCH 05/27] Fix indentation --- lib/install/config/webpack/configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/install/config/webpack/configuration.js b/lib/install/config/webpack/configuration.js index 993c2c337..10cd34634 100644 --- a/lib/install/config/webpack/configuration.js +++ b/lib/install/config/webpack/configuration.js @@ -10,7 +10,7 @@ const configPath = path.resolve('config', 'webpack') const { paths } = yaml.safeLoad(fs.readFileSync(path.join(configPath, 'paths.yml'), 'utf8')) const { dev_server } = yaml.safeLoad(fs.readFileSync(path.join(configPath, 'dev_server.yml'), 'utf8')) const publicPath = env.NODE_ENV !== 'production' && dev_server.enabled ? - `http://${dev_server.host}:${dev_server.port}/` : `/${paths.dist_dir}/` + `http://${dev_server.host}:${dev_server.port}/` : `/${paths.dist_dir}/` module.exports = { dev_server, From bd5c74fe6b7929a34444c904402222ef56ec7b94 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 16 Mar 2017 06:50:04 +0000 Subject: [PATCH 06/27] Use manifest.json to lookup paths in all env and get rid of redundant source and dev_server file --- lib/install/config/webpack/dev_server.yml | 1 + lib/install/config/webpack/paths.yml | 1 + lib/install/config/webpack/shared.js | 11 +++----- lib/install/template.rb | 2 +- lib/webpacker/dev_server.rb | 32 ----------------------- lib/webpacker/helper.rb | 16 +++--------- lib/webpacker/manifest.rb | 1 + lib/webpacker/railtie.rb | 6 ----- lib/webpacker/source.rb | 26 ------------------ 9 files changed, 12 insertions(+), 84 deletions(-) delete mode 100644 lib/webpacker/dev_server.rb delete mode 100644 lib/webpacker/source.rb diff --git a/lib/install/config/webpack/dev_server.yml b/lib/install/config/webpack/dev_server.yml index dddce8547..f7047c973 100644 --- a/lib/install/config/webpack/dev_server.yml +++ b/lib/install/config/webpack/dev_server.yml @@ -1,3 +1,4 @@ +# Restart webpack-dev-server if you make changes here dev_server: enabled: true host: localhost diff --git a/lib/install/config/webpack/paths.yml b/lib/install/config/webpack/paths.yml index 62c6735b7..4d1a14691 100644 --- a/lib/install/config/webpack/paths.yml +++ b/lib/install/config/webpack/paths.yml @@ -1,3 +1,4 @@ +# Restart webpack-watcher or webpack-dev-server if you make changes here paths: src_path: app/javascript config_path: config/webpack diff --git a/lib/install/config/webpack/shared.js b/lib/install/config/webpack/shared.js index b2c183ab0..1eedbef6c 100644 --- a/lib/install/config/webpack/shared.js +++ b/lib/install/config/webpack/shared.js @@ -5,6 +5,7 @@ const path = require('path') const glob = require('glob') const ExtractTextPlugin = require('extract-text-webpack-plugin') const ManifestPlugin = require('webpack-manifest-plugin') +const WriteFilePlugin = require('write-file-webpack-plugin') const extname = require('path-complete-extname') const { env, paths, publicPath } = require('./configuration.js') @@ -64,13 +65,9 @@ module.exports = { plugins: [ new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(env))), - new ExtractTextPlugin( - env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css' - ), - new ManifestPlugin({ - fileName: 'manifest.json', - publicPath: `/${paths.dist_dir}/` - }) + new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css'), + new ManifestPlugin({ fileName: 'manifest.json', publicPath }), + new WriteFilePlugin({ test: /manifest.json$/, log: false }) ], resolve: { diff --git a/lib/install/template.rb b/lib/install/template.rb index 36ae1eaea..6f2dfc8e0 100644 --- a/lib/install/template.rb +++ b/lib/install/template.rb @@ -12,7 +12,7 @@ EOS run "./bin/yarn add webpack webpack-merge js-yaml path-complete-extname " \ -"webpack-manifest-plugin babel-loader coffee-loader coffee-script " \ +"webpack-manifest-plugin write-file-webpack-plugin babel-loader coffee-loader coffee-script " \ "babel-core babel-preset-env compression-webpack-plugin rails-erb-loader glob " \ "extract-text-webpack-plugin node-sass file-loader sass-loader css-loader style-loader" diff --git a/lib/webpacker/dev_server.rb b/lib/webpacker/dev_server.rb deleted file mode 100644 index bb3163044..000000000 --- a/lib/webpacker/dev_server.rb +++ /dev/null @@ -1,32 +0,0 @@ -# Loads webpack-dev-server configuration from config/webpack/dev_server.yml -require "webpacker/configuration" -require "webpacker/file_loader" - -class Webpacker::DevServer < Webpacker::FileLoader - class << self - def dev_server - load if Rails.env.development? - raise Webpacker::FileLoader::FileLoaderError.new("Webpacker::DevServer.load must be called first") unless instance - instance.data.fetch(:dev_server, {}) - end - - def file_path - Rails.root.join(Webpacker::Configuration.webpack_config_path, "dev_server.yml") - end - - def resolve(filename) - "http://#{dev_server.fetch(:host, 'localhost')}:#{dev_server.fetch(:port, 8080)}/#{filename}" - end - - def running? - return false unless Rails.env.development? - dev_server.fetch(:enabled, true) - end - end - - private - def load - return super unless File.exist?(@path) - HashWithIndifferentAccess.new(YAML.load(File.read(@path))) - end -end diff --git a/lib/webpacker/helper.rb b/lib/webpacker/helper.rb index 874ac3c39..9660c7d70 100644 --- a/lib/webpacker/helper.rb +++ b/lib/webpacker/helper.rb @@ -1,25 +1,17 @@ -require "webpacker/source" +require "webpacker/manifest" module Webpacker::Helper # Computes the full path for a given webpacker asset. # Return relative path using manifest.json and passes it to asset_url helper # This will use asset_path internally, so most of their behaviors will be the same. - # If :type options is set, a file extension will be appended - # If :host options is set, it overwrites global config.action_controller.asset_host setting # Examples: # # In development mode: # <%= asset_pack_path 'calendar.js' %> # => "/packs/calendar.js" # In production mode: # <%= asset_pack_path 'calendar.css' %> # => "/packs/calendar-1016838bab065ae1e122.css" - # - # With type option: - # <%= asset_pack_path 'calendar', type: :javascript %> # => "/packs/calendar.js" - # When asset_host option is given: - # <%= asset_pack_path 'calendar', type: :javascript %> # => - # "http://example.com/packs/calendar.js" def asset_pack_path(name, **options) - asset_path(Webpacker::Source.new(name, **options).path, **options) + asset_path(Webpacker::Manifest.lookup(name), **options) end # Creates a script tag that references the named pack file, as compiled by Webpack per the entries list # in config/webpack/shared.js. By default, this list is auto-generated to match everything in @@ -35,7 +27,7 @@ def asset_pack_path(name, **options) # <%= javascript_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => # def javascript_pack_tag(name, **options) - javascript_include_tag(Webpacker::Source.new(name, type: :javascript).path, **options) + javascript_include_tag(Webpacker::Manifest.lookup("#{name}#{compute_asset_extname(name, type: :javascript)}"), **options) end # Creates a link tag that references the named pack file, as compiled by Webpack per the entries list @@ -52,6 +44,6 @@ def javascript_pack_tag(name, **options) # <%= stylesheet_pack_tag 'calendar', 'data-turbolinks-track': 'reload' %> # => # def stylesheet_pack_tag(name, **options) - stylesheet_link_tag(Webpacker::Source.new(name, type: :stylesheet).path, **options) + stylesheet_link_tag(Webpacker::Manifest.lookup("#{name}#{compute_asset_extname(name, type: :stylesheet)}"), **options) end end diff --git a/lib/webpacker/manifest.rb b/lib/webpacker/manifest.rb index 2efef6dea..bf00d7899 100644 --- a/lib/webpacker/manifest.rb +++ b/lib/webpacker/manifest.rb @@ -6,6 +6,7 @@ # "/packs/calendar-1016838bab065ae1e314.css" for long-term caching require "webpacker/file_loader" +require "webpacker/configuration" class Webpacker::Manifest < Webpacker::FileLoader class << self diff --git a/lib/webpacker/railtie.rb b/lib/webpacker/railtie.rb index 60615846c..f710a0264 100644 --- a/lib/webpacker/railtie.rb +++ b/lib/webpacker/railtie.rb @@ -10,12 +10,6 @@ class Webpacker::Engine < ::Rails::Engine # Loads webpacker config data from config/webpack/paths.yml Webpacker::Configuration.load - - # Loads webpack-dev-server config data from config/webpack/dev_server.yml - if Rails.env.development? - Webpacker::DevServer.load - end - # Loads manifest data from public/packs/manifest.json Webpacker::Manifest.load end diff --git a/lib/webpacker/source.rb b/lib/webpacker/source.rb deleted file mode 100644 index a8f9eb532..000000000 --- a/lib/webpacker/source.rb +++ /dev/null @@ -1,26 +0,0 @@ -require "webpacker/manifest" -require "webpacker/dev_server" - -# Translates a logical reference for a pack source into the final -# path needed in the HTML using generated manifest.json file. -class Webpacker::Source - def initialize(name, options = {}) - @name = name - @options = options - end - - def path - if Webpacker::DevServer.running? - Webpacker::DevServer.resolve(compute_source_with_extname) - else - Webpacker::Manifest.lookup(compute_source_with_extname) - end - end - - private - attr_accessor :name, :options - - def compute_source_with_extname - "#{name}#{ActionController::Base.helpers.compute_asset_extname(name, options)}" - end -end From 64e05e22167ccd1f4bfad9ae93df4ebf944cccea Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 16 Mar 2017 13:35:25 +0000 Subject: [PATCH 07/27] Remove redundant options for dev-server --- lib/install/config/webpack/development.server.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/install/config/webpack/development.server.js b/lib/install/config/webpack/development.server.js index 0e2f36863..c82c800e3 100644 --- a/lib/install/config/webpack/development.server.js +++ b/lib/install/config/webpack/development.server.js @@ -11,8 +11,6 @@ module.exports = merge(devConfig, { port: dev_server.port, compress: true, historyApiFallback: true, - https: false, - noInfo: true, contentBase: path.resolve(paths.dist_path), publicPath } From 57b568aa8cada0b303d69da548bf6914471b4080 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 16 Mar 2017 13:41:40 +0000 Subject: [PATCH 08/27] Fix error messages --- lib/install/bin/webpack-dev-server.tt | 4 ++-- lib/install/bin/webpack.tt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/install/bin/webpack-dev-server.tt b/lib/install/bin/webpack-dev-server.tt index 2661c68c8..f8e9ea478 100644 --- a/lib/install/bin/webpack-dev-server.tt +++ b/lib/install/bin/webpack-dev-server.tt @@ -21,8 +21,8 @@ begin WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack-dev-server" DEV_SERVER_CONFIG = "#{WEBPACK_CONFIG_PATH}/development.server.js" -rescue Errno::ENOENT - puts "config/webpacker/paths.yml file not found." +rescue Errno::ENOENT, NoMethodError + puts "Configuration not found in config/webpacker/paths.yml." puts "Please run bundle exec rails webpacker:install to install webpacker" exit! end diff --git a/lib/install/bin/webpack.tt b/lib/install/bin/webpack.tt index 57698150d..fa02b1014 100644 --- a/lib/install/bin/webpack.tt +++ b/lib/install/bin/webpack.tt @@ -26,12 +26,12 @@ begin "Disable to serve assets directly from packs directory" end rescue Errno::ENOENT, NoMethodError - puts "config/webpack/paths.yml or config/webpack/dev_server.yml file not found." + puts "Configuration not found in config/webpack/paths.yml or config/webpack/dev_server.yml." puts "Please run bundle exec rails webpacker:install to install webpacker" exit! end -WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack" +WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack" WEBPACK_CONFIG = "#{WEBPACK_CONFIG_PATH}/#{NODE_ENV}.js" Dir.chdir(APP_PATH) do From 573baa0de952853b35b81f5655f377b1cb667da2 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 16 Mar 2017 14:04:18 +0000 Subject: [PATCH 09/27] Fix angular and react installer to use new extenstions --- lib/tasks/installers/angular.rake | 2 +- lib/tasks/installers/react.rake | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/tasks/installers/angular.rake b/lib/tasks/installers/angular.rake index 1d8d2e227..e387294cb 100644 --- a/lib/tasks/installers/angular.rake +++ b/lib/tasks/installers/angular.rake @@ -18,7 +18,7 @@ namespace :webpacker do puts "The configuration file already has a reference to .ts extension, skipping the addition of this extension to the list..." else puts "Adding '.ts' in loader extensions in #{shared_config_path}..." - config.gsub!(/extensions:(.*')(\s*\])/, "extensions:\\1, '.ts'\\2") + config.gsub!(/extensions = (.*')(\s*\])/, "extensions = \\1, '.ts'\\2") end File.write shared_config_path, config diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index ec281ea15..09a517d15 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -21,6 +21,13 @@ namespace :webpacker do puts "Couldn't automatically update loader test in #{shared_config_path}. Please set test: /\\.jsx?(\\.erb)?$/." end + if config =~ /["'].jsx["']/ + puts "The configuration file already has a reference to .jsx extension, skipping the addition of this extension to the list..." + else + puts "Adding '.jsx' in loader extensions in #{shared_config_path}..." + config.gsub!(/extensions = (.*')(\s*\])/, "extensions = \\1, '.jsx'\\2") + end + File.write shared_config_path, config puts "Copying .babelrc to project directory" From 2d9d2de3badfc95c7238a9a17fff22d2ae97082d Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 16 Mar 2017 14:58:07 +0000 Subject: [PATCH 10/27] Add manifest loading in block to wait if manifest doesn't exist Retry only 10 times Fix error message Enable retrying only in development mode Use inline conditional --- lib/webpacker/manifest.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/webpacker/manifest.rb b/lib/webpacker/manifest.rb index bf00d7899..04ad6c871 100644 --- a/lib/webpacker/manifest.rb +++ b/lib/webpacker/manifest.rb @@ -17,13 +17,20 @@ def file_path def lookup(name) load if Rails.env.development? raise Webpacker::FileLoader::FileLoaderError.new("Webpacker::Manifest.load must be called first") unless instance - instance.data[name.to_s] || raise(Webpacker::FileLoader::NotFoundError.new("Can't find #{name} in #{@data}. Try reloading in case webpack is still compiling!")) + instance.data[name.to_s] || raise(Webpacker::FileLoader::NotFoundError.new("Can't find #{name} in #{file_path}. Is webpack still compiling?")) end end private def load - return super unless File.exist?(@path) - JSON.parse(File.read(@path)) + begin + retries ||= 0 + JSON.parse(File.read(@path)) + rescue Errno::ENOENT + return super unless Rails.env.development? + Rails.logger.info "Packs manifest not found #{Webpacker::Configuration.manifest_path}, waiting for #{retries + 1}sec before retrying" + sleep(retries += 1) + retries < 10 ? retry : super + end end end From dca884bc116d911713b21e6932dac420a94453b4 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 16 Mar 2017 16:28:39 +0000 Subject: [PATCH 11/27] Update readme and error messages --- README.md | 20 ++++++++++++++++++++ lib/install/bin/webpack.tt | 2 +- lib/tasks/webpacker/verify.rake | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f58a57f4c..ff685ec68 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,26 @@ To use Webpacker with Angular, just create a new app with `rails new myapp --web To use Webpacker with Vue, just create a new app with `rails new myapp --webpack=vue` (or run `rails webpacker:install:vue` on a Rails app already setup with webpacker). Vue and its supported libraries will be added via yarn and changes to the configuration files made. An example component is also added to your project in `app/javascript` so that you can experiment Vue right away. +## Troubleshooting + +* If you get an error `ENOENT: no such file or directory - node-sass` on Heroku +or elsewhere during `assets:precompile` or `bundle exec rails webpacker:compile` +then you would need to rebuild node-sass. It's a bit weird error, +basically, it can't find the `node-sass` binary. +An easy solution is to create a postinstall hook - `npm rebuild node-sass` in +`package.json` and that will ensure `node-sass` is rebuild whenever +you install any new modules. + +* `Can't find hello_react.js in manifest.json`. Webpacker uses a `manifest.json` +file to keep track of packs in all environments, +however since this file is generated after packs are compiled by webpack and so, +if you load your rails server whilst webpack is compiling you will get this error. +Therefore, make sure webpack binstub +(i.e `.bin/webpack-watcher` or `.bin/webpack-dev-sever`) is running and has +completed the compilation successfully. **Note:** The lookup currently waits +for manifest.json to be generated for a few seconds but will eventually fail +and raise this error. + ## Wishlist diff --git a/lib/install/bin/webpack.tt b/lib/install/bin/webpack.tt index fa02b1014..e97c5a0eb 100644 --- a/lib/install/bin/webpack.tt +++ b/lib/install/bin/webpack.tt @@ -23,7 +23,7 @@ begin if NODE_ENV == "development" && dev_server_config["dev_server"]["enabled"] puts "Warning: webpack-dev-server is currently enabled in #{DEV_SERVER_CONFIG_PATH}. " \ - "Disable to serve assets directly from packs directory" + "Disable to serve assets directly from public/packs directory" end rescue Errno::ENOENT, NoMethodError puts "Configuration not found in config/webpack/paths.yml or config/webpack/dev_server.yml." diff --git a/lib/tasks/webpacker/verify.rake b/lib/tasks/webpacker/verify.rake index 0b23ea9b8..7c8e21622 100644 --- a/lib/tasks/webpacker/verify.rake +++ b/lib/tasks/webpacker/verify.rake @@ -7,7 +7,7 @@ namespace :webpacker do begin File.read(Webpacker::Configuration.file_path) rescue Errno::ENOENT - puts "config/webpack/paths.yml configuration file is missing. \n"\ + puts "Configuration config/webpack/paths.yml file not found. \n"\ "Make sure webpacker:install is run successfully before " \ "running dependent tasks" exit! From 539f4485a24933ae05419977557d04a2b548c780 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Fri, 17 Mar 2017 06:37:33 +0000 Subject: [PATCH 12/27] Use writeToFileEmit option Load manifest if webpacker installed Just fail if not found --- README.md | 4 +--- lib/install/config/webpack/shared.js | 4 +--- lib/install/template.rb | 2 +- lib/webpacker/manifest.rb | 11 ++--------- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ff685ec68..dc3b904ee 100644 --- a/README.md +++ b/README.md @@ -222,9 +222,7 @@ however since this file is generated after packs are compiled by webpack and so, if you load your rails server whilst webpack is compiling you will get this error. Therefore, make sure webpack binstub (i.e `.bin/webpack-watcher` or `.bin/webpack-dev-sever`) is running and has -completed the compilation successfully. **Note:** The lookup currently waits -for manifest.json to be generated for a few seconds but will eventually fail -and raise this error. +completed the compilation successfully. ## Wishlist diff --git a/lib/install/config/webpack/shared.js b/lib/install/config/webpack/shared.js index 86c07b27d..e36c81621 100644 --- a/lib/install/config/webpack/shared.js +++ b/lib/install/config/webpack/shared.js @@ -5,7 +5,6 @@ const path = require('path') const glob = require('glob') const ExtractTextPlugin = require('extract-text-webpack-plugin') const ManifestPlugin = require('webpack-manifest-plugin') -const WriteFilePlugin = require('write-file-webpack-plugin') const extname = require('path-complete-extname') const { env, paths, publicPath } = require('./configuration.js') @@ -70,8 +69,7 @@ module.exports = { plugins: [ new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(env))), new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css'), - new ManifestPlugin({ fileName: 'manifest.json', publicPath }), - new WriteFilePlugin({ test: /manifest.json$/, log: false }) + new ManifestPlugin({ fileName: 'manifest.json', publicPath, writeToFileEmit: true }) ], resolve: { diff --git a/lib/install/template.rb b/lib/install/template.rb index 6f2dfc8e0..36ae1eaea 100644 --- a/lib/install/template.rb +++ b/lib/install/template.rb @@ -12,7 +12,7 @@ EOS run "./bin/yarn add webpack webpack-merge js-yaml path-complete-extname " \ -"webpack-manifest-plugin write-file-webpack-plugin babel-loader coffee-loader coffee-script " \ +"webpack-manifest-plugin babel-loader coffee-loader coffee-script " \ "babel-core babel-preset-env compression-webpack-plugin rails-erb-loader glob " \ "extract-text-webpack-plugin node-sass file-loader sass-loader css-loader style-loader" diff --git a/lib/webpacker/manifest.rb b/lib/webpacker/manifest.rb index 04ad6c871..eed6924c1 100644 --- a/lib/webpacker/manifest.rb +++ b/lib/webpacker/manifest.rb @@ -23,14 +23,7 @@ def lookup(name) private def load - begin - retries ||= 0 - JSON.parse(File.read(@path)) - rescue Errno::ENOENT - return super unless Rails.env.development? - Rails.logger.info "Packs manifest not found #{Webpacker::Configuration.manifest_path}, waiting for #{retries + 1}sec before retrying" - sleep(retries += 1) - retries < 10 ? retry : super - end + return super unless File.exist?(@path) + JSON.parse(File.read(@path)) end end From bc129290511062ddcedc8835811a26771e142aa5 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Fri, 17 Mar 2017 08:04:56 +0000 Subject: [PATCH 13/27] Update readme --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dc3b904ee..709c21e8d 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ To use Webpacker with Vue, just create a new app with `rails new myapp --webpack ## Troubleshooting -* If you get an error `ENOENT: no such file or directory - node-sass` on Heroku +* If you get this error `ENOENT: no such file or directory - node-sass` on Heroku or elsewhere during `assets:precompile` or `bundle exec rails webpacker:compile` then you would need to rebuild node-sass. It's a bit weird error, basically, it can't find the `node-sass` binary. @@ -216,14 +216,14 @@ An easy solution is to create a postinstall hook - `npm rebuild node-sass` in `package.json` and that will ensure `node-sass` is rebuild whenever you install any new modules. -* `Can't find hello_react.js in manifest.json`. Webpacker uses a `manifest.json` -file to keep track of packs in all environments, -however since this file is generated after packs are compiled by webpack and so, -if you load your rails server whilst webpack is compiling you will get this error. -Therefore, make sure webpack binstub +* If you get this error `Can't find hello_react.js in manifest.json` +when loading a view in browser it's because Webpack is still compiling packs. +Webpacker uses a `manifest.json` file to keep track of packs in all environments, +however since this file is generated after packs are compiled by webpack. So, +if you load a view in browser whilst webpack is compiling you will get this error. +Therefore, make sure webpack (i.e `.bin/webpack-watcher` or `.bin/webpack-dev-sever`) is running and has -completed the compilation successfully. - +completed the compilation successfully before loading a view. ## Wishlist From 524c746f981b5fc73446cc20fa668e65d23c6b87 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Mon, 20 Mar 2017 15:14:49 +0000 Subject: [PATCH 14/27] Fix aesthetics around configuration files --- README.md | 34 ++++----- lib/install/bin/webpack-dev-server.tt | 6 +- lib/install/bin/webpack.tt | 10 +-- lib/install/config/shared.js | 76 ------------------- lib/install/config/webpack/configuration.js | 21 +++-- lib/install/config/webpack/dev_server.yml | 7 +- .../config/webpack/development.server.js | 10 +-- lib/install/config/webpack/paths.yml | 11 ++- lib/install/config/webpack/shared.js | 17 ++--- lib/tasks/webpacker/compile.rake | 2 +- lib/webpacker/configuration.rb | 10 +-- 11 files changed, 58 insertions(+), 146 deletions(-) delete mode 100644 lib/install/config/shared.js diff --git a/README.md b/README.md index 709c21e8d..d55d87d2f 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,7 @@ If you'd rather not have to run the two processes separately by hand, you can us Alternatively, you can run `./bin/webpack-dev-server`. This will launch a [Webpack Dev Server](https://webpack.github.io/docs/webpack-dev-server.html) listening on http://localhost:8080/ -serving your pack files. It will recompile your files as you make changes. You also need to set -`config.x.webpacker[:dev_server_host]` in your `config/environments/development.rb` to tell Webpacker to load -your packs from the Webpack Dev Server. This setup allows you to leverage advanced Webpack features, such +serving your pack files. This setup allows you to leverage advanced Webpack features, such as [Hot Module Replacement](https://webpack.github.io/docs/hot-module-replacement-with-webpack.html). @@ -88,34 +86,28 @@ but all these options are configurable from `config/webpack/paths.yml` file. ```yml # config/webpack/paths.yml -paths: - src_path: app/javascript - config_path: config/webpack - node_modules_path: node_modules - dist_dir: packs - dist_path: public/packs +source: app/javascript +entry: packs +output: public +config: config/webpack +node_modules: node_modules ``` -**Note:** If you rename `packs` directory inside `app/javascript` from `packs` to `bundles`, make sure you also update your `dist_dir` and `dist_path`. - -```yml -paths: - dist_dir: bundles - dist_path: public/bundles -``` +*Note:* Behind the scenes, webpacker will use same `entry` directory name inside `output` +directory to emit bundles. For ex, `public/packs` Similary, you can also control and configure `webpack-dev-server` settings from `config/webpack/dev_server.yml` file ```yml # config/webpack/dev_server.yml -dev_server: - enabled: true - host: localhost - port: 8080 +enabled: true +host: localhost +port: 8080 ``` -By default, `webpack-dev-server` uses `dist_path` option specified in `paths.yml` as `contentBase`. +By default, `webpack-dev-server` uses `output` option specified in +`paths.yml` as `contentBase`. **Note:** Don't forget to disable `webpack-dev-server` incase you are using `./bin/webpack-watcher` to serve assets in development mode otherwise diff --git a/lib/install/bin/webpack-dev-server.tt b/lib/install/bin/webpack-dev-server.tt index f8e9ea478..4b21d6551 100644 --- a/lib/install/bin/webpack-dev-server.tt +++ b/lib/install/bin/webpack-dev-server.tt @@ -14,10 +14,10 @@ APP_PATH = File.expand_path("../", __dir__) CONFIG_PATH = File.join(APP_PATH, "config/webpack/paths.yml") begin - config = YAML.load(File.read(CONFIG_PATH)) + paths = YAML.load(File.read(CONFIG_PATH)) - NODE_MODULES_PATH = File.join(APP_PATH.shellescape, config["paths"]["node_modules_path"]) - WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, config["paths"]["config_path"]) + NODE_MODULES_PATH = File.join(APP_PATH.shellescape, paths["node_modules"]) + WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, paths["config"]) WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack-dev-server" DEV_SERVER_CONFIG = "#{WEBPACK_CONFIG_PATH}/development.server.js" diff --git a/lib/install/bin/webpack.tt b/lib/install/bin/webpack.tt index e97c5a0eb..0ec44f5c5 100644 --- a/lib/install/bin/webpack.tt +++ b/lib/install/bin/webpack.tt @@ -15,13 +15,13 @@ CONFIG_PATH = File.join(APP_PATH, "config/webpack/paths.yml") DEV_SERVER_CONFIG_PATH = File.join(APP_PATH, "config/webpack/dev_server.yml") begin - config = YAML.load(File.read(CONFIG_PATH)) - dev_server_config = YAML.load(File.read(DEV_SERVER_CONFIG_PATH)) + paths = YAML.load(File.read(CONFIG_PATH)) + dev_server = YAML.load(File.read(DEV_SERVER_CONFIG_PATH)) - NODE_MODULES_PATH = File.join(APP_PATH.shellescape, config["paths"]["node_modules_path"]) - WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, config["paths"]["config_path"]) + NODE_MODULES_PATH = File.join(APP_PATH.shellescape, paths["node_modules"]) + WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, paths["config"]) - if NODE_ENV == "development" && dev_server_config["dev_server"]["enabled"] + if NODE_ENV == "development" && dev_server["enabled"] puts "Warning: webpack-dev-server is currently enabled in #{DEV_SERVER_CONFIG_PATH}. " \ "Disable to serve assets directly from public/packs directory" end diff --git a/lib/install/config/shared.js b/lib/install/config/shared.js deleted file mode 100644 index 9ae6571de..000000000 --- a/lib/install/config/shared.js +++ /dev/null @@ -1,76 +0,0 @@ -// Note: You must restart bin/webpack-watcher for changes to take effect - -const webpack = require('webpack') -const path = require('path') -const process = require('process') -const glob = require('glob') -const extname = require('path-complete-extname') - -let distDir = process.env.WEBPACK_DIST_DIR - -if (distDir === undefined) { - distDir = 'packs' -} - -const extensions = ['.js', '.coffee'] -const extensionGlob = `*{${extensions.join(',')}}*` -const packPaths = glob.sync(path.join('app', 'javascript', 'packs', extensionGlob)) - -const config = { - entry: packPaths.reduce( - (map, entry) => { - const basename = path.basename(entry, extname(entry)) - const localMap = map - localMap[basename] = path.resolve(entry) - return localMap - }, {} - ), - - output: { filename: '[name].js', path: path.resolve('public', distDir) }, - - module: { - rules: [ - { test: /\.coffee(\.erb)?$/, loader: 'coffee-loader' }, - { - test: /\.js(\.erb)?$/, - exclude: /node_modules/, - loader: 'babel-loader', - options: { - presets: [ - ['env', { modules: false }] - ] - } - }, - { - test: /\.erb$/, - enforce: 'pre', - exclude: /node_modules/, - loader: 'rails-erb-loader', - options: { - runner: 'DISABLE_SPRING=1 bin/rails runner' - } - } - ] - }, - - plugins: [ - new webpack.EnvironmentPlugin(Object.keys(process.env)) - ], - - resolve: { - extensions, - modules: [ - path.resolve('app/javascript'), - path.resolve('node_modules') - ] - }, - - resolveLoader: { - modules: [path.resolve('node_modules')] - } -} - -module.exports = { - distDir, - config -} diff --git a/lib/install/config/webpack/configuration.js b/lib/install/config/webpack/configuration.js index 10cd34634..f576440d5 100644 --- a/lib/install/config/webpack/configuration.js +++ b/lib/install/config/webpack/configuration.js @@ -1,19 +1,18 @@ // Common configuration for webpacker loaded from config/webpack/paths.yml -const path = require('path') -const process = require('process') -const yaml = require('js-yaml') -const fs = require('fs') +const { join, resolve } = require('path') +const { env } = require('process') +const { safeLoad } = require('js-yaml') +const { readFileSync } = require('fs') -const env = process.env -const configPath = path.resolve('config', 'webpack') -const { paths } = yaml.safeLoad(fs.readFileSync(path.join(configPath, 'paths.yml'), 'utf8')) -const { dev_server } = yaml.safeLoad(fs.readFileSync(path.join(configPath, 'dev_server.yml'), 'utf8')) -const publicPath = env.NODE_ENV !== 'production' && dev_server.enabled ? - `http://${dev_server.host}:${dev_server.port}/` : `/${paths.dist_dir}/` +const configPath = resolve('config', 'webpack') +const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8')) +const devServer = safeLoad(readFileSync(join(configPath, 'dev_server.yml'), 'utf8')) +const publicPath = env.NODE_ENV !== 'production' && devServer.enabled ? + `http://${devServer.host}:${devServer.port}/` : `/${paths.entry}/` module.exports = { - dev_server, + devServer, env, paths, publicPath diff --git a/lib/install/config/webpack/dev_server.yml b/lib/install/config/webpack/dev_server.yml index f7047c973..5dbfb7c88 100644 --- a/lib/install/config/webpack/dev_server.yml +++ b/lib/install/config/webpack/dev_server.yml @@ -1,5 +1,4 @@ # Restart webpack-dev-server if you make changes here -dev_server: - enabled: true - host: localhost - port: 8080 +enabled: true +host: localhost +port: 8080 diff --git a/lib/install/config/webpack/development.server.js b/lib/install/config/webpack/development.server.js index c82c800e3..fe840c6e4 100644 --- a/lib/install/config/webpack/development.server.js +++ b/lib/install/config/webpack/development.server.js @@ -1,17 +1,17 @@ // Note: You must restart bin/webpack-dev-server for changes to take effect -const path = require('path') +const { resolve } = require('path') const merge = require('webpack-merge') const devConfig = require('./development.js') -const { dev_server, publicPath, paths } = require('./configuration.js') +const { devServer, publicPath, paths } = require('./configuration.js') module.exports = merge(devConfig, { devServer: { - host: dev_server.host, - port: dev_server.port, + host: devServer.host, + port: devServer.port, compress: true, historyApiFallback: true, - contentBase: path.resolve(paths.dist_path), + contentBase: resolve(paths.output, paths.entry), publicPath } }) diff --git a/lib/install/config/webpack/paths.yml b/lib/install/config/webpack/paths.yml index 4d1a14691..586f77728 100644 --- a/lib/install/config/webpack/paths.yml +++ b/lib/install/config/webpack/paths.yml @@ -1,7 +1,6 @@ # Restart webpack-watcher or webpack-dev-server if you make changes here -paths: - src_path: app/javascript - config_path: config/webpack - node_modules_path: node_modules - dist_dir: packs - dist_path: public/packs +config: config/webpack +entry: packs +output: public +node_modules: node_modules +source: app/javascript diff --git a/lib/install/config/webpack/shared.js b/lib/install/config/webpack/shared.js index e36c81621..1ae61656e 100644 --- a/lib/install/config/webpack/shared.js +++ b/lib/install/config/webpack/shared.js @@ -1,8 +1,8 @@ // Note: You must restart bin/webpack-watcher for changes to take effect const webpack = require('webpack') -const path = require('path') -const glob = require('glob') +const { basename, join, resolve } = require('path') +const { sync } = require('glob') const ExtractTextPlugin = require('extract-text-webpack-plugin') const ManifestPlugin = require('webpack-manifest-plugin') const extname = require('path-complete-extname') @@ -10,19 +10,18 @@ const { env, paths, publicPath } = require('./configuration.js') const extensions = ['.js', '.coffee'] const extensionGlob = `*{${extensions.join(',')}}*` -const packPaths = glob.sync(path.join(paths.src_path, paths.dist_dir, extensionGlob)) +const packPaths = sync(join(paths.source, paths.entry, extensionGlob)) module.exports = { entry: packPaths.reduce( (map, entry) => { - const basename = path.basename(entry, extname(entry)) const localMap = map - localMap[basename] = path.resolve(entry) + localMap[basename(entry, extname(entry))] = resolve(entry) return localMap }, {} ), - output: { filename: '[name].js', path: path.resolve(paths.dist_path) }, + output: { filename: '[name].js', path: resolve(paths.output, paths.entry) }, module: { rules: [ @@ -75,12 +74,12 @@ module.exports = { resolve: { extensions, modules: [ - path.resolve(paths.src_path), - path.resolve(paths.node_modules_path) + resolve(paths.source), + resolve(paths.node_modules) ] }, resolveLoader: { - modules: [paths.node_modules_path] + modules: [paths.node_modules] } } diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index 3cd38e991..10404b221 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -11,7 +11,7 @@ namespace :webpacker do exit! $?.exitstatus end - puts "Compiled digests for all packs in #{Webpacker::Configuration.packs_path}: " + puts "Compiled digests for all packs in #{Webpacker::Configuration.entry_path}: " puts JSON.parse(File.read(Webpacker::Configuration.manifest_path)) end end diff --git a/lib/webpacker/configuration.rb b/lib/webpacker/configuration.rb index 0b8c3ab3b..50beab89a 100644 --- a/lib/webpacker/configuration.rb +++ b/lib/webpacker/configuration.rb @@ -8,17 +8,17 @@ def file_path end def manifest_path - Rails.root.join(packs_path, "manifest.json") + Rails.root.join(entry_path, "manifest.json") end - def packs_path - Rails.root.join(paths.fetch(:dist_path, "public/packs")) + def entry_path + Rails.root.join(paths.fetch(:output, "public"), paths.fetch(:entry, "packs")) end def paths load if Rails.env.development? raise Webpacker::FileLoader::FileLoaderError.new("Webpacker::Configuration.load must be called first") unless instance - instance.data.fetch(:paths, {}) + instance.data end def shared_config_path @@ -26,7 +26,7 @@ def shared_config_path end def webpack_config_path - Rails.root.join(paths.fetch(:config_path, "config/webpack")) + Rails.root.join(paths.fetch(:config, "config/webpack")) end end From d73ca357663c3007544d70a31e1de4de25d05fc5 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Mon, 20 Mar 2017 15:19:27 +0000 Subject: [PATCH 15/27] Remove redundant method --- lib/webpacker/configuration.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/webpacker/configuration.rb b/lib/webpacker/configuration.rb index 50beab89a..384e8caf1 100644 --- a/lib/webpacker/configuration.rb +++ b/lib/webpacker/configuration.rb @@ -22,11 +22,7 @@ def paths end def shared_config_path - Rails.root.join(webpack_config_path, "shared.js") - end - - def webpack_config_path - Rails.root.join(paths.fetch(:config, "config/webpack")) + Rails.root.join(paths.fetch(:config, "config/webpack"), "shared.js") end end From 77b1cf5d1f96ab58a63039a3df71e329ca219741 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Tue, 21 Mar 2017 13:03:21 +0000 Subject: [PATCH 16/27] Rename dev_server.yml to development.server.yml --- README.md | 4 ++-- lib/install/bin/webpack.tt | 4 ++-- lib/install/config/webpack/configuration.js | 2 +- .../config/webpack/{dev_server.yml => development.server.yml} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename lib/install/config/webpack/{dev_server.yml => development.server.yml} (100%) diff --git a/README.md b/README.md index d55d87d2f..8c922b00b 100644 --- a/README.md +++ b/README.md @@ -97,10 +97,10 @@ node_modules: node_modules directory to emit bundles. For ex, `public/packs` Similary, you can also control and configure `webpack-dev-server` settings from -`config/webpack/dev_server.yml` file +`config/webpack/development.server.yml` file ```yml -# config/webpack/dev_server.yml +# config/webpack/development.server.yml enabled: true host: localhost port: 8080 diff --git a/lib/install/bin/webpack.tt b/lib/install/bin/webpack.tt index 0ec44f5c5..fcbd427fa 100644 --- a/lib/install/bin/webpack.tt +++ b/lib/install/bin/webpack.tt @@ -12,7 +12,7 @@ NODE_ENV = ENV["NODE_ENV"] APP_PATH = File.expand_path("../", __dir__) CONFIG_PATH = File.join(APP_PATH, "config/webpack/paths.yml") -DEV_SERVER_CONFIG_PATH = File.join(APP_PATH, "config/webpack/dev_server.yml") +DEV_SERVER_CONFIG_PATH = File.join(APP_PATH, "config/webpack/development.server.yml") begin paths = YAML.load(File.read(CONFIG_PATH)) @@ -26,7 +26,7 @@ begin "Disable to serve assets directly from public/packs directory" end rescue Errno::ENOENT, NoMethodError - puts "Configuration not found in config/webpack/paths.yml or config/webpack/dev_server.yml." + puts "Configuration not found in config/webpack/paths.yml or config/webpack/development.server.yml." puts "Please run bundle exec rails webpacker:install to install webpacker" exit! end diff --git a/lib/install/config/webpack/configuration.js b/lib/install/config/webpack/configuration.js index f576440d5..607c3e52c 100644 --- a/lib/install/config/webpack/configuration.js +++ b/lib/install/config/webpack/configuration.js @@ -7,7 +7,7 @@ const { readFileSync } = require('fs') const configPath = resolve('config', 'webpack') const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8')) -const devServer = safeLoad(readFileSync(join(configPath, 'dev_server.yml'), 'utf8')) +const devServer = safeLoad(readFileSync(join(configPath, 'development.server.yml'), 'utf8')) const publicPath = env.NODE_ENV !== 'production' && devServer.enabled ? `http://${devServer.host}:${devServer.port}/` : `/${paths.entry}/` diff --git a/lib/install/config/webpack/dev_server.yml b/lib/install/config/webpack/development.server.yml similarity index 100% rename from lib/install/config/webpack/dev_server.yml rename to lib/install/config/webpack/development.server.yml From 64e34c0a2eb421c55f66cd224545fced213ad4f7 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 04:06:12 +0000 Subject: [PATCH 17/27] Rename webpacker:install:verify to webpacker:install_verify --- lib/tasks/installers/angular.rake | 2 +- lib/tasks/installers/react.rake | 2 +- lib/tasks/installers/vue.rake | 2 +- lib/tasks/webpacker/compile.rake | 2 +- lib/tasks/webpacker/verify.rake | 17 ----------------- 5 files changed, 4 insertions(+), 21 deletions(-) delete mode 100644 lib/tasks/webpacker/verify.rake diff --git a/lib/tasks/installers/angular.rake b/lib/tasks/installers/angular.rake index e387294cb..ef30fabb6 100644 --- a/lib/tasks/installers/angular.rake +++ b/lib/tasks/installers/angular.rake @@ -3,7 +3,7 @@ require "webpacker/configuration" namespace :webpacker do namespace :install do desc "Install everything needed for Angular" - task angular: ["webpacker:install:verify"] do + task angular: ["webpacker:verify_install"] do shared_config_path = Webpacker::Configuration.shared_config_path config = File.read(shared_config_path) diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index 09a517d15..8218bfc64 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -3,7 +3,7 @@ require "webpacker/configuration" namespace :webpacker do namespace :install do desc "Install everything needed for react" - task react: ["webpacker:install:verify"] do + task react: ["webpacker:verify_install"] do shared_config_path = Webpacker::Configuration.shared_config_path config = File.read(shared_config_path) diff --git a/lib/tasks/installers/vue.rake b/lib/tasks/installers/vue.rake index 361e1d841..5f399f52e 100644 --- a/lib/tasks/installers/vue.rake +++ b/lib/tasks/installers/vue.rake @@ -3,7 +3,7 @@ require "webpacker/configuration" namespace :webpacker do namespace :install do desc "Install everything needed for Vue" - task vue: ["webpacker:install:verify"] do + task vue: ["webpacker:verify_install"] do shared_config_path = Webpacker::Configuration.shared_config_path config = File.read(shared_config_path) diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index 10404b221..8379719eb 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -3,7 +3,7 @@ REGEX_MAP = /\A.*\.map\z/ namespace :webpacker do desc "Compile javascript packs using webpack for production with digests" - task compile: ["webpacker:install:verify", :environment] do + task compile: ["webpacker:verify_install", :environment] do result = `NODE_ENV=production ./bin/webpack` unless $?.success? diff --git a/lib/tasks/webpacker/verify.rake b/lib/tasks/webpacker/verify.rake deleted file mode 100644 index 7c8e21622..000000000 --- a/lib/tasks/webpacker/verify.rake +++ /dev/null @@ -1,17 +0,0 @@ -require "webpacker/configuration" - -namespace :webpacker do - namespace :install do - desc "Verifies if webpacker is installed" - task :verify do - begin - File.read(Webpacker::Configuration.file_path) - rescue Errno::ENOENT - puts "Configuration config/webpack/paths.yml file not found. \n"\ - "Make sure webpacker:install is run successfully before " \ - "running dependent tasks" - exit! - end - end - end -end From 63ae3cd28c13278cdabb15b30649ceac6de49561 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 04:06:22 +0000 Subject: [PATCH 18/27] Rename the task file --- lib/tasks/webpacker/verify_install.rake | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/tasks/webpacker/verify_install.rake diff --git a/lib/tasks/webpacker/verify_install.rake b/lib/tasks/webpacker/verify_install.rake new file mode 100644 index 000000000..153ca403a --- /dev/null +++ b/lib/tasks/webpacker/verify_install.rake @@ -0,0 +1,15 @@ +require "webpacker/configuration" + +namespace :webpacker do + desc "Verifies if webpacker is installed" + task :verify_install do + begin + File.read(Webpacker::Configuration.file_path) + rescue Errno::ENOENT + puts "Configuration config/webpack/paths.yml file not found. \n"\ + "Make sure webpacker:install is run successfully before " \ + "running dependent tasks" + exit! + end + end +end From e4736d18d78a9d67e5363c94a11e22f2ee8a4393 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 04:06:47 +0000 Subject: [PATCH 19/27] Add a separate yarn install task to enhance precompile with yarn install --- lib/tasks/webpacker/yarn_install.rake | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 lib/tasks/webpacker/yarn_install.rake diff --git a/lib/tasks/webpacker/yarn_install.rake b/lib/tasks/webpacker/yarn_install.rake new file mode 100644 index 000000000..5ef5d70de --- /dev/null +++ b/lib/tasks/webpacker/yarn_install.rake @@ -0,0 +1,13 @@ +namespace :webpacker do + desc "Install all JavaScript dependencies as specified via Yarn" + task :yarn_install do + system("./bin/yarn") + end +end + +# Run Yarn prior to Sprockets assets precompilation, so dependencies are available for use. +if Rake::Task.task_defined?("assets:precompile") + unless Rake::Task.task_defined?("yarn:install") + Rake::Task["assets:precompile"].enhance["webpacker:yarn_install"] + end +end From bb371529ad17a22e619546d682cba0edc2d8b15e Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 04:08:20 +0000 Subject: [PATCH 20/27] Move enhancement to compile task instead --- lib/tasks/webpacker/compile.rake | 3 +++ lib/tasks/webpacker/yarn_install.rake | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index 8379719eb..cbf19ef69 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -19,6 +19,9 @@ end # Compile packs after we've compiled all other assets during precompilation if Rake::Task.task_defined?("assets:precompile") Rake::Task["assets:precompile"].enhance do + unless Rake::Task.task_defined?("yarn:install") + Rake::Task["webpacker:yarn_install"].invoke + end Rake::Task["webpacker:compile"].invoke end end diff --git a/lib/tasks/webpacker/yarn_install.rake b/lib/tasks/webpacker/yarn_install.rake index 5ef5d70de..ec474b9ce 100644 --- a/lib/tasks/webpacker/yarn_install.rake +++ b/lib/tasks/webpacker/yarn_install.rake @@ -4,10 +4,3 @@ namespace :webpacker do system("./bin/yarn") end end - -# Run Yarn prior to Sprockets assets precompilation, so dependencies are available for use. -if Rake::Task.task_defined?("assets:precompile") - unless Rake::Task.task_defined?("yarn:install") - Rake::Task["assets:precompile"].enhance["webpacker:yarn_install"] - end -end From 596e77df1683a0fd697c07e1ef12382b1a4fa19b Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 04:10:38 +0000 Subject: [PATCH 21/27] Add a comment --- lib/tasks/webpacker/compile.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index cbf19ef69..9ac2aefdf 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -20,6 +20,7 @@ end if Rake::Task.task_defined?("assets:precompile") Rake::Task["assets:precompile"].enhance do unless Rake::Task.task_defined?("yarn:install") + # For Rails < 5.1 Rake::Task["webpacker:yarn_install"].invoke end Rake::Task["webpacker:compile"].invoke From ef48e6f0224e623d1a66d2696226bae004942e53 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 04:47:07 +0000 Subject: [PATCH 22/27] Fix task description --- lib/tasks/webpacker.rake | 2 ++ lib/tasks/webpacker/yarn_install.rake | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/tasks/webpacker.rake b/lib/tasks/webpacker.rake index 7dcee289f..1b18788d1 100644 --- a/lib/tasks/webpacker.rake +++ b/lib/tasks/webpacker.rake @@ -1,6 +1,8 @@ tasks = { "webpacker:install" => "Installs and setup webpack with yarn", "webpacker:compile" => "Compiles webpack bundles based on environment", + "webpacker:verify_install" => "Verifies if webpacker is installed", + "webpacker:yarn_install" => "Support for older Rails versions. Install all JavaScript dependencies as specified via Yarn", "webpacker:install:react" => "Installs and setup example react component", "webpacker:install:vue" => "Installs and setup example vue component", "webpacker:install:angular" => "Installs and setup example angular2 component" diff --git a/lib/tasks/webpacker/yarn_install.rake b/lib/tasks/webpacker/yarn_install.rake index ec474b9ce..0a0b824ec 100644 --- a/lib/tasks/webpacker/yarn_install.rake +++ b/lib/tasks/webpacker/yarn_install.rake @@ -1,5 +1,5 @@ namespace :webpacker do - desc "Install all JavaScript dependencies as specified via Yarn" + desc "Support for older Rails versions. Install all JavaScript dependencies as specified via Yarn" task :yarn_install do system("./bin/yarn") end From 2b0d4ed891276a1912d442baee50d8531ec64539 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 06:38:57 +0000 Subject: [PATCH 23/27] Add messages to verify task Fix typo --- lib/tasks/webpacker/compile.rake | 1 + lib/tasks/webpacker/verify_install.rake | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index 9ac2aefdf..cb93ad8e0 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -4,6 +4,7 @@ REGEX_MAP = /\A.*\.map\z/ namespace :webpacker do desc "Compile javascript packs using webpack for production with digests" task compile: ["webpacker:verify_install", :environment] do + puts "Compiling webpacker assets πŸŽ‰" result = `NODE_ENV=production ./bin/webpack` unless $?.success? diff --git a/lib/tasks/webpacker/verify_install.rake b/lib/tasks/webpacker/verify_install.rake index 153ca403a..06aa46f9b 100644 --- a/lib/tasks/webpacker/verify_install.rake +++ b/lib/tasks/webpacker/verify_install.rake @@ -3,9 +3,10 @@ require "webpacker/configuration" namespace :webpacker do desc "Verifies if webpacker is installed" task :verify_install do - begin - File.read(Webpacker::Configuration.file_path) - rescue Errno::ENOENT + if File.exist?(Webpacker::Configuration.file_path) + puts "Webpacker is installed πŸŽ‰ 🍰" + puts "Using #{Webpacker::Configuration.file_path} file for setting up webpack paths" + else puts "Configuration config/webpack/paths.yml file not found. \n"\ "Make sure webpacker:install is run successfully before " \ "running dependent tasks" From ec8681182691b03d0d36b84136c670399dc3a9f2 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 13:17:44 +0000 Subject: [PATCH 24/27] Make sure to use paths.yml for third-party installers --- lib/tasks/installers/angular.rake | 4 ++-- lib/tasks/installers/react.rake | 2 +- lib/tasks/installers/vue.rake | 4 ++-- lib/tasks/webpacker/compile.rake | 2 +- lib/webpacker/configuration.rb | 12 ++++++++++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/tasks/installers/angular.rake b/lib/tasks/installers/angular.rake index ef30fabb6..730eeda70 100644 --- a/lib/tasks/installers/angular.rake +++ b/lib/tasks/installers/angular.rake @@ -25,11 +25,11 @@ namespace :webpacker do puts "Copying Angular example to app/javascript/packs/hello_angular.js" FileUtils.copy File.expand_path("../../install/examples/angular/hello_angular.js", __dir__), - Rails.root.join("app/javascript/packs/hello_angular.js") + Rails.root.join(Webpacker::Configuration.entry_path, "hello_angular.js") puts "Copying Angular Hello app to app/javascript/hello_angular" FileUtils.copy_entry File.expand_path("../../install/examples/angular/hello_angular", __dir__), - Rails.root.join("app/javascript/hello_angular") + Rails.root.join(Webpacker::Configuration.source_path, "hello_angular") puts "Copying tsconfig.json to the Rails root directory" FileUtils.copy File.expand_path("../../install/examples/angular/tsconfig.json", __dir__), diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index 8218bfc64..345bd39d0 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -36,7 +36,7 @@ namespace :webpacker do puts "Copying react example to app/javascript/packs/hello_react.jsx" FileUtils.copy File.expand_path("../../install/examples/react/hello_react.jsx", __dir__), - Rails.root.join("app/javascript/packs/hello_react.jsx") + Rails.root.join(Webpacker::Configuration.entry_path, "hello_react.jsx") exec "./bin/yarn add react react-dom babel-preset-react" end diff --git a/lib/tasks/installers/vue.rake b/lib/tasks/installers/vue.rake index 5f399f52e..6da32b847 100644 --- a/lib/tasks/installers/vue.rake +++ b/lib/tasks/installers/vue.rake @@ -24,10 +24,10 @@ namespace :webpacker do puts "Copying the Vue example to app/javascript/packs/vue" FileUtils.copy File.expand_path("../../install/examples/vue/hello_vue.js", File.dirname(__FILE__)), - Rails.root.join("app/javascript/packs/hello_vue.js") + Rails.root.join(Webpacker::Configuration.entry_path, "hello_vue.js") FileUtils.copy File.expand_path("../../install/examples/vue/app.vue", File.dirname(__FILE__)), - Rails.root.join("app/javascript/packs/app.vue") + Rails.root.join(Webpacker::Configuration.entry_path, "app.vue") exec "./bin/yarn add vue vue-loader vue-template-compiler sass-loader node-sass css-loader axios" end diff --git a/lib/tasks/webpacker/compile.rake b/lib/tasks/webpacker/compile.rake index cb93ad8e0..2428a4f06 100644 --- a/lib/tasks/webpacker/compile.rake +++ b/lib/tasks/webpacker/compile.rake @@ -12,7 +12,7 @@ namespace :webpacker do exit! $?.exitstatus end - puts "Compiled digests for all packs in #{Webpacker::Configuration.entry_path}: " + puts "Compiled digests for all packs in #{Webpacker::Configuration.output_path}: " puts JSON.parse(File.read(Webpacker::Configuration.manifest_path)) end end diff --git a/lib/webpacker/configuration.rb b/lib/webpacker/configuration.rb index 384e8caf1..dbd3f2232 100644 --- a/lib/webpacker/configuration.rb +++ b/lib/webpacker/configuration.rb @@ -3,15 +3,19 @@ class Webpacker::Configuration < Webpacker::FileLoader class << self + def entry_path + Rails.root.join(source_path, paths.fetch(:entry, "packs")) + end + def file_path Rails.root.join("config", "webpack", "paths.yml") end def manifest_path - Rails.root.join(entry_path, "manifest.json") + Rails.root.join(output_path, "manifest.json") end - def entry_path + def output_path Rails.root.join(paths.fetch(:output, "public"), paths.fetch(:entry, "packs")) end @@ -24,6 +28,10 @@ def paths def shared_config_path Rails.root.join(paths.fetch(:config, "config/webpack"), "shared.js") end + + def source_path + Rails.root.join(paths.fetch(:source, "app/javascript")) + end end private From 101c4f6b202942aed83d24dd1d13272ca7993de6 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 13:28:40 +0000 Subject: [PATCH 25/27] Use paths from configuration --- lib/tasks/installers/angular.rake | 4 ++-- lib/tasks/installers/react.rake | 4 ++-- lib/tasks/installers/vue.rake | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/tasks/installers/angular.rake b/lib/tasks/installers/angular.rake index 730eeda70..dd985705c 100644 --- a/lib/tasks/installers/angular.rake +++ b/lib/tasks/installers/angular.rake @@ -23,11 +23,11 @@ namespace :webpacker do File.write shared_config_path, config - puts "Copying Angular example to app/javascript/packs/hello_angular.js" + puts "Copying Angular example to #{Webpacker::Configuration.entry_path}" FileUtils.copy File.expand_path("../../install/examples/angular/hello_angular.js", __dir__), Rails.root.join(Webpacker::Configuration.entry_path, "hello_angular.js") - puts "Copying Angular Hello app to app/javascript/hello_angular" + puts "Copying Angular Hello app to #{Webpacker::Configuration.source_path}" FileUtils.copy_entry File.expand_path("../../install/examples/angular/hello_angular", __dir__), Rails.root.join(Webpacker::Configuration.source_path, "hello_angular") diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index 345bd39d0..3af2764de 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -30,11 +30,11 @@ namespace :webpacker do File.write shared_config_path, config - puts "Copying .babelrc to project directory" + puts "Copying .babelrc to #{Rails.root}" FileUtils.copy File.expand_path("../../install/examples/react/.babelrc", __dir__), Rails.root - puts "Copying react example to app/javascript/packs/hello_react.jsx" + puts "Copying react example hello_react.jsx to #{Webpacker::Configuration.entry_path}" FileUtils.copy File.expand_path("../../install/examples/react/hello_react.jsx", __dir__), Rails.root.join(Webpacker::Configuration.entry_path, "hello_react.jsx") diff --git a/lib/tasks/installers/vue.rake b/lib/tasks/installers/vue.rake index 6da32b847..460fc4a8c 100644 --- a/lib/tasks/installers/vue.rake +++ b/lib/tasks/installers/vue.rake @@ -22,7 +22,7 @@ namespace :webpacker do File.write shared_config_path, config - puts "Copying the Vue example to app/javascript/packs/vue" + puts "Copying the Vue example to #{Webpacker::Configuration.entry_path}" FileUtils.copy File.expand_path("../../install/examples/vue/hello_vue.js", File.dirname(__FILE__)), Rails.root.join(Webpacker::Configuration.entry_path, "hello_vue.js") From 5842c703700549f4efbcd8d0571eee3bce595050 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Wed, 22 Mar 2017 13:34:30 +0000 Subject: [PATCH 26/27] Remove filename --- lib/tasks/installers/react.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index 3af2764de..a37d9b719 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -34,7 +34,7 @@ namespace :webpacker do FileUtils.copy File.expand_path("../../install/examples/react/.babelrc", __dir__), Rails.root - puts "Copying react example hello_react.jsx to #{Webpacker::Configuration.entry_path}" + puts "Copying react example to #{Webpacker::Configuration.entry_path}" FileUtils.copy File.expand_path("../../install/examples/react/hello_react.jsx", __dir__), Rails.root.join(Webpacker::Configuration.entry_path, "hello_react.jsx") From d7278d28a3e8f66ec7e5df6205e37550a68f0eb6 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Thu, 23 Mar 2017 07:54:52 +0000 Subject: [PATCH 27/27] Refactor loaders and installers --- lib/install/angular.rb | 18 +++++++ lib/install/config/.postcssrc.yml | 4 ++ lib/install/config/loaders/core/assets.js | 12 +++++ lib/install/config/loaders/core/babel.js | 10 ++++ lib/install/config/loaders/core/coffee.js | 4 ++ lib/install/config/loaders/core/erb.js | 9 ++++ lib/install/config/loaders/core/sass.js | 9 ++++ .../config/loaders/installers/angular.js | 4 ++ .../config/loaders/installers/react.js | 11 ++++ lib/install/config/loaders/installers/vue.js | 10 ++++ lib/install/config/webpack/configuration.js | 2 + lib/install/config/webpack/development.js | 1 - lib/install/config/webpack/paths.yml | 12 +++++ lib/install/config/webpack/shared.js | 52 ++++--------------- lib/install/examples/vue/hello_vue.js | 2 +- lib/install/react.rb | 15 ++++++ lib/install/template.rb | 14 ++++- lib/install/vue.rb | 15 ++++++ lib/tasks/installers/angular.rake | 35 ++----------- lib/tasks/installers/react.rake | 38 ++------------ lib/tasks/installers/vue.rake | 29 ++--------- lib/webpacker/configuration.rb | 8 +-- 22 files changed, 173 insertions(+), 141 deletions(-) create mode 100644 lib/install/angular.rb create mode 100644 lib/install/config/.postcssrc.yml create mode 100644 lib/install/config/loaders/core/assets.js create mode 100644 lib/install/config/loaders/core/babel.js create mode 100644 lib/install/config/loaders/core/coffee.js create mode 100644 lib/install/config/loaders/core/erb.js create mode 100644 lib/install/config/loaders/core/sass.js create mode 100644 lib/install/config/loaders/installers/angular.js create mode 100644 lib/install/config/loaders/installers/react.js create mode 100644 lib/install/config/loaders/installers/vue.js create mode 100644 lib/install/react.rb create mode 100644 lib/install/vue.rb diff --git a/lib/install/angular.rb b/lib/install/angular.rb new file mode 100644 index 000000000..ddf8e00be --- /dev/null +++ b/lib/install/angular.rb @@ -0,0 +1,18 @@ +require "webpacker/configuration" + +puts "Copying angular loader to #{Webpacker::Configuration.config_path}/loaders" +copy_file "#{__dir__}/config/loaders/installers/angular.js", "config/webpack/loaders/angular.js" + +puts "Copying angular example entry file to #{Webpacker::Configuration.entry_path}" +copy_file "#{__dir__}/examples/angular/hello_angular.js", "#{Webpacker::Configuration.entry_path}/hello_angular.js" + +puts "Copying hello_angular app to #{Webpacker::Configuration.source_path}" +directory "#{__dir__}/examples/angular/hello_angular", "#{Webpacker::Configuration.source_path}/hello_angular" + +puts "Copying tsconfig.json to the Rails root directory for typescript" +copy_file "#{__dir__}/examples/angular/tsconfig.json", "tsconfig.json" + +puts "Installing all angular dependencies" +run "./bin/yarn add typescript ts-loader core-js zone.js rxjs @angular/core @angular/common @angular/compiler @angular/platform-browser @angular/platform-browser-dynamic" + +puts "Webpacker now supports angular.js and typescript πŸŽ‰" diff --git a/lib/install/config/.postcssrc.yml b/lib/install/config/.postcssrc.yml new file mode 100644 index 000000000..bc4f02ab3 --- /dev/null +++ b/lib/install/config/.postcssrc.yml @@ -0,0 +1,4 @@ +plugins: + postcss-smart-import: {} + precss: {} + autoprefixer: {} diff --git a/lib/install/config/loaders/core/assets.js b/lib/install/config/loaders/core/assets.js new file mode 100644 index 000000000..c859daf0b --- /dev/null +++ b/lib/install/config/loaders/core/assets.js @@ -0,0 +1,12 @@ +const { env, publicPath } = require('../configuration.js') + +module.exports = { + test: /\.(jpeg|png|gif|svg|eot|ttf|woff|woff2)$/i, + use: [{ + loader: 'file-loader', + options: { + publicPath, + name: env.NODE_ENV === 'production' ? '[name]-[hash].[ext]' : '[name].[ext]' + } + }] +} diff --git a/lib/install/config/loaders/core/babel.js b/lib/install/config/loaders/core/babel.js new file mode 100644 index 000000000..82481e15e --- /dev/null +++ b/lib/install/config/loaders/core/babel.js @@ -0,0 +1,10 @@ +module.exports = { + test: /\.js(\.erb)?$/, + exclude: /node_modules/, + loader: 'babel-loader', + options: { + presets: [ + ['env', { modules: false }] + ] + } +} diff --git a/lib/install/config/loaders/core/coffee.js b/lib/install/config/loaders/core/coffee.js new file mode 100644 index 000000000..dae874249 --- /dev/null +++ b/lib/install/config/loaders/core/coffee.js @@ -0,0 +1,4 @@ +module.exports = { + test: /\.coffee(\.erb)?$/, + loader: 'coffee-loader' +} diff --git a/lib/install/config/loaders/core/erb.js b/lib/install/config/loaders/core/erb.js new file mode 100644 index 000000000..c1a6cc158 --- /dev/null +++ b/lib/install/config/loaders/core/erb.js @@ -0,0 +1,9 @@ +module.exports = { + test: /\.erb$/, + enforce: 'pre', + exclude: /node_modules/, + loader: 'rails-erb-loader', + options: { + runner: 'DISABLE_SPRING=1 bin/rails runner' + } +} diff --git a/lib/install/config/loaders/core/sass.js b/lib/install/config/loaders/core/sass.js new file mode 100644 index 000000000..faba9d5b0 --- /dev/null +++ b/lib/install/config/loaders/core/sass.js @@ -0,0 +1,9 @@ +const ExtractTextPlugin = require('extract-text-webpack-plugin') + +module.exports = { + test: /\.(scss|sass|css)$/i, + use: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: ['css-loader', 'postcss-loader', 'sass-loader'] + }) +} diff --git a/lib/install/config/loaders/installers/angular.js b/lib/install/config/loaders/installers/angular.js new file mode 100644 index 000000000..cbb916e33 --- /dev/null +++ b/lib/install/config/loaders/installers/angular.js @@ -0,0 +1,4 @@ +module.exports = { + test: /.ts$/, + loader: 'ts-loader' +} diff --git a/lib/install/config/loaders/installers/react.js b/lib/install/config/loaders/installers/react.js new file mode 100644 index 000000000..065dc4e07 --- /dev/null +++ b/lib/install/config/loaders/installers/react.js @@ -0,0 +1,11 @@ +module.exports = { + test: /\.(js|jsx)?(\.erb)?$/, + exclude: /node_modules/, + loader: 'babel-loader', + options: { + presets: [ + 'react', + ['env', { modules: false }] + ] + } +} diff --git a/lib/install/config/loaders/installers/vue.js b/lib/install/config/loaders/installers/vue.js new file mode 100644 index 000000000..7dbab0409 --- /dev/null +++ b/lib/install/config/loaders/installers/vue.js @@ -0,0 +1,10 @@ +module.exports = { + test: /.vue$/, + loader: 'vue-loader', + options: { + loaders: { + scss: 'vue-style-loader!css-loader!sass-loader', + sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax' + } + } +} diff --git a/lib/install/config/webpack/configuration.js b/lib/install/config/webpack/configuration.js index 607c3e52c..3617eab1c 100644 --- a/lib/install/config/webpack/configuration.js +++ b/lib/install/config/webpack/configuration.js @@ -6,6 +6,7 @@ const { safeLoad } = require('js-yaml') const { readFileSync } = require('fs') const configPath = resolve('config', 'webpack') +const loadersDir = join(__dirname, 'loaders') const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8')) const devServer = safeLoad(readFileSync(join(configPath, 'development.server.yml'), 'utf8')) const publicPath = env.NODE_ENV !== 'production' && devServer.enabled ? @@ -15,5 +16,6 @@ module.exports = { devServer, env, paths, + loadersDir, publicPath } diff --git a/lib/install/config/webpack/development.js b/lib/install/config/webpack/development.js index aec3ebec6..d98ec5b13 100644 --- a/lib/install/config/webpack/development.js +++ b/lib/install/config/webpack/development.js @@ -1,4 +1,3 @@ -/* eslint global-require: 0 */ // Note: You must restart bin/webpack-watcher for changes to take effect const merge = require('webpack-merge') diff --git a/lib/install/config/webpack/paths.yml b/lib/install/config/webpack/paths.yml index 586f77728..818d71fc8 100644 --- a/lib/install/config/webpack/paths.yml +++ b/lib/install/config/webpack/paths.yml @@ -4,3 +4,15 @@ entry: packs output: public node_modules: node_modules source: app/javascript +extensions: + - .coffee + - .js + - .jsx + - .ts + - .vue + - .sass + - .css + - .png + - .svg + - .gif + - .jpeg diff --git a/lib/install/config/webpack/shared.js b/lib/install/config/webpack/shared.js index 1ae61656e..39492aa44 100644 --- a/lib/install/config/webpack/shared.js +++ b/lib/install/config/webpack/shared.js @@ -1,15 +1,17 @@ // Note: You must restart bin/webpack-watcher for changes to take effect +/* eslint global-require: 0 */ +/* eslint import/no-dynamic-require: 0 */ const webpack = require('webpack') const { basename, join, resolve } = require('path') const { sync } = require('glob') +const { readdirSync } = require('fs') const ExtractTextPlugin = require('extract-text-webpack-plugin') const ManifestPlugin = require('webpack-manifest-plugin') const extname = require('path-complete-extname') -const { env, paths, publicPath } = require('./configuration.js') +const { env, paths, publicPath, loadersDir } = require('./configuration.js') -const extensions = ['.js', '.coffee'] -const extensionGlob = `*{${extensions.join(',')}}*` +const extensionGlob = `*{${paths.extensions.join(',')}}*` const packPaths = sync(join(paths.source, paths.entry, extensionGlob)) module.exports = { @@ -24,45 +26,9 @@ module.exports = { output: { filename: '[name].js', path: resolve(paths.output, paths.entry) }, module: { - rules: [ - { test: /\.coffee(\.erb)?$/, loader: 'coffee-loader' }, - { - test: /\.js(\.erb)?$/, - exclude: /node_modules/, - loader: 'babel-loader', - options: { - presets: [ - ['env', { modules: false }] - ] - } - }, - { - test: /\.erb$/, - enforce: 'pre', - exclude: /node_modules/, - loader: 'rails-erb-loader', - options: { - runner: 'DISABLE_SPRING=1 bin/rails runner' - } - }, - { - test: /\.(scss|sass|css)$/i, - use: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: ['css-loader', 'sass-loader'] - }) - }, - { - test: /\.(jpeg|png|gif|svg|eot|svg|ttf|woff|woff2)$/i, - use: [{ - loader: 'file-loader', - options: { - publicPath, - name: env.NODE_ENV === 'production' ? '[name]-[hash].[ext]' : '[name].[ext]' - } - }] - } - ] + rules: readdirSync(loadersDir).map(file => ( + require(join(loadersDir, file)) + )) }, plugins: [ @@ -72,7 +38,7 @@ module.exports = { ], resolve: { - extensions, + extensions: paths.extensions, modules: [ resolve(paths.source), resolve(paths.node_modules) diff --git a/lib/install/examples/vue/hello_vue.js b/lib/install/examples/vue/hello_vue.js index 5ae2e3605..6141a86ec 100644 --- a/lib/install/examples/vue/hello_vue.js +++ b/lib/install/examples/vue/hello_vue.js @@ -4,7 +4,7 @@ // like app/views/layouts/application.html.erb. // All it does is render
Hello Vue
at the bottom of the page. -import Vue from 'vue' +import Vue from 'vue/dist/vue.esm' import App from './app.vue' document.addEventListener('DOMContentLoaded', () => { diff --git a/lib/install/react.rb b/lib/install/react.rb new file mode 100644 index 000000000..b5f3b737f --- /dev/null +++ b/lib/install/react.rb @@ -0,0 +1,15 @@ +require "webpacker/configuration" + +puts "Copying react loader to #{Webpacker::Configuration.config_path}/loaders" +copy_file "#{__dir__}/config/loaders/installers/react.js", "config/webpack/loaders/react.js" + +puts "Copying .babelrc to app root directory" +copy_file "#{__dir__}/examples/react/.babelrc", ".babelrc" + +puts "Copying react example entry file to #{Webpacker::Configuration.entry_path}" +copy_file "#{__dir__}/examples/react/hello_react.jsx", "#{Webpacker::Configuration.entry_path}/hello_react.jsx" + +puts "Installing all react dependencies" +run "./bin/yarn add react react-dom babel-preset-react" + +puts "Webpacker now supports react.js πŸŽ‰" diff --git a/lib/install/template.rb b/lib/install/template.rb index 36ae1eaea..03217f3ec 100644 --- a/lib/install/template.rb +++ b/lib/install/template.rb @@ -1,19 +1,29 @@ -# Setup webpacker +# Install webpacker +puts "Creating javascript app source directory" directory "#{__dir__}/javascript", "app/javascript" +puts "Copying binstubs" directory "#{__dir__}/bin", "bin" chmod "bin", 0755 & ~File.umask, verbose: false +puts "Copying webpack core config and loaders" directory "#{__dir__}/config/webpack", "config/webpack" +directory "#{__dir__}/config/loaders/core", "config/webpack/loaders" +copy_file "#{__dir__}/config/.postcssrc.yml", ".postcssrc.yml" append_to_file ".gitignore", <<-EOS /public/packs /node_modules EOS +puts "Installing all JavaScript dependencies" run "./bin/yarn add webpack webpack-merge js-yaml path-complete-extname " \ "webpack-manifest-plugin babel-loader coffee-loader coffee-script " \ "babel-core babel-preset-env compression-webpack-plugin rails-erb-loader glob " \ -"extract-text-webpack-plugin node-sass file-loader sass-loader css-loader style-loader" +"extract-text-webpack-plugin node-sass file-loader sass-loader css-loader style-loader " \ +"postcss-loader autoprefixer postcss-smart-import precss" +puts "Installing dev server for live reloading" run "./bin/yarn add --dev webpack-dev-server" + +puts "Webpacker successfully installed πŸŽ‰ 🍰" diff --git a/lib/install/vue.rb b/lib/install/vue.rb new file mode 100644 index 000000000..512c1b998 --- /dev/null +++ b/lib/install/vue.rb @@ -0,0 +1,15 @@ +require "webpacker/configuration" + +puts "Copying vue loader to #{Webpacker::Configuration.config_path}/loaders" +copy_file "#{__dir__}/config/loaders/installers/vue.js", "config/webpack/loaders/vue.js" + +puts "Copying the example entry file to #{Webpacker::Configuration.entry_path}" +copy_file "#{__dir__}/examples/vue/hello_vue.js", "#{Webpacker::Configuration.entry_path}/hello_vue.js" + +puts "Copying vue app file to #{Webpacker::Configuration.entry_path}" +copy_file "#{__dir__}/examples/vue/app.vue", "#{Webpacker::Configuration.entry_path}/app.vue" + +puts "Installing all vue dependencies" +run "./bin/yarn add vue vue-loader vue-template-compiler sass-loader node-sass css-loader" + +puts "Webpacker now supports vue.js πŸŽ‰" diff --git a/lib/tasks/installers/angular.rake b/lib/tasks/installers/angular.rake index dd985705c..9bac3eb6c 100644 --- a/lib/tasks/installers/angular.rake +++ b/lib/tasks/installers/angular.rake @@ -1,41 +1,14 @@ -require "webpacker/configuration" +ANGULAR_TEMPLATE_PATH = File.expand_path("../../install/angular.rb", __dir__) namespace :webpacker do namespace :install do desc "Install everything needed for Angular" task angular: ["webpacker:verify_install"] do - shared_config_path = Webpacker::Configuration.shared_config_path - config = File.read(shared_config_path) - - if config.include?("ts-loader") - puts "The configuration file already has a reference to ts-loader, skipping the test rule..." + if Rails::VERSION::MAJOR >= 5 + exec "./bin/rails app:template LOCATION=#{ANGULAR_TEMPLATE_PATH}" else - puts "Adding a loader rule to include ts-loader for .ts files in #{shared_config_path}..." - config.gsub!(/rules:(\s*\[)(\s*\{)/, "rules:\\1\\2 test: /\.ts$/, loader: 'ts-loader' },\\2") + exec "./bin/rake rails:template LOCATION=#{ANGULAR_TEMPLATE_PATH}" end - - if config =~ /["'].ts["']/ - puts "The configuration file already has a reference to .ts extension, skipping the addition of this extension to the list..." - else - puts "Adding '.ts' in loader extensions in #{shared_config_path}..." - config.gsub!(/extensions = (.*')(\s*\])/, "extensions = \\1, '.ts'\\2") - end - - File.write shared_config_path, config - - puts "Copying Angular example to #{Webpacker::Configuration.entry_path}" - FileUtils.copy File.expand_path("../../install/examples/angular/hello_angular.js", __dir__), - Rails.root.join(Webpacker::Configuration.entry_path, "hello_angular.js") - - puts "Copying Angular Hello app to #{Webpacker::Configuration.source_path}" - FileUtils.copy_entry File.expand_path("../../install/examples/angular/hello_angular", __dir__), - Rails.root.join(Webpacker::Configuration.source_path, "hello_angular") - - puts "Copying tsconfig.json to the Rails root directory" - FileUtils.copy File.expand_path("../../install/examples/angular/tsconfig.json", __dir__), - Rails.root.join("tsconfig.json") - - exec "./bin/yarn add typescript ts-loader core-js zone.js rxjs @angular/core @angular/common @angular/compiler @angular/platform-browser @angular/platform-browser-dynamic" end end end diff --git a/lib/tasks/installers/react.rake b/lib/tasks/installers/react.rake index a37d9b719..9b934403d 100644 --- a/lib/tasks/installers/react.rake +++ b/lib/tasks/installers/react.rake @@ -1,44 +1,14 @@ -require "webpacker/configuration" +REACT_TEMPLATE_PATH = File.expand_path("../../install/react.rb", __dir__) namespace :webpacker do namespace :install do desc "Install everything needed for react" task react: ["webpacker:verify_install"] do - shared_config_path = Webpacker::Configuration.shared_config_path - config = File.read(shared_config_path) - - if config =~ /presets:\s*\[\s*\[\s*'env'/ - puts "Replacing loader presets to include react in #{shared_config_path}" - config.gsub!(/presets:(\s*\[)(\s*)\[(\s)*'env'/, "presets:\\1\\2'react',\\2[\\3'env'") - else - puts "Couldn't automatically update loader presets in #{shared_config_path}. Please set presets: [ 'react', [ 'env', { 'modules': false } ] ]." - end - - if config.include?("test: /\\.js(\\.erb)?$/") - puts "Replacing loader test to include react in #{shared_config_path}" - config.gsub!("test: /\\.js(\\.erb)?$/", "test: /\\.(js|jsx)?(\\.erb)?$/") + if Rails::VERSION::MAJOR >= 5 + exec "./bin/rails app:template LOCATION=#{REACT_TEMPLATE_PATH}" else - puts "Couldn't automatically update loader test in #{shared_config_path}. Please set test: /\\.jsx?(\\.erb)?$/." + exec "./bin/rake rails:template LOCATION=#{REACT_TEMPLATE_PATH}" end - - if config =~ /["'].jsx["']/ - puts "The configuration file already has a reference to .jsx extension, skipping the addition of this extension to the list..." - else - puts "Adding '.jsx' in loader extensions in #{shared_config_path}..." - config.gsub!(/extensions = (.*')(\s*\])/, "extensions = \\1, '.jsx'\\2") - end - - File.write shared_config_path, config - - puts "Copying .babelrc to #{Rails.root}" - FileUtils.copy File.expand_path("../../install/examples/react/.babelrc", __dir__), - Rails.root - - puts "Copying react example to #{Webpacker::Configuration.entry_path}" - FileUtils.copy File.expand_path("../../install/examples/react/hello_react.jsx", __dir__), - Rails.root.join(Webpacker::Configuration.entry_path, "hello_react.jsx") - - exec "./bin/yarn add react react-dom babel-preset-react" end end end diff --git a/lib/tasks/installers/vue.rake b/lib/tasks/installers/vue.rake index 460fc4a8c..0f612e16d 100644 --- a/lib/tasks/installers/vue.rake +++ b/lib/tasks/installers/vue.rake @@ -1,35 +1,14 @@ -require "webpacker/configuration" +VUE_TEMPLATE_PATH = File.expand_path("../../install/vue.rb", __dir__) namespace :webpacker do namespace :install do desc "Install everything needed for Vue" task vue: ["webpacker:verify_install"] do - shared_config_path = Webpacker::Configuration.shared_config_path - config = File.read(shared_config_path) - - # Module resolution https://webpack.js.org/concepts/module-resolution/ - if config.include?("'vue$':'vue/dist/vue.esm.js'") - puts "Couldn't automatically update module resolution in #{shared_config_path}. Please set resolve { alias:{ 'vue$':'vue/dist/vue.esm.js' } }." - else - config.gsub!(/resolve:(\s*\{)(\s*)extensions/, "resolve:\\1\\2alias: { 'vue$':'vue/dist/vue.esm.js' },\\2extensions") - end - - if config.include?("loader: 'vue-loader',") - puts "Couldn't automatically update vue-loader in #{shared_config_path}. Please set { test: /.vue$/, loader: 'vue-loader', options: { loaders: { 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'}}}." + if Rails::VERSION::MAJOR >= 5 + exec "./bin/rails app:template LOCATION=#{VUE_TEMPLATE_PATH}" else - config.gsub!(/module:(\s*\{)(\s*)rules:(\s*)\[/, "module:\\1\\2rules:\\3[\\2 {\\2 test: /\.vue$/, loader: 'vue-loader',\\2 options: {\\2 loaders: { 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'}\\2 }\\2 },") + exec "./bin/rake rails:template LOCATION=#{VUE_TEMPLATE_PATH}" end - - File.write shared_config_path, config - - puts "Copying the Vue example to #{Webpacker::Configuration.entry_path}" - FileUtils.copy File.expand_path("../../install/examples/vue/hello_vue.js", File.dirname(__FILE__)), - Rails.root.join(Webpacker::Configuration.entry_path, "hello_vue.js") - - FileUtils.copy File.expand_path("../../install/examples/vue/app.vue", File.dirname(__FILE__)), - Rails.root.join(Webpacker::Configuration.entry_path, "app.vue") - - exec "./bin/yarn add vue vue-loader vue-template-compiler sass-loader node-sass css-loader axios" end end end diff --git a/lib/webpacker/configuration.rb b/lib/webpacker/configuration.rb index dbd3f2232..ac96a89e6 100644 --- a/lib/webpacker/configuration.rb +++ b/lib/webpacker/configuration.rb @@ -3,6 +3,10 @@ class Webpacker::Configuration < Webpacker::FileLoader class << self + def config_path + Rails.root.join(paths.fetch(:config, "config/webpack")) + end + def entry_path Rails.root.join(source_path, paths.fetch(:entry, "packs")) end @@ -25,10 +29,6 @@ def paths instance.data end - def shared_config_path - Rails.root.join(paths.fetch(:config, "config/webpack"), "shared.js") - end - def source_path Rails.root.join(paths.fetch(:source, "app/javascript")) end