├── .eslintignore
├── .eslintrc
├── .github
├── FUNDING.yml
└── dependabot.yml
├── .gitignore
├── .nvmrc
├── .prettierrc
├── LICENSE
├── README.md
├── babel.config.js
├── jsconfig.json
├── package-lock.json
├── package.json
├── src
├── components
│ ├── App
│ │ └── index.js
│ └── Spinner
│ │ └── index.js
├── config.js
├── hooks
│ ├── useCsvData.js
│ ├── useJsonData.js
│ └── useTopoData.js
├── index.html
├── index.js
├── store
│ └── index.js
├── style
│ ├── global.js
│ ├── normalize.js
│ └── theme.js
└── utils
│ ├── browser-utils.js
│ ├── css-utils.js
│ └── data-utils.js
├── static
└── data
│ └── data.json
└── webpack
├── webpack.common.js
├── webpack.config.dev.js
└── webpack.config.prod.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | webpack/
3 | build/
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@babel/eslint-parser",
3 | "plugins": ["prettier"],
4 | "extends": ["airbnb", "prettier"],
5 | "rules": {
6 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
7 | "no-param-reassign": 0,
8 | "react/prop-types": 0,
9 | "prettier/prettier": "error"
10 | },
11 | "env": {
12 | "browser": true
13 | },
14 | "settings": {
15 | "import/resolver": {
16 | "webpack": {
17 | "config": "./webpack/webpack.config.dev.js"
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [wbkd]
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: weekly
7 | time: "04:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: webpack
11 | versions:
12 | - 5.17.0
13 | - 5.19.0
14 | - 5.21.2
15 | - 5.23.0
16 | - 5.24.2
17 | - 5.24.3
18 | - 5.25.1
19 | - 5.27.1
20 | - 5.28.0
21 | - 5.30.0
22 | - 5.31.2
23 | - dependency-name: mini-css-extract-plugin
24 | versions:
25 | - 1.3.4
26 | - 1.3.5
27 | - 1.3.6
28 | - 1.3.8
29 | - 1.3.9
30 | - 1.4.0
31 | - 1.4.1
32 | - dependency-name: eslint
33 | versions:
34 | - 7.18.0
35 | - 7.19.0
36 | - 7.20.0
37 | - 7.21.0
38 | - 7.22.0
39 | - 7.23.0
40 | - 7.24.0
41 | - dependency-name: core-js
42 | versions:
43 | - 3.10.0
44 | - 3.10.1
45 | - 3.8.3
46 | - 3.9.0
47 | - 3.9.1
48 | - dependency-name: postcss-loader
49 | versions:
50 | - 4.2.0
51 | - 5.0.0
52 | - 5.1.0
53 | - dependency-name: eslint-config-prettier
54 | versions:
55 | - 7.2.0
56 | - 8.0.0
57 | - 8.1.0
58 | - dependency-name: webpack-cli
59 | versions:
60 | - 4.4.0
61 | - 4.5.0
62 | - dependency-name: autoprefixer
63 | versions:
64 | - 10.2.3
65 | - 10.2.4
66 | - dependency-name: webpack-bundle-analyzer
67 | versions:
68 | - 4.4.0
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # production
5 | build
6 | bundle.zip
7 |
8 | # misc
9 | .DS_Store
10 |
11 | npm-debug.log
12 | yarn-error.log
13 | yarn.lock
14 | .yarnclean
15 |
16 | .vscode
17 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v12.13.1
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "es5",
4 | "singleQuote": true,
5 | "printWidth": 100
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2020 webkid GmbH
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Starter
2 |
3 | [](https://dependabot.com/)
4 |
5 | A minimal starting point for interactive applications that we build at [webkid](https://webkid.io). The boilerplate already includes some basic components as well as hooks and utils that we are often using in our projects.
6 |
7 | The starterkit is based on these libraries:
8 |
9 | - [react](https://facebook.github.io/react/)
10 | - [easy-peasy](https://easy-peasy.now.sh/)
11 | - [emotion](https://emotion.sh/docs/styled)
12 |
13 | ## Getting started
14 |
15 | **[Create a new Github repository with the template](https://github.com/wbkd/react-starter/generate)** or clone the repo:
16 |
17 | ```sh
18 | git clone git@github.com:wbkd/react-starter.git && cd react-starter
19 | ```
20 |
21 | then install the dependencies via [npm](https://npmjs.org):
22 |
23 | ```sh
24 | npm install
25 | ```
26 |
27 | you are now ready to develop your app.
28 |
29 | ## Development
30 |
31 | Builds the application and starts a webserver with hot loading.
32 | Runs on [localhost:8080](http://localhost:8080/)
33 |
34 | ```sh
35 | npm run start
36 | ```
37 |
38 | ## Build
39 |
40 | Builds a minified version of the application in the build folder.
41 |
42 | ```sh
43 | npm run build
44 | ```
45 |
46 | Additionally, a zipped version of the bundle is added as `build.zip`. We often use this for our clients to upload the application to their own servers.
47 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (api) => {
2 | api.cache(true);
3 |
4 | const presets = [
5 | '@babel/preset-react',
6 | [
7 | '@babel/preset-env',
8 | {
9 | useBuiltIns: 'usage',
10 | corejs: 3,
11 | },
12 | ],
13 | ];
14 |
15 | const plugins = [];
16 |
17 | return {
18 | presets,
19 | plugins,
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "*": ["src/*"]
6 | }
7 | },
8 | "exclude": ["node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-starter",
3 | "config": {
4 | "appname": "my-app-name"
5 | },
6 | "version": "7.0.0",
7 | "description": "lightweight react starterkit including webpack, unistore, styled components",
8 | "scripts": {
9 | "start": "webpack serve --config webpack/webpack.config.dev.js",
10 | "build": "cross-env BABEL_ENV=production webpack --config webpack/webpack.config.prod.js && npm run zip",
11 | "zip": "zip -r build/build.zip build"
12 | },
13 | "repository": {
14 | "url": "git@github.com:wbkd/react-starter.git",
15 | "type": "git"
16 | },
17 | "browserslist": "> 0.25%, ie 11",
18 | "author": "webkid.io",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "@babel/cli": "^7.16.8",
22 | "@babel/core": "^7.16.7",
23 | "@babel/eslint-parser": "^7.16.5",
24 | "@babel/preset-env": "^7.16.8",
25 | "@babel/preset-react": "^7.16.7",
26 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
27 | "autoprefixer": "^10.4.2",
28 | "babel-loader": "^8.2.3",
29 | "clean-webpack-plugin": "^4.0.0",
30 | "copy-webpack-plugin": "^10.2.0",
31 | "cross-env": "^7.0.3",
32 | "css-loader": "^6.5.1",
33 | "eslint": "^8.7.0",
34 | "eslint-config-airbnb": "^19.0.4",
35 | "eslint-config-prettier": "^8.3.0",
36 | "eslint-import-resolver-webpack": "^0.13.2",
37 | "eslint-plugin-import": "^2.25.4",
38 | "eslint-plugin-jsx-a11y": "^6.5.1",
39 | "eslint-plugin-prettier": "^4.0.0",
40 | "eslint-plugin-react": "^7.28.0",
41 | "eslint-plugin-react-hooks": "^4.3.0",
42 | "eslint-webpack-plugin": "^3.1.1",
43 | "file-loader": "^6.2.0",
44 | "html-webpack-plugin": "^5.5.0",
45 | "mini-css-extract-plugin": "^2.5.1",
46 | "postcss-loader": "^6.2.1",
47 | "postcss-preset-env": "^7.2.3",
48 | "prettier": "^2.5.1",
49 | "react-refresh": "^0.11.0",
50 | "react-svg-loader": "^3.0.3",
51 | "style-loader": "^3.3.1",
52 | "webpack": "^5.66.0",
53 | "webpack-bundle-analyzer": "^4.5.0",
54 | "webpack-cli": "^4.9.1",
55 | "webpack-dev-server": "^4.7.3",
56 | "webpack-merge": "^5.8.0"
57 | },
58 | "dependencies": {
59 | "@emotion/react": "^11.7.1",
60 | "@emotion/styled": "^11.6.0",
61 | "core-js": "^3.20.3",
62 | "d3-dsv": "^3.0.1",
63 | "easy-peasy": "^5.0.4",
64 | "emotion-normalize": "^11.0.1",
65 | "react": "^17.0.2",
66 | "react-dom": "^17.0.2",
67 | "topojson-client": "^3.1.0",
68 | "unfetch": "^4.2.0"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import { useStoreActions, useStoreState } from 'easy-peasy';
4 |
5 | import Spinner from 'components/Spinner';
6 |
7 | const Wrapper = styled.div`
8 | padding: 20px;
9 | `;
10 |
11 | function App() {
12 | const data = useStoreState((state) => state.app.data);
13 | const dataLoading = useStoreState((state) => state.app.dataLoading);
14 | const loadData = useStoreActions((actions) => actions.app.loadData);
15 |
16 | return (
17 |
18 | React Starterkit
19 | {dataLoading && }
20 | {data ? (
21 | data.length
22 | ) : (
23 |
26 | )}
27 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/src/components/Spinner/index.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { keyframes } from '@emotion/react';
3 |
4 | const Bounce = keyframes`
5 | 0%, 100% { -webkit-transform: scale(0.0) }
6 | 50% { -webkit-transform: scale(1.0) }
7 | `;
8 |
9 | const Loader = styled.div`
10 | width: ${(p) => p.size || 50}px;
11 | height: ${(p) => p.size || 50}px;
12 | position: relative;
13 |
14 | &:before,
15 | &:after {
16 | content: '';
17 | width: 100%;
18 | height: 100%;
19 | border-radius: 50%;
20 | opacity: 0.6;
21 | position: absolute;
22 | top: 0;
23 | left: 0;
24 | animation: ${Bounce} ${(p) => p.duration || 2000}ms infinite ease-in-out;
25 | background: ${(p) => p.theme.colors.primary};
26 | }
27 |
28 | &:after {
29 | animation-delay: -${(p) => (p.duration ? p.duration / 2 : 1000)}ms;
30 | }
31 | `;
32 |
33 | export default Loader;
34 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | const config = {};
2 |
3 | export default config;
4 |
--------------------------------------------------------------------------------
/src/hooks/useCsvData.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { dsvFormat } from 'd3-dsv';
3 | import { fetchCsv } from 'utils/data-utils';
4 |
5 | const defaultOptions = { delimiter: ',', onEachRow: (d) => d };
6 |
7 | export default function useCsvData(url, options = defaultOptions) {
8 | const [data, setData] = useState(null);
9 | const mergedOptions = { ...defaultOptions, ...options };
10 | const { delimiter, onEachRow } = mergedOptions;
11 | const csvParser = dsvFormat(delimiter);
12 |
13 | useEffect(() => {
14 | const loadData = async () => {
15 | const csv = await fetchCsv(url);
16 | const result = csvParser.parse(csv, onEachRow);
17 | setData(result);
18 | };
19 |
20 | loadData();
21 | }, [url]);
22 |
23 | return {
24 | data,
25 | isLoading: !data,
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/src/hooks/useJsonData.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { fetchJson } from 'utils/data-utils';
3 |
4 | export default function useJsonData(url) {
5 | const [data, setData] = useState(null);
6 |
7 | useEffect(() => {
8 | const loadData = async () => {
9 | const result = await fetchJson(url);
10 | setData(result);
11 | };
12 |
13 | loadData();
14 | }, [url]);
15 |
16 | return {
17 | data,
18 | isLoading: !data,
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/hooks/useTopoData.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { feature } from 'topojson-client';
3 | import { fetchJson } from 'utils/data-utils';
4 |
5 | export default function useTopoData(url) {
6 | const [data, setData] = useState(null);
7 |
8 | useEffect(() => {
9 | const loadData = async () => {
10 | const topo = await fetchJson(url);
11 | const topoKey = Object.keys(topo.objects)[0];
12 | const topoData = feature(topo, topo.objects[topoKey]);
13 | setData(topoData);
14 | };
15 |
16 | loadData();
17 | }, [url]);
18 |
19 | return {
20 | data,
21 | isLoading: !data,
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | react starter
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { StoreProvider } from 'easy-peasy';
4 | import { ThemeProvider } from '@emotion/react';
5 |
6 | import store from 'store';
7 | import theme from 'style/theme';
8 |
9 | import NormalizeStyle from 'style/normalize';
10 | import GlobalStyle from 'style/global';
11 | import App from 'components/App';
12 |
13 | function AppWrapper() {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | ReactDOM.render(, document.getElementById('root'));
26 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, thunk, action } from 'easy-peasy';
2 | import { fetchJson } from 'utils/data-utils';
3 |
4 | const model = {
5 | app: {
6 | data: null,
7 | dataLoading: false,
8 | loadData: thunk(async (actions) => {
9 | actions.loadDataStart();
10 | const data = await fetchJson('static/data/data.json');
11 | if (data) {
12 | actions.loadDataSuccess(data);
13 | } else {
14 | actions.loadDataFail();
15 | }
16 | }),
17 | loadDataStart: action((state) => {
18 | state.dataLoading = true;
19 | }),
20 | loadDataSuccess: action((state, payload) => {
21 | state.data = payload;
22 | state.dataLoading = false;
23 | }),
24 | loadDataFail: action((state) => {
25 | state.data = null;
26 | state.dataLoading = false;
27 | }),
28 | },
29 | };
30 |
31 | export default createStore(model);
32 |
--------------------------------------------------------------------------------
/src/style/global.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useTheme, Global, css } from '@emotion/react';
3 |
4 | function GlobalStyle() {
5 | const theme = useTheme();
6 |
7 | const globalStyles = css`
8 | html,
9 | body {
10 | font-family: ${theme.fonts.sans};
11 | font-weight: 400;
12 | letter-spacing: 0.5px;
13 | line-height: 1.5;
14 | font-size: 16px;
15 | padding: 0;
16 | margin: 0;
17 | color: ${theme.colors.text};
18 | }
19 |
20 | a {
21 | color: ${theme.colors.text};
22 | text-decoration: none;
23 | }
24 | a:visited,
25 | a:focus,
26 | a:active {
27 | color: ${theme.colors.text};
28 | text-decoration: none;
29 | }
30 | a:hover {
31 | color: ${theme.colors.text};
32 | text-decoration: none;
33 | }
34 | code,
35 | pre {
36 | font-family: ${theme.fonts.mono};
37 | }
38 | `;
39 |
40 | return ;
41 | }
42 |
43 | export default GlobalStyle;
44 |
--------------------------------------------------------------------------------
/src/style/normalize.js:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Global, css } from '@emotion/react';
3 | import emotionNormalize from 'emotion-normalize';
4 |
5 | const NormalizeStyle = memo(() => {
6 | const globalStyles = css`
7 | ${emotionNormalize}
8 | `;
9 |
10 | return ;
11 | });
12 |
13 | export default NormalizeStyle;
14 |
--------------------------------------------------------------------------------
/src/style/theme.js:
--------------------------------------------------------------------------------
1 | import { px, breakpoints } from 'utils/css-utils';
2 |
3 | const space = [0, 4, 8, 16, 32, 48, 64, 96, 128];
4 | const spacePx = space.map(px);
5 |
6 | const fontSizes = [12, 16, 20, 24, 36, 48, 54];
7 | const fontSizesPx = fontSizes.map(px);
8 | const breakpointsPx = Object.values(breakpoints).map(px);
9 |
10 | const theme = {
11 | fonts: {
12 | sans: "-apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, Arial, sans-serif",
13 | mono: 'monospace',
14 | },
15 | space,
16 | spacePx,
17 | fontSizes,
18 | fontSizesPx,
19 | breakpoints: breakpointsPx,
20 | boxShadow: '0px 16px 64px rgba(26, 25, 43, 0.32);',
21 | colors: {
22 | primary: '#0041D0',
23 | orange: '#FF6700',
24 | blue: '#0041D0',
25 | red: '#FF0072',
26 | purple: '#784BE8',
27 | green: '#00D7CA',
28 | text: '#1A192B',
29 | textLight: '#808080',
30 | lightGrey: '#D9D9D9',
31 | },
32 | };
33 |
34 | export default theme;
35 |
--------------------------------------------------------------------------------
/src/utils/browser-utils.js:
--------------------------------------------------------------------------------
1 | import { device } from 'utils/css-utils';
2 |
3 | // detect all IE <= 11
4 | export function isOldIE() {
5 | if (typeof navigator === 'undefined') {
6 | return false;
7 | }
8 |
9 | // https://stackoverflow.com/a/22242528
10 | return (
11 | navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > -1
12 | );
13 | }
14 |
15 | export function isMobile() {
16 | return !window.matchMedia(device.tablet);
17 | }
18 |
19 | export default {
20 | isOldIE,
21 | isMobile,
22 | };
23 |
--------------------------------------------------------------------------------
/src/utils/css-utils.js:
--------------------------------------------------------------------------------
1 | export const breakpoints = {
2 | s: 460,
3 | m: 768,
4 | l: 1024,
5 | };
6 |
7 | export const device = {
8 | phone: `(min-width: ${breakpoints.s}px)`,
9 | tablet: `(min-width: ${breakpoints.m}px)`,
10 | desktop: `(min-width: ${breakpoints.l}px)`,
11 | };
12 |
13 | export const rgba = (hex, alpha) => {
14 | const [r, g, b] = hex.match(/\w\w/g).map((x) => parseInt(x, 16));
15 | return `rgba(${r},${g},${b},${alpha})`;
16 | };
17 |
18 | export const px = (val) => `${val}px`;
19 |
20 | export default {
21 | breakpoints,
22 | device,
23 | rgba,
24 | px,
25 | };
26 |
--------------------------------------------------------------------------------
/src/utils/data-utils.js:
--------------------------------------------------------------------------------
1 | import fetch from 'unfetch';
2 |
3 | export async function fetchJson(url) {
4 | try {
5 | const response = await fetch(url);
6 | const json = await response.json();
7 | return json;
8 | } catch (err) {
9 | console.log('Error loading data', err);
10 | return null;
11 | }
12 | }
13 |
14 | export async function fetchCsv(url) {
15 | try {
16 | const response = await fetch(url);
17 | const csv = await response.text();
18 | return csv;
19 | } catch (err) {
20 | console.log('Error loading data', err);
21 | return null;
22 | }
23 | }
24 |
25 | export default {
26 | fetchJson,
27 | fetchCsv,
28 | };
29 |
--------------------------------------------------------------------------------
/static/data/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "color": "red",
4 | "value": "#f00"
5 | },
6 | {
7 | "color": "green",
8 | "value": "#0f0"
9 | },
10 | {
11 | "color": "blue",
12 | "value": "#00f"
13 | },
14 | {
15 | "color": "cyan",
16 | "value": "#0ff"
17 | },
18 | {
19 | "color": "magenta",
20 | "value": "#f0f"
21 | },
22 | {
23 | "color": "yellow",
24 | "value": "#ff0"
25 | },
26 | {
27 | "color": "black",
28 | "value": "#000"
29 | }
30 | ]
31 |
--------------------------------------------------------------------------------
/webpack/webpack.common.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const CopyWebpackPlugin = require('copy-webpack-plugin');
5 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
6 |
7 | module.exports = {
8 | entry: {
9 | app: Path.resolve(__dirname, '../src/index.js'),
10 | },
11 | output: {
12 | path: Path.join(__dirname, '../build'),
13 | filename: 'js/[name].js',
14 | },
15 | optimization: {
16 | splitChunks: {
17 | chunks: 'all',
18 | name: false,
19 | },
20 | },
21 | plugins: [
22 | new CleanWebpackPlugin(),
23 | new CopyWebpackPlugin({
24 | patterns: [{ from: Path.resolve(__dirname, '../static'), to: 'static' }],
25 | }),
26 | new HtmlWebpackPlugin({
27 | inject: true,
28 | template: Path.resolve(__dirname, '../src/index.html'),
29 | }),
30 | // new BundleAnalyzerPlugin()
31 | ],
32 | resolve: {
33 | modules: [Path.resolve(__dirname, '../src'), Path.resolve(__dirname, '../node_modules')],
34 | },
35 | module: {
36 | rules: [
37 | {
38 | test: /\.mjs$/,
39 | include: /node_modules/,
40 | type: 'javascript/auto',
41 | },
42 | {
43 | test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/,
44 | use: {
45 | loader: 'file-loader',
46 | options: {
47 | name: '[path][name].[ext]',
48 | },
49 | },
50 | },
51 | {
52 | test: /\.svg$/,
53 | use: [
54 | 'babel-loader',
55 | {
56 | loader: 'react-svg-loader',
57 | options: {
58 | jsx: true,
59 | },
60 | },
61 | ],
62 | },
63 | ],
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/webpack/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Webpack = require('webpack');
3 | const { merge } = require('webpack-merge');
4 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
5 | const ESLintPlugin = require('eslint-webpack-plugin');
6 |
7 | const common = require('./webpack.common.js');
8 |
9 | module.exports = merge(common, {
10 | mode: 'development',
11 | devtool: 'eval-cheap-source-map',
12 | output: {
13 | chunkFilename: 'js/[name].chunk.js',
14 | },
15 | devServer: {
16 | historyApiFallback: true,
17 | hot: true,
18 | host: '0.0.0.0',
19 | },
20 | plugins: [
21 | new ESLintPlugin({ quiet: true }),
22 | new Webpack.DefinePlugin({
23 | 'process.env.NODE_ENV': JSON.stringify('development'),
24 | }),
25 | new ReactRefreshWebpackPlugin(),
26 | ],
27 | module: {
28 | rules: [
29 | {
30 | test: /\.(js|jsx)$/,
31 | include: Path.resolve(__dirname, '../src'),
32 | use: [
33 | {
34 | loader: 'babel-loader',
35 | options: {
36 | plugins: ['react-refresh/babel'],
37 | },
38 | },
39 | ],
40 | },
41 | {
42 | test: /\.css$/,
43 | use: ['style-loader', 'css-loader'],
44 | },
45 | ],
46 | },
47 | });
48 |
--------------------------------------------------------------------------------
/webpack/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const Webpack = require('webpack');
2 | const Path = require('path');
3 | const { merge } = require('webpack-merge');
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
5 | const postcssPresetEnv = require('postcss-preset-env');
6 |
7 | const common = require('./webpack.common.js');
8 |
9 | module.exports = merge(common, {
10 | mode: 'production',
11 | stats: 'errors-only',
12 | bail: true,
13 | output: {
14 | filename: 'js/[name].[chunkhash:8].js',
15 | chunkFilename: 'js/[name].[chunkhash:8].chunk.js',
16 | },
17 | plugins: [
18 | new Webpack.DefinePlugin({
19 | 'process.env.NODE_ENV': JSON.stringify('production'),
20 | }),
21 | new MiniCssExtractPlugin({ filename: 'bundle.css' }),
22 | ],
23 | module: {
24 | rules: [
25 | {
26 | test: /\.css$/,
27 | use: [
28 | MiniCssExtractPlugin.loader,
29 | 'css-loader',
30 | {
31 | loader: 'postcss-loader',
32 | options: {
33 | sourceMap: true,
34 | postcssOptions: {
35 | plugins: [postcssPresetEnv],
36 | },
37 | },
38 | },
39 | ],
40 | },
41 | {
42 | test: /\.(js|jsx)$/,
43 | include: [
44 | Path.resolve(__dirname, '../src'),
45 | /**
46 | * add ES6 modules that should be transpiled here. For example:
47 | * Path.resolve(__dirname, '../node_modules/query-string'),
48 | */
49 | ],
50 | loader: 'babel-loader',
51 | },
52 | ],
53 | },
54 | });
55 |
--------------------------------------------------------------------------------