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

JavaScript(TypeScript)で メディアサイトを インフラから構築する方法 / jsconf-jp-2021

JavaScript(TypeScript)で メディアサイトを インフラから構築する方法 / jsconf-jp-2021

Hidetaka Okamoto
PRO

November 27, 2021
Tweet

More Decks by Hidetaka Okamoto

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. 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

    View Slide

  12. 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΁ͷ
    ม׵Ͱ୤ग़Մೳ

    View Slide

  13. 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
    port={8080}
    routes={[
    { path: "/api/", endpoint: api },
    { path: "/", endpoint: app }
    ]} />

    connectTo={pg} />

    ;
    }
    • React JSXͰߏ੒ఆٛ
    • AWSҎ֎Ͱ΋࢖͑Δ
    • v0.xͷͨΊɺ
    prodಋೖ͸৻ॏʹ

    View Slide

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

    View Slide

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

    View Slide

  16. 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 /
    }
    }

    View Slide

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

    View Slide

  18. 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ͷ஌͕ࣝଟ͘ඞཁ

    View Slide

  19. 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ͷ৔߹
    ɹ݁ہίʔυΛॻ͘͜ͱʹ

    View Slide

  20. @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Ͱ؅ཧͰ͖Δ

    View Slide

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

    View Slide

  22. 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ͷ࣮૷ʹखΛग़͞ͳ͍ͷ
    Ͱɺ؆୯ʹࣙΊΕΔ

    View Slide

  23. 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ͷΞΫηεݖݶΛ
    ֦ு

    View Slide

  24. σΟϨΫτϦߏ੒
    !"" 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Λ࡞੒

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. 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

    View Slide

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

    View Slide

  30. https://faustjs.org/

    View Slide

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

    View Slide

  32. 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

    View Slide

  33. 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ଆͷ࢓༷Λҙࣝͤͣ࢖͑Δ

    View Slide

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

    View Slide

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

    View Slide

  36. 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/

    View Slide