Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How We Build NG-MY Website

How We Build NG-MY Website

NG-MY is a speedy SPA, SEO friendly website and built with Angular.

In this talk we will share some techniques we apply in building NG-MY Website, you can apply these techniques in your website too!
- Cost
- Collaboration
- Performance
- Techniques

Jecelyn Yeen

July 06, 2019

More Decks by Jecelyn Yeen

Other Decks in Programming


  1. ng new --createApplication false ng generate application site2019 Create blank

    workspace Generate an application under the project
  2. <title>July 06-07 | NG-MY 2019</title> <meta name="description" content="NG-MY 2019..."> <meta

    property="og:url" content="https://2019.ng-my.org/" /> <meta property="og:description" content="NG-MY 2019..."> <meta property="og:type" content="website" /> <meta property="og:title" content="..." /> <meta property="og:image" content="img.png" /> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:image" content="img.png"> Title tag & Meta tags are essential for SEO
  3. export class PageService { } Utilize the built-in Title &

    Meta Service Set page title Update meta tag Inject title & meta services constructor(private title: Title, private meta: Meta) {} setPageMeta(title: string, metaDesc: string, metaImg: string) { this.title.setTitle(title); this.meta.updateTag({ property: 'og:title', content: title }); this.meta.updateTag({ name: 'description', content: metaDesc }); this.meta.updateTag({ name: 'twitter:image', content: metaImg }); ... }
  4. Dev (No crawling) Prod (Allow crawling) Robots.txt - To crawl

    or not to crawl? robots.txt robots.prod.txt
  5. ... "architect": { "build": { "assets": [ ..., "projects/site2019/src/robots.txt" ]

    ... } } angular.json Include robots.txt as output assets
  6. "configurations": { "production": { "fileReplacements": [ ... { "replace": "projects/site2019/src/robots.txt",

    "with": "projects/site2019/src/robots.prod.txt" } ] } } Replace file in angular.json Environment name Replace when env = production ng build -c production, or ng build --prod
  7. SEO Problem in SPA <html> <head> <script src="bundle.js"> </head> ...

    </html> Server sends HTML to Client (No meta tags) <html> <head> <title>home</title> <meta ...> <script src="bundle.js"> </head> ... </html> Client executes JS and add title & meta tags (too late!) Most crawlers only understand this. :(
  8. Brief: Angular CLI Build Process ng build --prod Generate &

    Optimize files - js, css, etc Generate index.html Webpack Index transform We can extend & customize these!
  9. Solution: Prerendering during build time ng build --prod Generate &

    Optimize files - js, css, etc Open browser Browse & Save each route as .html Deploy all files to server Generate static html pages = Prerendering Generate index.html
  10. Solution: Prerendering during build time ng build --prod Generate &

    Optimize files - js, css, etc Deploy all files to server Automate Prerendering using Puppeteer Generate index.html Program Puppeteer Browse & Save each route as .html
  11. Puppeteer - Programmable Chrome Browser const puppeteer = require('puppeteer'); (async

    () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); })(); const fs = require('fs').promises; await fs.writeFile('food.html', html, 'utf-8'); await browser.close(); await page.goto('https://ng-my.org/food', { waitUntil: 'networkidle2'}); const html = await page.content();
  12. Solution: Prerendering during build time ng build --prod Generate &

    Optimize files - js, css, etc Program Puppeteer Browse & Save each route as .html Deploy all files to server Generate index.html Extend Angular CLI to customize this step Find an even easier library to help us!
  13. Extend Angular CLI Webpack to handle Prerendering npm install -D

    @angular-builders/custom-webpack npm install -D prerender-xs Extend Angular CLI Library to prerender with Puppeteer https://github.com/just-jeb/angular-builders https://github.com/chybie/prerender-xs
  14. const Prerenderer = require('prerender-xs'); const routes = ['/food', '/team', '/speakers',

    ...]; module.exports = async (targetOptions, indexHtml) => { const data = await Prerenderer.render({ staticDir: 'dist/site2019', routes, indexHtml }); return data.find(x => x.route === '/').html; } index-html-transform.js Update angular.json to use this custom file
  15. Solution: Generate Sitemap Automatically ng build --prod Generate & Optimize

    files - js, css, etc Deploy all files to server Generate sitemap.xml Extend Angular CLI Webpack to do so Generate index.html
  16. Extend Angular CLI for Sitemap Generation npm install -D create-file-webpack

    https://github.com/Appius/create-file-webpack Webpack plugin to create file (any format)
  17. const CreateFileWebpack = require('create-file-webpack'); const routes = ['/food', '/team', '/speakers',

    ...] module.exports = { plugins: [ new CreateFileWebpack({ path: 'dist/site2019', fileName: 'sitemap.xml', content: generateSitemap(routes) })] } extra-webpack.config.js Our custom method to generate sitemap https://github.com/chybie/ng-my/blob/master/ projects/site2019/extra-webpack.config.js
  18. ➔ Work together & independently ➔ Mandatory Code Linting ➔

    Continuous Integration & Deployment (CI/CD)
  19. Husky - Git Hook Made Easy npm install -D husky

    // package.json { "husky": { "hooks": { "pre-commit": "ng lint" } } }
  20. Flow Create PR PR Approved Merge to Master Branch Trigger

    Build Trigger Deployment Complete feature development Automate with Github actions (beta)
  21. .github/workflows/ main.yml name: CI on: push: branches: - master jobs:

    build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: 12.8 - run: npm install - run: npm run lint - run: npm run deploy env: FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} Only run in master Lint, Build & Deploy to Firebase
  22. SVG

  23. 78% Global User WebP Has arrived Based on data from

    caniuse Source: bit.ly/webp-support Supported Supported Supported
  24. .png - 119 kb .webp - 28 kb .png -

    278 kb .webp - 30 kb
  25. Serve WebP and support browsers <picture> <source srcset="teh-tarik.webp" type="image/webp"> <img

    src="teh-tarik.png"> </picture> Image container If browser supports WebP Else PNG it is
  26. Lossy Image For most images, 80-85% quality will reduce file

    size by 30-40% with minimal effect on image quality
  27. Serve different image sizes <picture> <source media="(max-width: 800px)" srcset="teh-tarik-800w.webp" type="image/webp">

    <source srcset="teh-tarik.webp" type="image/webp"> <img src="teh-tarik.png" > </picture> Small screen and if browser supports WebP https://developer.mozilla.org/en-US/docs/Learn/HTML/ Multimedia_and_embedding/Responsive_images
  28. Native lazy loading Defer fetching offscreen images / iframes until

    a user scrolls near them. Load when scrolling down
  29. Display Font Immediately By default, if a font is not

    loaded, The browser will hide text for up to:
  30. Flash of Unstyled Text (FOUT) @font-face { font-family: Source Sans

    Pro', sans-serif; src: url('...') format('woff'); font-display: swap; } Display unstyled text until font loaded .5s improvement in “Visual Complete” on 3G
  31. Font import <head> <link href="https://fonts.googleapis..." rel="stylesheet"> </head> Eliminate render blocking

    above the fold <body> ... <style> @import "//fonts.googleapis..."; </style> </body> <head> <link href="https://fonts.googleapis..." rel="stylesheet"> </head>
  32. Lazy Loading Traditional SPA - big-bundle.js (60kb) Split & Lazy

    Load - route-speakers.js (20kb) - route-food.js (20kb) - route-schedule.js (20kb) - ….
  33. Lazy Loading 1-2 pages per module const routes: Routes =

    [ { path: 'speakers', loadChildren: () => import('./speakers/speakers.module') .then(m => m.SpeakersModule) }, ... ]; Latest Angular version 8 syntax
  34. Change detection @Component({ selector: 'my-root', ... changeDetection: ChangeDetectionStrategy.OnPush }) export

    class AppComponent { } Change detection perform only when the component have received different inputs
  35. CSS

  36. { "hosting": { "headers": [{ "source": "**/*.@(js|css|jpg|jpeg|gif|png|webp|svg)", "headers": [{ "key":

    "Cache-Control", "value": "max-age=31536000" }] }], ... }, } Efficient cache (firebase.json) js, css, images longer cache
  37. Performance Optimization we get for FREE! ➔ Minification & Uglify

    (CLI) ➔ Ahead of Time Compilation (CLI) ➔ GZIP (Firebase) ➔ Differential Loading (CLI) ng build --prod
  38. this.selectedPost.content = marked.parse(result); const result = await fetch(`post-01.txt`) .then(res =>

    res.text()); ![picture](banner.jpg) Let’s make it happen LAH. Are you coming? 5 (+1) reasons why you should! ### 1. Enjoy incredible talks NG-MY provides you... post-page.component.ts post-01.txt
  39. <ng-template let-title="title" let-list="day" #schedule> <section> <h2>{{ title }}</h2> <div *ngFor="let

    schedule of list"> ... </div> </section> </ng-template> Input variables Template reference Create ng-template (schedule-page)
  40. <section> <ng-container [ngTemplateOutlet]="schedule" [ngTemplateOutletContext]="{ day: day1, title: 'Day 1' }">

    </ng-container> </section> Template reference Assign variable values Use ng-template (schedule-page) … <section> <ng-container [ngTemplateOutlet]="schedule" [ngTemplateOutletContext]="{ day: day2, title: 'Day 2' }"> </ng-container> </section> Reuse, pass in day 2 schedule
  41. const form = ...; // from json const urlPattern =

    'https://docs.google.com/forms/d/e/{id} /viewform?embedded=true'; form.url = this.sanitizer.bypassSecurityTrustResou rceUrl(urlPattern.replace('{id}', form.id)); { "call-for-sponsors": { "name": "Call for Sponsors", "desc": "Interested to...", "id": "google-form-id", "height": 1633 }, ... } form-page.component.ts forms.json Assign to iframe
  42. :root { --hero-scroll: 0; } global.css .menu { opacity: var(--hero-scroll);

    } menu.component.css .bg:before { opacity: calc(1 - 1 * var(--hero-scroll)); } .bg { opacity: calc(0.3 + 0.4 * var(--hero-scroll)); } home.component.css
  43. @HostListener('window:scroll', ['$event']) onScroll(e) { const opacityValue = ...; // calculation

    document.documentElement.style.setProperty( '--hero-scroll', `${opacityValue}` ); } menu.component.ts Update CSS Variables Listen to scroll
  44. Define grid template 5 columns, 12 rows size = 1

    col size = 2 cols size = 2 rows, 2 cols
  45. .container { display: grid; grid-template-columns: repeat(5,200px); grid-template-rows: repeat(12,200px); grid-auto-flow: row

    dense; } 5 columns 12 rows Auto placement algorithm No. of rows and columns different based on screen size (media queries)
  46. How auto placement algorithm works? dense = fill the holes