Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add static assets support #153

Merged
merged 29 commits into from
Mar 23, 2017
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4625a6a
Add optional static asset task, refactor configs and add new manifest…
gauravtiwari Mar 2, 2017
769cd06
Add stylesheet_pack_tag helper doc to readme
gauravtiwari Mar 7, 2017
5772382
Fix aesthetics based on code review
gauravtiwari Mar 12, 2017
443eef4
Refactor configuration and use that to fetch configs
gauravtiwari Mar 14, 2017
90b2462
Fix indentation
gauravtiwari Mar 16, 2017
bd5c74f
Use manifest.json to lookup paths in all env and get rid of redundant…
gauravtiwari Mar 16, 2017
3a7fb6d
Sync with master
gauravtiwari Mar 16, 2017
64e05e2
Remove redundant options for dev-server
gauravtiwari Mar 16, 2017
57b568a
Fix error messages
gauravtiwari Mar 16, 2017
573baa0
Fix angular and react installer to use new extenstions
gauravtiwari Mar 16, 2017
2d9d2de
Add manifest loading in block to wait if manifest doesn't exist
gauravtiwari Mar 16, 2017
dca884b
Update readme and error messages
gauravtiwari Mar 16, 2017
47031e4
Sync with master
gauravtiwari Mar 16, 2017
539f448
Use writeToFileEmit option
gauravtiwari Mar 17, 2017
bc12929
Update readme
gauravtiwari Mar 17, 2017
524c746
Fix aesthetics around configuration files
gauravtiwari Mar 20, 2017
d73ca35
Remove redundant method
gauravtiwari Mar 20, 2017
77b1cf5
Rename dev_server.yml to development.server.yml
gauravtiwari Mar 21, 2017
64e34c0
Rename webpacker:install:verify to webpacker:install_verify
gauravtiwari Mar 22, 2017
63ae3cd
Rename the task file
gauravtiwari Mar 22, 2017
e4736d1
Add a separate yarn install task to enhance precompile with yarn install
gauravtiwari Mar 22, 2017
bb37152
Move enhancement to compile task instead
gauravtiwari Mar 22, 2017
596e77d
Add a comment
gauravtiwari Mar 22, 2017
ef48e6f
Fix task description
gauravtiwari Mar 22, 2017
2b0d4ed
Add messages to verify task
gauravtiwari Mar 22, 2017
ec86811
Make sure to use paths.yml for third-party installers
gauravtiwari Mar 22, 2017
101c4f6
Use paths from configuration
gauravtiwari Mar 22, 2017
5842c70
Remove filename
gauravtiwari Mar 22, 2017
d7278d2
Refactor loaders and installers
gauravtiwari Mar 23, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
AllCops:
Copy link
Contributor

Choose a reason for hiding this comment

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

awesome to have rubocop!

Copy link
Member

Choose a reason for hiding this comment

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

Can we use the same as rails/rails using the inherit_from option?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should follow https://github.com/github/rubocop-github and create a gem rubocop-rails or something and use it all over the rails repositories with inherit_gem

Copy link
Member

Choose a reason for hiding this comment

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

inherit_from is sufficient. We don't need a gem if we can download the config from github.

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
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ cache:
yarn: true

install:
- gem install rubocop
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't this get handled by the gemfile?

Copy link
Member Author

Choose a reason for hiding this comment

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

@justin808 hmm, may be but don't like dependencies. Also, it seems rubocop is more kinda of standalone program now, like yarn and npm

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it should be in the Gemfile. If it is a development dependency, it should be there.

- nvm install node
- node -v
- npm i -g yarn
- yarn

script:
- yarn lint
- rubocop
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
source 'https://rubygems.org'
source "https://rubygems.org"

gemspec
141 changes: 125 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ 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:

```ruby
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`.
Expand Down Expand Up @@ -59,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')
Expand All @@ -72,19 +69,114 @@ 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 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
```
Copy link
Member

Choose a reason for hiding this comment

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

When would these configurations differ in the different environments?

Copy link
Member Author

Choose a reason for hiding this comment

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

It wouldn't actually, but I added just to follow the convention in other files (database.yml) and in case someone gets fancy to change per/env basis. You suggested in another comment to split this in 2 files - perhaps just do this without env and in two files?


**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 => (
<div className="hello-react">
<img src={helloIcon} alt="hello-icon" />
<p>Hello {props.name}!</p>
</div>
)
```

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,

```erb
<%= stylesheet_pack_tag 'hello_react' %>
```

## 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 `<link rel="prefetch">` or `<img />`
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" %>
<img src="<%= asset_pack_path 'calendar.png' %>" />
<% # => <img src="/packs/calendar.png" /> %>
```

## 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
<!-- In development mode with webpack-dev-server -->
<script src="http://localhost:8080/calendar.js"></script>
<link rel="stylesheet" media="screen" href="http://localhost:8080/calendar.css">
<!-- In development mode -->
<script src="/packs/calendar.js"></script>
<link rel="stylesheet" media="screen" href="/packs/calendar.css">
<!-- In production mode -->
<script src="/packs/calendar-0bd141f6d9360cf4a7f5.js"></script>
<link rel="stylesheet" media="screen" href="/packs/calendar-dc02976b5f94b507e3b6.css">
```

Copy link
Contributor

Choose a reason for hiding this comment

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

Add the stylesheet_pack_tag docs.

## Linking to sprockets assets

Expand All @@ -102,25 +194,42 @@ This is enabled by the `rails-erb-loader` loader rule in `config/webpack/shared.

## 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.

## Troubleshooting

* 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.
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.

* 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 before loading a view.

## Wishlist

- 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).
38 changes: 22 additions & 16 deletions lib/install/bin/webpack-dev-server.tt
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
<%= shebang %>
require 'shellwords'
$stdout.sync = true

ENV['RAILS_ENV'] ||= 'development'
RAILS_ENV = ENV['RAILS_ENV']
require "shellwords"
require "yaml"

ENV['NODE_ENV'] ||= RAILS_ENV
NODE_ENV = ENV['NODE_ENV']
ENV["RAILS_ENV"] ||= "development"
RAILS_ENV = ENV["RAILS_ENV"]

APP_PATH = File.expand_path('../', __dir__)
ESCAPED_APP_PATH = APP_PATH.shellescape
ENV["NODE_ENV"] ||= RAILS_ENV
NODE_ENV = ENV["NODE_ENV"]

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"
APP_PATH = File.expand_path("../", __dir__)
CONFIG_PATH = File.join(APP_PATH, "config/webpack/paths.yml")

# Warn the user if the configuration is not set
RAILS_ENV_CONFIG = File.join("config", "environments", "#{RAILS_ENV}.rb")
begin
config = YAML.load(File.read(CONFIG_PATH))

# 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"
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, NoMethodError
puts "Configuration not found in config/webpacker/paths.yml."
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} --progress --color " \
"--config #{DEV_SERVER_CONFIG}"
end
Loading