React + Vite 应用环境搭建、部署备忘录
首先
我个人在开发中经常使用React。
然而,由于每次都会问自己“怎么来创建来着?”所以为了今后个人开发的顺利进行,我将其作为备忘录留下来。
只是列举命令、文件等内容,所以没有太多的解释说明。
请谅解。
创建项目
执行下列命令。
docker run --rm -it -v $PWD:/app -w /app --platform=linux/amd64 node:20-slim \
sh -c 'npm install -g npm && npm create -y vite@latest react-vite-sample -- --template react-swc-ts'
我正在执行创建Docker容器并创建vite项目的命令。
由于我使用的是Apple芯片的Mac终端,所以我加上了–platform=linux/amd64参数。
请将 “react-vite-sample” 替换为任意项目名称。
创建与Docker相关的文件
文件夹层次结构
プロジェクトルート
├─ .devcontainer
| └─ devcontainer.json
├─ docker-compose.yml
└─ Dockerfile
使用docker-compose,堆栈名称将成为父目录的名称。
过去我们将docker-compose.yml文件放在docker目录中,但是由于与其他项目的堆栈名称重复,所以现在我们将其放在项目根目录下。
文件
{
"service": "react-vite-sample",
"dockerComposeFile": "../docker-compose.yml",
"workspaceFolder": "/workdir",
"customizations": {
"vscode": {
"extensions": ["eamodio.gitlens", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"],
"settings": {
"prettier.configPath": ".prettierrc.json",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.tabSize": 2
}
}
}
}
version: '3.9'
services:
react-vite-sample:
ports:
- 5173:5173
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/workdir
tty: true
FROM --platform=linux/amd64 node:20-slim
RUN apt-get update && apt-get install -y git vim locales-all
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
WORKDIR /workdir
— 因为终端是苹果的Mac,所以我安装了平台为linux/amd64。
通过安装locales-all并使用ENV设置环境变量,在容器内使用git时,可以解决日语输入导致的乱码问题。
将上述内容放置在下面的命令中,即可在容器内进行提交等操作:通过设定”user.email”和”user.name”。
git config --global user.email <メールアドレス>
git config --global user.name <名前>
另外,还需要执行以下命令来安装库。
npm install
ESLint和Prettier的配置
文件夹结构
プロジェクトルート
├─ .eslintrc.cjs
├─ .prettier.json
├─ tsconfig.json
└─ vite.config.ts
文件
执行以下命令
npm install eslint prettier @typescript-eslint/eslint-plugin \
@typescript-eslint/parser eslint-config-prettier \
eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react \
eslint-import-resolver-typescript
修改 .eslintrc.cjs文件
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
+ 'plugin:import/recommended',
+ 'plugin:jsx-a11y/recommended',
+ 'eslint-config-prettier',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ 'import/resolver': {
+ typescript: {},
+ },
+ },
rules: {
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
+ 'no-restricted-imports': ['error', { patterns: ['./', '../'] }],
},
};
在项目文件夹的根目录下创建一个新的.prettier.json文件。
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 120,
"bracketSpacing": true
}
我们将使src目录下的文件可以通过@/的形式进行导入。
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
+ "baseUrl": "./",
+ "paths": {
+ "@/*": ["src/*"]
+ }
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: {
+ '@': '/src',
+ },
+ },
});
由于发生错误,我们将修复 main.tsx 和 App.tsx。
import React from 'react';
import ReactDOM from 'react-dom/client';
- import App from './App.tsx';
+ import App from '@/App.tsx';
- import '@/index.css';
+ import '@/index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
import { useState } from 'react';
- import reactLogo from './assets/react.svg';
+ import reactLogo from '@/assets/react.svg';
+ // eslint-disable-next-line import/no-unresolved
import viteLogo from '/vite.svg';
- import './App.css';
+ import '@/App.css';
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
</>
);
}
export default App;
考试准备
执行以下命令
npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom
在src目录下创建一个名为App.test.tsx的文件。
由于只有一个测试被描述,所以请根据需要进行添加和修改。
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import App from '@/App';
test('renders h1 text', () => {
render(<App />);
const headerElement = screen.getByText(/Vite \+ React/);
expect(headerElement).toBeInTheDocument();
});
将vite.config.ts文件更改如下。
+ /// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': '/src',
},
},
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ },
});
在package.json文件中的script属性中添加test。
{
"name": "test-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
+ "test": "vitest"
},
以下略...
}
创建CloudFormation模板
这是一个模板,使用CloudFront + S3进行托管,并通过将push推送到CodeCommit(在此配置中通过GitHub Actions镜像推送到CodeCommit)来触发CodePipeline,并进行部署。
我认为通过将”react-vite-sample”和”ReactViteSample”字符串替换为任何项目名称,可以轻松地将其应用到其他项目中。
文件夹结构
.
├─ cloudformation
│ ├─ parameters
│ │ ├─ codepipeline.json
│ │ ├─ hosting.json
│ │ └─ repository.json
│ ├─ shell
│ │ ├─ 01-repository.sh
│ │ ├─ 02-hosting.sh
│ │ ├─ 03-codebuild.sh
│ │ └─ 04-codepipeline.sh
│ └─ templates
│ ├─ codebuild.yml
│ ├─ codepipeline.yml
│ ├─ hosting.yml
│ └─ repository.yml
└─ buildspec.yml
参数
{
"Parameters": {
"RepositoryName": "react-vite-sample"
}
}
{
"Parameters": {
"SourceBucketName": "react-vite-sample-source-bucket",
"DestributionName": "react-vite-sample-destribution"
}
}
{
"Parameters": {
"CodePipeLineName": "react-vite-sample-pipeline",
"ArtifactBucketName": "react-vite-sample-pipeline-artifact-store",
"EventBridgeName": "react-vite-sample-eventbridge-pipeline",
"EventBridgeRoleName": "react-vite-sample-eventbridge-role",
"EventBridgePolicyName": "react-vite-sample-eventbridge-policy"
}
}
壳
在文件名中添加01等字符,但只要CodePipeline最后执行,其余的顺序都是无关紧要的。
#!/bin/bash
CHANGESET_OPTION="--no-execute-changeset"
if [ $# = 1 ] && [ $1 = "deploy" ]; then
echo "deploy mode"
CHANGESET_OPTION=""
fi
CFN_TEMPLATE=../templates/repository.yml
CFN_STACK_NAME=react-vite-sample-repository
aws cloudformation deploy --stack-name ${CFN_STACK_NAME} --template-file ${CFN_TEMPLATE} ${CHANGESET_OPTION}\
--parameter-overrides file://../parameters/repository.json
#!/bin/bash
CHANGESET_OPTION="--no-execute-changeset"
if [ $# = 1 ] && [ $1 = "deploy" ]; then
echo "deploy mode"
CHANGESET_OPTION=""
fi
CFN_TEMPLATE=../templates/hosting.yml
CFN_STACK_NAME=react-vite-sample-hosting
aws cloudformation deploy --stack-name ${CFN_STACK_NAME} --template-file ${CFN_TEMPLATE} ${CHANGESET_OPTION}\
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides file://../parameters/hosting.json
#!/bin/bash
CHANGESET_OPTION="--no-execute-changeset"
if [ $# = 1 ] && [ $1 = "deploy" ]; then
echo "deploy mode"
CHANGESET_OPTION=""
fi
CFN_TEMPLATE=../templates/codebuild.yml
CFN_STACK_NAME=react-vite-sample-codebuild
aws cloudformation deploy --stack-name ${CFN_STACK_NAME} --template-file ${CFN_TEMPLATE} ${CHANGESET_OPTION}\
--capabilities CAPABILITY_NAMED_IAM \
#!/bin/bash
CHANGESET_OPTION="--no-execute-changeset"
if [ $# = 1 ] && [ $1 = "deploy" ]; then
echo "deploy mode"
CHANGESET_OPTION=""
fi
CFN_TEMPLATE=../templates/codepipeline.yml
CFN_STACK_NAME=react-vite-sample-pipeline
aws cloudformation deploy --stack-name ${CFN_STACK_NAME} --template-file ${CFN_TEMPLATE} ${CHANGESET_OPTION}\
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides file://../parameters/codepipeline.json
模板
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
RepositoryName:
Type: String
Resources:
CodeCommitRepository:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: !Ref RepositoryName
Outputs:
RepositoryName:
Value: !Ref RepositoryName
Export:
Name: ReactViteSampleRepositoryName
RepositoryArn:
Value: !GetAtt CodeCommitRepository.Arn
Export:
Name: ReactViteSampleRepositoryArn
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
SourceBucketName:
Type: String
DestributionName:
Type: String
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref SourceBucketName
VersioningConfiguration:
Status: Enabled
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: Allow CloudFront Access
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub ${S3Bucket.Arn}/*
Condition:
StringEquals:
AWS:SourceArn: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${Distribution}
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- Id: S3Origin
DomainName: !GetAtt S3Bucket.DomainName
S3OriginConfig:
OriginAccessIdentity: ''
OriginAccessControlId: !GetAtt CloudFrontOriginAccessControl.Id
Enabled: true
DefaultRootObject: index.html
Comment: !Ref DestributionName
DefaultCacheBehavior:
TargetOriginId: S3Origin
CachePolicyId: !GetAtt CachePolicy.Id
ViewerProtocolPolicy: redirect-to-https
CloudFrontOriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Description: TestProject Origin Access Control
Name: TestProjectOAC
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
CachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
DefaultTTL: 86400
MaxTTL: 31536000
MinTTL: 0
Name: TestProjectCachePolicy
ParametersInCacheKeyAndForwardedToOrigin:
CookiesConfig:
CookieBehavior: none
EnableAcceptEncodingGzip: false
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none
Outputs:
URL:
Value: !Sub https://${Distribution.DomainName}
SourceBucketName:
Value: !Ref SourceBucketName
Export:
Name: ReactViteSampleSourceBucketName
AWSTemplateFormatVersion: '2010-09-09'
Resources:
CodeBuild:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: CODEPIPELINE
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-x86_64-standard:4.0
Type: LINUX_CONTAINER
Name: test-project-frontend-build
ServiceRole: !GetAtt CodeBuildRole.Arn
Source:
BuildSpec: buildspec.yml
Type: CODEPIPELINE
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: CodeBuildPolicy
PolicyDocument:
Statement:
- Effect: Allow
Resource: '*'
Action:
- 's3:*'
- 'logs:*'
Outputs:
CodeBuild:
Value: !Ref CodeBuild
Export:
Name: ReactViteSampleCodeBuild
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
CodePipeLineName:
Type: String
ArtifactBucketName:
Type: String
EventBridgeName:
Type: String
EventBridgeRoleName:
Type: String
EventBridgePolicyName:
Type: String
Resources:
CodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Ref CodePipeLineName
RoleArn: !GetAtt PipelineRole.Arn
ArtifactStore:
Type: S3
Location: !Ref ArtifactStoreBucket
Stages:
- Name: Source
Actions:
- ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: 1
Configuration:
RepositoryName: !ImportValue ReactViteSampleRepositoryName
BranchName: main
PollForSourceChanges: false
Name: Source
OutputArtifacts:
- Name: SourceArtifact
RunOrder: 1
- Name: Build
Actions:
- ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: 1
Configuration:
ProjectName: !ImportValue ReactViteSampleCodeBuild
InputArtifacts:
- Name: SourceArtifact
Name: Build
OutputArtifacts:
- Name: BuildArtifact
RunOrder: 1
- Name: Deploy
Actions:
- ActionTypeId:
Category: Deploy
Owner: AWS
Provider: S3
Version: 1
Configuration:
BucketName: !ImportValue ReactViteSampleSourceBucketName
Extract: true
Name: Deploy
InputArtifacts:
- Name: BuildArtifact
RunOrder: 1
RestartExecutionOnUpdate: false
ArtifactStoreBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Ref ArtifactBucketName
LifecycleConfiguration:
Rules:
- Id: clear-old-objects-rule
Status: Enabled
ExpirationInDays: 3
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
PipelineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: CodePipelinePolicy
PolicyDocument:
Statement:
- Effect: Allow
Resource: '*'
Action:
- 's3:*'
- 'codecommit:*'
- 'codebuild:*'
EventBridge:
Type: AWS::Events::Rule
Properties:
Description: for codepipeline
EventPattern:
source:
- aws.codecommit
detail-type:
- 'CodeCommit Repository State Change'
resources:
- !ImportValue ReactViteSampleRepositoryArn
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- main
Name: !Ref EventBridgeName
State: ENABLED
Targets:
- Arn: !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}
Id: CodePipeline
RoleArn: !GetAtt EventBridgeIAMRole.Arn
EventBridgeIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- !Ref EventBridgeIAMPolicy
RoleName: !Ref EventBridgeRoleName
EventBridgeIAMPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'codepipeline:StartPipelineExecution'
Resource:
- !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}
ManagedPolicyName: !Ref EventBridgePolicyName
建筑规范.
version: 0.2
phases:
install:
on-failure: ABORT
commands:
- if [ -e /tmp/node_modules.tar ]; then tar xf /tmp/node_modules.tar; fi
- npm install
pre_build:
on-failure: ABORT
commands:
- npm run test
build:
on-failure: ABORT
commands:
- npm run build
artifacts:
files:
- '**/*'
base-directory: dist
cache:
paths:
- /tmp/node_modules.tar
创建 GitHub 存储库的设置,用于镜像
请参考上述文章,并执行以下操作。
-
- SSHキーを作成
-
- CodeCommitの公開SSHキーとして登録
-
- GitHubリポジトリ作成
- 作成したリポジトリのActionsのSecretsを設定
创建用于GitHub仓库镜像的文件
使用CodePipeline时,与CodeCommit的协作更加方便,但不能进行公开。
为了能够公开存储库,我们将GitHub存储库镜像到CodeCommit。
通过这样做,CodePipeline可以与CodeCommit进行协作,从而使配置变得更加简便。
创建CodeCommit存储库
请移动到 cloudformation/shell目录,并执行以下命令。
由于容器中未安装 aws-cli,因此请在已安装 aws-cli 的主机终端上执行。
sh 01-repository.sh deploy
在上述命令中创建了CodeCommit存储库,因此请在CodeCommit页面上复制SSH URL。

文件夹的层级
プロジェクトルート
└─ .github
└─ workflows
└─ main.yml
我将创建下面的内容。请将复制的URL粘贴到target_repo_url中。
name: Mirroring
on: [ push, delete ]
jobs:
to_codecommit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url: <コピーしたCodeCommitリポジトリのSSHのURL>
ssh_private_key: ${{ secrets.CODECOMMIT_SSH_PRIVATE_KEY }}
ssh_username: ${{ secrets.CODECOMMIT_SSH_PRIVATE_KEY_ID }}
创建服务 fu wù)
按顺序执行除了cloudformation/shell目录下的仓库之外的shell脚本。这也不是在容器内执行,而是在安装了aws-cli的主机终端上执行。
sh 02-hosting.sh deploy
sh 03-codebuild.sh deploy
sh 04-codepipeline.sh deploy
部署
当你将代码提交到GitHub仓库,它会与CodeCommit进行协作,并触发CodePipeline开始自动部署。
git init
git commit -m "first commit"
git remote add <作成したgithubリポジトリのURL>
git push -u origin main
在部署后,您可以在CloudFront中选择相应的分发,将分发域名复制并粘贴到URL中,即可确认屏幕上的信息。
由于需要一些时间来反映,如果出现错误画面,请稍等一段时间后再确认一下。
最后
我认为使用Amplify可以很容易地进行构建,但由于不知道它在做什么,我感到有些害怕,所以我尝试进行了构建。
开发过程中,如果能够在开始时参考这篇文章并完成构建,那将是非常令人高兴的事情。
以下是GitHub存储库。
我认为最好是在适当的位置克隆并逐个复制所需的文件。
可以查看这篇文章。
ESLint是一个代码静态分析工具,用于在JavaScript代码中找到并修复问题。Prettier是一个代码格式化工具,可以自动调整代码的样式和排版。
-
- Setting up ESLint & Prettier in ViteJS
- [import/no-unresolved] when using with typescript “baseUrl” and “paths” option #1485
云形成
-
- CloudFormationの全てを味わいつくせ!「AWSの全てをコードで管理する方法〜その理想と現実〜」
-
- CloudFormation で OAI を使った CloudFront + S3 の静的コンテンツ配信インフラを作る
-
- CloudFormation】CloudFront の OAI を OAC に移行する
-
- AWS CodeBuildでnode_modulesをキャッシュしたらハマった
-
- CodePipelineをCloudFormationで作成してみた
- SPA(React)のビルドとデプロイを自動化するAWS CodePipeline用のCloudFormationテンプレート
测试
-
- Vitest公式
- Vitestを使ってUnit Testingにチャレンジ(Vue, React, Svelte)
GitHub仓库镜像化
- GitHubにプッシュすると、CodeCommitへ自動で同期させる方法