mirror of
https://github.com/morten-olsen/parcel.git
synced 2026-02-08 01:36:24 +01:00
init
This commit is contained in:
22
.github/workflows/publish.yml
vendored
Normal file
22
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Build and Deploy
|
||||
on: [push]
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@3.5.9
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages # The branch the action should deploy to.
|
||||
FOLDER: dist # The folder the action should deploy.
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
/dist
|
||||
/.cache
|
||||
19
babel.config.js
Normal file
19
babel.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const config = (api) => {
|
||||
api.cache(false);
|
||||
return {
|
||||
presets: [
|
||||
require.resolve('@babel/preset-env'),
|
||||
require.resolve('@babel/preset-react'),
|
||||
require.resolve('@babel/preset-typescript'),
|
||||
],
|
||||
plugins: [
|
||||
[require.resolve('babel-plugin-transform-inline-environment-variables'), {
|
||||
include: [
|
||||
'GITHUB_REPOSITORY',
|
||||
],
|
||||
}],
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
4091
docs/app.js
Normal file
4091
docs/app.js
Normal file
File diff suppressed because one or more lines are too long
9
docs/index.html
Normal file
9
docs/index.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Webpack App</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"></head>
|
||||
<body>
|
||||
<script src="app.js"></script></body>
|
||||
</html>
|
||||
41
package.json
Normal file
41
package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "dropbox",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "webpack"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.1",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@types/html-webpack-plugin": "^3.2.3",
|
||||
"@types/openpgp": "^4.4.12",
|
||||
"@types/react": "^16.9.46",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/styled-components": "^5.1.2",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-transform-inline-environment-variables": "^0.4.3",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "^3.9.7",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"nanoid": "^3.1.12",
|
||||
"openpgp": "^4.10.7",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-dropzone": "^11.0.3",
|
||||
"react-feather": "^2.0.8",
|
||||
"styled-components": "^5.1.1"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 1 Chrome versions"
|
||||
]
|
||||
}
|
||||
19
src/App.tsx
Normal file
19
src/App.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { GithubProvider } from './contexts/Github';
|
||||
import { EncryptionProvider } from './contexts/Encryption';
|
||||
import Encrypt from './screens/Encrypt';
|
||||
import theme from './theme';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<ThemeProvider theme={theme}>
|
||||
<GithubProvider username="morten-olsen">
|
||||
<div>Test</div>
|
||||
<EncryptionProvider>
|
||||
<Encrypt />
|
||||
</EncryptionProvider>
|
||||
</GithubProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
50
src/components/Add.tsx
Normal file
50
src/components/Add.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Theme } from '../theme';
|
||||
import AddText from './AddText';
|
||||
import AddFile from './AddFile';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
`;
|
||||
|
||||
const Top = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Button = styled.button<{
|
||||
active: boolean;
|
||||
theme: Theme;
|
||||
}>`
|
||||
background: ${({ active }) => active ? '#2c3e50' : 'transparent'};
|
||||
padding: ${({ theme }) => theme.margin.medium}px;
|
||||
border: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
`;
|
||||
|
||||
const Panel = styled.div<{
|
||||
theme: Theme;
|
||||
}>`
|
||||
background: #2c3e50;
|
||||
color: #fff;
|
||||
padding: ${({ theme }) => theme.margin.medium}px;
|
||||
`;
|
||||
|
||||
const Add: React.FC = () => {
|
||||
const [type, setType] = useState<'file' | 'text'>('text');
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Top>
|
||||
<Button active={type==='text'} onClick={() => setType('text')}>Text</Button>
|
||||
<Button active={type==='file'} onClick={() => setType('file')}>File</Button>
|
||||
</Top>
|
||||
<Panel>
|
||||
{type === 'file' && <AddFile />}
|
||||
{type === 'text' && <AddText />}
|
||||
</Panel>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Add;
|
||||
32
src/components/AddFile.tsx
Normal file
32
src/components/AddFile.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { useContext, useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import EncryptionContext from '../contexts/Encryption';
|
||||
import { Upload } from 'react-feather';
|
||||
|
||||
const DropWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 50px;
|
||||
`;
|
||||
|
||||
const AddFile: React.FC = () => {
|
||||
const { addFile } = useContext(EncryptionContext);
|
||||
const onDrop = useCallback(acceptedFiles => {
|
||||
acceptedFiles.forEach(addFile);
|
||||
}, [])
|
||||
const {getRootProps, getInputProps} = useDropzone({ onDrop });
|
||||
return (
|
||||
<div>
|
||||
<DropWrapper {...getRootProps()}>
|
||||
<input {...getInputProps()} />
|
||||
<Upload size={200} />
|
||||
<p>Drag 'n' drop some files here, or click to select files</p>
|
||||
</DropWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddFile;
|
||||
41
src/components/AddText.tsx
Normal file
41
src/components/AddText.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React, { useState, useCallback, useContext } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import EncryptionContext from '../contexts/Encryption';
|
||||
import { Theme } from '../theme';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const Text = styled.textarea<{
|
||||
theme: Theme;
|
||||
}>`
|
||||
border: none;
|
||||
height: 200px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
padding: ${({ theme }) => theme.margin.medium}px;
|
||||
font: inherit;
|
||||
`;
|
||||
|
||||
const AddText : React.FC = () => {
|
||||
const { addText } = useContext(EncryptionContext);
|
||||
const [text, setText] = useState('');
|
||||
|
||||
const add = useCallback(() => {
|
||||
addText(text);
|
||||
setText('');
|
||||
}, [text, addText]);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Text placeholder="Enter you message..." value={text} onChange={evt => setText(evt.target.value)} />
|
||||
<button onClick={add}>Save</button>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddText;
|
||||
|
||||
53
src/components/File.tsx
Normal file
53
src/components/File.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { File } from '../contexts/Encryption';
|
||||
import { CheckCircle, XCircle, Download, Trash, Loader } from 'react-feather';
|
||||
import Row, { Cell } from './Row';
|
||||
|
||||
interface Props {
|
||||
remove: () => void;
|
||||
file: File;
|
||||
}
|
||||
|
||||
const Button = styled.button`
|
||||
background: none;
|
||||
border: none;
|
||||
color: green;
|
||||
`;
|
||||
|
||||
const icons: {[name: string]: typeof CheckCircle} = {
|
||||
encrypting: Loader,
|
||||
failed: XCircle,
|
||||
encrypted: CheckCircle,
|
||||
};
|
||||
|
||||
const FileView: React.FC<Props> = ({
|
||||
file,
|
||||
remove,
|
||||
}) => {
|
||||
const Icon = icons[file.status];
|
||||
|
||||
return (
|
||||
<Row
|
||||
left={(
|
||||
<Cell><Icon /></Cell>
|
||||
)}
|
||||
title={file.name}
|
||||
body={`encrypted for ${file.reciever}`}
|
||||
right={!!file.link && (
|
||||
<>
|
||||
<Cell>
|
||||
<a target="_blank" href={file.link}>
|
||||
<Button><Download /></Button>
|
||||
</a>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Button onClick={remove}><Trash /></Button>
|
||||
</Cell>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileView;
|
||||
21
src/components/FileList.tsx
Normal file
21
src/components/FileList.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, { useContext } from 'react';
|
||||
import EncryptionContext from '../contexts/Encryption';
|
||||
import File from './File';
|
||||
|
||||
const Encrypt: React.FC = () => {
|
||||
const { files, deleteFile } = useContext(EncryptionContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{Object.entries(files).map(([id, file]) => (
|
||||
<File
|
||||
key={id}
|
||||
file={file}
|
||||
remove={() => deleteFile(id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Encrypt;
|
||||
58
src/components/Row.tsx
Normal file
58
src/components/Row.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Theme } from '../theme';
|
||||
|
||||
interface Props {
|
||||
left?: React.ReactNode;
|
||||
right?: React.ReactNode;
|
||||
title?: string;
|
||||
body?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Cell = styled.div<{ theme: Theme }>`
|
||||
padding: ${({ theme }) => theme.margin.medium / 2}px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Wrapper = styled(Cell)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: #dfe6e9;
|
||||
margin-top: ${({ theme }) => theme.margin.medium}px;
|
||||
`;
|
||||
|
||||
const Title = styled.h2`
|
||||
padding: 0px;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
`
|
||||
|
||||
const Main = styled(Cell)`
|
||||
flex: 1;
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
const Row: React.FC<Props> = ({
|
||||
left,
|
||||
right,
|
||||
title,
|
||||
body,
|
||||
children,
|
||||
}) => (
|
||||
<Wrapper style={{ display: 'flex' }}>
|
||||
{left}
|
||||
<Main>
|
||||
{title && <Title>{title}</Title>}
|
||||
{body}
|
||||
{children}
|
||||
</Main>
|
||||
{right}
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export {
|
||||
Cell,
|
||||
};
|
||||
|
||||
export default Row;
|
||||
15
src/config.ts
Normal file
15
src/config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
class Config {
|
||||
get repo() {
|
||||
if (!process.env.GITHUB_REPOSITORY) {
|
||||
return 'morten-olsen/foobar';
|
||||
}
|
||||
return process.env.GITHUB_REPOSITORY;
|
||||
}
|
||||
|
||||
get user() {
|
||||
const [user] = this.repo.split('/');
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Config();
|
||||
146
src/contexts/Encryption.tsx
Normal file
146
src/contexts/Encryption.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { useState, useCallback, useContext, createContext } from 'react';
|
||||
import * as openpgp from 'openpgp';
|
||||
import { nanoid } from 'nanoid';
|
||||
import GithubContext from './Github';
|
||||
|
||||
export interface FileType {
|
||||
name: string;
|
||||
reciever: string;
|
||||
status: 'encrypting' | 'failed' | 'encrypted';
|
||||
error?: any;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
interface EncryptionContextType {
|
||||
files: {[id: string]: FileType};
|
||||
addFile: (file: File) => Promise<void>;
|
||||
addText: (text: string) => Promise<void>;
|
||||
deleteFile: (id: string) => void;
|
||||
}
|
||||
|
||||
const EncryptionContext = createContext<EncryptionContextType>({
|
||||
files: {},
|
||||
addFile: async () => { throw new Error('Not using provider'); },
|
||||
addText: async () => { throw new Error('Not using provider'); },
|
||||
deleteFile: async () => { throw new Error('Not using provider'); },
|
||||
});
|
||||
|
||||
const encrypt = async (keys: string[], content: string) => {
|
||||
const armoredKeys = await Promise.all(keys.map(openpgp.key.readArmored));
|
||||
console.log(armoredKeys);
|
||||
const message = openpgp.message.fromText(content);
|
||||
const encrypted = await openpgp.encrypt({
|
||||
message,
|
||||
armor: true,
|
||||
publicKeys: armoredKeys.reduce((output, key: any) => [...output, ...key.keys], []),
|
||||
});
|
||||
const { data } = encrypted;
|
||||
const blob = new Blob([data], {
|
||||
type: 'text/text',
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
return url;
|
||||
};
|
||||
|
||||
const EncryptionProvider: React.FC = ({
|
||||
children,
|
||||
}) => {
|
||||
const { username, keys } = useContext(GithubContext);
|
||||
const [files, setFiles] = useState<EncryptionContextType['files']>({});
|
||||
|
||||
const deleteFile = useCallback((id: string) => {
|
||||
delete files[id];
|
||||
setFiles({
|
||||
...files,
|
||||
});
|
||||
}, [files]);
|
||||
|
||||
const add = (name: string = nanoid()) => {
|
||||
const file: FileType = {
|
||||
name: `${name}.asc`,
|
||||
reciever: username,
|
||||
status: 'encrypting',
|
||||
};
|
||||
setFiles(files => ({
|
||||
...files,
|
||||
[name]: file,
|
||||
}));
|
||||
|
||||
const setError = (err: any) => {
|
||||
console.error(err);
|
||||
setFiles(files => ({
|
||||
...files,
|
||||
[name]: {
|
||||
...files[name],
|
||||
status: 'failed',
|
||||
error: err,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const setContent = (text: string, keys: string[]) => {
|
||||
const run = async () => {
|
||||
try {
|
||||
const encrypted = await encrypt(keys, text);
|
||||
setFiles(files => ({
|
||||
...files,
|
||||
[name]: {
|
||||
...files[name],
|
||||
link: encrypted,
|
||||
status: 'encrypted'
|
||||
},
|
||||
}));
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
}
|
||||
};
|
||||
run();
|
||||
};
|
||||
|
||||
return {
|
||||
setContent,
|
||||
setError,
|
||||
};
|
||||
}
|
||||
|
||||
const addFile = useCallback(async (file: File) => {
|
||||
console.log('a', keys, file);
|
||||
if (!keys) return;
|
||||
const addedFile = add(file.name);
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onabort = addedFile.setError,
|
||||
reader.onerror = addedFile.setError,
|
||||
reader.onload = () => {
|
||||
console.log('foo', file);
|
||||
addedFile.setContent(reader.result as string, keys);
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}, [keys, username]);
|
||||
|
||||
const addText = useCallback(async (text: string) => {
|
||||
if (!keys) return;
|
||||
const file = add();
|
||||
file.setContent(text, keys);
|
||||
}, [keys, username]);
|
||||
|
||||
return (
|
||||
<EncryptionContext.Provider
|
||||
value={{
|
||||
files,
|
||||
addFile,
|
||||
addText,
|
||||
deleteFile,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</EncryptionContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
EncryptionProvider,
|
||||
};
|
||||
|
||||
export default EncryptionContext;
|
||||
|
||||
68
src/contexts/Github.tsx
Normal file
68
src/contexts/Github.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useState, useEffect, createContext } from 'react';
|
||||
|
||||
interface GithubContextType {
|
||||
username: string;
|
||||
user?: any;
|
||||
keys?: string[];
|
||||
error?: any;
|
||||
state: 'loading' | 'ready' | 'failed'
|
||||
}
|
||||
|
||||
interface Props {
|
||||
username: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const GithubContext = createContext<GithubContextType>({
|
||||
username: 'unknown',
|
||||
state: 'failed',
|
||||
});
|
||||
|
||||
const GithubProvider: React.FC<Props> = ({
|
||||
username,
|
||||
children,
|
||||
}) => {
|
||||
const [keys, setKeys] = useState<GithubContextType['keys'] | undefined>();
|
||||
const [state, setState] = useState<GithubContextType['state']>('loading');
|
||||
const [error, setError] = useState<GithubContextType['state'] | undefined>();
|
||||
const [user, setUser] = useState<GithubContextType['user'] | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const run = async () => {
|
||||
try {
|
||||
const keysRes = await fetch(`https://api.github.com/users/${username}/gpg_keys`);
|
||||
const userRes = await fetch(`https://api.github.com/users/${username}`);
|
||||
const keys = await keysRes.json();
|
||||
const user = await userRes.json();
|
||||
setState('ready');
|
||||
setKeys(keys.map((key: any) => key.raw_key));
|
||||
setUser(user);
|
||||
} catch (err) {
|
||||
setState('failed');
|
||||
setError(err);
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
}, [username]);
|
||||
|
||||
return (
|
||||
<GithubContext.Provider
|
||||
value={{
|
||||
username,
|
||||
user,
|
||||
keys,
|
||||
state,
|
||||
error,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</GithubContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
GithubProvider,
|
||||
};
|
||||
|
||||
export default GithubContext;
|
||||
12
src/index.html
Normal file
12
src/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
7
src/index.tsx
Normal file
7
src/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
const root = document.createElement('div');
|
||||
document.body.appendChild(root);
|
||||
render(<App />, root);
|
||||
17
src/screens/Encrypt.tsx
Normal file
17
src/screens/Encrypt.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React, { useContext } from 'react';
|
||||
import Add from '../components/Add';
|
||||
import FileList from '../components/FileList';
|
||||
import GithubContext from '../contexts/Github';
|
||||
|
||||
const Encrypt: React.FC = () => {
|
||||
const { username } = useContext(GithubContext);
|
||||
return (
|
||||
<div>
|
||||
<div>To: {username}</div>
|
||||
<Add />
|
||||
<FileList />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Encrypt;
|
||||
9
src/theme/index.tsx
Normal file
9
src/theme/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
const theme = {
|
||||
margin: {
|
||||
medium: 16,
|
||||
},
|
||||
};
|
||||
|
||||
export type Theme = typeof theme;
|
||||
|
||||
export default theme;
|
||||
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2018",
|
||||
"jsx": "react",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
29
webpack.config.ts
Normal file
29
webpack.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Configuration } from 'webpack';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import path from 'path';
|
||||
|
||||
const config: Configuration = {
|
||||
mode: 'development',
|
||||
entry: {
|
||||
app: [
|
||||
path.join(__dirname, 'src', 'index.tsx'),
|
||||
],
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, 'docs'),
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin(),
|
||||
],
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.tsx?/,
|
||||
use: ['babel-loader'],
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user