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/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,6 @@
1
+ declare module '*.png'
2
+ declare module '*.svg' {
3
+ const content: string
4
+ export default content
5
+ }
6
+ declare module '*.module.css'
@@ -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,8 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { App } from './App'
3
+
4
+ test('renders title', () => {
5
+ render(<App />)
6
+
7
+ expect(screen.getByText(/create application template/i)).toBeInTheDocument()
8
+ })
@@ -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:&nbsp;{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,3 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow: /
@@ -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,8 @@
1
+ const { createHash } = require('crypto')
2
+
3
+ module.exports = ({ processEnv }) => {
4
+ const hash = createHash('md5')
5
+ hash.update(JSON.stringify(processEnv))
6
+
7
+ return hash.digest('hex')
8
+ }
@@ -0,0 +1,8 @@
1
+ // NOTE passed in via webpack --env
2
+
3
+ module.exports = Object.freeze({
4
+ BUNDLER_ENV: {
5
+ prod: 'prod',
6
+ dev: 'dev',
7
+ },
8
+ })
@@ -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
+ }