quilt_rails 1.10.0 → 1.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +74 -592
- data/lib/generators/quilt/install_generator.rb +4 -46
- data/lib/generators/quilt/rails_setup/USAGE +5 -0
- data/lib/generators/quilt/rails_setup/rails_setup_generator.rb +31 -0
- data/lib/generators/quilt/rails_setup/templates/Procfile +2 -0
- data/lib/generators/quilt/{templates → rails_setup/templates}/routes.rb +0 -0
- data/lib/generators/quilt/react_app/USAGE +5 -0
- data/lib/generators/quilt/react_app/react_app_generator.rb +13 -0
- data/lib/generators/quilt/{templates → react_app/templates}/App.tsx +0 -0
- data/lib/generators/quilt/react_setup/USAGE +5 -0
- data/lib/generators/quilt/react_setup/react_setup_generator.rb +25 -0
- data/lib/generators/quilt/react_setup/templates/App.tsx +7 -0
- data/lib/generators/quilt/{templates → react_setup/templates}/tsconfig.json +5 -2
- data/lib/generators/quilt_rails/USAGE +5 -0
- data/lib/generators/quilt_rails/install_generator.rb +10 -0
- data/lib/generators/sewing_kit/USAGE +1 -1
- data/lib/generators/sewing_kit/install_generator.rb +21 -8
- data/lib/generators/sewing_kit/templates/package.json +31 -0
- data/lib/generators/sewing_kit/templates/sewing-kit.config.ts +7 -3
- data/lib/quilt_rails.rb +1 -0
- data/lib/quilt_rails/header_csrf_strategy.rb +34 -0
- data/lib/quilt_rails/performance/report.rb +1 -1
- data/lib/quilt_rails/performance/reportable.rb +9 -2
- data/lib/quilt_rails/react_renderable.rb +11 -7
- data/lib/quilt_rails/version.rb +1 -1
- metadata +19 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dee878b6b3d7207ed8c6883791e896ccc1208d6508aae5c592487d2da900cf91
|
4
|
+
data.tar.gz: 38f3fbd9695b816b0a873891b3fd6b90377f541595ac3427b11677d62289e6aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fb1891f8f988bb0b1b8c60c7b5b25e45c8859ba06ef855625d4656fd24f54a79ead749bad2ee0a2a15559ce097106215d5801a6eda9b3b6c55b7fdc2ee42895
|
7
|
+
data.tar.gz: 74d4d5a3b5ed3e96ce4d3348c67ecc89e6022c24cf631c3be192eb43b7ca4202e4d82351d0d22fd8385ddc75c3ddc82657ca54689b35a1341b44de8b3bf70aa9
|
data/README.md
CHANGED
@@ -8,53 +8,22 @@ A turn-key solution for integrating Quilt client-side libraries into your Rails
|
|
8
8
|
- [Quick start](#quick-start)
|
9
9
|
- [Generate Rails boilerplate](#generate-rails-boilerplate)
|
10
10
|
- [Add Ruby dependencies](#add-ruby-dependencies)
|
11
|
-
- [Generate
|
11
|
+
- [Generate app boilerplate](#generate-app-boilerplate)
|
12
12
|
- [Try it out](#try-it-out)
|
13
13
|
- [Manual Install](#manual-installation)
|
14
|
-
- [Install Dependencies](#install-dependencies)
|
15
|
-
- [Setup the Rails app](#setup-the-rails-app)
|
16
|
-
- [Add JavaScript](#add-javascript)
|
17
|
-
- [Run the server](#run-the-server)
|
18
14
|
- [Application Layout](#application-layout)
|
19
15
|
- [Advanced Use](#advanced-use)
|
20
16
|
- [Testing](#testing)
|
21
17
|
- [Interacting with the request and response in React code](#interacting-with-the-request-and-response-in-react-code)
|
22
18
|
- [Dealing with isomorphic state](#dealing-with-isomorphic-state)
|
23
|
-
- [Customizing the
|
19
|
+
- [Customizing the Node server](#customizing-the-node-server)
|
20
|
+
- [Fixing rejected CSRF tokens for new user sessions](#fixing-rejected-csrf-tokens-for-new-user-sessions)
|
24
21
|
- [Performance tracking a React app](#performance-tracking-a-react-app)
|
25
|
-
- [Install dependencies](#install-dependencies)
|
26
|
-
- [Setup an endpoint for performance reports](setup-an-endpoint-for-performance-reports)
|
27
|
-
- [Add annotations](#add-annotations)
|
28
|
-
- [Send the report](#send-the-report)
|
29
|
-
- [Verify in development](#verify-in-development)
|
30
|
-
- [Configure StatsD for production](#configure-statsd-for-production)
|
31
22
|
- [API](#api)
|
32
|
-
- [ReactRenderable](#reactrenderable)
|
33
|
-
- [Performance](#performance)
|
34
|
-
- [Engine](#engine)
|
35
|
-
- [Generators](#generators)
|
36
23
|
|
37
24
|
## Server-side-rendering
|
38
25
|
|
39
|
-
|
40
|
-
|
41
|
-
**Warning:** quilt_rails's server-side-rendering module `ReactRenderable` does not work at scale. Improvements to its architecture are being investigated. In its current state, it can be used for:
|
42
|
-
|
43
|
-
- Workshop applications
|
44
|
-
- Proof of concept applications
|
45
|
-
- Low traffic applications
|
46
|
-
|
47
|
-
For a description of the current architecture's problems, see [this Github comment](https://github.com/Shopify/quilt/issues/1059#issuecomment-539195340).
|
48
|
-
|
49
|
-
The ["decide on a scalable quilt_rails architecture" issue](https://github.com/Shopify/quilt/issues/1100) will track discussion of future architectures.
|
50
|
-
|
51
|
-
To scale up existing quilt_rails applications, skip server-side queries in your components. e.g.:
|
52
|
-
|
53
|
-
```ts
|
54
|
-
useQuery(MyQuery, {
|
55
|
-
skip: typeof document === 'undefined',
|
56
|
-
});
|
57
|
-
```
|
26
|
+
🗒 This guide is focused on internal Shopify developers with access to [`dev`](https://github.com/Shopify/dev) and [@shopify/sewing-kit](https://github.com/Shopify/sewing-kit). A similar setup can be achieved using the [manual installation](./docs/manual-installation) , and following the [react-server-webpack-plugin](../../packages/react-server-webpack-plugin/README.md) guide. Apps not running on Shopify infrastructure should [disable server-side GraphQL queries](./docs/FAQ.md) to avoid scalability issue.
|
58
27
|
|
59
28
|
### Quick start
|
60
29
|
|
@@ -62,173 +31,37 @@ Using the magic of generators, we can spin up a basic app with a few console com
|
|
62
31
|
|
63
32
|
#### Generate Rails boilerplate
|
64
33
|
|
65
|
-
`dev init`
|
66
|
-
|
34
|
+
With access to [`dev`](https://github.com/Shopify/dev), you can use `dev init` to scaffold out a Rails application.
|
67
35
|
When prompted, choose `rails`. This will generate a basic Rails application scaffold.
|
68
36
|
|
37
|
+
Alternatively, you can use [`rails new .`](https://guides.rubyonrails.org/command_line.html#rails-new) to do the same.
|
38
|
+
|
39
|
+
In either case, remove [`webpacker`](./docs/FAQ.md#i-run-into-webpacker-issue-while-setting-up-quilt_rails) before continuing.
|
40
|
+
|
69
41
|
#### Add Ruby dependencies
|
70
42
|
|
71
43
|
`bundle add sewing_kit quilt_rails`
|
72
44
|
|
73
45
|
This will install our ruby dependencies and update the project's gemfile.
|
74
46
|
|
75
|
-
#### Generate
|
47
|
+
#### Generate app boilerplate
|
76
48
|
|
77
|
-
`rails generate
|
49
|
+
`rails generate quilt_rails:install`
|
78
50
|
|
79
|
-
This will install
|
51
|
+
This will install Node dependencies, provide a basic React app (in TypeScript), and mount the Quilt engine in `config/routes.rb`. Basic linting and format configurations are also generated.
|
80
52
|
|
81
53
|
#### Try it out
|
82
54
|
|
83
|
-
`dev server`
|
84
|
-
|
85
|
-
Will run the application, starting up both servers and compiling assets.
|
86
|
-
|
87
|
-
### Manual installation
|
88
|
-
|
89
|
-
An application can also be setup manually using the following steps.
|
90
|
-
|
91
|
-
#### Install dependencies
|
92
|
-
|
93
55
|
```sh
|
94
|
-
# Add core Node dependencies
|
95
|
-
yarn add @shopify/sewing-kit @shopify/react-server
|
96
|
-
|
97
|
-
# Add React
|
98
|
-
yarn add react react-dom
|
99
|
-
|
100
|
-
yarn
|
101
56
|
dev up
|
57
|
+
dev server
|
102
58
|
```
|
103
59
|
|
104
|
-
#### Setup the Rails app
|
105
|
-
|
106
|
-
There are 2 ways to consume this package.
|
107
|
-
|
108
|
-
##### Option 1: Mount the Engine
|
109
|
-
|
110
|
-
Add the engine to `routes.rb`.
|
111
|
-
|
112
|
-
```ruby
|
113
|
-
# config/routes.rb
|
114
|
-
Rails.application.routes.draw do
|
115
|
-
# ...
|
116
|
-
mount Quilt::Engine, at: '/'
|
117
|
-
end
|
118
|
-
```
|
119
|
-
|
120
|
-
If only a sub-section of routes should respond with the React App, it can be configured using the `at` parameter.
|
121
|
-
|
122
|
-
```ruby
|
123
|
-
# config/routes.rb
|
124
|
-
Rails.application.routes.draw do
|
125
|
-
# ...
|
126
|
-
mount Quilt::Engine, at: '/path/to/react'
|
127
|
-
end
|
128
|
-
```
|
129
|
-
|
130
|
-
##### Option 2: Add a React controller and routes
|
131
|
-
|
132
|
-
Create a `ReactController` to handle react requests.
|
133
|
-
|
134
|
-
```ruby
|
135
|
-
class ReactController < ApplicationController
|
136
|
-
include Quilt::ReactRenderable
|
137
|
-
|
138
|
-
def index
|
139
|
-
render_react
|
140
|
-
end
|
141
|
-
end
|
142
|
-
```
|
143
|
-
|
144
|
-
Add routes to default to the `ReactController`.
|
145
|
-
|
146
|
-
```ruby
|
147
|
-
get '/*path', to: 'react#index'
|
148
|
-
root 'react#index'
|
149
|
-
```
|
150
|
-
|
151
|
-
#### Add JavaScript
|
152
|
-
|
153
|
-
`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.
|
154
|
-
|
155
|
-
We will add a basic entrypoint using React with some HTML.
|
156
|
-
|
157
|
-
```tsx
|
158
|
-
// app/ui/index.tsx
|
159
|
-
|
160
|
-
import React from 'react';
|
161
|
-
|
162
|
-
function App() {
|
163
|
-
return <h1>My application ❤️</h1>;
|
164
|
-
}
|
165
|
-
|
166
|
-
export default App;
|
167
|
-
```
|
168
|
-
|
169
|
-
#### Run the server
|
170
|
-
|
171
|
-
`dev server`
|
172
|
-
|
173
60
|
Will run the application, starting up both servers and compiling assets.
|
174
61
|
|
175
|
-
###
|
176
|
-
|
177
|
-
#### Minimal
|
178
|
-
|
179
|
-
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.
|
180
|
-
|
181
|
-
```
|
182
|
-
├── Gemfile (must contain "gem 'sewing_kit" and "gem 'quilt_rails'")
|
183
|
-
├── package.json (must specify '@shopify/sewing-kit' and `@shopify/react-server` as 'dependencies')
|
184
|
-
│
|
185
|
-
└── app
|
186
|
-
└── ui
|
187
|
-
│ └─- index.{js|ts} (exports a React component)
|
188
|
-
└── controllers
|
189
|
-
└─- react_controller.rb (see above)
|
190
|
-
```
|
191
|
-
|
192
|
-
#### Rails and React
|
193
|
-
|
194
|
-
A more complex application will want a more complex layout. The following shows scalable locations for:
|
195
|
-
|
196
|
-
- Global SCSS settings
|
197
|
-
- App sections (roughly analogous to Rails routes)
|
198
|
-
- Components
|
199
|
-
- Co-located CSS modules
|
200
|
-
- Co-located unit tests
|
201
|
-
- Test setup files
|
62
|
+
### Manual installation
|
202
63
|
|
203
|
-
|
204
|
-
└── app
|
205
|
-
└── ui
|
206
|
-
├─- index.{js|ts} (exports a React component)
|
207
|
-
├── styles (optional)
|
208
|
-
└── shared.scss (common functions/mixins you want available in every scss file. Requires configuring `plugin.sass`'s `autoInclude` option in `sewing-kit.config.js`)
|
209
|
-
│
|
210
|
-
└── tests (optional)
|
211
|
-
│ └── each-test.{js|ts}
|
212
|
-
│ └── setup.{js|ts}
|
213
|
-
└── features (optional)
|
214
|
-
├── App
|
215
|
-
│ ├── index.{js|ts}
|
216
|
-
│ ├── App.{js|ts}x
|
217
|
-
│ └── tests
|
218
|
-
│ └── App.test.{js|ts}x
|
219
|
-
│
|
220
|
-
├-─ MyComponent
|
221
|
-
│ ├-─ index.{js|ts}
|
222
|
-
│ ├-─ MyComponent.{js|ts}x
|
223
|
-
│ ├── MyComponent.scss (optional; component-scoped CSS styles, mixins, etc)
|
224
|
-
│ └── tests
|
225
|
-
│ └── MyComponent.test.{js|ts}x
|
226
|
-
│
|
227
|
-
└── sections (optional; container views that compose presentation components into UI blocks)
|
228
|
-
└── Home
|
229
|
-
├-─ index.{js|ts}
|
230
|
-
└── Home.{js|ts}
|
231
|
-
```
|
64
|
+
Follow [this guide](./docs/manual-installation) on how to do manual setup without the generator.
|
232
65
|
|
233
66
|
### Advanced use
|
234
67
|
|
@@ -300,65 +133,67 @@ function App() {
|
|
300
133
|
export default App;
|
301
134
|
```
|
302
135
|
|
303
|
-
##### Example: sending headers from Rails controller
|
136
|
+
##### Example: sending custom headers from Rails controller
|
137
|
+
|
138
|
+
In some cases you may want to send custom headers from Rails to your React server. Quilt facilitates this case by providing consumers with a `headers` argument on the `render_react` call.
|
304
139
|
|
305
140
|
```ruby
|
306
141
|
class ReactController < ApplicationController
|
307
142
|
include Quilt::ReactRenderable
|
308
143
|
|
309
144
|
def index
|
310
|
-
render_react(headers: {
|
145
|
+
render_react(headers: {'x-custom-header': 'header-value-a'})
|
311
146
|
end
|
312
147
|
end
|
313
148
|
```
|
314
149
|
|
315
|
-
|
316
|
-
|
317
|
-
```tsx
|
318
|
-
// app/ui/foundation/CustomUniversalProvider.tsx
|
319
|
-
import {createContext} from 'react';
|
320
|
-
import {createUniversalProvider} from '@shopify/react-universal-provider';
|
321
|
-
|
322
|
-
export const CustomContext = createContext<string | null>(null);
|
323
|
-
export const CustomUniversalProvider = createUniversalProvider('custom-key', CustomContext);
|
324
|
-
```
|
150
|
+
Headers can be accessed during server-side-rendering with the `useRequestHeader` hook from `@shopify/react-network`.
|
325
151
|
|
326
152
|
```tsx
|
327
153
|
// app/ui/index.tsx
|
328
154
|
|
329
155
|
import React from 'react';
|
330
156
|
import {useRequestHeader} from '@shopify/react-network';
|
331
|
-
import {CustomUniversalProvider} from './foundation/CustomUniversalProvider';
|
332
|
-
import {ComponentWithCustomHeader} from './components/ComponentWithCustomHeader';
|
333
157
|
|
334
158
|
function App() {
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
return (
|
339
|
-
<CustomUniversalProvider value={customHeader}>
|
340
|
-
<h1>My application ❤️</h1>
|
341
|
-
<ComponentWithCustomHeader />
|
342
|
-
</CustomUniversalProvider>
|
343
|
-
);
|
159
|
+
const header = useRequestHeader('x-custom-header');
|
160
|
+
return <h1>Data: {header}</h1>;
|
344
161
|
}
|
345
162
|
|
346
163
|
export default App;
|
347
164
|
```
|
348
165
|
|
166
|
+
##### Example: sending custom data from Rails controller
|
167
|
+
|
168
|
+
In some cases you may want to send basic data from Rails to your React server. Quilt facilitates this case by providing consumers with a `data` argument on the `render_react` call.
|
169
|
+
|
170
|
+
**Note:** The data passed should be data that is unlikely or will never change over the course of the session before they render any React components.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
class ReactController < ApplicationController
|
174
|
+
include Quilt::ReactRenderable
|
175
|
+
|
176
|
+
def index
|
177
|
+
render_react(data: {'some_id': 123})
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
If using the webpack plugin, this will be automatically passed into your application as the `data` prop.
|
183
|
+
|
349
184
|
```tsx
|
350
|
-
// app/ui/
|
185
|
+
// app/ui/index.tsx
|
351
186
|
|
352
|
-
import React
|
353
|
-
import {CustomContext} from '../foundation/CustomUniversalProvider';
|
187
|
+
import React from 'react';
|
354
188
|
|
355
|
-
|
356
|
-
//
|
357
|
-
|
358
|
-
const customHeader = useContext(CustomContext);
|
189
|
+
function App({data}: {data: Record<string, any>}) {
|
190
|
+
// Logs {"some_id":123}
|
191
|
+
console.log(data);
|
359
192
|
|
360
|
-
return <
|
193
|
+
return <h1>Data: {data}</h1>;
|
361
194
|
}
|
195
|
+
|
196
|
+
export default App;
|
362
197
|
```
|
363
198
|
|
364
199
|
##### Example: redirecting
|
@@ -385,57 +220,31 @@ With SSR enabled React apps, state must be serialized on the server and deserial
|
|
385
220
|
|
386
221
|
`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).
|
387
222
|
|
388
|
-
#### Customizing the
|
223
|
+
#### Customizing the Node server
|
224
|
+
|
225
|
+
By default, sewing-kit bundles in [`@shopify/react-server-webpack-plugin`](../../packages/react-server-webpack-plugin/README.md) for `quilt_rails` applications to get apps up and running fast without needing to manually write any Node server code.
|
389
226
|
|
390
|
-
|
227
|
+
If what it provides is not sufficient, a completely custom server can be defined by adding a `server.js` or `server.ts` file to the `app/ui` folder. The simplest way to customize the server is to export the object created by [`@shopify/react-server`](../../packages/react-server/README.md#node-usage)'s `createServer` call in `server.ts` file.
|
391
228
|
|
392
229
|
```
|
393
|
-
└──
|
230
|
+
└── appeon
|
394
231
|
└── ui
|
395
232
|
└─- app.{js|ts}x
|
396
233
|
└─- index.{js|ts}
|
397
234
|
└─- server.{js|ts}x
|
398
235
|
```
|
399
236
|
|
400
|
-
```tsx
|
401
|
-
// app/ui/server.tsx
|
402
|
-
import '@shopify/polyfills/fetch';
|
403
|
-
import {createServer} from '@shopify/react-server';
|
404
|
-
import {Context} from 'koa';
|
405
|
-
import React from 'react';
|
406
|
-
|
407
|
-
import App from './app';
|
408
|
-
|
409
|
-
// The simplest way to build a custom server that will work with this library is to use the APIs provided by @shopify/react-server.
|
410
|
-
// https://github.com/Shopify/quilt/blob/master/packages/react-server/README.md#L8
|
411
|
-
const app = createServer({
|
412
|
-
port: process.env.PORT ? parseInt(process.env.PORT, 10) : 8081,
|
413
|
-
ip: process.env.IP,
|
414
|
-
assetPrefix: process.env.CDN_URL || 'localhost:8080/assets/webpack',
|
415
|
-
render: (ctx, {locale}) => {
|
416
|
-
const whatever = /* do something special with the koa context */;
|
417
|
-
// any special data we add to the incoming request in our rails controller we can access here to pass into our component
|
418
|
-
return <App server someCustomProp={whatever} location={ctx.request.url} locale={locale} />;
|
419
|
-
},
|
420
|
-
});
|
421
|
-
|
422
|
-
export default app;
|
423
|
-
```
|
424
|
-
|
425
237
|
#### Fixing rejected CSRF tokens for new user sessions
|
426
238
|
|
427
|
-
|
428
|
-
|
429
|
-
To fix this:
|
239
|
+
When a React component sends HTTP requests 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.
|
430
240
|
|
431
|
-
|
432
|
-
- Add a `protect_from_forgery with: Quilt::TrustedUiServerCsrfStrategy` override to Node-accessed controllers
|
241
|
+
If your API **does not** require session data, the easiest way to deal with this is to use `protect_from_forgery with: :null_session`. This will work for APIs that either have no authentication requirements, or use header based authentication.
|
433
242
|
|
434
|
-
|
243
|
+
##### Example
|
435
244
|
|
436
245
|
```rb
|
437
246
|
class GraphqlController < ApplicationController
|
438
|
-
protect_from_forgery with:
|
247
|
+
protect_from_forgery with: :null_session
|
439
248
|
|
440
249
|
def execute
|
441
250
|
# Get GraphQL query, etc
|
@@ -447,363 +256,36 @@ class GraphqlController < ApplicationController
|
|
447
256
|
end
|
448
257
|
```
|
449
258
|
|
450
|
-
|
451
|
-
|
452
|
-
Using [`Quilt::Performance::Reportable`](#performanceReportable) and [@shopify/react-performance](https://www.npmjs.com/package/@shopify/react-performance) it's easy to add performance tracking to apps using[`sewing_kit`](https://github.com/Shopify/sewing-kit/tree/master/gems/sewing_kit#sewing_kit-) for client-side-rendering or `quilt_rails` for server-side-rendering.
|
453
|
-
|
454
|
-
### Install dependencies
|
455
|
-
|
456
|
-
1. Install the gem (if your app is not already using `quilt_rails`).
|
457
|
-
|
458
|
-
```bash
|
459
|
-
bundle add quilt_rails
|
460
|
-
```
|
461
|
-
|
462
|
-
2. Install `@shopify/react-performance`, the library we will use to annotate our React application and send performance reports to our server.
|
463
|
-
|
464
|
-
```bash
|
465
|
-
yarn add @shopify/react-performance
|
466
|
-
```
|
467
|
-
|
468
|
-
### Setup an endpoint for performance reports
|
469
|
-
|
470
|
-
If your application is not using `Quilt::Engine`, you will need to manually configure the server-side portion of performance tracking. If it _is_ using the engine, the following will be done automatically.
|
471
|
-
|
472
|
-
1. Add a `PerformanceController` and the corresponding routes to your Rails app.
|
473
|
-
|
474
|
-
```ruby
|
475
|
-
# app/controllers/performance_report_controller.rb
|
476
|
-
|
477
|
-
class PerformanceReportController < ActionController::Base
|
478
|
-
include Quilt::Performance::Reportable
|
479
|
-
protect_from_forgery with: :null_session
|
480
|
-
|
481
|
-
def create
|
482
|
-
process_report
|
483
|
-
|
484
|
-
render(json: { result: 'success' }, status: 200)
|
485
|
-
rescue ActionController::ParameterMissing => error
|
486
|
-
render(json: { error: error.message }, status: 422)
|
487
|
-
end
|
488
|
-
end
|
489
|
-
```
|
490
|
-
|
491
|
-
2. Add a route pointing at the controller.
|
492
|
-
|
493
|
-
```ruby
|
494
|
-
# config/routes.rb
|
259
|
+
If your API **does** require session data, you can follow these steps:
|
495
260
|
|
496
|
-
|
261
|
+
- Add an `x-shopify-react-xhr` header to all GraphQL requests with a value of 1 (this is done automatically if you are using `@shopify/react-graphql-universal-provider`)
|
262
|
+
- Add a `protect_from_forgery with: Quilt::HeaderCsrfStrategy` override to your controllers
|
497
263
|
|
498
|
-
|
499
|
-
```
|
500
|
-
|
501
|
-
### Add annotations
|
502
|
-
|
503
|
-
Add a [`usePerformanceMark`](https://github.com/Shopify/quilt/tree/master/packages/react-performance#useperformancemark) call to each of your route-level components.
|
504
|
-
|
505
|
-
```tsx
|
506
|
-
// app/ui/features/Home/Home.tsx
|
507
|
-
import {usePerformanceMark} from '@shopify/react-performance';
|
508
|
-
|
509
|
-
export function Home() {
|
510
|
-
// tell the library the page has finished rendering completely
|
511
|
-
usePerformanceMark('complete', 'Home');
|
512
|
-
|
513
|
-
return <>{/* your Home page JSX goes here*/}</>;
|
514
|
-
}
|
515
|
-
```
|
516
|
-
|
517
|
-
### Send the report
|
518
|
-
|
519
|
-
Add a [`usePerformanceReport`](https://github.com/Shopify/quilt/tree/master/packages/react-performance#usePerformanceReport) call to your top-level `<App />` component.
|
520
|
-
|
521
|
-
```tsx
|
522
|
-
// app/ui/foundation/App/App.tsx
|
523
|
-
import {usePerformanceReport} from '@shopify/react-performance';
|
524
|
-
|
525
|
-
export function App() {
|
526
|
-
// send the report to the server
|
527
|
-
usePerformanceReport('/performance_report');
|
528
|
-
|
529
|
-
return <>{/* your app JSX goes here*/}</>;
|
530
|
-
}
|
531
|
-
```
|
532
|
-
|
533
|
-
For more details on how to use the APIs from `@shopify/react-performance` check out its [documentation](https://github.com/Shopify/quilt/tree/master/packages/react-performance).
|
534
|
-
|
535
|
-
### Verify in development
|
536
|
-
|
537
|
-
By default `quilt_rails` will not send metrics in development mode. To verify your app is setup correctly you can check in your network tab when visiting your application and see that POST requests are sent to `/performance_report`, and recieve a `200 OK` response.
|
264
|
+
##### Example
|
538
265
|
|
539
|
-
|
266
|
+
```rb
|
267
|
+
class GraphqlController < ApplicationController
|
268
|
+
protect_from_forgery with: Quilt::HeaderCsrfStrategy
|
540
269
|
|
541
|
-
|
542
|
-
#
|
270
|
+
def execute
|
271
|
+
# Get GraphQL query, etc
|
543
272
|
|
544
|
-
|
545
|
-
include Quilt::Performance::Reportable
|
546
|
-
protect_from_forgery with: :null_session
|
273
|
+
result = MySchema.execute(query, operation_name: operation_name, variables: variables, context: context)
|
547
274
|
|
548
|
-
|
549
|
-
# customize process_report's behaviour with a block
|
550
|
-
process_report do |client|
|
551
|
-
client.on_distribution do |name, value, tags|
|
552
|
-
# We log out the details of each distribution that would be sent in production.
|
553
|
-
Rails.logger.debug("Distribution: #{name}, #{value}, #{tags}")
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
render json: { result: 'success' }, status: 200
|
558
|
-
rescue ActionController::ParameterMissing => error
|
559
|
-
render json: { error: error.message, status: 422 }
|
275
|
+
render(json: result)
|
560
276
|
end
|
561
277
|
end
|
562
278
|
```
|
563
279
|
|
564
|
-
|
565
|
-
|
566
|
-
### Configure StatsD for production
|
567
|
-
|
568
|
-
> Attention Shopifolk! If using `dev` your `StatsD` endpoint will already be configured for you in production. You should not need to do the following. ✨
|
280
|
+
## Performance tracking a React app
|
569
281
|
|
570
|
-
To
|
282
|
+
To setup performance tracking with your React app with `quilt_rails`.
|
283
|
+
Follow details guide [here](./docs/performance-tracking).
|
571
284
|
|
572
285
|
## API
|
573
286
|
|
574
|
-
|
575
|
-
|
576
|
-
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`.
|
577
|
-
|
578
|
-
```ruby
|
579
|
-
class ReactController < ApplicationController
|
580
|
-
include Quilt::ReactRenderable
|
581
|
-
|
582
|
-
def index
|
583
|
-
render_react
|
584
|
-
end
|
585
|
-
end
|
586
|
-
```
|
587
|
-
|
588
|
-
### Performance
|
589
|
-
|
590
|
-
#### Reportable
|
591
|
-
|
592
|
-
The `Quilt::Performance::Reportable` mixin is intended to be used in Rails controllers, and provides only the `process_report` method. This method handles parsing an incoming report from [@shopify/react-performance's](https://www.npmjs.com/package/@shopify/react-performance) `<PerformanceReport />` component (or a custom report in the same format) and sending it to your application's StatsD endpoint as `distribution`s using [`StatsD-Instrument`](https://rubygems.org/gems/statsd-instrument).
|
593
|
-
|
594
|
-
> **Note** `Quilt::Performance::Reportable` does not require you to use the `React::Renderable` mixin, React-Server, or even any server-side-rendering solution at all. It should work perfectly fine for applications using something like `sewing_kit_script_tag` based client-side-rendering.
|
595
|
-
|
596
|
-
```ruby
|
597
|
-
class PerformanceController < ApplicationController
|
598
|
-
include Quilt::Performance::Reportable
|
599
|
-
|
600
|
-
def create
|
601
|
-
process_report
|
602
|
-
end
|
603
|
-
end
|
604
|
-
```
|
605
|
-
|
606
|
-
The params sent to the controller are expected to be of type `application/json`. Given the following example JSON sent by `@shopify/react-performance`,
|
607
|
-
|
608
|
-
```json
|
609
|
-
{
|
610
|
-
"connection": {
|
611
|
-
"rtt": 100,
|
612
|
-
"downlink": 2,
|
613
|
-
"effectiveType": "3g",
|
614
|
-
"type": "4g"
|
615
|
-
},
|
616
|
-
"navigations": [
|
617
|
-
{
|
618
|
-
"details": {
|
619
|
-
"start": 12312312,
|
620
|
-
"duration": 23924,
|
621
|
-
"target": "/",
|
622
|
-
"events": [
|
623
|
-
{
|
624
|
-
"type": "script",
|
625
|
-
"start": 23123,
|
626
|
-
"duration": 124
|
627
|
-
},
|
628
|
-
{
|
629
|
-
"type": "style",
|
630
|
-
"start": 23,
|
631
|
-
"duration": 14
|
632
|
-
}
|
633
|
-
],
|
634
|
-
"result": 0
|
635
|
-
},
|
636
|
-
"metadata": {
|
637
|
-
"index": 0,
|
638
|
-
"supportsDetailedTime": true,
|
639
|
-
"supportsDetailedEvents": true
|
640
|
-
}
|
641
|
-
}
|
642
|
-
],
|
643
|
-
"events": [
|
644
|
-
{
|
645
|
-
"type": "ttfb",
|
646
|
-
"start": 2,
|
647
|
-
"duration": 1000
|
648
|
-
}
|
649
|
-
]
|
650
|
-
}
|
651
|
-
```
|
652
|
-
|
653
|
-
given the the above controller input, the library would send the following metrics:
|
654
|
-
|
655
|
-
```ruby
|
656
|
-
StatsD.distribution('time_to_first_byte', 2, tags: {
|
657
|
-
browser_connection_type:'3g',
|
658
|
-
})
|
659
|
-
StatsD.distribution('time_to_first_byte', 2, tags: {
|
660
|
-
browser_connection_type:'3g' ,
|
661
|
-
})
|
662
|
-
StatsD.distribution('navigation_complete', 23924, tags: {
|
663
|
-
browser_connection_type:'3g' ,
|
664
|
-
})
|
665
|
-
StatsD.distribution('navigation_usable', 23924, tags: {
|
666
|
-
browser_connection_type:'3g' ,
|
667
|
-
})
|
668
|
-
```
|
669
|
-
|
670
|
-
##### Default Metrics
|
671
|
-
|
672
|
-
The full list of metrics sent by default are as follows:
|
673
|
-
|
674
|
-
###### For full-page load
|
675
|
-
|
676
|
-
- `AppName.time_to_first_byte`, representing the time from the start of the request to when the server began responding with data.
|
677
|
-
- `AppName.time_to_first_paint`, representing the time from the start of the request to when the browser rendered anything to the screen.
|
678
|
-
- `AppName.time_to_first_contentful_paint` representing the time from the start of the request to when the browser rendered meaningful content to the screen.
|
679
|
-
- `AppName.dom_content_loaded` representing the time from the start of the request to when the browser fired the [DOMContentLoaded](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event) event.
|
680
|
-
- `AppName.dom_load` representing the time from the start of the request to when the browser fired the [window.load](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event) event.
|
681
|
-
|
682
|
-
###### For both full-page navigations and client-side page transitions
|
683
|
-
|
684
|
-
- `AppName.navigation_usable`, representing the time it took before for the page to be rendered in a usable state. Usually this does not include data fetching or asynchronous tasks.
|
685
|
-
- `AppName.navigation_complete` representing the time it took for the page to be fully loaded, including any data fetching which blocks above-the-fold content.
|
686
|
-
- `AppName.navigation_download_size`, representing the total weight of all client-side assets (eg. CSS, JS, images). This will only be sent if there are any events with a `type` of `script` or `style`.
|
687
|
-
- `AppName.navigation_cache_effectiveness`, representing what percentage of client-side assets (eg. CSS, JS, images) were returned from the browser's cache. This will only be sent if there are any events with a `type` of `script` or `style`.
|
688
|
-
|
689
|
-
##### Customizing `process_report` with a block
|
690
|
-
|
691
|
-
The behaviour of `process_report` can be customized by manipulating the `Quilt::Performance::Client` instance yielded into its implicit block parameter.
|
692
|
-
|
693
|
-
```ruby
|
694
|
-
process_report do |client|
|
695
|
-
# client.on_distribution do ....
|
696
|
-
end
|
697
|
-
```
|
698
|
-
|
699
|
-
#### Client
|
700
|
-
|
701
|
-
The `Quilt::Performance::Client` class is yielded into the block parameter for `process_report`, and is the primary API for customizing what metrics are sent for a given POST.
|
702
|
-
|
703
|
-
##### Client#on_distribution
|
704
|
-
|
705
|
-
The `on_distribution` method takes a block which is run for each distribution (including custom ones) sent during `process_report`.
|
706
|
-
|
707
|
-
The provided callback can be used to easily add logging or other side-effects to your measurements.
|
708
|
-
|
709
|
-
```ruby
|
710
|
-
client.on_distribution do |metric_name, value, tags|
|
711
|
-
Rails.logger.debug "#{metric_name}: #{value}, tags: #{tags}"
|
712
|
-
end
|
713
|
-
```
|
714
|
-
|
715
|
-
##### Client#on_navigation
|
716
|
-
|
717
|
-
The `on_navigation` method takes a block which is run once per navigation reported to the performance controller _before_ the default distributions for the navigation are sent.
|
718
|
-
|
719
|
-
The provided callback can be used to add tags to the default `distributions` for a given navigation.
|
720
|
-
|
721
|
-
```ruby
|
722
|
-
client.on_navigation do |navigation, tags|
|
723
|
-
# add tags to be sent with each distribution for this navigation
|
724
|
-
tags[:connection_rtt] = navigation.connection.rtt
|
725
|
-
tags[:connection_type] = navigation.connection.type
|
726
|
-
tags[:navigation_target] = navigation.target
|
727
|
-
|
728
|
-
# add a tag to allow filtering out navigations that are too long
|
729
|
-
# this is useful when you are unable to rule out missing performance marks on some pages
|
730
|
-
tags[:too_long_dont_read] = navigation.duration > 30.seconds.in_milliseconds
|
731
|
-
end
|
732
|
-
```
|
733
|
-
|
734
|
-
It can also be used to compute and send entirely custom metrics.
|
735
|
-
|
736
|
-
```ruby
|
737
|
-
client.on_navigation do |navigation, tags|
|
738
|
-
# calculate and then send an additional distribution
|
739
|
-
weight = navigation.events_with_size.reduce(0) do |total, event|
|
740
|
-
total + event.size
|
741
|
-
end
|
742
|
-
client.distribution('navigation_total_resource_weight', weight, tags)
|
743
|
-
end
|
744
|
-
```
|
745
|
-
|
746
|
-
##### Client#on_event
|
747
|
-
|
748
|
-
The `on_event` method takes a block which is run once per event reported to the performance controller _before_ the default distributions for the event are sent.
|
749
|
-
|
750
|
-
The provided callback can be used to add tags to the default `distributions` for a given event, or perform other side-effects.
|
751
|
-
|
752
|
-
```ruby
|
753
|
-
client.on_event do |event, tags|
|
754
|
-
# add tags to be sent with each distribution for this event
|
755
|
-
tags[:connection_rtt] = event.connection.rtt
|
756
|
-
tags[:connection_type] = event.connection.type
|
757
|
-
end
|
758
|
-
```
|
759
|
-
|
760
|
-
### Engine
|
761
|
-
|
762
|
-
`Quilt::Engine` provides:
|
763
|
-
|
764
|
-
- a preconfigured `UiController` which consumes `ReactRenderable`
|
765
|
-
- a preconfigured `PerformanceReportController` which consumes `Performance::Reportable`
|
766
|
-
- a `/performance_report` route mapped to `performance_report#index`
|
767
|
-
- a catch-all index route mapped to the `UiController#index`
|
768
|
-
|
769
|
-
```ruby
|
770
|
-
# config/routes.rb
|
771
|
-
Rails.application.routes.draw do
|
772
|
-
# ...
|
773
|
-
mount Quilt::Engine, at: '/my-front-end'
|
774
|
-
end
|
775
|
-
```
|
776
|
-
|
777
|
-
The above is the equivalent of
|
778
|
-
|
779
|
-
```ruby
|
780
|
-
post '/my-front-end/performance_report', to: 'performance_report#create'
|
781
|
-
get '/my-front-end/*path', to: 'ui#index'
|
782
|
-
get '/my-front-end', to: 'ui#index'
|
783
|
-
```
|
784
|
-
|
785
|
-
### Configuration
|
786
|
-
|
787
|
-
The `configure` method allows customization of the address the service will proxy to for UI rendering.
|
788
|
-
|
789
|
-
```ruby
|
790
|
-
# config/initializers/quilt.rb
|
791
|
-
Quilt.configure do |config|
|
792
|
-
config.react_server_host = "localhost:3000"
|
793
|
-
config.react_server_protocol = 'https'
|
794
|
-
end
|
795
|
-
```
|
796
|
-
|
797
|
-
### StatsD environment variables
|
798
|
-
|
799
|
-
The `Performance::Reportable` mixin uses [https://github.com/Shopify/statsd-instrument](StatsD-Instrument) to send distributions. For detailed instructions on configuring where it sends data see [the documentation](https://github.com/Shopify/statsd-instrument#configuration).
|
800
|
-
|
801
|
-
### Generators
|
802
|
-
|
803
|
-
#### `quilt:install`
|
804
|
-
|
805
|
-
Installs the Node dependencies, provide a basic React app (in TypeScript) and mounts the Quilt engine in `config/routes.rb`.
|
287
|
+
Find all features this gem offer in this [API doc](./docs/api).
|
806
288
|
|
807
|
-
|
289
|
+
## FAQ
|
808
290
|
|
809
|
-
|
291
|
+
Find your [here](./docs/FAQ).
|
@@ -2,52 +2,10 @@
|
|
2
2
|
|
3
3
|
module Quilt
|
4
4
|
class InstallGenerator < Rails::Generators::Base
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def install_js_dependencies
|
10
|
-
say "Installing @shopify/react-server and @shopify/sewing-kit dependencies"
|
11
|
-
system("yarn add "\
|
12
|
-
"@shopify/sewing-kit "\
|
13
|
-
"@shopify/react-server "\
|
14
|
-
"typescript "\
|
15
|
-
"react "\
|
16
|
-
"react-dom "\
|
17
|
-
"@types/react "\
|
18
|
-
"@types/react-dom") unless Rails.env.test?
|
19
|
-
end
|
20
|
-
|
21
|
-
def create_tsconfig
|
22
|
-
tsconfig_path = "tsconfig.json"
|
23
|
-
|
24
|
-
unless File.exist?(tsconfig_path)
|
25
|
-
copy_file "tsconfig.json", tsconfig_path
|
26
|
-
|
27
|
-
log(tsconfig_path, 'wrote')
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def create_app_file
|
32
|
-
app_path = "app/ui/index.tsx"
|
33
|
-
|
34
|
-
unless File.exist?(app_path)
|
35
|
-
copy_file "App.tsx", app_path
|
36
|
-
|
37
|
-
log("React App at #{app_path}", 'wrote')
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def create_route_file
|
42
|
-
routes_path = "config/routes.rb"
|
43
|
-
|
44
|
-
if File.exist?(routes_path)
|
45
|
-
route "mount Quilt::Engine, at: '/'"
|
46
|
-
else
|
47
|
-
copy_file "routes.rb", routes_path
|
48
|
-
end
|
49
|
-
|
50
|
-
say "Added Quilt engine mount"
|
5
|
+
def run_all_generators
|
6
|
+
generate("quilt:rails_setup")
|
7
|
+
generate("quilt:react_setup")
|
8
|
+
generate("quilt:react_app")
|
51
9
|
end
|
52
10
|
end
|
53
11
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
class RailsSetupGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
desc "This generator mounts the Quilt engine and add Procfile."
|
8
|
+
|
9
|
+
def create_procfile_entry
|
10
|
+
procfile_path = "Procfile"
|
11
|
+
|
12
|
+
if File.exist?(procfile_path)
|
13
|
+
append_file(procfile_path, File.read(File.expand_path(find_in_source_paths(procfile_path))))
|
14
|
+
else
|
15
|
+
copy_file procfile_path
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_route_file
|
20
|
+
routes_path = "config/routes.rb"
|
21
|
+
|
22
|
+
if File.exist?(routes_path)
|
23
|
+
route "mount Quilt::Engine, at: '/'"
|
24
|
+
else
|
25
|
+
copy_file "routes.rb", routes_path
|
26
|
+
end
|
27
|
+
|
28
|
+
say "Added Quilt engine mount"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
class ReactAppGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
desc "This generator adds a React app."
|
8
|
+
|
9
|
+
def create_app_file
|
10
|
+
copy_file("App.tsx", "app/ui/index.tsx")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
File without changes
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
class ReactSetupGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
desc "This generator adds a React app."
|
8
|
+
|
9
|
+
def install_js_dependencies
|
10
|
+
say "Installing @shopify/react-server and @shopify/sewing-kit dependencies"
|
11
|
+
system("yarn add "\
|
12
|
+
"@shopify/sewing-kit "\
|
13
|
+
"@shopify/react-server "\
|
14
|
+
"typescript@~3.8.0 "\
|
15
|
+
"react@~16.11.0 "\
|
16
|
+
"react-dom@~16.11.0 "\
|
17
|
+
"@types/react@~16.9.0 "\
|
18
|
+
"@types/react-dom@~16.9.0 ") unless Rails.env.test?
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_tsconfig
|
22
|
+
copy_file("tsconfig.json")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -6,16 +6,29 @@ module SewingKit
|
|
6
6
|
|
7
7
|
desc "This generator creates a sewing-kit config file."
|
8
8
|
|
9
|
-
def
|
10
|
-
|
9
|
+
def initialize(args, *options)
|
10
|
+
@application_name = Rails.application.class.module_parent.to_s.underscore
|
11
|
+
super(args, *options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_package_json
|
15
|
+
package_json_path = "package.json"
|
16
|
+
|
17
|
+
copy_file(package_json_path)
|
18
|
+
gsub_file(package_json_path, "\${application_name}", @application_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_sk_config
|
22
|
+
sk_config_path = "config/sewing-kit.config.ts"
|
11
23
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
copy_file "sewing-kit.config.ts", config_path
|
24
|
+
copy_file("sewing-kit.config.ts", sk_config_path)
|
25
|
+
gsub_file(sk_config_path, "\${application_name}", @application_name)
|
26
|
+
end
|
16
27
|
|
17
|
-
|
18
|
-
|
28
|
+
def create_config_files
|
29
|
+
copy_file(".editorconfig")
|
30
|
+
copy_file(".eslintignore")
|
31
|
+
copy_file(".prettierignore")
|
19
32
|
end
|
20
33
|
end
|
21
34
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
{
|
2
|
+
"name": "${application_name}",
|
3
|
+
"private": true,
|
4
|
+
"sideEffects": false,
|
5
|
+
"scripts": {
|
6
|
+
"dev": "sewing-kit dev",
|
7
|
+
"check": "sewing-kit check",
|
8
|
+
"lint": "sewing-kit lint",
|
9
|
+
"type-check": "sewing-kit type-check",
|
10
|
+
"nuke": "sewing-kit nuke",
|
11
|
+
"test": "sewing-kit test"
|
12
|
+
},
|
13
|
+
"eslintConfig": {
|
14
|
+
"extends": [
|
15
|
+
"plugin:@shopify/typescript",
|
16
|
+
"plugin:@shopify/react",
|
17
|
+
"plugin:@shopify/prettier",
|
18
|
+
"plugin:@shopify/jest",
|
19
|
+
"plugin:@shopify/polaris"
|
20
|
+
]
|
21
|
+
},
|
22
|
+
"prettier": "@shopify/prettier-config",
|
23
|
+
"stylelint": {
|
24
|
+
"extends": [
|
25
|
+
"@shopify/stylelint-plugin/prettier"
|
26
|
+
]
|
27
|
+
},
|
28
|
+
"resolutions": {
|
29
|
+
"babel-plugin-dynamic-import-node": "2.3.0"
|
30
|
+
}
|
31
|
+
}
|
@@ -1,7 +1,11 @@
|
|
1
1
|
/* eslint-env node */
|
2
2
|
|
3
|
-
import {
|
3
|
+
import {Plugins} from '@shopify/sewing-kit';
|
4
4
|
|
5
|
-
export default function sewingKitConfig(
|
6
|
-
return {
|
5
|
+
export default function sewingKitConfig(plugins: Plugins) {
|
6
|
+
return {
|
7
|
+
name: "${application_name}",
|
8
|
+
plugins: [
|
9
|
+
],
|
10
|
+
};
|
7
11
|
}
|
data/lib/quilt_rails.rb
CHANGED
@@ -9,4 +9,5 @@ require "quilt_rails/configuration"
|
|
9
9
|
require "quilt_rails/react_renderable"
|
10
10
|
require "quilt_rails/performance"
|
11
11
|
require "quilt_rails/trusted_ui_server_csrf_strategy"
|
12
|
+
require "quilt_rails/header_csrf_strategy"
|
12
13
|
require "quilt_rails/monkey_patches/active_support_reloader" if Rails.env.development?
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
class HeaderCsrfStrategy
|
5
|
+
HEADER = "x-shopify-react-xhr"
|
6
|
+
HEADER_VALUE = "1"
|
7
|
+
|
8
|
+
def initialize(controller)
|
9
|
+
@controller = controller
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle_unverified_request
|
13
|
+
raise NoSameSiteHeaderError unless same_site?
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def same_site?
|
19
|
+
@controller.request.headers[HEADER] == HEADER_VALUE
|
20
|
+
end
|
21
|
+
|
22
|
+
def fallback_handler
|
23
|
+
ActionController::RequestForgeryProtection::ProtectionMethods::Exception.new(@controller)
|
24
|
+
end
|
25
|
+
|
26
|
+
class NoSameSiteHeaderError < StandardError
|
27
|
+
def initialize
|
28
|
+
# rubocop:disable LineLength
|
29
|
+
super "CSRF verification failed. This request is missing the `x-shopify-react-xhr` header, or it does not have the expected value."
|
30
|
+
# rubocop:enable LineLength
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -9,7 +9,7 @@ module Quilt
|
|
9
9
|
|
10
10
|
def self.from_params(params)
|
11
11
|
params.transform_keys! { |key| key.underscore.to_sym }
|
12
|
-
params
|
12
|
+
params[:connection] = { effectiveType: 'unknown' } if params[:connection].blank?
|
13
13
|
|
14
14
|
connection = Connection.from_params(params[:connection])
|
15
15
|
|
@@ -4,8 +4,15 @@ module Quilt
|
|
4
4
|
module Performance
|
5
5
|
module Reportable
|
6
6
|
def process_report(&block)
|
7
|
-
|
8
|
-
|
7
|
+
Client.send!(Report.from_params(normalized_params), &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def normalized_params
|
13
|
+
return params unless request.content_type == 'text/plain'
|
14
|
+
|
15
|
+
ActionController::Parameters.new(JSON.parse(request.body.read))
|
9
16
|
end
|
10
17
|
end
|
11
18
|
end
|
@@ -6,29 +6,29 @@ module Quilt
|
|
6
6
|
module ReactRenderable
|
7
7
|
include ReverseProxy::Controller
|
8
8
|
|
9
|
-
def render_react(headers: {})
|
9
|
+
def render_react(headers: {}, data: {})
|
10
10
|
raise DoNotIntegrationTestError if Rails.env.test?
|
11
11
|
|
12
12
|
# Allow concurrent loading to prevent this thread from blocking class
|
13
13
|
# loading in controllers called by the Node server.
|
14
14
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
15
|
-
call_proxy(headers)
|
15
|
+
call_proxy(headers, data)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def call_proxy(headers)
|
21
|
+
def call_proxy(headers, data)
|
22
22
|
if defined? ShopifySecurityBase
|
23
23
|
ShopifySecurityBase::HTTPHostRestriction.whitelist([Quilt.configuration.react_server_host]) do
|
24
|
-
proxy(headers)
|
24
|
+
proxy(headers, data)
|
25
25
|
end
|
26
26
|
else
|
27
|
-
proxy(headers)
|
27
|
+
proxy(headers, data)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def proxy(headers)
|
31
|
+
def proxy(headers, data)
|
32
32
|
url = "#{Quilt.configuration.react_server_protocol}://#{Quilt.configuration.react_server_host}"
|
33
33
|
Quilt::Logger.log("[ReactRenderable] proxying to React server at #{url}")
|
34
34
|
|
@@ -37,7 +37,11 @@ module Quilt
|
|
37
37
|
end
|
38
38
|
|
39
39
|
begin
|
40
|
-
|
40
|
+
|
41
|
+
reverse_proxy(
|
42
|
+
url,
|
43
|
+
headers: headers.merge('X-CSRF-Token': form_authenticity_token, 'X-Quilt-Data': headers.merge(data).to_json)
|
44
|
+
) do |callbacks|
|
41
45
|
callbacks.on_response do |status_code, _response|
|
42
46
|
Quilt::Logger.log("[ReactRenderable] #{url} returned #{status_code}")
|
43
47
|
end
|
data/lib/quilt_rails/version.rb
CHANGED
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.
|
4
|
+
version: 1.13.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: 2020-
|
11
|
+
date: 2020-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -95,15 +95,27 @@ files:
|
|
95
95
|
- config/routes.rb
|
96
96
|
- lib/generators/quilt/USAGE
|
97
97
|
- lib/generators/quilt/install_generator.rb
|
98
|
-
- lib/generators/quilt/
|
99
|
-
- lib/generators/quilt/
|
100
|
-
- lib/generators/quilt/templates/
|
98
|
+
- lib/generators/quilt/rails_setup/USAGE
|
99
|
+
- lib/generators/quilt/rails_setup/rails_setup_generator.rb
|
100
|
+
- lib/generators/quilt/rails_setup/templates/Procfile
|
101
|
+
- lib/generators/quilt/rails_setup/templates/routes.rb
|
102
|
+
- lib/generators/quilt/react_app/USAGE
|
103
|
+
- lib/generators/quilt/react_app/react_app_generator.rb
|
104
|
+
- lib/generators/quilt/react_app/templates/App.tsx
|
105
|
+
- lib/generators/quilt/react_setup/USAGE
|
106
|
+
- lib/generators/quilt/react_setup/react_setup_generator.rb
|
107
|
+
- lib/generators/quilt/react_setup/templates/App.tsx
|
108
|
+
- lib/generators/quilt/react_setup/templates/tsconfig.json
|
109
|
+
- lib/generators/quilt_rails/USAGE
|
110
|
+
- lib/generators/quilt_rails/install_generator.rb
|
101
111
|
- lib/generators/sewing_kit/USAGE
|
102
112
|
- lib/generators/sewing_kit/install_generator.rb
|
113
|
+
- lib/generators/sewing_kit/templates/package.json
|
103
114
|
- lib/generators/sewing_kit/templates/sewing-kit.config.ts
|
104
115
|
- lib/quilt_rails.rb
|
105
116
|
- lib/quilt_rails/configuration.rb
|
106
117
|
- lib/quilt_rails/engine.rb
|
118
|
+
- lib/quilt_rails/header_csrf_strategy.rb
|
107
119
|
- lib/quilt_rails/logger.rb
|
108
120
|
- lib/quilt_rails/monkey_patches/active_support_reloader.rb
|
109
121
|
- lib/quilt_rails/performance.rb
|
@@ -121,7 +133,8 @@ files:
|
|
121
133
|
homepage: https://github.com/Shopify/quilt/tree/master/gems/quilt_rails
|
122
134
|
licenses:
|
123
135
|
- MIT
|
124
|
-
metadata:
|
136
|
+
metadata:
|
137
|
+
allowed_push_host: https://rubygems.org
|
125
138
|
post_install_message:
|
126
139
|
rdoc_options: []
|
127
140
|
require_paths:
|