@dg-scripts/webpack-template 0.0.0-1867

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,98 @@
1
+ {
2
+ "name": "@dg-scripts/webpack-template",
3
+ "version": "0.0.0-1867",
4
+ "packageManager": "pnpm@8.15.5",
5
+ "description": "Minimal webpack boilerplate",
6
+ "author": "sabertazimi",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/sabertazimi/bod",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/sabertazimi/bod.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/sabertazimi/bod/issues"
15
+ },
16
+ "keywords": [
17
+ "webpack",
18
+ "boilerplate",
19
+ "template"
20
+ ],
21
+ "main": "./src/index.js",
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "browserslist": {
26
+ "production": [
27
+ ">0.2%",
28
+ "not dead",
29
+ "not op_mini all"
30
+ ],
31
+ "development": [
32
+ "last 1 chrome version",
33
+ "last 1 firefox version",
34
+ "last 1 safari version"
35
+ ]
36
+ },
37
+ "scripts": {
38
+ "badge": "ts-node scripts/badge.ts",
39
+ "build": "cross-env NODE_ENV=production webpack",
40
+ "dev": "cross-env NODE_ENV=development webpack serve",
41
+ "lint": "pnpm lint:style && pnpm lint:type-check",
42
+ "lint:style": "stylelint ./src/**/*.css && eslint ./src",
43
+ "lint:fix": "stylelint ./src/**/*.css --fix && eslint ./src --fix",
44
+ "lint:type-check": "tsc --noEmit",
45
+ "changeset": "commit-and-tag-version --dry-run -s",
46
+ "release": "commit-and-tag-version -s",
47
+ "start": "pnpm dev",
48
+ "test": "jest",
49
+ "test:watch": "jest --watch"
50
+ },
51
+ "devDependencies": {
52
+ "@babel/core": "^7.24.3",
53
+ "@babel/plugin-transform-class-properties": "^7.24.1",
54
+ "@babel/plugin-transform-object-rest-spread": "^7.24.1",
55
+ "@babel/preset-env": "^7.24.3",
56
+ "@dg-scripts/eslint-config": "0.0.0-1867",
57
+ "@dg-scripts/stylelint-config": "0.0.0-1867",
58
+ "@svgr/webpack": "^8.1.0",
59
+ "@types/jest": "^29.5.12",
60
+ "@types/node": "^20.12.2",
61
+ "babel-loader": "^9.1.3",
62
+ "cross-env": "^7.0.3",
63
+ "css-loader": "^6.10.0",
64
+ "css-minimizer-webpack-plugin": "^6.0.0",
65
+ "dotenv": "^16.4.5",
66
+ "eslint": "^8.57.0",
67
+ "eslint-webpack-plugin": "^4.1.0",
68
+ "file-loader": "^6.2.0",
69
+ "html-loader": "^5.0.0",
70
+ "html-webpack-plugin": "^5.6.0",
71
+ "jest": "^29.7.0",
72
+ "jest-environment-jsdom": "^29.7.0",
73
+ "mini-css-extract-plugin": "^2.8.1",
74
+ "postcss": "^8.4.38",
75
+ "postcss-flexbugs-fixes": "^5.0.2",
76
+ "postcss-loader": "^8.1.1",
77
+ "postcss-preset-env": "^9.5.2",
78
+ "prettier": "^3.2.5",
79
+ "sass-loader": "^14.1.1",
80
+ "style-loader": "^3.3.4",
81
+ "stylelint": "^16.3.1",
82
+ "stylelint-webpack-plugin": "^5.0.0",
83
+ "ts-jest": "^29.1.2",
84
+ "ts-loader": "^9.5.1",
85
+ "ts-node": "^10.9.2",
86
+ "tsconfig-paths-webpack-plugin": "^4.1.0",
87
+ "tslib": "^2.6.2",
88
+ "typescript": "^5.4.3",
89
+ "undici": "^6.10.2",
90
+ "url-loader": "^4.1.1",
91
+ "webpack": "^5.91.0",
92
+ "webpack-bundle-analyzer": "^4.10.1",
93
+ "webpack-cli": "^5.1.4",
94
+ "webpack-dev-server": "^5.0.4",
95
+ "webpackbar": "^6.0.1"
96
+ },
97
+ "gitHead": "27b404c9bab635676f0939f84764ddc292fa681d"
98
+ }
@@ -0,0 +1,14 @@
1
+ module.exports = {
2
+ plugins: [
3
+ 'postcss-flexbugs-fixes',
4
+ [
5
+ 'postcss-preset-env',
6
+ {
7
+ autoprefixer: {
8
+ flexbox: 'no-2009',
9
+ },
10
+ stage: 3,
11
+ },
12
+ ],
13
+ ],
14
+ };
@@ -0,0 +1,77 @@
1
+ import cp from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'path';
4
+ import { fetch } from 'undici';
5
+
6
+ const rootPath = path.join(__dirname, '..');
7
+ const SummaryFilePath = path.join(rootPath, 'coverage/coverage-summary.json');
8
+ const OutputBadgePath = path.join(rootPath, 'dist');
9
+ const CoverageType = ['statements', 'branches', 'functions', 'lines'];
10
+ const BadgeStyle = [
11
+ 'for-the-badge',
12
+ 'flat',
13
+ 'flat-square',
14
+ 'plastic',
15
+ 'social',
16
+ ];
17
+
18
+ const getCoveragePercentage = (
19
+ summaryFilePath: string,
20
+ coverageType: string
21
+ ) => {
22
+ try {
23
+ const summary = fs.readFileSync(summaryFilePath, 'utf8');
24
+ return JSON.parse(summary).total[coverageType].pct;
25
+ } catch (error) {
26
+ if (error instanceof Error) console.error(error.message);
27
+ return 0;
28
+ }
29
+ };
30
+
31
+ const getBadgeColor = (percentage: number) => {
32
+ if (percentage < 80) return 'red';
33
+ if (percentage < 90) return 'yellow';
34
+ return 'brightgreen';
35
+ };
36
+
37
+ const getBadgeUrl = (
38
+ summaryFilePath: string,
39
+ coverageType: string,
40
+ badgeStyle: string
41
+ ) => {
42
+ const percentage = getCoveragePercentage(summaryFilePath, coverageType);
43
+ const coverage = `${percentage}${encodeURI('%')}`;
44
+ const color = getBadgeColor(percentage);
45
+ const url = `https://img.shields.io/badge/${coverageType}-${coverage}-${color}?logo=jest&style=${badgeStyle}`;
46
+ return url;
47
+ };
48
+
49
+ const downloadBadgeFile = async (url: string) => {
50
+ const response = await fetch(url);
51
+ const data = await response.text();
52
+ return data;
53
+ };
54
+
55
+ const generateCoverageFile = async (
56
+ summaryFilePath: string,
57
+ coverageType: string,
58
+ badgeStyle: string,
59
+ outputDir: string
60
+ ) => {
61
+ cp.spawnSync('mkdir', ['-p', outputDir]);
62
+ const badgeUrl = getBadgeUrl(summaryFilePath, coverageType, badgeStyle);
63
+ const output = path.join(outputDir, `coverage-${coverageType}.svg`);
64
+ const file = await downloadBadgeFile(badgeUrl);
65
+ fs.writeFileSync(output, file, { encoding: 'utf8' });
66
+ };
67
+
68
+ const main = () => {
69
+ generateCoverageFile(
70
+ SummaryFilePath,
71
+ CoverageType[3],
72
+ BadgeStyle[0],
73
+ OutputBadgePath
74
+ );
75
+ };
76
+
77
+ main();
package/src/index.css ADDED
@@ -0,0 +1,30 @@
1
+ @import url('https://fonts.googleapis.com/css?family=Raleway:400,500');
2
+
3
+ :root {
4
+ --color-primary: #7048e8;
5
+ --color-secondary: #495057;
6
+ --color-info: #1c7ed6;
7
+ --color-success: #37b24d;
8
+ --color-warning: #f59f00;
9
+ --color-danger: #f03e3e;
10
+ --font-stack: 'Raleway', 'HelveticaNeue', 'Helvetica Neue', helvetica,
11
+ 'Open Sans', arial, sans-serif, serif;
12
+ }
13
+
14
+ body {
15
+ box-sizing: border-box;
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ width: 100%;
20
+ min-height: 100vh;
21
+ padding: 0;
22
+ margin: 0 auto;
23
+ font-family: var(--font-stack);
24
+ }
25
+
26
+ *,
27
+ *::before,
28
+ *::after {
29
+ box-sizing: inherit;
30
+ }
package/src/index.html ADDED
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1, shrink-to-fit=no"
8
+ />
9
+ <meta name="theme-color" content="#000000" />
10
+ <title>Boilerplate</title>
11
+ </head>
12
+
13
+ <body>
14
+ <noscript> You need to enable JavaScript to run this app. </noscript>
15
+ <div>Click blank space to spawn particles.</div>
16
+ <canvas id="canvas"></canvas>
17
+ </body>
18
+ </html>
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import './index.css'
2
+ import { ParticleSystem } from './particle'
3
+
4
+ const particleSystem = new ParticleSystem()
5
+ particleSystem.start()
@@ -0,0 +1,94 @@
1
+ interface Speed {
2
+ x: number
3
+ y: number
4
+ }
5
+
6
+ interface ParticleProps {
7
+ x?: number
8
+ y?: number
9
+ color?: number[]
10
+ duration?: number
11
+ speed?: Speed
12
+ radius?: number
13
+ life?: number
14
+ }
15
+
16
+ class ExplodingParticle {
17
+ startX: number
18
+ startY: number
19
+ color: number[]
20
+ speed: Speed
21
+ radius: number
22
+ startTime: number
23
+ animationDuration: number
24
+ life: number
25
+ remainingLife: number
26
+
27
+ static getDefaultProps(): ParticleProps {
28
+ const defaultProps: ParticleProps = {
29
+ x: 0,
30
+ y: 0,
31
+ color: [156, 39, 176],
32
+ duration: 1000,
33
+ speed: {
34
+ x: -5 + Math.random() * 10,
35
+ y: -5 + Math.random() * 10,
36
+ },
37
+ radius: 5 + Math.random() * 5,
38
+ life: 30 + Math.random() * 10,
39
+ }
40
+
41
+ return defaultProps
42
+ }
43
+
44
+ constructor(props?: ParticleProps) {
45
+ const defaultProps = ExplodingParticle.getDefaultProps()
46
+ const { x, y, color, duration, speed, radius, life } = {
47
+ ...defaultProps,
48
+ ...props,
49
+ }
50
+
51
+ this.startX = x as number
52
+ this.startY = y as number
53
+ this.color = color as number[]
54
+
55
+ // Speed
56
+ this.speed = speed as Speed
57
+
58
+ // Size particle
59
+ this.radius = radius as number
60
+
61
+ // Set how long particle to animate for X ms
62
+ this.startTime = Date.now()
63
+ this.animationDuration = duration as number
64
+
65
+ // Set a max time to live for particle
66
+ this.life = life as number
67
+ this.remainingLife = this.life
68
+ }
69
+
70
+ // This function will be called by animation logic
71
+ draw(ctx: CanvasRenderingContext2D): void {
72
+ const shouldDraw = this.radius > 0 && this.remainingLife > 0
73
+
74
+ if (shouldDraw) {
75
+ // Draw a circle at the current location
76
+ ctx.save()
77
+ ctx.beginPath()
78
+ ctx.arc(this.startX, this.startY, this.radius, 0, Math.PI * 2)
79
+ ctx.fillStyle = `rgba(${this.color[0]},${this.color[1]},${this.color[2]},1)`
80
+ ctx.fill()
81
+ ctx.closePath()
82
+ ctx.restore()
83
+
84
+ // Update the particle's location and life
85
+ this.startX += this.speed.x
86
+ this.startY += this.speed.y
87
+ this.radius -= 0.25
88
+ this.remainingLife--
89
+ }
90
+ }
91
+ }
92
+
93
+ export default ExplodingParticle
94
+ export type { Speed, ParticleProps }
@@ -0,0 +1,25 @@
1
+ import type { ParticleProps } from './ExplodingParticle'
2
+ import ExplodingParticle from './ExplodingParticle'
3
+
4
+ class ParticleFactory {
5
+ particles: ExplodingParticle[]
6
+ constructor() {
7
+ this.particles = []
8
+ }
9
+
10
+ getParticles(): ExplodingParticle[] {
11
+ return this.particles
12
+ }
13
+
14
+ clearParticles(): void {
15
+ while (this.particles.length)
16
+ this.particles.pop()
17
+ }
18
+
19
+ emit(particleProps?: ParticleProps): void {
20
+ const particle = new ExplodingParticle(particleProps)
21
+ this.particles.push(particle)
22
+ }
23
+ }
24
+
25
+ export default ParticleFactory
@@ -0,0 +1,120 @@
1
+ import type { ParticleProps } from './ExplodingParticle'
2
+ import ParticleFactory from './ParticleFactory'
3
+
4
+ class ParticleSystem {
5
+ factory: ParticleFactory
6
+ bufferCanvas: HTMLCanvasElement
7
+ buffer: CanvasRenderingContext2D
8
+ screenCanvas: HTMLCanvasElement
9
+ screen: CanvasRenderingContext2D
10
+ factor: number
11
+
12
+ constructor(factor = 20) {
13
+ this.factory = new ParticleFactory()
14
+ this.bufferCanvas = document.createElement('canvas')
15
+ this.buffer = this.bufferCanvas.getContext('2d') as CanvasRenderingContext2D
16
+ this.screenCanvas = document.getElementById('canvas') as HTMLCanvasElement
17
+ this.screen = this.screenCanvas.getContext('2d') as CanvasRenderingContext2D
18
+ this.factor = factor
19
+
20
+ this.init()
21
+ }
22
+
23
+ init(): void {
24
+ this.resize()
25
+ this.initStyle()
26
+ this.handleClick()
27
+ }
28
+
29
+ resize(): void {
30
+ // Size canvas
31
+ this.screenCanvas.width = window.innerWidth
32
+ this.screenCanvas.height = window.innerHeight
33
+ this.bufferCanvas.width = window.innerWidth
34
+ this.bufferCanvas.height = window.innerHeight
35
+ }
36
+
37
+ initStyle(): void {
38
+ // Position out canvas
39
+ this.screenCanvas.style.position = 'absolute'
40
+ this.screenCanvas.style.top = '0'
41
+ this.screenCanvas.style.left = '0'
42
+
43
+ // Make sure it's on top of other elements
44
+ this.screenCanvas.style.zIndex = '1001'
45
+ }
46
+
47
+ handleClick(): void {
48
+ // bind click event to screen canvas
49
+ this.screenCanvas.addEventListener('click', (event) => {
50
+ const x = event.clientX
51
+ const y = event.clientY
52
+ const getRandomInt = (min: number, max: number) => () =>
53
+ Math.floor(Math.random() * (max - min)) + min
54
+ const getColor = getRandomInt(0, 255)
55
+
56
+ for (let count = this.factor; count > 0; --count) {
57
+ const color = [getColor(), getColor(), getColor()]
58
+ const speed = {
59
+ x: -5 + Math.random() * 10,
60
+ y: -5 + Math.random() * 10,
61
+ }
62
+ this.emit({ x, y, color, speed })
63
+ count--
64
+ }
65
+ })
66
+ }
67
+
68
+ emit({ x = 0, y = 0, color = [156, 39, 176], speed }: ParticleProps): void {
69
+ this.factory.emit({ x, y, color, speed })
70
+ }
71
+
72
+ draw(): void {
73
+ // render particles to offscreen canvas
74
+ const particles = this.factory.getParticles()
75
+
76
+ particles.forEach((particle, index, particles) => {
77
+ particle.draw(this.buffer)
78
+
79
+ // Simple way to clean up if the last particle is done animating
80
+ if (index === particles.length - 1) {
81
+ const percent
82
+ = (Date.now() - particle.startTime) / particle.animationDuration
83
+
84
+ if (percent > 1)
85
+ this.factory.clearParticles()
86
+ }
87
+ })
88
+
89
+ // render to screen canvas
90
+ this.screen.drawImage(this.bufferCanvas, 0, 0)
91
+ }
92
+
93
+ clear(): void {
94
+ this.buffer.clearRect(
95
+ 0,
96
+ 0,
97
+ this.bufferCanvas.width,
98
+ this.bufferCanvas.height,
99
+ )
100
+
101
+ this.screen.clearRect(
102
+ 0,
103
+ 0,
104
+ this.screenCanvas.width,
105
+ this.screenCanvas.height,
106
+ )
107
+ }
108
+
109
+ update(): void {
110
+ this.clear()
111
+ this.draw()
112
+ window.requestAnimationFrame(this.update.bind(this))
113
+ }
114
+
115
+ start(): void {
116
+ window.requestAnimationFrame(this.update.bind(this))
117
+ }
118
+ }
119
+
120
+ export default ParticleSystem
@@ -0,0 +1,114 @@
1
+ import ExplodingParticle from '../ExplodingParticle'
2
+
3
+ describe('explodingParticle', () => {
4
+ let mockContextFunctions: object
5
+ let mockContextProps: object
6
+ let mockContext: CanvasRenderingContext2D
7
+
8
+ beforeEach(() => {
9
+ mockContextFunctions = {
10
+ save: jest.fn(),
11
+ restore: jest.fn(),
12
+ beginPath: jest.fn(),
13
+ closePath: jest.fn(),
14
+ arc: jest.fn(),
15
+ fill: jest.fn(),
16
+ }
17
+ mockContextProps = {
18
+ fillStyle: '',
19
+ }
20
+ mockContext = {
21
+ ...mockContextFunctions,
22
+ ...mockContextProps,
23
+ } as unknown as CanvasRenderingContext2D
24
+ })
25
+
26
+ afterEach(() => {
27
+ mockContextFunctions = {}
28
+ mockContextProps = {}
29
+ mockContext = {} as unknown as CanvasRenderingContext2D
30
+ })
31
+
32
+ it('should get same x, y, color and duration with empty props', () => {
33
+ const particle = new ExplodingParticle()
34
+ const defaultProps = ExplodingParticle.getDefaultProps()
35
+
36
+ expect(particle.startX).toBe(defaultProps.x)
37
+ expect(particle.startY).toBe(defaultProps.y)
38
+ expect(particle.color).toStrictEqual(defaultProps.color)
39
+ expect(particle.animationDuration).toBe(defaultProps.duration)
40
+ })
41
+
42
+ it('should get random radius, speed and life with empty props', () => {
43
+ const particle = new ExplodingParticle()
44
+ const defaultProps = ExplodingParticle.getDefaultProps()
45
+
46
+ expect(particle.radius).not.toBe(defaultProps.radius)
47
+ expect(particle.speed).not.toStrictEqual(defaultProps.speed)
48
+ expect(particle.life).toBe(particle.remainingLife)
49
+ expect(particle.life).not.toBe(defaultProps.life)
50
+ expect(particle.remainingLife).not.toBe(defaultProps.life)
51
+ })
52
+
53
+ it('should move according to its speed', () => {
54
+ const particle = new ExplodingParticle({
55
+ x: 1,
56
+ y: 2,
57
+ speed: { x: 1, y: -2 },
58
+ })
59
+
60
+ expect(particle.startX).toBe(1)
61
+ expect(particle.startY).toBe(2)
62
+ particle.draw(mockContext)
63
+ expect(particle.startX).toBe(2)
64
+ expect(particle.startY).toBe(0)
65
+ particle.draw(mockContext)
66
+ expect(particle.startX).toBe(3)
67
+ expect(particle.startY).toBe(-2)
68
+ Object.values(mockContextFunctions).forEach((mockFunction) => {
69
+ expect(mockFunction).toHaveBeenCalledTimes(2)
70
+ })
71
+ })
72
+
73
+ it('should shrink radius when time passed', () => {
74
+ const particle = new ExplodingParticle({
75
+ radius: 1.25,
76
+ })
77
+
78
+ expect(particle.radius).toBe(1.25)
79
+ particle.draw(mockContext)
80
+ expect(particle.radius).toBe(1)
81
+ particle.draw(mockContext)
82
+ expect(particle.radius).toBe(0.75)
83
+ particle.draw(mockContext)
84
+ expect(particle.radius).toBe(0.5)
85
+ Object.values(mockContextFunctions).forEach((mockFunction) => {
86
+ expect(mockFunction).toHaveBeenCalledTimes(3)
87
+ })
88
+ })
89
+
90
+ it('should disappear (freezed) when radius becomes zero', () => {
91
+ const particle = new ExplodingParticle({
92
+ x: 0,
93
+ y: 0,
94
+ speed: { x: 1, y: 1 },
95
+ radius: 0.25,
96
+ })
97
+
98
+ expect(particle.startX).toBe(0)
99
+ expect(particle.startY).toBe(0)
100
+ expect(particle.radius).toBe(0.25)
101
+ expect(particle.speed).toStrictEqual({ x: 1, y: 1 })
102
+ particle.draw(mockContext)
103
+ expect(particle.startX).toBe(1)
104
+ expect(particle.startY).toBe(1)
105
+ expect(particle.radius).toBe(0)
106
+ particle.draw(mockContext)
107
+ expect(particle.startX).toBe(1)
108
+ expect(particle.startY).toBe(1)
109
+ expect(particle.radius).toBe(0)
110
+ Object.values(mockContextFunctions).forEach((mockFunction) => {
111
+ expect(mockFunction).toHaveBeenCalledTimes(1)
112
+ })
113
+ })
114
+ })
@@ -0,0 +1,30 @@
1
+ import ParticleFactory from '../ParticleFactory'
2
+
3
+ describe('particleFactory', () => {
4
+ it('should initial with empty particles array', () => {
5
+ const factory = new ParticleFactory()
6
+ expect(factory.getParticles()).toHaveLength(0)
7
+ })
8
+
9
+ it('should emit new particle toe particles array', () => {
10
+ const factory = new ParticleFactory()
11
+
12
+ expect(factory.getParticles()).toHaveLength(0)
13
+ factory.emit()
14
+ expect(factory.getParticles()).toHaveLength(1)
15
+ factory.emit()
16
+ expect(factory.getParticles()).toHaveLength(2)
17
+ })
18
+
19
+ it('should clear all particles when flush particles array', () => {
20
+ const factory = new ParticleFactory()
21
+
22
+ expect(factory.getParticles()).toHaveLength(0)
23
+ factory.emit()
24
+ factory.emit()
25
+ factory.emit()
26
+ expect(factory.getParticles()).toHaveLength(3)
27
+ factory.clearParticles()
28
+ expect(factory.getParticles()).toHaveLength(0)
29
+ })
30
+ })
@@ -0,0 +1 @@
1
+ export { default as ParticleSystem } from './ParticleSystem'
package/tsconfig.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "jsx": "react",
5
+ "lib": ["DOM", "ESNext", "ES2021"],
6
+ "baseUrl": "./",
7
+ "module": "ESNext",
8
+ "moduleResolution": "Bundler",
9
+ "paths": {},
10
+ "allowJs": true,
11
+ "strict": true,
12
+ "declaration": false,
13
+ "importHelpers": true,
14
+ "outDir": "./dist",
15
+ "removeComments": true,
16
+ "sourceMap": true,
17
+ "allowSyntheticDefaultImports": true,
18
+ "esModuleInterop": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "isolatedModules": true,
21
+ "skipLibCheck": true
22
+ },
23
+ "include": ["./src/**/*", "index.d.ts"],
24
+ "exclude": [
25
+ "node_modules",
26
+ "build",
27
+ "coverage",
28
+ "dist",
29
+ "typings"
30
+ ],
31
+ "ts-node": {
32
+ "files": true,
33
+ "compilerOptions": {
34
+ "target": "es6",
35
+ "module": "commonjs"
36
+ }
37
+ }
38
+ }