Getting ready for production
@MichalZalecki
michalzalecki.com
woumedia.com
Minification
...duh!
uglifyjs pdf.js -c -o pdf.min.js
new webpack.optimize.UglifyJsPlugin({ minimize: true, comments: false, })
OR
Gzip
Reduce response size by about 70%
Used by 69.3% of all the websites
HTML
CSS
JS
PNG
JPG
MP4
nginx
gzip on;
node/express
const compression = require("compression");
const app = express();
app.use(compression());
Tree-shaking
Thanks to ES6 modules syntax bundlers are able to determine which exports are unused
// src/helpers.js
export function toUpper(str) {
return str.toUpperCase();
}
export function toLower(str) {
return str.toLowerCase();
}
// src/app.js
import { toUpper } from "./common/helpers";
const root = document.querySelector("#root");
const name = prompt("What's your name?");
root.innerHTML = `
<h1>Hello ${toUpper(name)}!</h1>
`;
// build/app.js
function toUpper(str) {
return str.toUpperCase();
}
const root = document.querySelector("#root");
const name = prompt("What's your name?");
root.innerHTML = `
<h1>Hello ${toUpper(name)}!</h1>
`;
rollup
// build/app.js
/* unused harmony export toLower */
function toUpper(str) {
return str.toUpperCase();
}
function toLower(str) {
return str.toLowerCase();
}
webpack 2
// build/app.js
function toUpper(str) {
return str.toUpperCase();
}
webpack 2 + uglify
Code Splitting
Webpack is capable of on demand code splitting as for routes or predictable user behavior
<Route
path="/users"
getComponent={(_nextState, cb) => {
System.import("./users/components/UsersPage")
.then((UsersPage) => {
cb(null, UsersPage.default);
});
}}
/>
Asset Size Chunks Chunk Names 0.app.js 1.26 kB 0 [emitted] app.js 214 kB 1 [emitted] main
Resource splitting
const ExtractTextPlugin =
require("extract-text-webpack-plugin");
plugins: [
new ExtractTextPlugin({
filename: "styles.[hash].css",
allChunks: true,
}),
],
module: {
rules: [
{ test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallbackLoader: "style-loader",
loader: "css-loader",
}),
},
],
},
DocumentsPage (uses helpers.js) -> 0.js (includes helpers.js) UsersPage (uses helpers.js) -> 1.js (includes helpers.js)
entry: {
commons: ["./src/common/helpers"],
app: "./src/index",
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
filename: "commons.js",
}),
],
DocumentsPage (uses helpers.js) -> 0.js (__webpack_require__) UsersPage (uses helpers.js) -> 1.js (__webpack_require__) -> commons.js (includes helpers.js)
NODE_ENV=production
Server-Side Rendering
Perform initial render on the server making initial load
great again
-
match on location
-
preload state
-
create store
-
render to string
-
send the response
app.get("*", async (req, res) => {
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const preloadedState = await fetchStateFromYourAPI(req);
const head = `
<script>
window.__PRELOADED_STATE__=${JSON.stringify(preloadedState)};
</script>`;
const store = createStore(rootReducer, preloadedState);
const reactHtml = renderToString(
React.createElement(Provider, { store }, routerContext(renderProps))
);
res.render("index", { head, reactHtml });
} else {
res.status(404).send("Not found");
}
});
});
Prerendering
The quickest and cheapest way to (only) make crawlers like your webapp
app.use(require("prerender-node"));
ServiceWorker
if ("serviceWorker" in navigator) {
// Service worker registered
navigator.serviceWorker.register("/sw.js")
.catch(err => {
// Service worker registration failed
});
} else {
// Service worker is not supported
}
Caching strategies
function cacheableRequestFailingToCache({ event, cache }) {
return fetch(event.request)
.then(throwOnError) // do not cache errors
.then(response => {
cache.put(event.request, response.clone());
return response;
})
.catch(() => cache.match(event.request));
}
Cacheable request failing to cache
function cacheFailingToCacheableRequest({ event, cache }) {
return cache.match(event.request)
.then(throwOnError)
.catch(() => fetch(event.request)
.then(throwOnError)
.then(response => {
cache.put(event.request, response.clone());
return response;
})
);
}
Cache failing to cacheable request strategy
function requestFailingToCache({ event, cache }) {
return fetch(event.request)
.catch(() => cache.match(event.request));
}
Request failing to cache strategy
function requestFailingWithNotFound({ event }) {
return fetch(event.request)
.catch(() => {
const body = JSON.stringify({
error: "Sorry, you are off-line. Please, try later." });
const headers = { "Content-Type": "application/json" };
const response = new Response(body, {
status: 404, statusText: "Not Found", headers });
return response;
});
}
Request failing with not found strategy
HTTP/2
Performance improvement for free
-
Single bidirectional TLS connection
-
Server push
-
Easier caching
-
No "up to 6 connections" limit
-
Supported in all major browsers
Concatenation and sprites
Requires HTTPS, yeah!
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
GoogleChrome/simplehttp2server
Thank you!
Getting ready for production
By Michał Załęcki
Getting ready for production
- 1,601