Jun 03, 2020
@borisnadion [email protected]
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
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 [email protected] aka @mihap
http://astrails.com/blogslides will be available