building and deploying react applications€¦ · building and deploying react applications boris...

Post on 03-Jun-2020

35 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Building and Deploying React Applications

Boris Nadion boris@astrails.com

@borisnadion

@borisnadion boris@astrails.com

astrailshttp://astrails.com

2005

awesome web and mobile apps

building & deploying

building & deploying

facebookincubator/create-react-appCreate React apps with no build configuration.

npm install -g create-react-app

create-react-app my-app cd my-app/ npm start

customizelike adding css preprocessors

yarn eject / npm eject

yarn eject / npm eject

not trivial

start from scratch

setup internals

environments✓ development

✓ production

x test

package.json

"babel": { "presets": [ [ "es2015", { "modules": false } ], "react", "stage-0" ], "plugins": [ "transform-runtime", "babel-plugin-transform-class-properties", "transform-object-rest-spread" ], "env": { "production": { "plugins": [ "transform-react-inline-elements", "transform-react-constant-elements", "transform-react-remove-prop-types" ] } } },

babel-plugin-transform-class-properties

class Bork { //Property initializer syntax instanceProperty = "bork"; boundFunction = () => { return this.instanceProperty; } //Static class properties static staticProperty = "babelIsCool"; static staticFunction = function() { return Bork.staticProperty; } }

let n = { x, y, ...z };

transform-object-rest-spread

transform-react-inline-elements

babelHelpers.jsx(Baz, { foo: "bar" }, "1");

const Hr = () => { return <hr className="hr" />; }; const _ref = <hr className="hr" />; const Hr = () => { return _ref; };

transform-react-inline-elements

Baz.propTypes = {…}

transform-react-remove-prop-types

“start":"env NODE_ENV=development webpack-dev-server --progress --colors",

“build":"rimraf dist &&env NODE_ENV=production webpack --colors &&cp ./dist/* ../public/assets/",

webpack config

webpack config

http://imgur.com/gallery/EnAmi

const ENV = process.env.NODE_ENV; const VALID_ENVIRONMENTS = ['development', 'production']; if (!VALID_ENVIRONMENTS.includes(ENV)) { throw new Error(`${ ENV } is not valid environment!`); }

const DEVELOPMENT_CONFIG = require('./config/webpack.dev'); const PRODUCTION_CONFIG = require('./config/webpack.prod'); const config = { development: DEVELOPMENT_CONFIG, production: PRODUCTION_CONFIG }[ENV]; const COMMON_CONFIG = { … };

module.exports = webpackMerge.smart(COMMON_CONFIG, config);

let’s look at the codewebpack.config.js + dev + prod

3 bundlesbundle.js

+ cssclient.js

+ cssasync.js

+ css

yarn start v0.24.4 $ env NODE_ENV=development webpack-dev-server --progress --colors 10% building modules 2/2 modules 0 active Project is running at http://0.0.0.0:9001/ webpack output is served from http://localhost:9001/assets/ 404s will fallback to /index.html Hash: 9a91c0c826ebb4c40f2a Version: webpack 2.6.1 Time: 6507ms Asset Size Chunks Chunk Names 0.js 806 bytes 0 [emitted] client.js 313 kB 1 [emitted] [big] client vendor.js 1.45 MB 2 [emitted] [big] vendor 0.js.map 572 bytes 0 [emitted] client.js.map 360 kB 1 [emitted] client index.html 421 bytes [emitted] webpack: Compiled successfully.

dev mode demohot reload, async load, eslint errors

//… import { AppContainer } from 'react-hot-loader'; //…const hotRender = () => { render( <AppContainer> <Application store={ store } /> </AppContainer>, document.getElementById('root') ); }; hotRender(); module.hot.accept('components/Application', hotRender);

hot reload

import asyncComponent from 'components/AsyncComponent'; const AsyncDashboard = asyncComponent(() => import('./Dashboard').then(module => module.default) ); export default AsyncDashboard;

async load

import React from 'react'; const asyncComponent = (getComponent) => class AsyncComponent extends React.Component { state = { Component: null }; componentWillMount() { if (!this.state.Component) { getComponent().then(Component => { this.setState({ Component }); }); } } render() { const { Component } = this.state; if (Component) { return <Component { ...this.props } />; } return null; } }; export default asyncComponent;

async load

prod buildyarn build

yarn build v0.24.4 $ rimraf dist && env NODE_ENV=production webpack --colors && cp ./dist/* ../public/assets/ Hash: f5404348a5a4eadca2c5 Version: webpack 2.6.1 Time: 9894ms Asset Size Chunks Chunk Names client-149e7f81934ccd4797d6.bundle.js.map 183 kB 1 [emitted] client 0-b065752a37e19efffbe1.bundle.js 318 bytes 0 [emitted] webpack-chunk-manifest.json 79 bytes [emitted] vendor-411f8db22ac4a264ff0d.bundle.js 265 kB 2 [emitted] [big] vendor client-e9da9d78d42878a4c3a5a7ab1330ea79.css 2.7 kB 1 [emitted] client 0-b065752a37e19efffbe1.bundle.js.map 2.11 kB 0 [emitted] client-149e7f81934ccd4797d6.bundle.js 24 kB 1 [emitted] client client-e9da9d78d42878a4c3a5a7ab1330ea79.css.map 120 bytes 1 [emitted] client client-149e7f81934ccd4797d6.bundle.js.gz 8.35 kB [emitted] vendor-411f8db22ac4a264ff0d.bundle.js.gz 77.9 kB [emitted] index.html 493 bytes [emitted] webpack-asset-manifest.json 468 bytes [emitted]

new webpack.HashedModuleIdsPlugin(),

new ManifestPlugin({ fileName: 'webpack-asset-manifest.json' }), new ChunkManifestPlugin({ filename: 'webpack-chunk-manifest.json', manifestVariable: 'webpackManifest' }),

// webpack-chunk-manifest.json {"0":"0-b065752a37e19efffbe1.bundle.js"}

// webpack-asset-manifest.json { "0-b065752a37e19efffbe1.bundle.js": "0-b065752a37e19efffbe1.bundle.js", "0-b065752a37e19efffbe1.bundle.js.map": "0-b065752a37e19efffbe1.bundle.js.map", "client.css": "client-e9da9d78d42878a4c3a5a7ab1330ea79.css", "client.css.map": "client-e9da9d78d42878a4c3a5a7ab1330ea79.css.map", "client.js": "client-149e7f81934ccd4797d6.bundle.js", "client.js.map": "client-149e7f81934ccd4797d6.bundle.js.map", "vendor.js": "vendor-411f8db22ac4a264ff0d.bundle.js" }

4 bundlesvendor.js client.js 0.js client.css

html from server

/* … */ <script type=“text/javascript”> window.apiEndPoint = "http://stage.example.com" </script> <link href="//xxx/client.css" rel="stylesheet" /> <script src="//xxx/vendor.js”></script> <script src="//xxx/client.js"></script> /* … */

} xxx=?

<div id="root"></div> <%= api_endpoint_from_environment %> <%= client_application_stylesheet_tag 'client.css' %><%= client_application_javascript_tag 'vendor.js' %> <%= client_application_javascript_tag 'client.js' %>

server template

module ClientApplicationHelper

# client_application_javascript_tag 'client.js' def client_application_javascript_tag(bundle) src = if client_application[:use_manifest]

# "client.js": "client-149e7f81934ccd4797d6.bundle.js", manifest = client_application[:asset_manifest][bundle] # static asset "/assets/#{bundle}" else # dev mode "http://localhost:9001/assets/#{bundle}" end javascript_include_tag(src) end def client_application_stylesheet_tag(bundle) # … # almost the same but no need to render in dev mode end end

serve from

• webpack dev server (for dev mode)

• same server, static assets

• static assets through CDN

• CDN direct

• whatever

awesome

almost awesome

<%= client_application_stylesheet_tag 'client.css' %> <%= client_application_javascript_tag 'vendor.js' %> <%= client_application_javascript_tag 'client.js' %>

in a context of a request

current_userreq.current_user, request.user[. is_authenticated], …

module ClientApplicationHelper def client_application_javascript_tag(bundle) src = if client_application[:use_manifest] # "client.js": "client-149e7f81934ccd4797d6.bundle.js", manifest = assets_manifest_for(current_user)[bundle] # … end javascript_include_tag(src) end end

storing manifests per userS3, database, redis, memcache, etc

+ default manifest for the rest of the users

assets_manifest_for(current_user)[bundle]

• A/B testing

• features testing in production env

• UI experiments

• gradually rolling out new features

assets_manifest_for(current_user)[bundle]

bundles v1.12default

bundles v1.13debugging an issue

bundles v2.0testing new release

user with a bug in v1.12

marketing user

all users

separate server and client deployments

client lifecycle

• build: get new bundles + manifest

• deploy: upload bundles to remote storage (S3) + warm up CDN

• release: update user’s or default manifest

awesome

almost awesome

http://www.enjoyart.com/single_posters/animals_art_photo/NoahsArkTakinoAnimalsArtPrintPoster.htm

zoo

bundles v2.0bundles v2.1bundles v2.2bundles v2.3bundles v2.4server

compatibility

APIcompatibility

develop deploy test releasenew frontend version

new backend versionlocal staging production

release = update default manifestfor all the users

server firstserver is always backward compatible

easier to maintain compatibility on server with API versioning

zoo = not an engineering issuebut administrative one

awesome

really awesome

https://github.com/astrails/rails_react_webpackthanks to mike@astrails.com aka @mihap

http://astrails.com/blogslides will be available

thanks!

Boris Nadion http://astrails.com boris@astrails.com

@borisnadion

top related