├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cdk ├── .npmignore ├── bin │ └── cdk-vue-artifact.ts ├── cdk.json ├── jest.config.js ├── lib │ ├── cdk-vue-application-pipeline.ts │ └── lambda │ │ └── ab-lambda-function.js ├── package-lock.json ├── package.json ├── test │ └── cdk-vue-artifact.test.ts └── tsconfig.json ├── docs ├── arch.png ├── cdk_cd.png ├── cdk_pipeline.png ├── cdk_stack.png ├── cloudfront_distribution.png ├── experiment_a.png ├── experiment_b.png ├── pipeline_deploy.png ├── secret_confirm.png ├── secret_name.png ├── secret_rotation.png ├── secrets.png ├── store_secret.png └── vue_app.png └── vue-web-component-app ├── .editorconfig ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── AdditionApp.vue ├── CounterApp.vue ├── assets │ └── logo.png ├── components │ ├── Addition.vue │ └── Counter.vue ├── main.ts ├── shims-tsx.d.ts ├── shims-vue.d.ts └── store │ └── index.ts ├── tests └── unit │ └── example.spec.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | *.js 4 | !jest.config.js 5 | *.d.ts 6 | node_modules 7 | 8 | # CDK asset staging directory 9 | .cdk.staging 10 | cdk.out 11 | 12 | # Parcel default cache directory 13 | .parcel-cache 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Build a CDK pipeline for B/G deployments of Single Page Applications (SPAs) 2 | 3 | Many Customers have a use case of building [single-page application](https://en.wikipedia.org/wiki/Single-page_application)(SPA) based web apps to lower frontend complexity and provide a standardized development. We will see how to automate the process of creating pipeline infrastructure and hosting the web components on AWS using CloudFront and S3. This solution also shows how to deploy the web apps using B/G deployment with the help of various AWS technologies. 4 | 5 | This blog post explains how to build a CDK pipeline for B/G deployments of VueJs WebComponents. The CDK stack consists of a CloudFront distribution with S3 bucket as an origin to host the VueJS bundle and a code pipeline to deploy the app from a Github repository. The stack also consist of a Lambda@edge function to perform A/B testing and route users to serve the content of website based on B/G deployment. In this post, we are using VueJs framework for web app but you can use any other framework based on your business needs. 6 | 7 | ### Benefits of B/G deployment 8 | 9 | Customers want to automate the end-to-end deployment workflow and leverage continuous methodologies utilizing AWS developer tools services for performing a blue/green deployment with zero downtime. A blue/green deployment is a deployment strategy wherein you create two separate, but identical environments. One environment (blue) is running the updated application version, and one environment (green) is running the current application version. The blue/green deployment strategy increases application availability by generally isolating the two application environments and ensuring that spinning up a parallel green environment won’t affect the blue environment resources. This isolation reduces deployment risk by simplifying the rollback process if a deployment fails. 10 | 11 | In this blog post, We will guide you through an automated process to deploy a single-page-application (SPA) on Amazon CloudFront and S3 to host application source code, and utilizing a blue/green deployment with AWS code suite services in order to deploy the application source code with no downtime. 12 | 13 | ### Architecture design 14 | 15 | ![Architecture Diagram](docs/arch.png) 16 | 17 | **Pipeline Build and Deploy Steps** 18 | 19 | 1. Developer makes commit to GitHub triggering AWS CodePipeline. 20 | 2. AWS CodeBuild job runs to build CDK code and generate CloudFormation artifacts. 21 | 3. AWS CodeBuild job runs to deploy CDK CloudFormation stacks. This stage will self-mutate this infrastructure pipeline with the CDK folder’s updates. 22 | 4. AWS CodeBuild job runs to build Vue code and generate application artifact. 23 | 5. Approve deployment to "Blue" S3 bucket origin to begin A/B testing the new build. 24 | 6. AWS CodePipeline deploys Vue application artifact to "Blue" S3 bucket origin. 25 | 7. Once the "Blue" deployment has been verified, approve deployment to "Green" S3 bucket origin. 26 | 8. AWS CodePipeline deploys Vue application artifact to "Green" S3 bucket origin and both environments are now synchronized. 27 | 28 | **User Requests Application Steps** 29 | 30 | 1. User requests file from Amazon CloudFront distribution. 31 | 2. CloudFront ViewerRequest triggers Lambda@Edge function to determine which origin to forward the user to. 32 | 1. For new users, a cookie will be randomly assigned to determine the version that the user will be forwarded to. There is a 25% chance the "Blue" origin is selected. 33 | 2. When a user already has a valid cookie, the origin will be automatically determined. 34 | 3. Cache "Blue" origin content into CloudFront distribution. 35 | 4. Cache "Green" origin content into CloudFront distribution. 36 | 37 | ### Prerequisite 38 | 39 | To follow along with this post, you need the following prerequisites: 40 | 41 | 1. Sign up for an [AWS account](https://aws.amazon.com/). 42 | 2. Sign up for [Github account](https://github.com/join). 43 | 3. [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html). 44 | 4. [Install the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) and set up your [AWS credentials for command-line use](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_prerequisites). 45 | 5. [Download and Install nodejs](https://nodejs.org/en/download/). 46 | 47 | ### Getting Started 48 | 49 | To implement the architecture described above, we will be doing the following: 50 | 51 | 52 | 1. Cloning the github repository. 53 | 2. Storing a new secret using AWS Secrets Manager. 54 | 3. Deploy the CDK and Web App using the code cloned from repository. 55 | 4. Test the B/G deployment and run the application. 56 | 57 | ## Setting up 58 | 59 | ### **GitHub Setup** 60 | 61 | 1. [Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) this GitHub repo and clone to your local machine: https://github.com/aws-samples/aws-cdk-spa-bg-deployment 62 | 2. Create a GitHub access token with admin:repo_hook and repo permissions: https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token 63 | 1. Store the generated token in a temporary secure location for later use. 64 | 3. Create a new GitHub repository and take note of the following in the newly created repository: 65 | 1. Repository Owner 66 | 2. Repository Name 67 | 3. Repository Deployment Branch 68 | 4. Repository token (token created in previous step) 69 | 5. Notification Email (email used for deployment notifications) 70 | 4. Run the following commands against the locally cloned repository to change the remote origin to the newly created GitHub repository origin and push: 71 | ``` 72 | git remote set-url origin https://github.com// 73 | git push 74 | ``` 75 | 76 | ### **AWS Setup** 77 | 78 | 1. Login to your AWS account: https://console.aws.amazon.com/console/home 79 | 2. Navigate to the AWS Secrets Manager and select “Store a new secret”: https://console.aws.amazon.com/secretsmanager/home 80 | 81 | ![Secrets Manager Home](docs/secrets.png) 82 | 83 | 3. Using the GitHub repository information from above, select “Other type of secrets” and enter the following: 84 | 1. repo-owner=Repository Owner 85 | 2. repo-name=Repository Name 86 | 3. repo-branch=Repository Deployment Branch 87 | 4. repo-token=Repository token 88 | 5. notifications-email=Notification Email 89 | 4. After confirming all information is correct, leave the encryption key as default and select “Next”. NOTE: This information will be picked by CloudFormation/CDK when the stack is deployed. 90 | 91 | ![Secret Name](docs/store_secret.png) 92 | 93 | 5. Set the secret name to “GitHubSecrets” and optionally enter a description and tags for this secret. Select “Next” when you are done. 94 | 95 | ![Secret Name](docs/secret_name.png) 96 | 97 | 6. Leave “Disable automatic rotation” selected on the next screen and select “Next”. Finally, review the secret and select “Store” on the final page when you are ready. 98 | 99 | ![Secret Rotation](docs/secret_rotation.png) 100 | 101 | ![Secret Confirm](docs/secret_confirm.png) 102 | 103 | 7. From your local machine navigate to the directory which you have cloned the example repository. Change the directory to “cdk”. 104 | 105 | ![CDK CD](docs/cdk_cd.png) 106 | 107 | 8. Install the NodeJS dependencies, build the application, bootstrap CDK, and deploy the CDK application. 108 | 1. Run `npm install` to install dependencies. 109 | 2. Run `npm run build` to build the CDK application. 110 | 3. Run `npm run bootstrap` to bootstrap the CDK environment. 111 | 4. Run `npm run deploy` to deploy the CDK application stacks to the bootstrapped environment. 112 | 5. NOTE: The CDK commands that NodeJS is executing behind the scenes are available in the ‘package.json’ file. 113 | 9. Once the stack has been deployed you should see a new pipeline in the bootstrapped region as below. NOTE: You should also see the CloudFormation stacks that were deployed by CDK available in the CloudFormation console. 114 | 115 | ![CDK Pipeline](docs/cdk_pipeline.png) 116 | 117 | ![CDK Stack](docs/cdk_stack.png) 118 | 119 | 10. Once the CloudFormation stack indicates the status of “UPDATE_COMPLETE”, you may push a change to your GitHub repository to test the pipeline. 120 | 11. When the pipeline reaches the stages “ApprovalGreen” and “ApprovalBlue”, you should receive an email notification indicating that your pipeline is awaiting an approval. Once the approvals are reviewed and accepted, the web application will be deployed to the respective S3 origin and all stages should have a green check mark. 121 | 122 | ![Pipeline Deploy](docs/pipeline_deploy.png) 123 | 124 | 12. To access the deployed web application, navigate to the [CloudFront console UI](https://console.aws.amazon.com/cloudfront/v3/home) and find the generated CloudFront distribution (The origin should contain the name “green-vue-component-bucket”). 125 | 126 | ![CloudFront Distribution](docs/cloudfront_distribution.png) 127 | 128 | 13. Copy the CloudFront distribution’s domain name and navigate to the URL in a separate browser tab. After navigating to the URL, the web application should load into either the blue or green deployment of the application. NOTE: See the “vue-web-component-app” folder to understand the inner details of the deployed web application. 129 | 130 | ![Vue App](docs/vue_app.png) 131 | 132 | ## Test 133 | 134 | Once the user visits the CloudFront domain, they will be randomly assigned a cookie name “X-Experiment-Name” with a value of either “A” or “B”. To test that the user can be redirected to both deployments, you can clear the cookie from application cache (using Chrome developer console below) and refresh the page until the desired value is assigned. 135 | 136 | **Experiment A (Green)**: 137 | ![Experiment A](docs/experiment_a.png) 138 | 139 | **Experiment B (Blue)**: 140 | ![Experiment B](docs/experiment_b.png) 141 | 142 | ## Cleanup 143 | 144 | To clean up after this tutorial: 145 | 146 | 1. Log into the AWS console of the account you used 147 | 2. Go to the [AWS CloudFormation console](https://console.aws.amazon.com/cloudformation/home) of the region where you chose to deploy, and select and click ‘Delete’ on the stack named ‘CdkVueApplicationPipelineStack’. 148 | 3. You may optionally delete the bootstrapping stack for CDK named ‘CDKToolkit’ if you do not want to be charged for its deployed resources. 149 | 150 | ## Conclusion 151 | 152 | To recap, there is a common use case of building [single-page application](https://en.wikipedia.org/wiki/Single-page_application) (SPA) based web apps to lower frontend complexity and provide a standardized development stack. Using the blue/green deployment strategy with your SPA will increase application availability and ensure that spinning up a parallel green environment won’t affect the blue environment resources. This isolation reduces deployment risk by simplifying the rollback process if a deployment fails and always having a known “good” environment ready to serve users. 153 | 154 | ## Security 155 | 156 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 157 | 158 | ## License 159 | 160 | This library is licensed under the MIT-0 License. See the LICENSE file. 161 | 162 | -------------------------------------------------------------------------------- /cdk/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /cdk/bin/cdk-vue-artifact.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | import {App} from '@aws-cdk/core'; 20 | import {CdkVueApplicationPipeline} from '../lib/cdk-vue-application-pipeline'; 21 | 22 | const app = new App(); 23 | new CdkVueApplicationPipeline(app, 'CdkVueApplicationPipelineStack', { 24 | env: { 25 | region: process.env.CDK_DEFAULT_REGION, 26 | account: process.env.CDK_DEFAULT_ACCOUNT, 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node bin/cdk-vue-artifact.ts", 3 | "context": { 4 | "@aws-cdk/core:enableStackNameDuplicates": "true", 5 | "aws-cdk:enableDiffNoFail": "true", 6 | "@aws-cdk/core:stackRelativeExports": "true", 7 | "@aws-cdk/core:newStyleStackSynthesis": "true" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cdk/jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | module.exports = { 20 | roots: ['/test'], 21 | testMatch: ['**/*.test.ts'], 22 | transform: { 23 | '^.+\\.tsx?$': 'ts-jest' 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /cdk/lib/cdk-vue-application-pipeline.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | import {Stack, Construct, StackProps, RemovalPolicy} from '@aws-cdk/core'; 20 | import {SecretValue} from '@aws-cdk/core'; 21 | import {Bucket} from '@aws-cdk/aws-s3'; 22 | import {Artifact} from '@aws-cdk/aws-codepipeline' 23 | import {BuildSpec, LinuxBuildImage, PipelineProject} from '@aws-cdk/aws-codebuild' 24 | import {Topic} from '@aws-cdk/aws-sns' 25 | import { 26 | CodeBuildAction, 27 | GitHubSourceAction, 28 | GitHubTrigger, 29 | ManualApprovalAction, 30 | S3DeployAction 31 | } from '@aws-cdk/aws-codepipeline-actions' 32 | import {CdkPipeline, SimpleSynthAction} from '@aws-cdk/pipelines'; 33 | import {Distribution, LambdaEdgeEventType, OriginAccessIdentity, ViewerProtocolPolicy} from '@aws-cdk/aws-cloudfront'; 34 | import {S3Origin} from '@aws-cdk/aws-cloudfront-origins'; 35 | import {EdgeFunction} from "@aws-cdk/aws-cloudfront/lib/experimental"; 36 | import {Code, Runtime} from "@aws-cdk/aws-lambda"; 37 | 38 | export class CdkVueApplicationPipeline extends Stack { 39 | constructor(scope: Construct, id: string, props?: StackProps) { 40 | super(scope, id, props); 41 | 42 | // Retrieve secrets for configuring the GitHub repository webhook and pipeline notifications email. 43 | const githubRepoOwner = SecretValue.secretsManager('GitHubSecrets', {jsonField: 'repo-owner'}); 44 | const githubRepoName = SecretValue.secretsManager('GitHubSecrets', {jsonField: 'repo-name'}); 45 | const githubRepoBranch = SecretValue.secretsManager('GitHubSecrets', {jsonField: 'repo-branch'}); 46 | const githubRepoToken = SecretValue.secretsManager('GitHubSecrets', {jsonField: 'repo-token'}); 47 | const notificationsEmail = SecretValue.secretsManager('GitHubSecrets', {jsonField: 'notifications-email'}); 48 | 49 | // Pipeline artifacts to store repository source and build stage outputs. 50 | const pipelineArtifact = new Artifact('RepoSource'); 51 | const buildArtifact = new Artifact('BuildOutput'); 52 | const vueBuildArtifact = new Artifact('VueBuildOutput'); 53 | 54 | // Create SNS topic for pipeline notifications like approvals. 55 | const pipelineNotificationTopic = new Topic(this, 'ApprovalSnsTopic', { 56 | topicName: 'ApprovalSnsTopic' 57 | }); 58 | 59 | // CdkPipeline to auto-mutate this CDK stack and deploy the Vue application. 60 | const cdkPipeline = new CdkPipeline(this, 'VueComponentCdkPipeline', { 61 | pipelineName: 'VueComponentPipeline', 62 | cloudAssemblyArtifact: buildArtifact, 63 | synthAction: SimpleSynthAction.standardNpmSynth({ 64 | sourceArtifact: pipelineArtifact, 65 | cloudAssemblyArtifact: buildArtifact, 66 | actionName: 'BuildCdk', 67 | subdirectory: 'cdk', 68 | buildCommand: 'npm run build', 69 | testCommands: [ 70 | 'npm run test' 71 | ] 72 | }), 73 | sourceAction: new GitHubSourceAction({ 74 | actionName: 'GitHubSource', 75 | branch: githubRepoBranch.toString(), 76 | output: pipelineArtifact, 77 | owner: githubRepoOwner.toString(), 78 | repo: githubRepoName.toString(), 79 | oauthToken: githubRepoToken, 80 | trigger: GitHubTrigger.WEBHOOK, 81 | runOrder: 1 82 | }) 83 | }); 84 | 85 | // Add build stage for the Vue application. 86 | const vueApplicationStage = cdkPipeline.addStage('BuildVue'); 87 | vueApplicationStage.addActions(new CodeBuildAction({ 88 | actionName: 'BuildVue', 89 | input: pipelineArtifact, 90 | outputs: [ 91 | vueBuildArtifact 92 | ], 93 | project: new PipelineProject(this, "BuildVue", { 94 | buildSpec: BuildSpec.fromObject({ 95 | version: "0.2", 96 | phases: { 97 | install: { 98 | "runtime-versions": { 99 | nodejs: 12 100 | } 101 | }, 102 | "pre_build": { 103 | commands: [ 104 | "cd vue-web-component-app", 105 | "npm install" 106 | ] 107 | }, 108 | build: { 109 | commands: [ 110 | "npm run lint", 111 | "npm run test", 112 | "npm run build", 113 | "cp public/* dist" 114 | ] 115 | } 116 | }, 117 | artifacts: { 118 | files: [ 119 | "**/*" 120 | ], 121 | "base-directory": "vue-web-component-app/dist" 122 | } 123 | }), 124 | environment: { 125 | buildImage: LinuxBuildImage.AMAZON_LINUX_2_3 126 | } 127 | }), 128 | runOrder: 1 129 | })); 130 | 131 | // Creating S3 bucket to deploy the Vue application to. 132 | const deployBlueBucket = new Bucket(this, 'BlueVueComponentsBucket', { 133 | versioned: false, 134 | bucketName: `blue-vue-component-bucket-${this.region}-${this.account}`, 135 | publicReadAccess: false, 136 | removalPolicy: RemovalPolicy.DESTROY 137 | }); 138 | 139 | // Creating S3 bucket to deploy the Vue application to. 140 | const deployGreenBucket = new Bucket(this, 'GreenVueComponentsBucket', { 141 | versioned: false, 142 | bucketName: `green-vue-component-bucket-${this.region}-${this.account}`, 143 | publicReadAccess: false, 144 | removalPolicy: RemovalPolicy.DESTROY 145 | }); 146 | 147 | // Create CloudFront distribution OAI for the S3 bucket containing the Vue application. 148 | const oai = new OriginAccessIdentity(this, 'OriginAccessIdentity', {comment: "Origin Access Identity for Origin S3 bucket"}); 149 | deployBlueBucket.grantRead(oai); 150 | deployGreenBucket.grantRead(oai); 151 | 152 | // Create Lambda@Edge function for A/B testing different application deployments. 153 | const edgeFunction = new EdgeFunction(this, 'ABEdgeFunction', { 154 | code: Code.fromAsset("lib/lambda"), 155 | handler: "ab-lambda-function.handler", 156 | runtime: Runtime.NODEJS_14_X 157 | }); 158 | 159 | const greenOrigin = new S3Origin( 160 | deployGreenBucket, 161 | { 162 | originAccessIdentity: oai 163 | } 164 | ); 165 | 166 | // Create CloudFront distribution with Vue deployment bucket as origin and AB Lambda@Edge function. 167 | const cfDistro = new Distribution(this, 'VueComponentDistribution', { 168 | defaultBehavior: { 169 | origin: greenOrigin, 170 | viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, 171 | edgeLambdas: [{ 172 | eventType: LambdaEdgeEventType.VIEWER_REQUEST, 173 | functionVersion: edgeFunction 174 | }] 175 | }, 176 | defaultRootObject: 'index.html' 177 | }); 178 | 179 | const blueOrigin = new S3Origin( 180 | deployBlueBucket, 181 | { 182 | originAccessIdentity: oai 183 | } 184 | ); 185 | 186 | cfDistro.addBehavior('/blue/*', 187 | blueOrigin, 188 | { 189 | viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS 190 | } 191 | ) 192 | 193 | // Add approval stage before deploying the Vue application 194 | const approvalBlueStage = cdkPipeline.addStage('ApprovalBlue'); 195 | approvalBlueStage.addActions(new ManualApprovalAction({ 196 | actionName: 'ApproveBlueDeploy', 197 | notifyEmails: [ 198 | notificationsEmail.toString() 199 | ], 200 | additionalInformation: 'Approve Deployment to S3 for the blue deployment group?', 201 | externalEntityLink: `https://github.com/${githubRepoOwner}/${githubRepoName}`, 202 | notificationTopic: pipelineNotificationTopic, 203 | runOrder: 1 204 | })); 205 | 206 | // Add pipeline stage for deploying the Vue application to the "Blue" S3 target. 207 | const deployBlueStage = cdkPipeline.addStage('DeployBlue'); 208 | deployBlueStage.addActions(new S3DeployAction({ 209 | actionName: 'DeployVue', 210 | bucket: deployBlueBucket, 211 | input: vueBuildArtifact 212 | })); 213 | 214 | // Add approval stage before deploying the Vue application 215 | const approvalGreenStage = cdkPipeline.addStage('ApprovalGreen'); 216 | approvalGreenStage.addActions(new ManualApprovalAction({ 217 | actionName: 'ApproveDeploy', 218 | notifyEmails: [ 219 | notificationsEmail.toString() 220 | ], 221 | additionalInformation: 'Approve Deployment to S3 for the green deployment group?', 222 | externalEntityLink: `https://github.com/${githubRepoOwner}/${githubRepoName}`, 223 | notificationTopic: pipelineNotificationTopic, 224 | runOrder: 1 225 | })); 226 | 227 | // Add pipeline stage for deploying the Vue application to the "Green" S3 target. 228 | const deployGreenStage = cdkPipeline.addStage('DeployGreen'); 229 | deployGreenStage.addActions(new S3DeployAction({ 230 | actionName: 'DeployVue', 231 | bucket: deployGreenBucket, 232 | input: vueBuildArtifact 233 | })); 234 | 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /cdk/lib/lambda/ab-lambda-function.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | 'use strict'; 20 | 21 | exports.handler = (event, context, callback) => { 22 | // Grab Viewer Request from the event 23 | const request = event.Records[0].cf.request; 24 | // Output the request to CloudWatch 25 | console.log('Lambda@Edge Request: %j', request); 26 | const headers = request.headers; 27 | const groupBUri = '/blue/index.html'; 28 | 29 | // Name of cookie to check for. Application will be decided randomly when not present. 30 | const cookieExperimentA = 'X-Experiment-Name=A'; 31 | const cookieExperimentB = 'X-Experiment-Name=B'; 32 | let response = { 33 | status: '302', 34 | statusDescription: 'Found' 35 | }; 36 | 37 | // Check for a cookie to determine if experimental group has been previously selected 38 | let selectedExperiment = cookieExperimentA; 39 | let cookiePresent = false; 40 | if (headers.cookie) { 41 | // Check for the experimental cookie and select the appropriate experiment when present. 42 | for (let i = 0; i < headers.cookie.length; i++) { 43 | if (headers.cookie[i].value.indexOf(cookieExperimentA) >= 0) { 44 | console.log('Experiment A cookie found'); 45 | selectedExperiment = cookieExperimentA; 46 | cookiePresent = true; 47 | break; 48 | } else if (headers.cookie[i].value.indexOf(cookieExperimentB) >= 0) { 49 | console.log('Experiment B cookie found'); 50 | selectedExperiment = cookieExperimentB; 51 | cookiePresent = true; 52 | break; 53 | } 54 | } 55 | } 56 | 57 | // When the cookie is not present then it needs to be set. 58 | if (!cookiePresent) { 59 | // When there is no cookie, then randomly decide which app version will be used. 60 | console.log('Experiment cookie has not been found. Throwing dice...'); 61 | if (Math.random() < 0.75) { 62 | console.log('Experiment A chosen'); 63 | selectedExperiment = cookieExperimentA; 64 | } else { 65 | console.log('Experiment B chosen'); 66 | selectedExperiment = cookieExperimentB; 67 | } 68 | // Set header to appropriate experiment. 69 | response.headers = { 70 | 'location': [{ 71 | key: 'Location', 72 | value: selectedExperiment === cookieExperimentB ? groupBUri : '/' 73 | }], 74 | 'set-cookie': [{ 75 | key: 'Set-Cookie', 76 | value: selectedExperiment 77 | }] 78 | }; 79 | } else { 80 | //Generate HTTP redirect response to experimental group B. 81 | console.log('Experiment cookie has been found. Experimental group selected: %s', selectedExperiment); 82 | if (selectedExperiment === cookieExperimentB && request.uri === '/') { 83 | // Redirect to blue group. 84 | response.headers = { 85 | 'location': [{ 86 | key: 'Location', 87 | value: groupBUri 88 | }] 89 | }; 90 | } else { 91 | response = request; 92 | } 93 | } 94 | 95 | // Display final response in logs 96 | console.log("Final response: %j", response); 97 | callback(null, response); 98 | }; -------------------------------------------------------------------------------- /cdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-vue-artifact", 3 | "version": "0.1.0", 4 | "bin": { 5 | "cdk-vue-artifact": "bin/cdk-vue-artifact.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk", 12 | "bootstrap": "cdk bootstrap", 13 | "deploy": "cdk deploy" 14 | }, 15 | "devDependencies": { 16 | "@aws-cdk/assert": "^2.68.0", 17 | "@types/jest": "^29.5.11", 18 | "@types/node": "20.11.6", 19 | "aws-cdk": "^2.123.0", 20 | "jest": "^29.7.0", 21 | "ts-jest": "^29.1.2", 22 | "ts-node": "^10.9.2", 23 | "typescript": "~5.3.3" 24 | }, 25 | "dependencies": { 26 | "@aws-cdk/aws-cloudfront": "^1.204.0", 27 | "@aws-cdk/aws-cloudfront-origins": "^1.204.0", 28 | "@aws-cdk/aws-codebuild": "^1.204.0", 29 | "@aws-cdk/aws-codepipeline": "^1.204.0", 30 | "@aws-cdk/aws-codepipeline-actions": "^1.204.0", 31 | "@aws-cdk/aws-s3": "1.204.0", 32 | "@aws-cdk/aws-secretsmanager": "^1.204.0", 33 | "@aws-cdk/aws-sns": "^1.204.0", 34 | "@aws-cdk/core": "1.204.0", 35 | "@aws-cdk/pipelines": "^1.204.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cdk/test/cdk-vue-artifact.test.ts: -------------------------------------------------------------------------------- 1 | import {expect, haveResource} from '@aws-cdk/assert'; 2 | import {App} from '@aws-cdk/core'; 3 | import {CdkVueApplicationPipeline} from '../lib/cdk-vue-application-pipeline'; 4 | 5 | test('Empty Stack', () => { 6 | const app = new App(); 7 | // WHEN 8 | const stack = new CdkVueApplicationPipeline(app, 'MyTestStack', { 9 | env: { 10 | account: 'test', 11 | region: 'us-east-1' 12 | } 13 | }); 14 | expect(stack).to(haveResource('AWS::CodePipeline::Pipeline')) 15 | }); 16 | -------------------------------------------------------------------------------- /cdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /docs/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/arch.png -------------------------------------------------------------------------------- /docs/cdk_cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/cdk_cd.png -------------------------------------------------------------------------------- /docs/cdk_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/cdk_pipeline.png -------------------------------------------------------------------------------- /docs/cdk_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/cdk_stack.png -------------------------------------------------------------------------------- /docs/cloudfront_distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/cloudfront_distribution.png -------------------------------------------------------------------------------- /docs/experiment_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/experiment_a.png -------------------------------------------------------------------------------- /docs/experiment_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/experiment_b.png -------------------------------------------------------------------------------- /docs/pipeline_deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/pipeline_deploy.png -------------------------------------------------------------------------------- /docs/secret_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/secret_confirm.png -------------------------------------------------------------------------------- /docs/secret_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/secret_name.png -------------------------------------------------------------------------------- /docs/secret_rotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/secret_rotation.png -------------------------------------------------------------------------------- /docs/secrets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/secrets.png -------------------------------------------------------------------------------- /docs/store_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/store_secret.png -------------------------------------------------------------------------------- /docs/vue_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/docs/vue_app.png -------------------------------------------------------------------------------- /vue-web-component-app/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /vue-web-component-app/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /vue-web-component-app/README.md: -------------------------------------------------------------------------------- 1 | # vue-web-component-app 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your unit tests 19 | ``` 20 | npm run test:unit 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /vue-web-component-app/babel.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | module.exports = { 20 | presets: [ 21 | '@vue/cli-plugin-babel/preset' 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /vue-web-component-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-web-component-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "./node_modules/.bin/vue-cli-service serve", 7 | "build": "./node_modules/.bin/vue-cli-service build --target wc --inline-vue --name vue-wc src/AdditionApp.vue,src/CounterApp.vue", 8 | "test:unit": "vue-cli-service test:unit", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "@vue/web-component-wrapper": "^1.3.0", 13 | "core-js": "^3.35.1", 14 | "vue": "^3.4.15", 15 | "vue-class-component": "^7.2.6", 16 | "vue-property-decorator": "^9.1.2", 17 | "vue-router": "^4.2.5", 18 | "vuex": "^4.1.0" 19 | }, 20 | "devDependencies": { 21 | "@types/jest": "^29.5.11", 22 | "@typescript-eslint/eslint-plugin": "^6.19.1", 23 | "@typescript-eslint/parser": "^6.19.1", 24 | "@vue/cli-plugin-babel": "^5.0.8", 25 | "@vue/cli-plugin-eslint": "^5.0.8", 26 | "@vue/cli-plugin-router": "^5.0.8", 27 | "@vue/cli-plugin-typescript": "^5.0.8", 28 | "@vue/cli-plugin-unit-jest": "^5.0.8", 29 | "@vue/cli-plugin-vuex": "^5.0.8", 30 | "@vue/cli-service": "^5.0.8", 31 | "@vue/eslint-config-standard": "^8.0.1", 32 | "@vue/eslint-config-typescript": "^12.0.0", 33 | "@vue/test-utils": "^2.4.3", 34 | "eslint": "^8.56.0", 35 | "eslint-plugin-import": "^2.29.1", 36 | "eslint-plugin-node": "^11.1.0", 37 | "eslint-plugin-promise": "^6.1.1", 38 | "eslint-plugin-standard": "^5.0.0", 39 | "eslint-plugin-vue": "^9.20.1", 40 | "node-sass": "^9.0.0", 41 | "sass-loader": "^14.0.0", 42 | "typescript": "^5.3.3", 43 | "vue-template-compiler": "^2.7.16" 44 | }, 45 | "eslintConfig": { 46 | "root": true, 47 | "env": { 48 | "node": true 49 | }, 50 | "extends": [ 51 | "plugin:vue/essential", 52 | "@vue/standard", 53 | "@vue/typescript/recommended" 54 | ], 55 | "parserOptions": { 56 | "ecmaVersion": 2020 57 | }, 58 | "rules": {}, 59 | "overrides": [ 60 | { 61 | "files": [ 62 | "**/__tests__/*.{j,t}s?(x)", 63 | "**/tests/unit/**/*.spec.{j,t}s?(x)" 64 | ], 65 | "env": { 66 | "jest": true 67 | } 68 | } 69 | ] 70 | }, 71 | "browserslist": [ 72 | "> 1%", 73 | "last 2 versions", 74 | "not dead" 75 | ], 76 | "jest": { 77 | "preset": "@vue/cli-plugin-unit-jest/presets/typescript-and-babel" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /vue-web-component-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/vue-web-component-app/public/favicon.ico -------------------------------------------------------------------------------- /vue-web-component-app/public/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <%= htmlWebpackPlugin.options.title %> 25 | 26 | 27 | 28 | 31 |
32 | 33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /vue-web-component-app/src/AdditionApp.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | 27 | 60 | 61 | 83 | -------------------------------------------------------------------------------- /vue-web-component-app/src/CounterApp.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | 27 | 60 | 61 | 83 | -------------------------------------------------------------------------------- /vue-web-component-app/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-spa-bg-deployment/dcad23ee846f6e1dedffc0133d48e724eb98c75e/vue-web-component-app/src/assets/logo.png -------------------------------------------------------------------------------- /vue-web-component-app/src/components/Addition.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | 26 | 40 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /vue-web-component-app/src/components/Counter.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 41 | 42 | 43 | 45 | -------------------------------------------------------------------------------- /vue-web-component-app/src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | import Vue from 'vue' 20 | import wrap from '@vue/web-component-wrapper' 21 | import CounterApp from './CounterApp.vue' 22 | import AdditionApp from './AdditionApp.vue' 23 | 24 | const WrappedElementCounter = wrap(Vue, CounterApp) 25 | const WrappedElementAddition = wrap(Vue, AdditionApp) 26 | window.customElements.define('vue-wc-counter-app', WrappedElementCounter) 27 | window.customElements.define('vue-wc-addition-app', WrappedElementAddition) 28 | 29 | Vue.config.productionTip = false 30 | -------------------------------------------------------------------------------- /vue-web-component-app/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | import Vue, { VNode } from 'vue' 20 | 21 | declare global { 22 | namespace JSX { 23 | // tslint:disable no-empty-interface 24 | interface Element extends VNode {} 25 | // tslint:disable no-empty-interface 26 | interface ElementClass extends Vue {} 27 | interface IntrinsicElements { 28 | [elem: string]: any; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vue-web-component-app/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | declare module '*.vue' { 20 | import Vue from 'vue' 21 | export default Vue 22 | } 23 | 24 | declare module "@vue/web-component-wrapper"; 25 | -------------------------------------------------------------------------------- /vue-web-component-app/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | }, 9 | mutations: { 10 | }, 11 | actions: { 12 | }, 13 | modules: { 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /vue-web-component-app/tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: MIT-0 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | * software and associated documentation files (the "Software"), to deal in the Software 7 | * without restriction, including without limitation the rights to use, copy, modify, 8 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | import {mount, createLocalVue, shallowMount} from '@vue/test-utils' 20 | import Counter from '@/components/Counter.vue' 21 | import Addition from '@/components/Addition.vue' 22 | 23 | const localVue = createLocalVue(); 24 | 25 | describe('Counter and Addition Component Tests', () => { 26 | it('Render Counter Component', () => { 27 | const wrapper = shallowMount(Counter, { localVue }) 28 | expect(wrapper.find('#count').attributes().placeholder).toMatch('edit me') 29 | }) 30 | it('Render Addition Component', () => { 31 | const wrapper = shallowMount(Addition, { localVue }) 32 | expect(wrapper.find('#addition').attributes().placeholder).toMatch('edit me') 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /vue-web-component-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "webpack-env", 17 | "jest" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "src/*" 22 | ] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "tests/**/*.ts", 36 | "tests/**/*.tsx" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | --------------------------------------------------------------------------------