quilt_rails 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c16733d68449d4f24bbb9e007537b1c679dc18c801823afded7e2d13ff7ad06
4
- data.tar.gz: aea20cc2f46de9c08a4a3b3bf1332e3e31ec21cf2dcb1029812c6c30f3254860
3
+ metadata.gz: 7cb007cf5708ce9cc49b8eea19e79e8922faeaddf91223348962242c1ce03a8f
4
+ data.tar.gz: d351101e370001003ce4f75c613922c72263998fad1d449f29ac023584112284
5
5
  SHA512:
6
- metadata.gz: 24a89f2f889b0cc3334746bb6362a973a76a1b03ca81472e52ee1da425161a2cff51b6a6d6bded69e4d6663d438299d79fa5f80da40c8a51e4d71b9493a1515b
7
- data.tar.gz: 945937d84d52991a2a382ac6b93e45f14e81b5bc98f02123b44077f2eb7239f30528eb1ffd69c187edd6e34bd001e0e7e9c1df0020e23b256567c1d50f935448
6
+ metadata.gz: dc19e93667fc50d44613cc76d6a63ede39cd7b855850ab9c6e3257637754b066969351d17421f244ac5c6ffe75ee33f9e9363ab546f89bf80269ad3ffecb2840
7
+ data.tar.gz: 61c9d32098b159d7a0b940b96ba02eb274e53dae54e252a25a6ed430c9446cbda9859e9e7ca15808f464833f015a1e32c6bf6e23d5addcdcc0a9c70bb5c49b62
data/README.md CHANGED
@@ -2,47 +2,79 @@
2
2
 
3
3
  A turn-key solution for integrating server-rendered react into your Rails app using Quilt libraries.
4
4
 
5
- This document focuses on Rails integration. For details of `@shopify/react-server`'s configuration and usage, see the [react-server documentation](/packages/react-server/README.md).
6
-
7
5
  ## Table of Contents
8
6
 
9
7
  1. [Quick Start](#quick-start)
8
+ 1. [Generate Rails boilerplate](#generate-rails-boilerplate)
9
+ 1. [Add Ruby dependencies](#add-ruby-dependencies)
10
+ 1. [Generate Quilt boilerplate](#generate-quilt-boilerplate)
11
+ 1. [Try it out](#try-it-out)
12
+ 1. [Manual Install](#manual-installation)
13
+ 1. [Install Dependencies](#install-dependencies)
14
+ 1. [Setup the Rails app](#setup-the-rails-app)
15
+ 1. [Add JavaScript](#add-javascript)
16
+ 1. [Run the server](#run-the-server)
17
+ 1. [Application Layout](#application-layout)
18
+ 1. [API](#api)
19
+ 1. [ReactRenderable](#reactrenderable)
20
+ 1. [Engine](#engine)
21
+ 1. [Generators](#generators)
10
22
  1. [Advanced Use](#advanced-use)
23
+ 1. [Testing](#testing)
24
+ 1. [Interacting with the request and response in React code](#interacting-with-the-request-and-response-in-react-code)
25
+ 1. [Dealing with isomorphic state](#dealing-with-isomorphic-state)
26
+ 1. [Customizing the node server](#customizing-the-node-server)
11
27
 
12
28
  ## Quick Start
13
29
 
30
+ Using the magic of generators, we can spin up a basic app with a few console commands.
31
+
32
+ ### Generate Rails boilerplate
33
+
34
+ `dev init`
35
+
36
+ When prompted, choose `rails`. This will generate a basic Rails application scaffold.
37
+
14
38
  ### Add Ruby dependencies
15
39
 
16
40
  `bundle add sewing_kit quilt_rails`
17
41
 
18
- First, create a Rails project using `dev init`. Next, run `rails generate quilt:install`. This will install the Node dependencies, provide a basic React app (in TypeScript) and mounts the Quilt engine inside of your `config/routes.rb` file.
42
+ This will install our ruby dependencies and update the project's gemfile.
43
+
44
+ ### Generate Quilt boilerplate
45
+
46
+ `rails generate quilt:install`
47
+
48
+ This will install the Node dependencies, provide a basic React app (in TypeScript) and mounts the Quilt engine inside of `config/routes.rb`.
49
+
50
+ ### Try it out
51
+
52
+ `dev server`
53
+
54
+ Will run the application, starting up both servers and compiling assets.
19
55
 
20
56
  ## Manual Installation
21
57
 
22
- You can also perform the steps within the rake task manually by following the guide below.
58
+ An application can also be setup manually using the following steps.
23
59
 
24
60
  ### Install Dependencies
25
61
 
26
62
  ```sh
27
- # Add Node dependencies
63
+ # Add core Node dependencies
28
64
  yarn add @shopify/sewing-kit @shopify/react-server
29
65
 
30
- # Optional - add Polaris and quilt libraries
31
- yarn add @shopify/polaris @shopify/react-self-serializers react react-dom
66
+ # Add Polaris and React
67
+ yarn add @shopify/polaris react react-dom
32
68
 
33
69
  yarn
34
70
  dev up
35
71
  ```
36
72
 
37
- ### Add JavaScript
38
-
39
- sewing_kit looks for JavaScript in `app/ui/index.js`. The code in `index.js` (and any imported JS/CSS) will be built into a `main` bundle.
40
-
41
- ### Rails Setup
73
+ ### Setup the Rails app
42
74
 
43
75
  There are 2 ways to consume this package.
44
76
 
45
- ### Option 1: Mount the Engine
77
+ #### Option 1: Mount the Engine
46
78
 
47
79
  Add the engine to `routes.rb`.
48
80
 
@@ -54,7 +86,7 @@ Rails.application.routes.draw do
54
86
  end
55
87
  ```
56
88
 
57
- Where `at` is the path where your App will respond with the React App. If you only want a sub-section of routes to respond with the React App, you can pass in the path to that sub-section here. For example:
89
+ If only a sub-section of routes should respond with the React App, it can be configured using the `at` parameter.
58
90
 
59
91
  ```ruby
60
92
  # config/routes.rb
@@ -64,7 +96,7 @@ Rails.application.routes.draw do
64
96
  end
65
97
  ```
66
98
 
67
- ### Option 2: Add your react controller and routes
99
+ #### Option 2: Add a React controller and routes
68
100
 
69
101
  Create a `ReactController` to handle react requests.
70
102
 
@@ -78,27 +110,18 @@ class ReactController < ApplicationController
78
110
  end
79
111
  ```
80
112
 
81
- Have your routes wired up to default to your react controller.
113
+ Add routes to default to the `ReactController`.
82
114
 
83
115
  ```ruby
84
116
  get '/*path', to: 'react#index'
85
117
  root 'react#index'
86
118
  ```
87
119
 
88
- ## Minimal Project Layout
120
+ ### Add JavaScript
89
121
 
90
- ```
91
- ├── Gemfile (must contain "gem 'sewing_kit" and "gem 'quilt_rails'")
92
- ├── package.json (must specify '@shopify/sewing-kit' and `@shopify/react-server` as 'dependencies')
93
-
94
- └── app
95
- └── ui
96
- │ └─- index.js
97
- └── controllers
98
- └─- react_controller (see above)
99
- ```
122
+ `sewing_kit` looks for the top level component of your React app in `app/ui/index`. The component exported from this component (and any imported JS/CSS) will be built into a `main` bundle, and used to render the initial server-rendered markup.
100
123
 
101
- ## Example minimal React/Polaris/Quilt entrypoint
124
+ We will add a basic entrypoint using React with Polaris components.
102
125
 
103
126
  ```tsx
104
127
  // app/ui/index.tsx
@@ -119,21 +142,204 @@ function App() {
119
142
  export default App;
120
143
  ```
121
144
 
122
- ## Rails Generators
145
+ ### Run the server
146
+
147
+ `dev server`
148
+
149
+ Will run the application, starting up both servers and compiling assets.
150
+
151
+ ## Application layout
152
+
153
+ ### Minimal
154
+
155
+ The basic layout for an app using `quilt_rails` and friends will have a `ui` folder nested inside the normal Rails `app` folder, containing at _least_ an index.js file exporting a React component.
156
+
157
+ ```
158
+ ├── Gemfile (must contain "gem 'sewing_kit" and "gem 'quilt_rails'")
159
+ ├── package.json (must specify '@shopify/sewing-kit' and `@shopify/react-server` as 'dependencies')
160
+
161
+ └── app
162
+ └── ui
163
+ │ └─- index.{js|ts} (exports a React component)
164
+ └── controllers
165
+ └─- react_controller.rb (see above)
166
+ ```
167
+
168
+ ### Rails, Polaris, and React
169
+
170
+ A more complex application will want a more complex layout. The following shows scalable locations for:
171
+
172
+ - Global SCSS settings
173
+ - App sections (roughly analogous to Rails routes)
174
+ - Components
175
+ - Co-located CSS modules
176
+ - Co-located unit tests
177
+ - Test setup files
178
+
179
+ ```
180
+ └── app
181
+ └── ui
182
+ ├─- index.{js|ts} (exports a React component)
183
+ ├── styles (optional)
184
+ │ └── settings.scss (global vars and @polaris overrides)
185
+
186
+ └── tests (optional)
187
+ │ └── each-test.{js|ts}
188
+ │ └── setup.{js|ts}
189
+ └── features (optional)
190
+ ├── App
191
+ │ ├── index.{js|ts}
192
+ │ ├── App.{js|ts}x
193
+ │ └── tests
194
+ │ └── App.test.{js|ts}x
195
+
196
+ ├-─ MyComponent
197
+ │ ├-─ index.{js|ts}
198
+ │ ├-─ MyComponent.{js|ts}x
199
+ │ ├── MyComponent.scss (optional; component-scoped CSS styles, mixins, etc)
200
+ │ └── tests
201
+ │ └── MyComponent.test.{js|ts}x
202
+
203
+ └── sections (optional; container views that compose presentation components into UI blocks)
204
+ └── Home
205
+ ├-─ index.{js|ts}
206
+ └── Home.{js|ts}
207
+ ```
208
+
209
+ ## API
210
+
211
+ ### ReactRenderable
212
+
213
+ The `ReactRenderable` mixin is intended to be used in Rails controllers, and provides only the `render_react` method. This method handles proxying to a running `@shopify/react-server`.
214
+
215
+ ```ruby
216
+ class ReactController < ApplicationController
217
+ include Quilt::ReactRenderable
218
+
219
+ def index
220
+ render_react
221
+ end
222
+ end
223
+ ```
224
+
225
+ ### Engine
226
+
227
+ `Quilt::Engine` provides a preconfigured controller which consumes `ReactRenderable` and provides an index route which uses it.
228
+
229
+ ```ruby
230
+ # config/routes.rb
231
+ Rails.application.routes.draw do
232
+ # ...
233
+ mount Quilt::Engine, at: '/path/to/react'
234
+ end
235
+ ```
236
+
237
+ ### Configuration
238
+
239
+ The `configure` method allows customization of the address the service will proxy to for UI rendering.
240
+
241
+ ```ruby
242
+ # config/initializers/quilt.rb
243
+ Quilt.configure do |config|
244
+ config.react_server_host = "localhost:3000"
245
+ config.react_server_protocol = 'https'
246
+ end
247
+ ```
248
+
249
+ ### Generators
123
250
 
124
- ### `quilt:install`
251
+ #### `quilt:install`
125
252
 
126
- Installs the Node dependencies, provide a basic React app (in TypeScript) and mounts the Quilt engine inside of your `config/routes.rb` file.
253
+ Installs the Node dependencies, provide a basic React app (in TypeScript) and mounts the Quilt engine in `config/routes.rb`.
127
254
 
128
- ### `sewing_kit:install`
255
+ #### `sewing_kit:install`
129
256
 
130
257
  Adds a basic `sewing-kit.config.ts` file.
131
258
 
132
259
  ## Advanced use
133
260
 
134
- ### Interacting with the request / response in your React code
261
+ ### Testing
262
+
263
+ For fast tests with consistent results, test front-end components using the tools provided by sewing-kit instead of Rails integration tests.
264
+
265
+ Use [`sewing-kit test`](https://github.com/Shopify/sewing-kit/blob/master/docs/commands/test.md#L3) to run all `.test.{js|ts}x` files in the `app/ui` directory. [Jest](https://jestjs.io/) is used as a test runner, with customization available via [its sewing-kit plugin](https://github.com/Shopify/sewing-kit/blob/master/docs/plugins/jest.md).
266
+
267
+ For testing React applications we provide and support [`@shopify/react-testing`](https://github.com/Shopify/quilt/tree/master/packages/react-testing).
268
+
269
+ #### Example
270
+
271
+ Given a component `MyComponent.tsx`
272
+
273
+ ```tsx
274
+ // app/ui/components/MyComponent/MyComponent.tsx
275
+ export function MyComponent({name}: {name: string}) {
276
+ return <div>Hello, {name}!</div>;
277
+ }
278
+ ```
279
+
280
+ A test would be written using Jest and `@shopify/react-testing`'s `mount` feature.
281
+
282
+ ```tsx
283
+ // app/ui/components/MyComponent/tests/MyComponent.test.tsx
284
+ import {MyComponent} from '../MyComponent';
285
+
286
+ describe('MyComponent', () => {
287
+ it('greets the given named person', () => {
288
+ const wrapper = mount(<MyComponent name="Kokusho" />);
289
+
290
+ // toContainReactText is a custom matcher provided by @shopify/react-testing/matchers
291
+ expect(wrapper).toContainReactText('Hello, Kokusho');
292
+ });
293
+ });
294
+ ```
295
+
296
+ ### Test setup files
297
+
298
+ By default, the jest plugin will look for test setup files under `/app/ui/tests`.
299
+
300
+ `setup` can be used to add any custom polyfills needed for the testing environment.
301
+
302
+ ```tsx
303
+ // app/ui/tests/setup.ts
304
+
305
+ import 'isomorphic-fetch';
306
+ import 'raf/polyfill';
307
+ import {URL, URLSearchParams} from 'url';
135
308
 
136
- React-server sets up [@shopify/react-network](https://github.com/Shopify/quilt/blob/master/packages/react-network/src/hooks.ts#L25) for you, so most interactions with the request or response can be done from inside your React code.
309
+ (global as any).URL = URL;
310
+ (global as any).URLSearchParams = URLSearchParams;
311
+ ```
312
+
313
+ `each-test` can be used for any logic that needs to run for each individual test suite. Any setup logic that needs to happen with `jest` globals in scope, such as importing custom matchers, should also be done here.
314
+
315
+ ```tsx
316
+ // app/ui/tests/each-test.ts
317
+
318
+ // we cannot import these in `setup` because `expect` will not be defined
319
+ import '@shopify/react-testing/matchers';
320
+
321
+ beforeAll(() => {
322
+ console.log('I will run before every test suite');
323
+ });
324
+
325
+ beforeEach(() => {
326
+ console.log('I will run before every test case');
327
+ });
328
+
329
+ afterEach(() => {
330
+ console.log('I will run after every test case');
331
+ });
332
+
333
+ afterAll(() => {
334
+ console.log('I will run after every test suite');
335
+ });
336
+ ```
337
+
338
+ For more complete documentation of the jest plugin see [it's documentation](https://github.com/Shopify/sewing-kit/tree/master/docs/plugins/jest.md).
339
+
340
+ ### Interacting with the request and response in React code
341
+
342
+ React-server sets up [@shopify/react-network](https://github.com/Shopify/quilt/blob/master/packages/react-network/src/hooks.ts#L25) automatically, so most interactions with the request or response can be done from inside the React app.
137
343
 
138
344
  #### Example: getting headers
139
345
 
@@ -186,16 +392,22 @@ function App() {
186
392
  export default App;
187
393
  ```
188
394
 
395
+ ### Isomorphic state
396
+
397
+ With SSR enabled React apps, state must be serialized on the server and deserialized on the client to keep it consistent. When using `@shopify/react-server`, the best tool for this job is [`@shopify/react-html`](https://github.com/Shopify/quilt/tree/master/packages/react-html)'s [`useSerialized`](https://github.com/Shopify/quilt/tree/master/packages/react-html#in-your-application-code) hook.
398
+
399
+ `useSerialized` can be used to implement [universal-providers](https://github.com/Shopify/quilt/tree/master/packages/react-universal-provider#what-is-a-universal-provider-), allowing application code to manage what is persisted between the server and client without adding any custom code to client or server entrypoints. We offer some for common use cases such as [CSRF](https://github.com/Shopify/quilt/tree/master/packages/react-csrf-universal-provider), [GraphQL](https://github.com/Shopify/quilt/tree/master/packages/react-graphql-universal-provider), [I18n](https://github.com/Shopify/quilt/tree/master/packages/react-i18n-universal-provider), and the [Shopify App Bridge](https://github.com/Shopify/quilt/tree/master/packages/react-app-bridge-universal-provider).
400
+
189
401
  ### Customizing the node server
190
402
 
191
- By default, sewing-kit bundles in `@shopify/react-server-webpack-plugin` for `quilt_rails` applications to get you up and running fast without needing to manually write any node server code. If you would like to customize what data your react application receives from the incoming request, you can add your own `server.js` / `server.ts` file to the app folder.
403
+ By default, sewing-kit bundles in `@shopify/react-server-webpack-plugin` for `quilt_rails` applications to get apps up and running fast without needing to manually write any node server code. If what it provides is not sufficient, a custom server can be defined by adding a `server.js` or `server.ts` file to the app folder.
192
404
 
193
405
  ```
194
406
  └── app
195
407
  └── ui
196
- └─- app.js
197
- └─- index.js
198
- └─- server.js
408
+ └─- app.{js|ts}x
409
+ └─- index.{js|ts}
410
+ └─- server.{js|ts}x
199
411
  ```
200
412
 
201
413
  ```tsx
@@ -207,17 +419,12 @@ import React from 'react';
207
419
 
208
420
  import App from './app';
209
421
 
210
- // you could create your own server from scratch, but the easiest way to is using `@shopify/react-server`
422
+ // The simplest way to build a custom server that will work with this library is to use the APIs provided by @shopify/react-server.
211
423
  // https://github.com/Shopify/quilt/blob/master/packages/react-server/README.md#L8
212
424
  const app = createServer({
213
425
  port: process.env.PORT ? parseInt(process.env.PORT, 10) : 8081,
214
426
  ip: process.env.IP,
215
427
  assetPrefix: process.env.CDN_URL || 'localhost:8080/assets/webpack',
216
- serverMiddleware: [(ctx, next) => {
217
- // you can add your own middleware to extend the server's functionality.
218
- console.log('I am a custom middleware!');
219
- await next();
220
- }]
221
428
  render: (ctx, {locale}) => {
222
429
  const whatever = /* do something special with the koa context */;
223
430
  // any special data we add to the incoming request in our rails controller we can access here to pass into our component
@@ -228,6 +435,27 @@ const app = createServer({
228
435
  export default app;
229
436
  ```
230
437
 
231
- ### Isomorphic state
438
+ ### Fixing rejected CSRF tokens for new user sessions
439
+
440
+ If a React component calls back to a Rails endpoint (e.g., `/graphql`), Rails may throw a `Can't verify CSRF token authenticity` exception. This stems from the Rails CSRF tokens not persisting until after the first `UiController` call ends.
441
+
442
+ To fix this:
232
443
 
233
- With SSR enabled React apps, state must be serialized on the server and deserialized on the client to keep it consistent. With `@shopify/react-server`, the main way you will accomplish is using [`@shopify/react-html`](https://github.com/Shopify/quilt/tree/master/packages/react-html)'s [`useSerialized`](https://github.com/Shopify/quilt/tree/master/packages/react-html#in-your-application-code) hook to implement [self-serializers](https://github.com/Shopify/quilt/blob/master/packages/react-self-serializers/README.md#self-serializers). We offer some common ones out of the box in [`@shopify/react-self-serializers`](https://github.com/Shopify/quilt/blob/master/packages/react-self-serializers/README.md#self-serializers).
444
+ - Add an `X-Shopify-Server-Side-Rendered: 1` header to all server-side GraphQL requests
445
+ - Add a `protect_from_forgery with: Quilt::TrustedUiServerCsrfStrategy` override to Node-accessed controllers
446
+
447
+ e.g.:
448
+
449
+ ```rb
450
+ class GraphqlController < ApplicationController
451
+ protect_from_forgery with: Quilt::TrustedUiServerCsrfStrategy
452
+
453
+ def execute
454
+ # Get GraphQL query, etc
455
+
456
+ result = MySchema.execute(query, operation_name: operation_name, variables: variables, context: context)
457
+
458
+ render(json: result)
459
+ end
460
+ end
461
+ ```
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quilt
4
+ class TrustedUiServerCsrfStrategy
5
+ def initialize(controller)
6
+ @controller = controller
7
+ end
8
+
9
+ def handle_unverified_request
10
+ return if node_server_side_render?
11
+
12
+ fallback_handler.handle_unverified_request
13
+ end
14
+
15
+ private
16
+
17
+ def node_server_side_render?
18
+ @controller.request.headers['x-shopify-server-side-rendered'] == '1'
19
+ end
20
+
21
+ def fallback_handler
22
+ ActionController::RequestForgeryProtection::ProtectionMethods::Exception.new(@controller)
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Quilt
3
- VERSION = "1.5.0"
3
+ VERSION = "1.6.0"
4
4
  end
data/lib/quilt_rails.rb CHANGED
@@ -7,3 +7,4 @@ require "quilt_rails/engine"
7
7
  require "quilt_rails/logger"
8
8
  require "quilt_rails/configuration"
9
9
  require "quilt_rails/react_renderable"
10
+ require "quilt_rails/trusted_ui_server_csrf_strategy"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quilt_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mathew Allen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-04 00:00:00.000000000 Z
11
+ date: 2019-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -90,6 +90,7 @@ files:
90
90
  - lib/quilt_rails/engine.rb
91
91
  - lib/quilt_rails/logger.rb
92
92
  - lib/quilt_rails/react_renderable.rb
93
+ - lib/quilt_rails/trusted_ui_server_csrf_strategy.rb
93
94
  - lib/quilt_rails/version.rb
94
95
  homepage: https://github.com/Shopify/quilt/tree/master/gems/quilt_rails
95
96
  licenses: