├── .npmignore ├── .gitignore ├── src ├── shims.js ├── router.js └── serverless.js ├── docs └── assets │ └── diagram.png ├── LICENSE ├── package.json ├── README.md ├── index.js ├── serverless.yml └── SvelteKitSite.ts /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /files 4 | -------------------------------------------------------------------------------- /src/shims.js: -------------------------------------------------------------------------------- 1 | import { installPolyfills } from '@sveltejs/kit/node/polyfills'; 2 | installPolyfills(); -------------------------------------------------------------------------------- /docs/assets/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarbsemaj/sveltekit-adapter-lambda/HEAD/docs/assets/diagram.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) James Bray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@yarbsemaj/adapter-lambda", 3 | "version": "1.2.2", 4 | "license": "MIT", 5 | "description": "An adapter for [SvelteKit](https://kit.svelte.dev/) for AWS Lambda. [Serverless](https://www.serverless.com/) or [CDK](https://aws.amazon.com/cdk/) deployment.", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/yarbsemaj/adapter-lambda.git" 9 | }, 10 | "keywords": [ 11 | "sveltekit", 12 | "lambda", 13 | "api-gateway", 14 | "AWS", 15 | "adapter", 16 | "CDK" 17 | ], 18 | "author": "James Bray (https://yarbsemaj.com)", 19 | "bugs": { 20 | "url": "https://github.com/yarbsemaj/adapter-lambda/issues" 21 | }, 22 | "homepage": "https://github.com/yarbsemaj/adapter-lambda#readme", 23 | "main": "index.js", 24 | "files": [ 25 | "files" 26 | ], 27 | "scripts": { 28 | "build": "esbuild src/serverless.js --bundle --format=esm --platform=node --external:'../index.*' --external:'../manifest.*' --outfile=files/serverless.js && cp src/shims.js files/shims.js && cp src/router.js files/router.js", 29 | "prepare": "npm run build" 30 | }, 31 | "dependencies": { 32 | "@silvermine/serverless-plugin-cloudfront-lambda-edge": "^2.2.3", 33 | "diagnostics_channel": "^1.1.0", 34 | "esbuild": "^0.17.0", 35 | "fs-extra": "^10.0.0", 36 | "serverless": "^3.0.0", 37 | "serverless-s3-deploy": "^0.10.1", 38 | "set-cookie-parser": "^2.5.0" 39 | }, 40 | "devDependencies": { 41 | "aws-cdk-lib": "^2.143.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import staticFiles from './static.js' 4 | 5 | exports.handler = (event, context, callback) => { 6 | const request = event.Records[0].cf.request; 7 | 8 | //Fix for sveltekit action urls 9 | request.querystring = request.querystring.replace('/', '%2F'); 10 | 11 | //Only send GET request to S3 12 | if (request.method !== 'GET') { 13 | callback(null, request); 14 | return; 15 | } 16 | 17 | let uri = request.uri; 18 | //If our path matches a static file, perfrom an origin re-write to S3; 19 | if (staticFiles.includes(uri)) { 20 | callback(null, performReWrite(uri, request)); 21 | return; 22 | } 23 | 24 | //Remove the leading slash (if any) to normalise the path 25 | if (uri.slice(-1) === "/") { 26 | uri = uri.substring(0, uri.length - 1); 27 | } 28 | 29 | //Pre-rendered pages could be named `/index.html` or `route/name.html` lets try looking for those as well 30 | if (staticFiles.includes(uri + '/index.html')) { 31 | callback(null, performReWrite(uri + '/index.html', request)); 32 | return; 33 | } 34 | if (staticFiles.includes(uri + '.html')) { 35 | callback(null, performReWrite(uri + '.html', request)); 36 | return; 37 | } 38 | 39 | callback(null, request); 40 | }; 41 | 42 | function performReWrite(uri, request) { 43 | request.uri = uri; 44 | //Lambda@edge does not support ENV vars, so instead we have to pass in a customHeaders. 45 | const domainName = request.origin.custom.customHeaders["s3-host"][0].value; 46 | request.origin.custom.domainName = domainName; 47 | request.origin.custom.path = ""; 48 | request.headers["host"] = [{ key: "host", value: domainName }]; 49 | return request; 50 | } -------------------------------------------------------------------------------- /src/serverless.js: -------------------------------------------------------------------------------- 1 | import { Server } from '../index.js'; 2 | import { manifest } from '../manifest.js'; 3 | import { splitCookiesString } from 'set-cookie-parser'; 4 | 5 | export async function handler(event, context) { 6 | const app = new Server(manifest); 7 | const { rawPath, headers, rawQueryString, body, requestContext, isBase64Encoded, cookies } = event; 8 | 9 | const encoding = isBase64Encoded ? 'base64' : headers['content-encoding'] || 'utf-8'; 10 | const domainName = headers['x-forwarded-host'] 11 | const rawBody = typeof body === 'string' ? Buffer.from(body, encoding) : body; 12 | 13 | if (cookies) { 14 | headers['cookie'] = cookies.join('; ') 15 | } 16 | 17 | let rawURL = `https://${domainName}${rawPath}${rawQueryString ? `?${rawQueryString}` : ''}` 18 | 19 | await app.init({ 20 | env: process.env 21 | }); 22 | 23 | //Render the app 24 | const rendered = await app.respond(new Request(rawURL, { 25 | method: requestContext.http.method, 26 | headers: new Headers(headers), 27 | body: rawBody, 28 | }),{ 29 | platform: { context } 30 | }); 31 | 32 | //Parse the response into lambda proxy response 33 | if (rendered) { 34 | const resp = { 35 | headers: {}, 36 | cookies: [], 37 | body: await rendered.text(), 38 | statusCode: rendered.status 39 | } 40 | 41 | for (let k of rendered.headers.keys()) { 42 | let header = rendered.headers.get(k) 43 | 44 | if (k == 'set-cookie') { 45 | resp.cookies = resp.cookies.concat(splitCookiesString(header)); 46 | } else { 47 | //For multivalue headers, join them 48 | if (header instanceof Array) { 49 | header = header.join(',') 50 | } 51 | resp.headers[k] = header 52 | } 53 | } 54 | return resp 55 | } 56 | return { 57 | statusCode: 404, 58 | body: 'Not found.' 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # adapter-lambda for SvelteKit 2 | 3 | An adapter to build a [SvelteKit](https://kit.svelte.dev/) app into a lambda ready for deployment with lambda proxy via the Serverless framework or CDK. 4 | 5 | ## Installation 6 | ``` 7 | npm install --save-dev @yarbsemaj/adapter-lambda 8 | ``` 9 | ## Usage 10 | 11 | In your `svelte.config.js` configure the adapter as below; 12 | 13 | ```js 14 | import serverless from '@yarbsemaj/adapter-lambda'; 15 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 16 | 17 | /** @type {import('@sveltejs/kit').Config} */ 18 | const config = { 19 | preprocess: vitePreprocess(), 20 | 21 | kit: { 22 | adapter: serverless() //See Below for optional arguments 23 | } 24 | }; 25 | 26 | export default config; 27 | ``` 28 | 29 | ### For serverless 30 | Copy `serverless.yml` from the root of this repo to the root of your project, make sure to change the service name. 31 | 32 | After building your app run `sls deploy` to deploy code to AWS using the build tool [serverless](https://www.serverless.com/). 33 | 34 | An example project using serverless can be found [here](https://github.com/yarbsemaj/sveltekit-serverless-starter). 35 | 36 | ### For CDK 37 | Copy `SvelteKitSite.ts` from the root of this repo into your project and add it to a CDK stack. 38 | 39 | Deploy your stack using `cdk deploy --all` 40 | 41 | An example project using cdk can be found [here](https://github.com/yarbsemaj/sveltekit-cdk-starter). 42 | 43 | ### Tada 🎉 44 | No matter how you deploy, your app can then be accessed via the CloudFront distribution created as a part of the stack. 45 | 46 | ## Options 47 | | Argument | Description | Type | Default | 48 | | ------------------- | ------------------------------------------ | ------- | ------- | 49 | | **out** | the output directory of build files | string | build | 50 | | **esbuildOverride** | overrides for the [default esbuild options](https://github.com/yarbsemaj/sveltekit-adapter-lambda/blob/master/index.js#L50) | [esbuild Build Options](https://github.com/evanw/esbuild/blob/fc37c2fa9de2ad77476a6d4a8f1516196b90187e/lib/shared/types.ts#L110) | {} | 51 | 52 | ## Static Assets and precompiled pages 53 | To server static assets and precompiled pages, this adapter makes use of S3. In order to route traffic to the correct destination a Lambda@edge function is used to perform an origin rewrite to redirect traffic to the S3 Bucket. 54 | 55 | ## Infrastructure Diagram 56 | ![Infra](https://github.com/yarbsemaj/sveltekit-adapter-lambda/blob/master/docs/assets/diagram.png?raw=true) 57 | 58 | 59 | ## Help! I'm getting an error while building or serving my app. 60 | Please raise an issue on [Github](https://github.com/yarbsemaj/sveltekit-adapter-lambda/issues), and I will be happy to issue a fix. 61 | 62 | ## Versions 63 | | Adapter Version | Sveltekit Version | 64 | | --------------- | ----------------- | 65 | | 1.1.x - 1.2.x | 1.22.0 (Official) | 66 | | 1.x.x | 1.0.0 (Official) | 67 | | 0.12.x | 1.0.0-next.433 | 68 | | 0.11.x | 1.0.0-next.401 | 69 | | 0.10.x | 1.0.0-next.380 | 70 | | 0.9.x | 1.0.0-next.348 | 71 | | 0.6.x - 0.8.x | 1.0.0-next.301 | 72 | | 0.5.x | 1.0.0-next.286 | 73 | | 0.3.x - 0.4.x | 1.0.0-next.286 | 74 | | 0.2.x | 1.0.0-next.239 | 75 | | 0.1.x | 1.0.0-next.169 | 76 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { copyFileSync, unlinkSync, existsSync, statSync, mkdirSync, emptyDirSync, readdirSync, writeFileSync } = require('fs-extra'); 2 | const { join } = require('path/posix'); 3 | 4 | const esbuild = require('esbuild'); 5 | 6 | /** 7 | * @param {{ 8 | * out?: string; 9 | * esbuildOverride?: import('esbuild').BuildOptions; 10 | * }} options 11 | */ 12 | module.exports = function ({ out = 'build', esbuildOverride = {} } = {}) { 13 | /** @type {import('@sveltejs/kit').Adapter} */ 14 | const adapter = { 15 | name: 'adapter-serverless', 16 | 17 | async adapt(builder) { 18 | emptyDirSync(out); 19 | 20 | const static_directory = join(out, 'assets'); 21 | if (!existsSync(static_directory)) { 22 | mkdirSync(static_directory, { recursive: true }); 23 | } 24 | 25 | const prerendered_directory = join(out, 'prerendered'); 26 | if (!existsSync(prerendered_directory)) { 27 | mkdirSync(prerendered_directory, { recursive: true }); 28 | } 29 | 30 | const server_directory = join(out, 'server'); 31 | if (!existsSync(server_directory)) { 32 | mkdirSync(server_directory, { recursive: true }); 33 | } 34 | 35 | const edge_directory = join(out, 'edge'); 36 | if (!existsSync(edge_directory)) { 37 | mkdirSync(edge_directory, { recursive: true }); 38 | } 39 | 40 | builder.log.minor('Copying assets'); 41 | builder.writeClient(static_directory); 42 | 43 | builder.log.minor('Copying server'); 44 | builder.writeServer(out); 45 | copyFileSync(`${__dirname}/files/serverless.js`, `${server_directory}/_serverless.js`); 46 | copyFileSync(`${__dirname}/files/shims.js`, `${server_directory}/shims.js`); 47 | 48 | 49 | builder.log.minor('Building lambda'); 50 | esbuild.buildSync({ 51 | ...{ 52 | entryPoints: [`${server_directory}/_serverless.js`], 53 | outfile: `${server_directory}/serverless.js`, 54 | inject: [join(`${server_directory}/shims.js`)], 55 | external: ['node:*'], 56 | format: 'cjs', 57 | bundle: true, 58 | platform: 'node', 59 | }, ...esbuildOverride 60 | }); 61 | 62 | builder.log.minor('Prerendering static pages'); 63 | await builder.writePrerendered(prerendered_directory); 64 | 65 | console.log('Building router'); 66 | copyFileSync(`${__dirname}/files/router.js`, `${edge_directory}/_router.js`); 67 | let files = JSON.stringify([...getAllFiles(static_directory), ...getAllFiles(prerendered_directory)]) 68 | writeFileSync(`${edge_directory}/static.js`, `export default ${files}`) 69 | 70 | esbuild.buildSync({ 71 | entryPoints: [`${edge_directory}/_router.js`], 72 | outfile: `${edge_directory}/router.js`, 73 | format: 'cjs', 74 | bundle: true, 75 | platform: 'node', 76 | }); 77 | 78 | 79 | builder.log.minor('Cleanup'); 80 | unlinkSync(`${server_directory}/_serverless.js`); 81 | unlinkSync(`${edge_directory}/_router.js`); 82 | unlinkSync(`${out}/index.js`); 83 | }, 84 | }; 85 | 86 | return adapter; 87 | }; 88 | 89 | const getAllFiles = function (dirPath, basePath, arrayOfFiles) { 90 | files = readdirSync(dirPath) 91 | 92 | arrayOfFiles = arrayOfFiles || [] 93 | basePath = basePath || dirPath 94 | 95 | files.forEach(function (file) { 96 | if (statSync(dirPath + "/" + file).isDirectory()) { 97 | arrayOfFiles = getAllFiles(dirPath + "/" + file, basePath, arrayOfFiles) 98 | } else { 99 | arrayOfFiles.push(join("/", dirPath.replace(basePath, ''), "/", file)) 100 | } 101 | }) 102 | 103 | return arrayOfFiles 104 | } -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: 'sveltekit-app' 2 | 3 | frameworkVersion: "3" 4 | 5 | plugins: 6 | - '@silvermine/serverless-plugin-cloudfront-lambda-edge' 7 | - serverless-s3-deploy 8 | 9 | provider: 10 | name: aws 11 | runtime: nodejs18.x 12 | lambdaHashingVersion: 20201221 13 | region: us-east-1 #Lambda@Edge must be deployed in us-east-1 14 | stage: ${opt:stage, 'dev'} 15 | 16 | package: 17 | individually: true 18 | exclude: 19 | - ./** 20 | include: 21 | - build/server/** 22 | - build/edge/** 23 | 24 | custom: 25 | assets: 26 | auto: true 27 | targets: 28 | - bucket: 29 | Ref: StaticAssets 30 | files: 31 | - source: ./build/assets/ 32 | globs: 33 | - '**' 34 | empty: true 35 | headers: 36 | CacheControl: max-age=31104000 37 | - source: ./build/prerendered/ 38 | globs: 39 | - '**' 40 | empty: true 41 | headers: 42 | CacheControl: max-age=60 43 | 44 | 45 | functions: 46 | #SSR Function 47 | svelte: 48 | handler: build/server/serverless.handler 49 | memorySize: 256 50 | timeout: 15 51 | url: true 52 | 53 | #Router Function 54 | cfLambda: 55 | handler: build/edge/router.handler 56 | memorySize: 128 57 | timeout: 1 58 | lambdaAtEdge: 59 | distribution: 'WebsiteDistribution' 60 | eventType: origin-request 61 | 62 | resources: 63 | Resources: 64 | StaticAssets: 65 | Type: AWS::S3::Bucket 66 | Properties: 67 | BucketName: ${self:provider.stage}-${self:service}-static-assets 68 | OwnershipControls: 69 | Rules: 70 | - ObjectOwnership: BucketOwnerPreferred 71 | PublicAccessBlockConfiguration: 72 | BlockPublicAcls: false 73 | BlockPublicPolicy: false 74 | IgnorePublicAcls: false 75 | RestrictPublicBuckets: false 76 | 77 | StaticAssetsS3BucketPolicy: 78 | Type: AWS::S3::BucketPolicy 79 | Properties: 80 | Bucket: 81 | Ref: StaticAssets 82 | PolicyDocument: 83 | Statement: 84 | - Sid: PublicReadGetObject 85 | Effect: Allow 86 | Principal: "*" 87 | Action: 88 | - s3:GetObject 89 | Resource: 90 | Fn::Join: ["", ["arn:aws:s3:::", { "Ref": "StaticAssets" }, "/*"]] 91 | 92 | WebsiteDistribution: 93 | Type: 'AWS::CloudFront::Distribution' 94 | Properties: 95 | DistributionConfig: 96 | Origins: 97 | - 98 | DomainName: !Select [2, !Split ["/", !GetAtt ["SvelteLambdaFunctionUrl", "FunctionUrl"]]] 99 | Id: default 100 | OriginCustomHeaders: 101 | #Lambda@edge does not support ENV vars, so instead we have to pass in a customHeaders. 102 | - 103 | HeaderName: 's3-host' 104 | HeaderValue: '${self:provider.stage}-${self:service}-static-assets.s3.amazonaws.com' 105 | CustomOriginConfig: 106 | HTTPPort: 80 107 | HTTPSPort: 443 108 | OriginProtocolPolicy: 'https-only' 109 | Enabled: true 110 | Comment: '${self:service}_${self:provider.stage}' 111 | DefaultCacheBehavior: 112 | TargetOriginId: default 113 | Compress: true 114 | AllowedMethods: 115 | - DELETE 116 | - GET 117 | - HEAD 118 | - OPTIONS 119 | - PATCH 120 | - POST 121 | - PUT 122 | CachedMethods: 123 | - GET 124 | - HEAD 125 | - OPTIONS 126 | ForwardedValues: 127 | Headers: 128 | - x-forwarded-host 129 | Cookies: 130 | Forward: all 131 | QueryString: True 132 | ViewerProtocolPolicy: 'redirect-to-https' 133 | FunctionAssociations: 134 | - EventType: viewer-request 135 | FunctionARN: !GetAtt XForwardFunction.FunctionMetadata.FunctionARN 136 | 137 | XForwardFunction: 138 | Type: AWS::CloudFront::Function 139 | Properties: 140 | AutoPublish: true 141 | Name: "${self:provider.stage}-${self:service}-XForwardFunction" 142 | FunctionCode: !Sub | 143 | function handler(event) { 144 | event.request.headers['x-forwarded-host'] = event.request.headers['host'] 145 | return event.request 146 | } 147 | FunctionConfig: 148 | Comment: 'Add x-forwarded-host' 149 | Runtime: cloudfront-js-1.0 -------------------------------------------------------------------------------- /SvelteKitSite.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import { Duration } from 'aws-cdk-lib'; 3 | import { CacheCookieBehavior, CacheHeaderBehavior, CacheQueryStringBehavior, OriginRequestCookieBehavior, OriginRequestHeaderBehavior, OriginRequestQueryStringBehavior, ViewerProtocolPolicy } from 'aws-cdk-lib/aws-cloudfront'; 4 | import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; 5 | import { FunctionUrlAuthType } from 'aws-cdk-lib/aws-lambda'; 6 | import { BlockPublicAccess, BucketAccessControl } from 'aws-cdk-lib/aws-s3'; 7 | import { Construct } from 'constructs'; 8 | import path from 'path' 9 | 10 | const __dirname = process.cwd(); 11 | 12 | type PartialBy = Omit & Partial> 13 | 14 | 15 | export type SveltekitSiteProps = { 16 | lambdaProps?: PartialBy, 'architecture' | 'timeout' | 'memorySize'>; 17 | cloudfrontProps?: Omit & { 18 | defaultBehavior: Omit 19 | }; 20 | } 21 | 22 | export class SvelteKitSite extends Construct { 23 | public svelteLambda : cdk.aws_lambda.Function 24 | public cloudfrontDistribution: cdk.aws_cloudfront.Distribution 25 | constructor(scope: Construct, id: string, props?: SveltekitSiteProps) { 26 | super(scope, id); 27 | 28 | const svelte = new cdk.aws_lambda.Function(this, `${id}-svelte-lambda`, { 29 | runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, 30 | architecture: cdk.aws_lambda.Architecture.ARM_64, 31 | memorySize: 1024, 32 | timeout: Duration.seconds(10), 33 | handler: 'serverless.handler', 34 | code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, './build/server')), 35 | ...props?.lambdaProps, 36 | }); 37 | 38 | const svelteURL = svelte.addFunctionUrl({ authType: FunctionUrlAuthType.NONE }) 39 | 40 | const edgeFunction = new cdk.aws_cloudfront.experimental.EdgeFunction(this, `${id}-svelte-lambda-edge`, { 41 | runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, 42 | handler: 'router.handler', 43 | memorySize: 128, 44 | code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, './build/edge')), 45 | }); 46 | 47 | const staticAssets = new cdk.aws_s3.Bucket(this, `${id}-static-asset-bucket`, { 48 | blockPublicAccess: BlockPublicAccess.BLOCK_ACLS, 49 | accessControl: BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, 50 | }) 51 | 52 | staticAssets.addToResourcePolicy(new PolicyStatement({ 53 | actions: ['s3:GetObject'], 54 | effect: Effect.ALLOW, 55 | resources: [staticAssets.arnForObjects('*')], 56 | sid: 'PublicReadGetObject', 57 | principals: [new cdk.aws_iam.AnyPrincipal()] 58 | })) 59 | 60 | const forwardHeaderFunction = new cdk.aws_cloudfront.Function(this, `${id}-forward-header-function`, { 61 | code: cdk.aws_cloudfront.FunctionCode.fromInline(`function handler(event) { 62 | event.request.headers['x-forwarded-host'] = event.request.headers['host'] 63 | return event.request 64 | }`), 65 | }); 66 | 67 | new cdk.aws_s3_deployment.BucketDeployment(this, `${id}-deploy-prerender`, { 68 | sources: [cdk.aws_s3_deployment.Source.asset(path.join(__dirname, './build/prerendered'))], 69 | destinationBucket: staticAssets, 70 | prune: false, 71 | cacheControl: [ 72 | cdk.aws_s3_deployment.CacheControl.maxAge(Duration.minutes(5)), 73 | ], 74 | }); 75 | 76 | new cdk.aws_s3_deployment.BucketDeployment(this, `${id}-deploy-assets`, { 77 | sources: [cdk.aws_s3_deployment.Source.asset(path.join(__dirname, './build/assets/'))], 78 | destinationBucket: staticAssets, 79 | prune: false, 80 | cacheControl: [ 81 | cdk.aws_s3_deployment.CacheControl.maxAge(Duration.days(365)), 82 | cdk.aws_s3_deployment.CacheControl.immutable(), 83 | ], 84 | }); 85 | 86 | new cdk.aws_s3_deployment.BucketDeployment(this, `${id}-deploy-static`, { 87 | sources: [cdk.aws_s3_deployment.Source.asset(path.join(__dirname, './build/assets/_app'))], 88 | destinationBucket: staticAssets, 89 | destinationKeyPrefix: '_app', 90 | prune: false, 91 | cacheControl: [ 92 | cdk.aws_s3_deployment.CacheControl.maxAge(Duration.days(365)), 93 | cdk.aws_s3_deployment.CacheControl.immutable(), 94 | ], 95 | }); 96 | 97 | 98 | const distribution = new cdk.aws_cloudfront.Distribution(this, `${id}-svelte-cloudfront`, { 99 | ...props?.cloudfrontProps, 100 | defaultBehavior: { 101 | allowedMethods: cdk.aws_cloudfront.AllowedMethods.ALLOW_ALL, 102 | origin: new cdk.aws_cloudfront_origins.HttpOrigin(cdk.Fn.select(2, cdk.Fn.split('/', svelteURL.url)), { 103 | customHeaders: { 104 | 's3-host': staticAssets.virtualHostedUrlForObject().replace('https://', '') 105 | } 106 | }), 107 | viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, 108 | compress: true, 109 | originRequestPolicy: new cdk.aws_cloudfront.OriginRequestPolicy(this, `${id}-svelte-orp`, { 110 | cookieBehavior: OriginRequestCookieBehavior.all(), 111 | queryStringBehavior: OriginRequestQueryStringBehavior.all(), 112 | headerBehavior: OriginRequestHeaderBehavior.allowList('x-forwarded-host') 113 | }), 114 | cachePolicy: new cdk.aws_cloudfront.CachePolicy(this, `${id}-svelte-cp`, { 115 | cookieBehavior: CacheCookieBehavior.all(), 116 | queryStringBehavior: CacheQueryStringBehavior.all(), 117 | headerBehavior: CacheHeaderBehavior.allowList('x-forwarded-host'), 118 | enableAcceptEncodingBrotli: true, 119 | enableAcceptEncodingGzip: true 120 | }), 121 | ...props?.cloudfrontProps?.defaultBehavior, 122 | edgeLambdas: [ 123 | { 124 | functionVersion: edgeFunction.currentVersion, 125 | eventType: cdk.aws_cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST, 126 | }, 127 | ...(props?.cloudfrontProps?.defaultBehavior?.edgeLambdas || []) 128 | ], 129 | functionAssociations: [{ 130 | function: forwardHeaderFunction, 131 | eventType: cdk.aws_cloudfront.FunctionEventType.VIEWER_REQUEST, 132 | }, 133 | ...(props?.cloudfrontProps?.defaultBehavior?.functionAssociations || []) 134 | ], 135 | }, 136 | }); 137 | 138 | this.svelteLambda = svelte 139 | this.cloudfrontDistribution = distribution 140 | } 141 | } --------------------------------------------------------------------------------