Skip to content

Commit fb328c8

Browse files
committed
first commit
1 parent 1888c88 commit fb328c8

18 files changed

+711
-0
lines changed

Diff for: .gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
.vscode
3+
4+
node_modules
5+
npm-debug.log
6+
yarn-debug.log
7+
yarn-error.log

Diff for: .npmignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
.vscode
3+
.git*
4+
5+
node_modules
6+
npm-debug.log
7+
yarn-debug.log
8+
yarn-error.log

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Change Log
2+
3+
1.0.0 (May 5, 2018)
4+
- Initial Public release

Diff for: README.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Create React App S3 Uploader
2+
---
3+
`create-react-app-s3-uploader`는 webpack 플러그인이며 [create-react-app](https://github.com/facebook/create-react-app) 환경에서 사용을 목적으로 제작하였습니다.
4+
production 환경에서 react를 빌드하였을 시, static 디렉토리의 소스들을 aws s3의 버킷에 업로드 해줍니다.
5+
6+
`create-react-app-s3-uploader` is webpack plugin. (for [create-react-app](https://github.com/facebook/create-react-app)).
7+
When react builded on production, this plugin upload static directory to aws s3 bucket
8+
9+
## install
10+
---
11+
```bash
12+
npm install --save create-react-app-s3-uploader
13+
```
14+
15+
## usage
16+
---
17+
가장 우선, `create-react-app` project에서 `npm eject`를 실행시켜줘야 합니다.
18+
first of all, you shoud run `npm eject` in your `create-react-app` project
19+
20+
**webpack.config.prod.js**
21+
```
22+
const CreateReactAppS3Uploader = require("create-react-app-s3-uploader");
23+
24+
module.exports = {
25+
plugins: [
26+
new CreateReactAppS3Uploader({
27+
accessKeyId: {AWS ACCESS KEY },
28+
secretAccessKey: { AWS SECRET KEY },
29+
buildPath: paths.appBuild,
30+
region: "ap-northeast-2",
31+
bucket: "create-react-app-s3-uploader",
32+
})
33+
]
34+
};
35+
```
36+
**CreateReactAppS3Uploader Parameters**
37+
|Name|Type|Required|Description|
38+
|:--:|:--:|:-----:|:----------|
39+
|**accessKeyId**|`{String}`|Required|AWS Access Key|
40+
|**secretAccessKey**|`{String}`|Required|AWS Secret Key|
41+
|**buildPath**|`{String}`|Required|`create-react-app` build directory path|
42+
|**region**|`{String}`|Required|AWS S3 Region|
43+
|**bucket**|`{String}`|Required|AWS S3 Bucket|
44+
|**key**|`{String}`|Optional|If you set this param, you can upload to specific directory in your s3 bucket|
45+
|**acl**|`{String}`|Optional|AWS S3 ACL, default `public-read`|
46+
|**cloudfront**|`{String}`|Optional|S3's CloudFront Domain.|
47+
|**replaceHtml**|`{boolean}`|Optional|default `false`. If you set to `true`, automatically change index.html's css & js's src to uploaded url|
48+
49+
50+
## Reference
51+
---
52+
- ACL : [AWS ACL LIST](https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/dev/acl-overview.html)
53+
- Content-Type : [HTTP MIME TYPE](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types)

Diff for: dist/FileUtil.js

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const path = require("path");
4+
exports.getContentType = ($filePath) => {
5+
switch (path.extname($filePath)) {
6+
case ".aac": return "audio/aac";
7+
case ".abw": return "application/x-abiword";
8+
case ".arc": return "application/octet-stream";
9+
case ".avi": return "video/x-msvideo";
10+
case ".azw": return "application/vnd.amazon.ebook";
11+
case ".bin": return "application/octet-stream";
12+
case ".bz": return "application/x-bzip";
13+
case ".bz2": return "application/x-bzip2";
14+
case ".csh": return "application/x-csh";
15+
case ".css": return "text/css";
16+
case ".csv": return "text/csv";
17+
case ".doc": return "application/msword";
18+
case ".docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
19+
case ".eot": return "application/vnd.ms-fontobject";
20+
case ".epub": return "application/epub+zip";
21+
case ".es": return "application/ecmascript";
22+
case ".gif": return "image/gif";
23+
case ".htm": return "text/html";
24+
case ".html": return "text/html";
25+
case ".ico": return "image/x-icon";
26+
case ".ics": return "text/calendar";
27+
case ".jar": return "application/java-archive";
28+
case ".jpeg": return "image/jpeg";
29+
case ".jpg": return "image/jpeg";
30+
case ".js": return "application/javascript";
31+
case ".json": return "application/json";
32+
case ".mid": return "audio/midi";
33+
case ".midi": return "audio/midi";
34+
case ".mpeg": return "video/mpeg";
35+
case ".mpkg": return "application/vnd.apple.installer+xml";
36+
case ".odp": return "application/vnd.oasis.opendocument.presentation";
37+
case ".ods": return "application/vnd.oasis.opendocument.spreadsheet";
38+
case ".odt": return "application/vnd.oasis.opendocument.text";
39+
case ".oga": return "audio/ogg";
40+
case ".ogv": return "video/ogg";
41+
case ".ogx": return "application/ogg";
42+
case ".otf": return "font/otf";
43+
case ".png": return "image/png";
44+
case ".pdf": return "application/pdf";
45+
case ".ppt": return "application/vnd.ms-powerpoint";
46+
case ".pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
47+
case ".rar": return "application/x-rar-compressed";
48+
case ".rtf": return "application/rtf";
49+
case ".sh": return "application/x-sh";
50+
case ".svg": return "image/svg+xml";
51+
case ".swf": return "application/x-shockwave-flash";
52+
case ".tar": return "application/x-tar";
53+
case ".tif": return "image/tiff";
54+
case ".tiff": return "image/tiff";
55+
case ".ts": return "application/typescript";
56+
case ".ttf": return "font/ttf";
57+
case ".vsd": return "application/vnd.visio";
58+
case ".wav": return "audio/wav";
59+
case ".weba": return "audio/webm";
60+
case ".webm": return "video/webm";
61+
case ".webp": return "image/webp";
62+
case ".woff": return "font/woff";
63+
case ".woff2": return "font/woff2";
64+
case ".xhtml": return "application/xhtml+xml";
65+
case ".xls": return "application/vnd.ms-excel";
66+
case ".xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
67+
case ".xml": return "application/xml";
68+
case ".xul": return "application/vnd.mozilla.xul+xml";
69+
case ".zip": return "application/zip";
70+
case ".3gp": return "video/3gpp";
71+
case ".3g2": return "video/3gpp2";
72+
case ".7z": return "application/x-7z-compressed";
73+
default: return "text/plain";
74+
}
75+
};

Diff for: dist/S3Uploader.js

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"use strict";
2+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3+
return new (P || (P = Promise))(function (resolve, reject) {
4+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6+
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
7+
step((generator = generator.apply(thisArg, _arguments || [])).next());
8+
});
9+
};
10+
Object.defineProperty(exports, "__esModule", { value: true });
11+
const path = require("path");
12+
const aws = require("aws-sdk");
13+
const fs = require("fs");
14+
const S3UploaderUtil_1 = require("./S3UploaderUtil");
15+
const FileUtil_1 = require("./FileUtil");
16+
class S3Uploader {
17+
constructor($option) {
18+
this.configS3 = () => {
19+
aws.config.update({
20+
accessKeyId: this.options.accessKeyId,
21+
secretAccessKey: this.options.secretAccessKey,
22+
region: this.options.region,
23+
signatureVersion: "v4",
24+
});
25+
this.s3 = new aws.S3({ apiVersion: "2006-03-01" });
26+
};
27+
this.apply = (compiler) => {
28+
compiler.plugin("done", compilation => {
29+
console.log("[S3Uploader apply] compile finished");
30+
fs.readdir(this.staticPath, ($err, $subDir) => {
31+
if ($err) {
32+
console.log(`\x1b[31m[S3Uploader apply] static directory read error : ${$err.message} \x1b[0m`);
33+
}
34+
else {
35+
this.uploadList = $subDir.reduce(($result, $dir) => {
36+
const subDirPath = path.join(this.staticPath, $dir);
37+
return $result.concat(fs.readdirSync(subDirPath).map($file => path.join(subDirPath, $file)));
38+
}, []);
39+
this.upload().then($res => {
40+
console.log("\x1b[33m%s\x1b[0m", `\n-- UPLOAD COMPLETE --\n`);
41+
console.log("\x1b[33m%s\x1b[0m", ` JS : ${this.uploadResult.js}`);
42+
console.log("\x1b[33m%s\x1b[0m", ` CSS : ${this.uploadResult.css}\n`);
43+
if (this.options.replaceHtml) {
44+
this.replaceIndexHtml();
45+
}
46+
});
47+
}
48+
});
49+
});
50+
};
51+
this.upload = () => __awaiter(this, void 0, void 0, function* () {
52+
console.log(`[S3Uploader upload] list count ${this.uploadList.length}`);
53+
for (const file of this.uploadList) {
54+
if (path.extname(file) === ".js") {
55+
this.updateUrlPublicPath(file, false);
56+
}
57+
else if (path.extname(file) === ".css") {
58+
this.updateUrlPublicPath(file, true);
59+
}
60+
yield new Promise(resolve => {
61+
const key = file.replace(this.staticPath, `${(!this.options.key) ? "" : this.options.key + "/"}static`);
62+
this.s3.putObject({
63+
ACL: this.options.acl || "public-read",
64+
Bucket: this.options.bucket,
65+
Key: key,
66+
ContentType: FileUtil_1.getContentType(file),
67+
Body: fs.createReadStream(file),
68+
}, ($err, $res) => {
69+
if ($err) {
70+
console.log(`\x1b[31m[S3Uploader upload] s3 putObject error : ${$err.message} \x1b[0m`);
71+
}
72+
else {
73+
const url = `https://${this.uploadDomain}/${key}`;
74+
console.log("\x1b[33m%s\x1b[0m", url);
75+
if (path.extname(key) === ".js") {
76+
this.uploadResult.js = url;
77+
}
78+
else if (path.extname(key) === ".css") {
79+
this.uploadResult.css = url;
80+
}
81+
resolve();
82+
}
83+
});
84+
});
85+
}
86+
return Promise.resolve();
87+
});
88+
this.updateUrlPublicPath = ($filePath, $isCSS) => {
89+
const file = fs.readFileSync($filePath, "utf8");
90+
const reg = $isCSS ? /\/static\/media\//g : /n.p\+\"static\/media\//g;
91+
const https = $isCSS ? `https://` : `"https://`;
92+
fs.writeFileSync($filePath, file.replace(reg, `${https}${this.uploadDomain}/static/media/`), "utf8");
93+
};
94+
this.replaceIndexHtml = () => {
95+
fs.readFile(this.htmlPath, "utf8", ($err, $html) => {
96+
if ($err) {
97+
console.log(`\x1b[31m[S3Uploader replaceIndexHtml] read index.html error : ${$err.message} \x1b[0m`);
98+
}
99+
else {
100+
const cssTag = `<link href="`;
101+
const jsTag = `<script type="text/javascript" src="`;
102+
const cssSrc = this.uploadResult.css.substring(0, this.uploadResult.css.indexOf("static/css") + "static/css".length);
103+
const jsSrc = this.uploadResult.js.substring(0, this.uploadResult.js.indexOf("static/js") + "static/js".length);
104+
const updateHtml = $html.replace(`${cssTag}/static/css`, `${cssTag}${cssSrc}`).replace(`${jsTag}/static/js`, `${jsTag}${jsSrc}`);
105+
fs.writeFile(this.htmlPath, updateHtml, "utf8", ($error) => {
106+
if ($error) {
107+
console.log(`\x1b[31m[S3Uploader replaceIndexHtml] replace index.html's css & js error : ${$error.message} \x1b[0m`);
108+
}
109+
else {
110+
console.log("\x1b[33m%s\x1b[0m", "[S3Uploader replaceIndexHtml] replace index.html success");
111+
}
112+
});
113+
}
114+
});
115+
};
116+
S3UploaderUtil_1.validateParam($option);
117+
this.options = $option;
118+
this.staticPath = path.join(this.options.buildPath, "static");
119+
this.htmlPath = path.join(this.options.buildPath, "index.html");
120+
this.uploadDomain = this.options.cloudfront || `s3.${this.options.region}.amazonaws.com/${this.options.bucket}`;
121+
this.uploadResult = { css: "", js: "" };
122+
this.configS3();
123+
}
124+
}
125+
exports.default = S3Uploader;

Diff for: dist/S3UploaderOptions.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });

Diff for: dist/S3UploaderUtil.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const ACL = /^(private|public-read|public-read-write|aws-exec-read|authenticated-read|bucket-owner-read|bucket-owner-full-control)$/;
4+
const validateACL = ($acl) => $acl && $acl.match(ACL) ? $acl : "public-read";
5+
exports.validateParam = ($option) => {
6+
if (!$option) {
7+
throw new Error("[S3Uploader] You must pass parameters");
8+
}
9+
else if (!$option.accessKeyId) {
10+
throw new Error("[S3Uploader] parameter accessKeyId is required");
11+
}
12+
else if (!$option.secretAccessKey) {
13+
throw new Error("[S3Uploader] parameter secretAccessKey is required");
14+
}
15+
else if (!$option.buildPath) {
16+
throw new Error("[S3Uploader] parameter buildPath is required");
17+
}
18+
else if (!$option.region) {
19+
throw new Error("[S3Uploader] parameter region is must required");
20+
}
21+
else if (!$option.bucket) {
22+
throw new Error("[S3Uploader] parameter bucket is required");
23+
}
24+
$option.acl = validateACL($option.acl);
25+
$option.replaceHtml = $option.replaceHtml || false;
26+
};

Diff for: example_cloudfront.gif

1.81 MB
Loading

Diff for: example_s3.gif

1.89 MB
Loading

Diff for: index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require("./dist/S3Uploader.js").default;

Diff for: package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "create-react-app-s3-uploader",
3+
"version": "1.0.0",
4+
"description": "webpack plugin ( for create-react-app ), when source build on production, this plugin upload static directory to aws s3 bucket",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"engines": {
10+
"node": ">= 6.10.0",
11+
"npm": ">= 3.10.10"
12+
},
13+
"keywords": [
14+
"reactjs",
15+
"create-react-app",
16+
"aws",
17+
"s3",
18+
"webpack"
19+
],
20+
"author": {
21+
"name": "kimcoder",
22+
"email": "[email protected]"
23+
},
24+
"license": "MIT",
25+
"dependencies": {
26+
"aws-sdk": "^2.229.1",
27+
"path": "^0.12.7",
28+
"tslint": "^5.9.1",
29+
"tslint-config-airbnb": "^5.8.0",
30+
"typescript": "^2.6.2"
31+
},
32+
"devDependencies": {
33+
"@types/node": "^10.0.0"
34+
}
35+
}

0 commit comments

Comments
 (0)