create-application-template 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +21 -0
- package/.eslintrc.js +304 -0
- package/.husky/pre-commit +3 -0
- package/.stylelintrc.js +13 -0
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/bin/create-application-template.js +75 -0
- package/jest/cssTransform.js +13 -0
- package/jest/setup.js +5 -0
- package/jest/setupTests.js +15 -0
- package/jest/svgTransform.js +12 -0
- package/jest.config.js +50 -0
- package/package.json +101 -0
- package/src/app.d.ts +6 -0
- package/src/assets/cat.svg +14 -0
- package/src/assets/favicon.ico +0 -0
- package/src/components/App.spec.tsx +8 -0
- package/src/components/App.tsx +24 -0
- package/src/components/Counter.spec.tsx +15 -0
- package/src/components/Counter.tsx +21 -0
- package/src/fonts/Exo2-Regular.ttf +0 -0
- package/src/index.html +17 -0
- package/src/index.tsx +13 -0
- package/src/public/robots.txt +3 -0
- package/src/styles/app.css +53 -0
- package/src/styles/counter.css +14 -0
- package/src/styles/index.css +16 -0
- package/tsconfig.json +30 -0
- package/webpack/utilities/createEnvironmentHash.js +8 -0
- package/webpack/utilities/env.js +8 -0
- package/webpack/utilities/generateAssetManifest.js +16 -0
- package/webpack/utilities/getPaths.js +57 -0
- package/webpack/utilities/getTerserOptions.js +47 -0
- package/webpack/webpack.common.js +161 -0
- package/webpack/webpack.config.js +14 -0
- package/webpack/webpack.dev.js +40 -0
- package/webpack/webpack.prod.js +34 -0
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-application-template",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "provides a configured application template for you to build upon",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-application-template": "bin/create-application-template.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "webpack serve --config webpack/webpack.config.js --env BUNDLER_ENV=dev",
|
|
11
|
+
"build": "webpack --config webpack/webpack.config.js --env BUNDLER_ENV=prod BUNDLER_WITH_PROFILING=false",
|
|
12
|
+
"build:profile": "webpack --config webpack/webpack.config.js --env BUNDLER_ENV=prod BUNDLER_WITH_PROFILING=true",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"test:watch": "jest --watch",
|
|
15
|
+
"dev": "concurrently \"npm run test:watch\" \"npm run start\"",
|
|
16
|
+
"lint": "eslint \"src/**/*.{js,jsx,ts,tsx,json}\"",
|
|
17
|
+
"lint:fix": "eslint --fix \"src/**/*.{js,jsx,ts,tsx,json}\"",
|
|
18
|
+
"stylelint": "npx stylelint \"src/**/*.css\"",
|
|
19
|
+
"stylelint:fix": "npx stylelint --fix \"src/**/*.css\"",
|
|
20
|
+
"prepare": "husky"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"template",
|
|
24
|
+
"react",
|
|
25
|
+
"typescript",
|
|
26
|
+
"webpack",
|
|
27
|
+
"jest",
|
|
28
|
+
"eslint",
|
|
29
|
+
"styelint"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/daveKontro/create-application-template.git"
|
|
34
|
+
},
|
|
35
|
+
"author": "Dave Kontrovitz",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"proxy": "http://localhost:3000",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": "18"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"concurrently": "8.2.2",
|
|
43
|
+
"dotenv-flow": "4.1.0",
|
|
44
|
+
"husky": "9.0.11",
|
|
45
|
+
"react": "18.2.0",
|
|
46
|
+
"react-dom": "18.2.0",
|
|
47
|
+
"yargs": "17.7.2"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@babel/core": "7.23.7",
|
|
51
|
+
"@babel/plugin-transform-runtime": "7.23.7",
|
|
52
|
+
"@babel/preset-env": "7.23.8",
|
|
53
|
+
"@babel/preset-react": "7.23.3",
|
|
54
|
+
"@babel/preset-typescript": "7.23.3",
|
|
55
|
+
"@babel/runtime": "7.23.8",
|
|
56
|
+
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
|
|
57
|
+
"@testing-library/jest-dom": "6.4.2",
|
|
58
|
+
"@testing-library/react": "14.2.1",
|
|
59
|
+
"@testing-library/user-event": "14.5.2",
|
|
60
|
+
"@types/jest": "29.5.12",
|
|
61
|
+
"@types/node": "20.11.19",
|
|
62
|
+
"@types/react": "18.2.48",
|
|
63
|
+
"@types/react-dom": "18.2.18",
|
|
64
|
+
"@typescript-eslint/eslint-plugin": "6.5.0",
|
|
65
|
+
"@typescript-eslint/parser": "6.5.0",
|
|
66
|
+
"babel-jest": "29.7.0",
|
|
67
|
+
"babel-loader": "9.1.3",
|
|
68
|
+
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
|
69
|
+
"confusing-browser-globals": "1.0.11",
|
|
70
|
+
"copy-webpack-plugin": "12.0.2",
|
|
71
|
+
"css-loader": "6.9.1",
|
|
72
|
+
"css-minimizer-webpack-plugin": "6.0.0",
|
|
73
|
+
"dotenv-webpack": "8.0.1",
|
|
74
|
+
"eslint": "8.56.0",
|
|
75
|
+
"eslint-plugin-eslint-comments": "3.2.0",
|
|
76
|
+
"eslint-plugin-import": "2.29.1",
|
|
77
|
+
"eslint-plugin-jsx-a11y": "6.8.0",
|
|
78
|
+
"eslint-plugin-react": "7.33.2",
|
|
79
|
+
"eslint-plugin-react-hooks": "4.6.0",
|
|
80
|
+
"html-webpack-plugin": "5.6.0",
|
|
81
|
+
"identity-obj-proxy": "3.0.0",
|
|
82
|
+
"jest": "29.7.0",
|
|
83
|
+
"jest-environment-jsdom": "29.7.0",
|
|
84
|
+
"jest-extended": "4.0.2",
|
|
85
|
+
"jest-watch-typeahead": "2.2.2",
|
|
86
|
+
"mini-css-extract-plugin": "2.8.0",
|
|
87
|
+
"react-refresh": "0.14.0",
|
|
88
|
+
"style-loader": "3.3.4",
|
|
89
|
+
"stylelint": "16.2.1",
|
|
90
|
+
"stylelint-config-recess-order": "5.0.0",
|
|
91
|
+
"stylelint-config-standard": "36.0.0",
|
|
92
|
+
"terser-webpack-plugin": "5.3.10",
|
|
93
|
+
"typescript": "5.2.2",
|
|
94
|
+
"webpack": "5.89.0",
|
|
95
|
+
"webpack-cli": "5.1.4",
|
|
96
|
+
"webpack-dev-server": "4.15.1",
|
|
97
|
+
"webpack-manifest-plugin": "5.0.0",
|
|
98
|
+
"webpack-merge": "5.10.0",
|
|
99
|
+
"whatwg-fetch": "3.6.20"
|
|
100
|
+
}
|
|
101
|
+
}
|
package/src/app.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
2
|
+
<svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" fill="#cec2eb">
|
|
3
|
+
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
|
4
|
+
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
<g id="SVGRepo_iconCarrier">
|
|
6
|
+
<style type="text/css"> .st0{fill:#cec2eb;}</style>
|
|
7
|
+
<g>
|
|
8
|
+
<path class="st0" d="M430.762,126.907c-42.384-89.329-67.753-89.329-78.722-89.329c-1.947,0-3.884,0.167-5.785,0.482 c-18.1,3.014-41.374,22.031-69.302,56.579h-20.955h-20.974c-27.909-34.548-51.202-53.566-69.301-56.579 c-1.891-0.315-3.829-0.482-5.758-0.482h-0.25c-11.108,0-36.412,0.649-78.499,89.329C34.188,164.608-32.49,256.506,18.091,366.094 c49.996,108.328,191.425,108.328,237.907,108.328c46.472,0,187.911,0,237.907-108.318 C544.495,256.506,477.809,164.608,430.762,126.907z M468.055,354.161c-41.15,89.134-161.411,91.776-212.056,91.776 c-50.626,0-170.906-2.642-212.038-91.776c-56.969-123.432,60.121-208.886,60.121-208.886s36.551-79.213,55.875-79.213l1.094,0.093 c18.99,3.162,60.14,56.969,60.14,56.969h34.808h34.808c0,0,41.15-53.807,60.14-56.969l1.094-0.093 c19.305,0,55.875,79.213,55.875,79.213S525.024,230.729,468.055,354.161z"/>
|
|
9
|
+
<path class="st0" d="M317.222,368.467c-7.983,1.91-14.715,2.708-20.324,2.708c-7.446,0-12.832-1.391-16.866-3.422 c-5.981-3.059-9.458-7.621-11.841-13.5c-2.327-5.832-3.171-12.851-3.162-19.157c0-3.95,0.333-7.427,0.695-10.329l16.718-16.718 c3.078-3.079,4.005-7.714,2.336-11.73c-1.669-4.024-5.582-6.648-9.94-6.648h-37.683c-4.45,0-8.437,2.745-10.032,6.899 c-1.614,4.154-0.482,8.864,2.819,11.85l16.096,14.576c0.464,3.115,0.927,7.325,0.918,12.118c0.045,8.438-1.531,18.016-5.954,24.581 c-2.216,3.32-5.016,6.018-9.04,8.057c-4.025,2.031-9.42,3.422-16.857,3.422c-5.601,0-12.341-0.798-20.326-2.716 c-4.339-1.039-8.697,1.641-9.736,5.971c-1.029,4.339,1.651,8.697,5.981,9.736c8.976,2.142,16.941,3.143,24.081,3.152 c9.448,0,17.496-1.808,24.126-5.146c7.779-3.895,13.222-9.866,16.857-16.347c0.52,0.918,0.853,1.864,1.428,2.763 c3.551,5.387,8.623,10.246,15.243,13.584c6.639,3.338,14.688,5.156,24.136,5.146c7.14,0,15.104-1.01,24.08-3.162 c4.339-1.038,7.009-5.387,5.972-9.726C325.911,370.1,321.553,367.43,317.222,368.467z"/>
|
|
10
|
+
<path class="st0" d="M146.011,241.809c0,13.983,11.349,25.323,25.332,25.323c13.982,0,25.313-11.34,25.313-25.323 c0-13.983-11.331-25.322-25.313-25.322C157.36,216.487,146.011,227.826,146.011,241.809z"/>
|
|
11
|
+
<path class="st0" d="M340.663,267.132c13.982,0,25.323-11.34,25.323-25.323c0-13.983-11.341-25.322-25.323-25.322 c-13.983,0-25.322,11.34-25.322,25.322C315.341,255.792,326.68,267.132,340.663,267.132z"/>
|
|
12
|
+
</g>
|
|
13
|
+
</g>
|
|
14
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import '../styles/app.css'
|
|
2
|
+
import logo from '../assets/cat.svg'
|
|
3
|
+
import { Counter } from './Counter'
|
|
4
|
+
|
|
5
|
+
export const App = () => {
|
|
6
|
+
return (
|
|
7
|
+
<div className='container--main'>
|
|
8
|
+
<header className='header--wrapper'>
|
|
9
|
+
<h1>Create Application Template</h1>
|
|
10
|
+
<h2>Configured and under your control!</h2>
|
|
11
|
+
</header>
|
|
12
|
+
<section className='section--wrapper'>
|
|
13
|
+
<code className='card--env'>[NODE_ENV={process.env.NODE_ENV}]</code>
|
|
14
|
+
<code className='card--env'>[EXAMPLE={process.env.EXAMPLE}]</code>
|
|
15
|
+
</section>
|
|
16
|
+
<section className='section--wrapper'>
|
|
17
|
+
<img src={logo} className='logo--app' alt='logo' />
|
|
18
|
+
</section>
|
|
19
|
+
<section className='section--wrapper'>
|
|
20
|
+
<Counter />
|
|
21
|
+
</section>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react'
|
|
2
|
+
import { userEvent } from '@testing-library/user-event'
|
|
3
|
+
import { Counter } from './Counter'
|
|
4
|
+
|
|
5
|
+
test('count increases per click', async () => {
|
|
6
|
+
render(<Counter />)
|
|
7
|
+
const button = await screen.findByRole('button', { name: /count/i })
|
|
8
|
+
|
|
9
|
+
const user = userEvent.setup()
|
|
10
|
+
await user.click(button)
|
|
11
|
+
const results = button.innerHTML.match(/\d/g)
|
|
12
|
+
|
|
13
|
+
expect(results).toBeArrayOfSize(1)
|
|
14
|
+
expect(results).toIncludeAllMembers(['1'])
|
|
15
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import '../styles/counter.css'
|
|
2
|
+
import { FC, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
// NOTE update count then edit App.tsx... thanks
|
|
5
|
+
// to ReactRefreshWebpackPlugin state is preserved
|
|
6
|
+
|
|
7
|
+
type Count = number
|
|
8
|
+
|
|
9
|
+
export const Counter: FC = (): JSX.Element => {
|
|
10
|
+
const [count, setCount] = useState<Count>(0)
|
|
11
|
+
|
|
12
|
+
const handleClickCounter = () => setCount((count: Count) => ++count)
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div>
|
|
16
|
+
<button id='counter' onClick={handleClickCounter}>
|
|
17
|
+
count: {count}
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
Binary file
|
package/src/index.html
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang='en'>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset='utf-8' />
|
|
5
|
+
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
|
6
|
+
<meta name='theme-color' content='#000000' />
|
|
7
|
+
<meta
|
|
8
|
+
name='Create Application Template'
|
|
9
|
+
content='demo app'
|
|
10
|
+
/>
|
|
11
|
+
<title><%= htmlWebpackPlugin.options.title %></title>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<noscript>You must enable JavaScript to run this app.</noscript>
|
|
15
|
+
<div id='root'></div>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import './styles/index.css'
|
|
4
|
+
import { App } from './components/App'
|
|
5
|
+
|
|
6
|
+
const container = document.getElementById('root')
|
|
7
|
+
const root = createRoot(container!)
|
|
8
|
+
|
|
9
|
+
root.render(
|
|
10
|
+
<StrictMode>
|
|
11
|
+
<App />
|
|
12
|
+
</StrictMode>
|
|
13
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.container--main {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
min-height: 100vh;
|
|
7
|
+
font-size: max(1em, 18px);
|
|
8
|
+
color: #cec2eb;
|
|
9
|
+
background-color: #454145;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.header--wrapper {
|
|
13
|
+
text-align: center;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.section--wrapper {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
margin: 2vh;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.logo--app {
|
|
23
|
+
height: 240px;
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
28
|
+
.logo--app {
|
|
29
|
+
animation: logo--app-pulse infinite 10s ease;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@keyframes logo--app-pulse {
|
|
34
|
+
0% {
|
|
35
|
+
opacity: 0.2;
|
|
36
|
+
transform: scale(0.8);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
50% {
|
|
40
|
+
opacity: 1.0;
|
|
41
|
+
transform: scale(1.2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
100% {
|
|
45
|
+
opacity: 0.2;
|
|
46
|
+
transform: scale(0.8);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.card--env {
|
|
51
|
+
margin: 0.4vh;
|
|
52
|
+
filter: brightness(0.8);
|
|
53
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
button#counter {
|
|
2
|
+
height: 40px;
|
|
3
|
+
padding: 10px;
|
|
4
|
+
font-size: 18px;
|
|
5
|
+
color: #cec2eb;
|
|
6
|
+
background-color: #454145;
|
|
7
|
+
border: 1px solid #cec2eb;
|
|
8
|
+
border-radius: 8px;
|
|
9
|
+
transition-duration: 0.5s;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
button#counter:hover {
|
|
13
|
+
border-radius: 20px;
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@font-face {
|
|
2
|
+
font-family: 'exo2-regular';
|
|
3
|
+
src: url('../fonts/Exo2-Regular.ttf');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
font-family: 'exo2-regular', sans-serif;
|
|
9
|
+
-webkit-font-smoothing: antialiased;
|
|
10
|
+
-moz-osx-font-smoothing: grayscale;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
code {
|
|
14
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
15
|
+
monospace;
|
|
16
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES5",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"lib": [
|
|
7
|
+
"DOM",
|
|
8
|
+
"ESNext"
|
|
9
|
+
],
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"forceConsistentCasingInFileNames": true,
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"types": [
|
|
19
|
+
"@types/node",
|
|
20
|
+
"@types/react",
|
|
21
|
+
"@types/react-dom",
|
|
22
|
+
"@types/jest",
|
|
23
|
+
"jest-extended",
|
|
24
|
+
"@testing-library/jest-dom"
|
|
25
|
+
],
|
|
26
|
+
// "allowJs": true, /* allow javascript files to be compiled */,
|
|
27
|
+
// "checkJs": true, /* report errors in .js files; works with allowJs */,
|
|
28
|
+
},
|
|
29
|
+
"include": ["src/**/*"]
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = (seed, files, entrypoints) => {
|
|
2
|
+
const manifestFiles = files.reduce((manifest, file) => {
|
|
3
|
+
manifest[file.name] = file.path
|
|
4
|
+
|
|
5
|
+
return manifest
|
|
6
|
+
}, seed)
|
|
7
|
+
|
|
8
|
+
const entrypointFiles = entrypoints.main.filter(
|
|
9
|
+
fileName => !fileName.endsWith('.map')
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
files: manifestFiles,
|
|
14
|
+
entrypoints: entrypointFiles,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const env = require('./env')
|
|
3
|
+
|
|
4
|
+
// NOTE root path assumes execution from react app root
|
|
5
|
+
|
|
6
|
+
const getPaths = ({ BUNDLER_ENV }) => {
|
|
7
|
+
const isProduction = (BUNDLER_ENV === env.BUNDLER_ENV.prod)
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
root: process.cwd(),
|
|
11
|
+
get jsconfigJson() { return path.join(this.root, 'jsconfig.json') },
|
|
12
|
+
get tsconfigJson() { return path.join(this.root, 'tsconfig.json') },
|
|
13
|
+
get build() { return path.join(this.root, 'build') },
|
|
14
|
+
static: {
|
|
15
|
+
static: 'static',
|
|
16
|
+
get css() {
|
|
17
|
+
return {
|
|
18
|
+
css: path.join(this.static, 'css'),
|
|
19
|
+
get filenameCss() { return path.join(this.css, '[name].[contenthash].css') },
|
|
20
|
+
get chunkFilenameCss() { return path.join(this.css, '[name].[contenthash].chunk.css') },
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
get js() {
|
|
24
|
+
const filenameJs = isProduction ? '[name].[contenthash].js' : '[name].js'
|
|
25
|
+
const chunkFilenameJs = isProduction ? '[name].[contenthash].chunk.js' : '[name].chunk.js'
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
js: path.join(this.static, 'js'),
|
|
29
|
+
get filenameJs() { return path.join(this.js, filenameJs) },
|
|
30
|
+
get chunkFilenameJs() { return path.join(this.js, chunkFilenameJs) },
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
get media() {
|
|
34
|
+
return {
|
|
35
|
+
media: path.join(this.static, 'media'),
|
|
36
|
+
get filenameExt() { return path.join(this.media, '[name].[hash][ext]') },
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
get src() {
|
|
41
|
+
return {
|
|
42
|
+
src: path.join(this.root, 'src'),
|
|
43
|
+
get indexTsx() { return path.join(this.src, 'index.tsx') },
|
|
44
|
+
get indexHtml() { return path.join(this.src, 'index.html') },
|
|
45
|
+
get public() { return path.join(this.src, 'public') },
|
|
46
|
+
get assets() {
|
|
47
|
+
return {
|
|
48
|
+
assets: path.join(this.src, 'assets'),
|
|
49
|
+
get faviconIco() { return path.join(this.assets, 'favicon.ico') },
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = getPaths
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// NOTE npm module react-scripts' options where the starting point,
|
|
2
|
+
// comments marked with :rs: are paraphrased from react-scripts
|
|
3
|
+
|
|
4
|
+
module.exports = ({ BUNDLER_WITH_PROFILING }) => {
|
|
5
|
+
const withProfiling = (BUNDLER_WITH_PROFILING)
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
parse: {
|
|
9
|
+
// :rs: want terser to parse ecma 8 code however, don't want it
|
|
10
|
+
// to apply minification steps that turns valid ecma 5 code
|
|
11
|
+
// into invalid ecma 5 code... this is also why compress and
|
|
12
|
+
// output only apply transformations that are ecma 5 safe
|
|
13
|
+
// https://github.com/facebook/create-react-app/pull/4234
|
|
14
|
+
ecma: 8,
|
|
15
|
+
},
|
|
16
|
+
compress: {
|
|
17
|
+
ecma: 5,
|
|
18
|
+
warnings: false,
|
|
19
|
+
// :rs: disabled b/c of an issue with Uglify breaking seemingly valid code:
|
|
20
|
+
// https://github.com/facebook/create-react-app/issues/2376
|
|
21
|
+
// pending further investigation:
|
|
22
|
+
// https://github.com/mishoo/UglifyJS2/issues/2011
|
|
23
|
+
comparisons: false,
|
|
24
|
+
// :rs: disabled because of an issue with Terser breaking valid code:
|
|
25
|
+
// https://github.com/facebook/create-react-app/issues/5250
|
|
26
|
+
// pending further investigation:
|
|
27
|
+
// https://github.com/terser-js/terser/issues/120
|
|
28
|
+
inline: 2,
|
|
29
|
+
},
|
|
30
|
+
mangle: {
|
|
31
|
+
// to work around the Safari 10 loop iterator
|
|
32
|
+
// warning "Cannot declare a let variable twice"
|
|
33
|
+
// (although, i think safari is kind of right)
|
|
34
|
+
safari10: true,
|
|
35
|
+
},
|
|
36
|
+
// :rs: for profiling in devtools
|
|
37
|
+
keep_classnames: withProfiling,
|
|
38
|
+
keep_fnames: withProfiling,
|
|
39
|
+
output: {
|
|
40
|
+
ecma: 5,
|
|
41
|
+
comments: false,
|
|
42
|
+
// :rs: emoji and regex not minified properly using default
|
|
43
|
+
// https://github.com/facebook/create-react-app/issues/2488
|
|
44
|
+
ascii_only: true,
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
|
3
|
+
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
4
|
+
const Dotenv = require('dotenv-webpack')
|
|
5
|
+
const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
|
|
6
|
+
const CopyPlugin = require('copy-webpack-plugin')
|
|
7
|
+
const env = require('./utilities/env')
|
|
8
|
+
const getPaths = require('./utilities/getPaths')
|
|
9
|
+
const createEnvironmentHash = require('./utilities/createEnvironmentHash')
|
|
10
|
+
const generateAssetManifest = require('./utilities/generateAssetManifest')
|
|
11
|
+
|
|
12
|
+
// styles regexps
|
|
13
|
+
const reCss = /\.css$/
|
|
14
|
+
const reCssModule = /\.module\.css$/
|
|
15
|
+
|
|
16
|
+
module.exports = (webpackEnv) => {
|
|
17
|
+
const { BUNDLER_ENV } = webpackEnv
|
|
18
|
+
const { INLINE_SIZE_LIMIT } = process.env
|
|
19
|
+
|
|
20
|
+
const paths = getPaths({ BUNDLER_ENV })
|
|
21
|
+
|
|
22
|
+
// NOTE it's preferable to isolate settings in appropriate
|
|
23
|
+
// files, but it's not always practical, hence "isProduction"
|
|
24
|
+
const isProduction = (BUNDLER_ENV === env.BUNDLER_ENV.prod)
|
|
25
|
+
const isDevelopment = (BUNDLER_ENV === env.BUNDLER_ENV.dev)
|
|
26
|
+
|
|
27
|
+
const styleLoaders = [
|
|
28
|
+
...(isProduction
|
|
29
|
+
? [{
|
|
30
|
+
loader: MiniCssExtractPlugin.loader,
|
|
31
|
+
options: {
|
|
32
|
+
// css is located in 'static/css', use publicPath
|
|
33
|
+
// to locate index.html directory relative to css
|
|
34
|
+
publicPath: '../../',
|
|
35
|
+
},
|
|
36
|
+
}]
|
|
37
|
+
: ['style-loader']
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
entry: paths.src.indexTsx,
|
|
43
|
+
resolve: {
|
|
44
|
+
// can now leave off extentions when importing
|
|
45
|
+
extensions: ['.tsx', '.jsx', '.ts', '.js'],
|
|
46
|
+
},
|
|
47
|
+
watchOptions: {
|
|
48
|
+
ignored: ['**/node_modules'],
|
|
49
|
+
},
|
|
50
|
+
module: {
|
|
51
|
+
rules: [
|
|
52
|
+
{
|
|
53
|
+
test: /\.(ts|js)x?$/,
|
|
54
|
+
exclude: /node_modules/,
|
|
55
|
+
use: [
|
|
56
|
+
{
|
|
57
|
+
loader: 'babel-loader',
|
|
58
|
+
options: {
|
|
59
|
+
plugins: [
|
|
60
|
+
// for use with ReactRefreshWebpackPlugin
|
|
61
|
+
isDevelopment
|
|
62
|
+
&& require.resolve('react-refresh/babel'),
|
|
63
|
+
].filter(Boolean),
|
|
64
|
+
cacheDirectory: true,
|
|
65
|
+
cacheCompression: false,
|
|
66
|
+
compact: isProduction,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
test: reCss,
|
|
73
|
+
use: [...styleLoaders, 'css-loader'],
|
|
74
|
+
exclude: reCssModule,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
test: reCssModule,
|
|
78
|
+
use: [
|
|
79
|
+
...styleLoaders,
|
|
80
|
+
{
|
|
81
|
+
loader: 'css-loader',
|
|
82
|
+
options: {
|
|
83
|
+
importLoaders: 1,
|
|
84
|
+
modules: true,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
|
|
91
|
+
type: 'asset/resource',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
|
|
95
|
+
type: 'asset',
|
|
96
|
+
parser: {
|
|
97
|
+
dataUrlCondition: {
|
|
98
|
+
maxSize: parseInt(INLINE_SIZE_LIMIT || 10000),
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
output: {
|
|
105
|
+
path: paths.build,
|
|
106
|
+
pathinfo: isProduction ? false : true,
|
|
107
|
+
filename: paths.static.js.filenameJs,
|
|
108
|
+
chunkFilename: paths.static.js.chunkFilenameJs,
|
|
109
|
+
asyncChunks: true,
|
|
110
|
+
clean: true,
|
|
111
|
+
assetModuleFilename: paths.static.media.filenameExt,
|
|
112
|
+
publicPath: '', // public url of build, use in build/index.html
|
|
113
|
+
},
|
|
114
|
+
cache: { // used in development mode
|
|
115
|
+
type: 'filesystem',
|
|
116
|
+
version: createEnvironmentHash({ processEnv: process.env }),
|
|
117
|
+
cacheDirectory: paths.appWebpackCache,
|
|
118
|
+
store: 'pack',
|
|
119
|
+
buildDependencies: {
|
|
120
|
+
defaultWebpack: ['webpack/lib/'],
|
|
121
|
+
config: [__filename],
|
|
122
|
+
tsconfig: [paths.tsconfigJson, paths.jsconfigJson].filter(file =>
|
|
123
|
+
fs.existsSync(file)
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
plugins: [
|
|
128
|
+
new HtmlWebpackPlugin({
|
|
129
|
+
template: paths.src.indexHtml,
|
|
130
|
+
favicon: paths.src.assets.faviconIco,
|
|
131
|
+
title: 'Application Template',
|
|
132
|
+
...(isProduction && {
|
|
133
|
+
minify: {
|
|
134
|
+
removeComments: true,
|
|
135
|
+
collapseWhitespace: true,
|
|
136
|
+
removeRedundantAttributes: true,
|
|
137
|
+
useShortDoctype: true,
|
|
138
|
+
removeEmptyAttributes: true,
|
|
139
|
+
removeStyleLinkTypeAttributes: true,
|
|
140
|
+
keepClosingSlash: true,
|
|
141
|
+
minifyJS: true,
|
|
142
|
+
minifyCSS: true,
|
|
143
|
+
minifyURLs: true,
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
}),
|
|
147
|
+
new WebpackManifestPlugin({
|
|
148
|
+
fileName: 'asset-manifest.json',
|
|
149
|
+
publicPath: '/',
|
|
150
|
+
generate: generateAssetManifest,
|
|
151
|
+
}),
|
|
152
|
+
new CopyPlugin({
|
|
153
|
+
patterns: [{
|
|
154
|
+
from: paths.src.public,
|
|
155
|
+
to: paths.build,
|
|
156
|
+
}],
|
|
157
|
+
}),
|
|
158
|
+
new Dotenv(),
|
|
159
|
+
],
|
|
160
|
+
}
|
|
161
|
+
}
|