Slide 1

Slide 1 text

JavaScript(TypeScript)Ͱ ϝσΟΞαΠτΛ Πϯϑϥ͔Βߏங͢Δํ๏ JSConf JP 2021 2021/11/27 Hidetaka Okamoto

Slide 2

Slide 2 text

Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ • AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ৔߹ͷ஫ҙ఺

Slide 3

Slide 3 text

About me • Ԭຊलߴ(Okamoto Hidetaka) • JS(TS) developer • React / NestJS / AWS CDK • AWS / Stripe / Alexa / Shifter • དྷि͔Β࠶ͼձࣾһ

Slide 4

Slide 4 text

Talk about… • My Blog ( https://wp-kyoto.net ) • Backend: WordPress • Frontend: Next.js (with Ionic) • Infrastructure: AWS (AWS CDK) • Misc: Stripe / AWS Amplify / Algolia

Slide 5

Slide 5 text

Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ • AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ৔߹ͷ஫ҙ఺

Slide 6

Slide 6 text

WebαΠτߏஙͰͷΠϯϑϥͷଟ༷Խ(Ұྫ) • αʔόʔ΍ωοτϫʔΫΛ෺ཧ͔Β༻ҙɾηοτΞοϓ • αʔόʔΛआΓͯɺͦͷதʹLAMP / MEAN stackͳͲΛߏ੒ (VPS) • ίϯςφΛར༻ͨ͠ɺImmutableͳ؀ڥΛڞ༗ɾσϓϩΠ (GAE/EKS) • ϑϧϚωʔδυͳSaaSΛ࢖ͬͯɺΞϓϦͷΈσϓϩΠ (Netlify / Vercel) • ΞϓϦ΋ϚωʔδυܕͷSaaSͰɺαΠτӡӦʹूத (WP.com/STUDIO)

Slide 7

Slide 7 text

Πϯϑϥͷίʔυ؅ཧ “Infrastructure as Code (IaC)” • Chef / Ansible / terraform / Serverless Framework / Docker / etc… • ʮ͋Δ΂͖ߏ੒ʯΛίʔυͰఆٛ͠ɺVCSͰ؅ཧ͢Δ • ʮͦͷίʔυ͕͋Ε͹ɺ୭Ͱ΋ಉ͡؀ڥ͕࡞ΕΔʯ • ʮԶͷ؀ڥͰ͸ಈ͍͚ͨͲʁʯͷղফ • αʔόʔ૿ڧ΍ೖΕସ͑࣌ͷޮ཰Խͱ࡞ۀϛεͷ༧๷

Slide 8

Slide 8 text

IaCͷఆ͕ٛ JavaScriptͰॻ͚Ε͹ɺ Ͱ͖Δ͜ͱ͕૿͑Δɾɾɾʁ

Slide 9

Slide 9 text

Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ • AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ৔߹ͷ஫ҙ఺

Slide 10

Slide 10 text

JSͰΠϯϑϥఆ͕ٛՄೳͳπʔϧ(Ұ෦) • Serverless Framework (AWSͷΈ) • AWS CDK • Adapt

Slide 11

Slide 11 text

Serverless Framework (JS Objectܕ) import type { AWS } from '@serverless/typescript'; const serverlessConfiguration: AWS = { service: 'aws-nodejs-typescript', frameworkVersion: '*', provider: { name: 'aws', runtime: 'nodejs12.x', }, functions: { hello: { handler: 'handler.hello', events: [ { http: { method: 'get', path: 'hello', } } ] } } } • AWSͷΈରԠ(2021/11) • ܕ৘ใ: @types/serverless • AWS Lambda + API GW

Slide 12

Slide 12 text

AWS CDK (Classܕ) import * as core from "@aws-cdk/core"; import * as apigateway from "@aws-cdk/aws-apigateway"; import * as lambda from "@aws-cdk/aws-lambda"; export class WidgetService extends core.Construct { constructor(scope: core.Construct, id: string) { super(scope, id); const handler = new lambda.Function(this, "WidgetHandler", { runtime: lambda.Runtime.NODEJS_14_X, // So we can use async in widget.js code: lambda.Code.fromAsset("resources"), handler: "main", }); bucket.grantReadWrite(handler); // was: handler.role); const api = new apigateway.RestApi(this, "widgets-api", { restApiName: "Widget Service", description: "This service serves widgets." }); const getWidgetsIntegration = new apigateway.LambdaIntegration(handler, { requestTemplates: { "application/json": '{ "statusCode": "200" }' } }); api.root.addMethod("GET", getWidgetsIntegration); // GET / } } • CloudFormationͷϥούʔ • AWSϦιʔεఆٛΛ TypeScriptͰॻ͚Δ • CloudFormation YAML΁ͷ ม׵Ͱ୤ग़Մೳ

Slide 13

Slide 13 text

Adapt (JSXܕ) import Adapt, { Group, handle } from "@adpt/core"; import { UrlRouter } from "@adpt/cloud/http"; import { NodeService, ReactApp } from "@adpt/cloud/nodejs"; import { Postgres } from "@adpt/cloud/postgres"; import { k8sProdStyle, k8sTestStyle, laptopStyle } from "./ styles"; function App() { const pg = handle(); const app = handle(); const api = handle(); return ; } • React JSXͰߏ੒ఆٛ • AWSҎ֎Ͱ΋࢖͑Δ • v0.xͷͨΊɺ prodಋೖ͸৻ॏʹ

Slide 14

Slide 14 text

JSΛ࢖ͬͨΠϯϑϥఆٛ • Class / Object / JSXͳͲɺ༷ʑͳه๏͕Մೳ • TypeScriptͷܕఆٛΛ׆༻࣮ͯ͠૷͕جຊܗ • AWS / GCPͳͲͷαʔϏεಠࣗͷઃఆ஋ఆٛʹTSͷྗ͕࢖͑Δ • ʢ؍ଌൣғͰ͸ʣެࣜͰπʔϧΛϦϦʔε͍ͯ͠ΔAWS͕ڧΊ

Slide 15

Slide 15 text

Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ • AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ৔߹ͷ஫ҙ఺

Slide 16

Slide 16 text

AWS CDK import * as core from "@aws-cdk/core"; import * as apigateway from "@aws-cdk/aws-apigateway"; import * as lambda from "@aws-cdk/aws-lambda"; export class WidgetService extends core.Construct { constructor(scope: core.Construct, id: string) { super(scope, id); const handler = new lambda.Function(this, "WidgetHandler", { runtime: lambda.Runtime.NODEJS_14_X, // So we can use async in widget.js code: lambda.Code.fromAsset("resources"), handler: "main", }); bucket.grantReadWrite(handler); // was: handler.role); const api = new apigateway.RestApi(this, "widgets-api", { restApiName: "Widget Service", description: "This service serves widgets." }); const getWidgetsIntegration = new apigateway.LambdaIntegration(handler, { requestTemplates: { "application/json": '{ "statusCode": "200" }' } }); api.root.addMethod("GET", getWidgetsIntegration); // GET / } }

Slide 17

Slide 17 text

AWS CDKΛ༻͍ͨߏஙํ๏ • CloudFormationΛTypeScriptͰهड़͢Δ • Construct LibraryΛར༻͢Δ • AWS AmplifyΛOverwrite͢ΔʢNEW !ʣ

Slide 18

Slide 18 text

CloudFormationΛ TypeScriptͰ هड़͢Δ const bucket = new s3.CfnBucket(this, "MyBucket", { bucketName: "MyBucket", corsConfiguration: { corsRules: [{ allowedOrigins: ["*"], allowedMethods: ["GET"] }] } }); const cfnDistribution = new cloudfront.CfnDistribution(this, 'MyCfnDistribution', { distributionConfig: { enabled: false, origins: [{ domainName: 'domainName', id: 'id', // the properties below are optional connectionAttempts: 123, connectionTimeout: 123, originCustomHeaders: [{ headerName: 'headerName', headerValue: 'headerValue', }], originPath: 'originPath', originShield: { enabled: false, originShieldRegion: 'originShieldRegion', }, s3OriginConfig: { originAccessIdentity: 'originAccessIdentity', }, }], … • AWSϦιʔεΛ 1ͭ1ͭఆٛ • 👍 ৄࡉͳઃఆ͕Մೳ • 😖 AWSͷ஌͕ࣝଟ͘ඞཁ

Slide 19

Slide 19 text

AWS CDKͷ Construct Library Λར༻͢Δ import * as core from "@aws-cdk/core"; import * as apigateway from "@aws-cdk/aws-apigateway"; import * as lambda from "@aws-cdk/aws-lambda"; import * as s3 from "@aws-cdk/aws-s3"; export class WidgetService extends core.Construct { constructor(scope: core.Construct, id: string) { super(scope, id); const bucket = new s3.Bucket(this, "WidgetStore"); const handler = new lambda.Function(this, "WidgetHandler", { runtime: lambda.Runtime.NODEJS_14_X, // So we can use async in widget.js code: lambda.Code.fromAsset("resources"), handler: "widgets.main", environment: { BUCKET: bucket.bucketName } }); bucket.grantReadWrite(handler); // was: handler.role); const api = new apigateway.RestApi(this, "widgets-api", { restApiName: "Widget Service", description: "This service serves widgets." }); const getWidgetsIntegration = new apigateway.LambdaIntegration(handler, { requestTemplates: { "application/json": '{ "statusCode": "200" }' } }); api.root.addMethod("GET", getWidgetsIntegration); // GET / } } • CFNϦιʔεΛ ந৅Խͨ͠ϥΠϒϥϦ܈ • 👍 ͋Δఔ౓ఆٛࡁΈͷ ɹߏ੒͕࢖͑Δ • 😖 ઃఆ͕not for meͷ৔߹ ɹ݁ہίʔυΛॻ͘͜ͱʹ

Slide 20

Slide 20 text

@aws-cdk/aws- lambda-nodejs ͕ศར new lambda.NodejsFunction(this, 'my-handler', { bundling: { commandHooks: { beforeBundling(inputDir: string, outputDir: string): string[] { return [ `echo hello > ${inputDir}/a.txt`, ]; }, afterBundling(inputDir: string, outputDir: string): string[] { return [`cp ${inputDir}/b.txt ${outputDir}/txt`]; }, beforeInstall() { return []; }, }, minify: true, // minify code, defaults to false sourceMap: true, // include source map, defaults to false target: 'es2020', loader: { // Use the 'dataurl' loader for '.png' files '.png': 'dataurl', }, define: { // Replace strings during build time 'process.env.API_KEY': JSON.stringify('xxx-xxxx-xxx'), }, logLevel: lambda.LogLevel.SILENT, // defaults to LogLevel.WARNING keepNames: true, // defaults to false tsconfig: 'custom-tsconfig.json', // use custom-tsconfig.json instead of default, … • TypeScriptͷίʔυΛ esbuildͰίϯύΠϧͯ͠ AWS LambdaʹσϓϩΠ • Lambdaͷ࣮૷ͱߏ੒ ྆ํ·ͱΊͯTSͰ؅ཧͰ͖Δ

Slide 21

Slide 21 text

Next.jsΛ࢖͏৔߹͸ Serverless Next.js https://github.com/serverless-nextjs/serverless-next.js

Slide 22

Slide 22 text

serverless-nextjs for AWS CDK // Πϯϑϥఆٛ import { NextJSLambdaEdge } from "@sls-next/cdk-construct"; const app = new NextJSLambdaEdge(this, "NextJsApp", { serverlessBuildOutDir: "./build", domain: { domainNames: ["wp-kyoto.net"], certificate: Certificate.fromCertificateArn( this, “WPKYOTOACM", "arn:aws:acm:us-east-1:xxxxxx" ), }, }); ==================================== // Next.jsͷϏϧυઃఆ import * as cdk from "@aws-cdk/core"; import { Builder } from "@sls-next/lambda-at-edge"; const builder = new Builder(".", "./build", { args: ["build"] }); builder .build().then(() => { const app = new cdk.App(); new MyStack(app, `MyStack`); }) • NextJSLambdaEdgeͰ Next.jsΛCDNΤοδʹσϓϩΠ • CDKͳͷͰΧελϚΠζ΋༰қ • Next.jsͷ࣮૷ʹखΛग़͞ͳ͍ͷ Ͱɺ؆୯ʹࣙΊΕΔ

Slide 23

Slide 23 text

CDKͰͷ ΧελϚΠζྫ const app = new NextJSLambdaEdge(…); // Ωϟογϡઃఆ app.nextLambdaCachePolicy = new CachePolicy(this, "NextLambdaCache", { queryStringBehavior: CacheQueryStringBehavior.all(), headerBehavior: CacheHeaderBehavior.allowList( "Authorization", “Origin" ), cookieBehavior: { behavior: "all", }, defaultTtl: Duration.seconds(0), maxTtl: Duration.days(365), minTtl: Duration.seconds(0), enableAcceptEncodingBrotli: true, enableAcceptEncodingGzip: true, }); // AWSϦιʔε΁ͷΞΫηεݖ const targets = [app.defaultNextLambda, app.defaultNextLambda]; targets.forEach((target) => { target.addToRolePolicy( new PolicyStatement({ resources: ["*"], actions: ["ssm:GetParameter"], }) ); }); • ΫϥεͷpropsΛ ্ॻ͖ͯ͠ߋ৽ • CDNઃఆΛҰ෦্ॻ͖ • AWSͷΞΫηεݖݶΛ ֦ு

Slide 24

Slide 24 text

σΟϨΫτϦߏ੒ !"" README.md !"" next-env.d.ts !"" next.config.js !"" tsconfig.json !"" package.json !"" pages !"" public !"" styles !"" components !"" hooks !"" tests # %"" cdk !"" tsconfig.cdk.json !"" cdk # !"" bin # %"" serverless-nextjs-cdk-example-stack.ts !"" cdk.json !"" jest.cdk.config.js %"" yarn.lock • Next.jsجຊߏ੒ʹ cdkσΟϨΫτϦΛ௥Ճ • CDKελοΫͷTS͸ ผ్tsconfigΛ࡞੒

Slide 25

Slide 25 text

GitHubʹࢼͤΔ αϯϓϧPJ͋Γ·͢ https://bit.ly/3cVCXCt ͳ͔ͬͨͷͰPull RequestΛग़ͯ͠ࡌͤͯ΋Βͬͨ

Slide 26

Slide 26 text

Serverless Next.jsΛ࢖͏৔߹ͷ஫ҙ఺ • Lambda@edge্Ͱಈ࡞͍ͯ͠Δ͜ͱΛ๨Εͳ͍ • ϑΝΠϧαΠζ΍࣮ߦ࣌ؒͷ੍ݶ͕͋Δ • FileSystemΛ࢖͏ܥͷػೳ͸ࣄނΓ΍͍͢ • ϩά͕ʮΞΫηε͞ΕͨϦʔδϣϯͷCloudWatch Logʯʹ෼ࢄ • ʮ࠷৽ͷػೳʯ͸࢖͑ΔΑ͏ʹͳΔ·ͰλΠϜϥά͕ग़Δ

Slide 27

Slide 27 text

AWS AmplifyΛ Overwrite͢Δ import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; export function override( resources: AmplifyS3ResourceTemplate ) { resources.s3Bucket.versioningConfiguration = { status: "Enabled" } } • amplify override • 👍 ΧελϜ͍ͨ͠৔ॴ͚ͩ ɹίʔυͰมߋͰ͖Δ • 😖 શϦιʔεʹ͸ ɹ·ͩະରԠ

Slide 28

Slide 28 text

AWSͰJSΛ࢖ͬͯΠϯϑϥΛఆٛ͢Δ • REST API։ൃத৺ -> Serverless FW or AWS CDK (Nodejs Construct) • ࡉ͔͘ઃఆΛॻ͖͍ͨΘ͚Ͱ͸ແ͍ -> AWS Amplify (+ amplify overrideͰAWS CDKʣ • Next.jsΛ྿Ձ͔ͭServerlessʹӡ༻͍ͨ͠ -> Serverless Next.js • ΄΅CloudFormationΛॻ͘ͷʹ͍ۙ -> AWS CDK

Slide 29

Slide 29 text

Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ • AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ৔߹ͷ஫ ҙ఺

Slide 30

Slide 30 text

https://faustjs.org/

Slide 31

Slide 31 text

WordPress / GraphQL / Next.js -> Faust.js • WP EngineʢWPϗεςΟϯάձࣾʣ͕ϦϦʔε • WordPressͷϑϩϯτΤϯυΛNext.jsʹ͢ΔͨΊͷFW (ݱঢ়) • WordPress؅ཧը໘࿈ܞ • ϓϨϏϡʔػೳ • GraphQLεΩʔϚ΍Auth༻React Hookͷఏڙ etc…

Slide 32

Slide 32 text

Setup npx create-next-app \ -e https://github.com/wpengine/faustjs/tree/canary \ --example-path examples/next/getting-started \ --use-npm \ my-app cd my-app

Slide 33

Slide 33 text

import { GetServerSidePropsContext } from 'next'; import { getNextServerSideProps } from '@faustjs/next'; import { client } from 'client'; export default function MyPage() { const { usePosts } = client; return ( <>

Recent Posts

{usePosts()?.nodes.map((post) => (
  • {post.title()}
  • ))} > ); } export async function getServerSideProps(context: GetServerSidePropsContext) { return getNextServerSideProps(context, { Page: MyPage, client, }); } WPͷσʔλऔಘ • Faust.jsఏڙͷ Hook΍ؔ਺Λ࢖͏ • GQtyͰGraphQLͷ APIݺͼग़͠ • WPଆͷ࢓༷Λҙࣝͤͣ࢖͑Δ

    Slide 34

    Slide 34 text

    AWSͰࢼ͢ࡍͷ஫ҙ఺ • Node.js v14Ҏ্͕ඞਢ • Lambda@edge΍AppRunner͸Node v12ʢ2021/11࣌఺ʣ • Serverless Next.js͕ɺݱঢ়࢖͑ͳ͍ʢAWSͷରԠ଴ͪʣ • v12ͷEOL͕2022/4͝ΖͳͷͰɺͦͷ͏ͪv14ରԠ͢Δʢ͸ͣʣ • ଴ͯͳ͍ਓ͸ɺFargate΍EC2ɾebΛ

    Slide 35

    Slide 35 text

    JavaScript(TS)ͰϝσΟΞαΠτΛ࡞Δ • αʔϏεɾπʔϧͷਐԽʹΑΓɺ JS͚ͩͰ࣮૷ɾఆٛͰ͖ΔྖҬ͕૿͑ͨ • Construct / 3rd party libraryʹΑΓɺ ʮத਎Λҙࣝͤͣʹಈ͔ͤΔ؀ڥઃఆʯ΋ೖख͕༰қʹ • ʮ0͔1͔ʯͰ͸ͳ͍ɻ͕ɺ ʮࣗ෼͚ͨͪͩͰ΋Ͱ͖Δํ๏ʯΛ஌͍ͬͯΔ͜ͱ͸ॏཁ

    Slide 36

    Slide 36 text

    Thanks • SNS: Twitter->@hide__dev GitHub->hideokamoto • Resources • AWS CDK: https://aws.amazon.com/jp/cdk/ • Serverless Framework: https://www.serverless.com/framework • Serverless Next.js: https://serverless-nextjs.com/ • Faust.js: https://faustjs.org/