refactor: use shared linting config

This commit is contained in:
Abhijith Vijayan [FLUXON] 2026-01-03 20:54:03 +05:30
parent 88fea12ee6
commit 74d10f2424
23 changed files with 4522 additions and 322 deletions

16
.editorconfig Normal file
View File

@ -0,0 +1,16 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.yml]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

View File

@ -1,5 +0,0 @@
node_modules/
dist/
extension/
.yarn/
.pnp.js

View File

@ -1,34 +0,0 @@
{
"extends": [
"@abhijithvijayan/eslint-config/typescript",
"@abhijithvijayan/eslint-config/node",
"@abhijithvijayan/eslint-config/react"
],
"parserOptions": {
"project": [
"./tsconfig.json"
],
"sourceType": "module"
},
"rules": {
"no-console": "off",
"no-extend-native": "off",
"react/jsx-props-no-spreading": "off",
"jsx-a11y/label-has-associated-control": "off",
"class-methods-use-this": "off",
"max-classes-per-file": "off",
"node/no-missing-import": "off",
"node/no-unpublished-import": "off",
"node/no-unsupported-features/es-syntax": ["error", {
"ignores": ["modules"]
}]
},
"env": {
"webextensions": true
},
"settings": {
"node": {
"tryExtensions": [".tsx"] // append tsx to the list as well
}
}
}

42
eslint.config.mjs Normal file
View File

@ -0,0 +1,42 @@
import nodeConfig from '@abhijithvijayan/eslint-config/node';
import tsConfig from '@abhijithvijayan/eslint-config/typescript';
import reactConfig from '@abhijithvijayan/eslint-config/react';
export default [
{
ignores: [
'node_modules/**',
'extension/**',
'*.js',
'*.mjs',
'vite.config.ts',
],
},
...nodeConfig({
files: ['**/*.ts', '**/*.tsx'],
}),
...tsConfig({
files: ['**/*.ts', '**/*.tsx'],
}),
...reactConfig({
files: ['**/*.tsx'],
}),
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'no-console': 'off',
'@typescript-eslint/no-use-before-define': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
// Disable due to resolver issues in ESM
'import-x/no-duplicates': 'off',
},
},
{
files: ['**/*.tsx'],
rules: {
'react/jsx-props-no-spreading': 'off',
'react/react-in-jsx-scope': 'off',
'jsx-a11y/label-has-associated-control': 'off',
},
},
];

4187
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,8 +20,8 @@
"build:chrome": "cross-env TARGET_BROWSER=chrome vite build --config vite.config.ts",
"build:firefox": "cross-env TARGET_BROWSER=firefox vite build --config vite.config.ts",
"build": "npm run build:chrome && npm run build:firefox",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix"
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"advanced-css-reset": "^2.1.3",
@ -30,15 +30,28 @@
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"@abhijithvijayan/eslint-config": "^3.0.0",
"@abhijithvijayan/tsconfig": "^1.5.1",
"@types/node": "^25.0.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/webextension-polyfill": "^0.12.4",
"@typescript-eslint/eslint-plugin": "^8.51.0",
"@typescript-eslint/parser": "^8.51.0",
"@vitejs/plugin-react": "^5.1.2",
"autoprefixer": "^10.4.23",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^9.1.2",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-n": "^17.23.1",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"globals": "^15.15.0",
"postcss": "^8.5.6",
"prettier": "^3.7.4",
"sass": "^1.97.1",
"typescript": "^5.9.3",
"vite": "^7.3.0",

View File

@ -21,6 +21,9 @@ browser.runtime.onMessage.addListener(
// Log when content script loads (if logging is enabled)
getStorage(['enableLogging']).then(({enableLogging}) => {
if (enableLogging) {
console.log('[Web Extension Starter] Content script loaded on:', window.location.href);
console.log(
'[Web Extension Starter] Content script loaded on:',
window.location.href
);
}
});

View File

@ -1,20 +1,19 @@
import * as React from "react";
import { useEffect, useState } from "react";
import type { FC } from "react";
import { getStorage, setStorage } from "../utils/storage";
import { Button } from "../components/Button/Button";
import { Input } from "../components/Input/Input";
import { Checkbox } from "../components/Checkbox/Checkbox";
import { GitHubIcon } from "../components/icons/GitHubIcon";
import styles from "./Options.module.scss";
import {useEffect, useState} from 'react';
import type {FC} from 'react';
import {getStorage, setStorage} from '../utils/storage';
import {Button} from '../components/Button/Button';
import {Input} from '../components/Input/Input';
import {Checkbox} from '../components/Checkbox/Checkbox';
import {GitHubIcon} from '../components/icons/GitHubIcon';
import styles from './Options.module.scss';
const Options: FC = () => {
const [username, setUsername] = useState("");
const [username, setUsername] = useState('');
const [enableLogging, setEnableLogging] = useState(false);
const [saved, setSaved] = useState(false);
useEffect(() => {
getStorage(["username", "enableLogging"]).then((result) => {
getStorage(['username', 'enableLogging']).then((result) => {
setUsername(result.username);
setEnableLogging(result.enableLogging);
});
@ -22,7 +21,7 @@ const Options: FC = () => {
const handleSave = async (e: React.FormEvent): Promise<void> => {
e.preventDefault();
await setStorage({ username, enableLogging });
await setStorage({username, enableLogging});
setSaved(true);
setTimeout(() => setSaved(false), 2000);
};

View File

@ -1,17 +1,17 @@
import * as React from 'react';
import ReactDOM from 'react-dom/client';
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import Options from './Options';
const container = document.getElementById('options-root');
if (!container) {
throw new Error("Could not find root container to mount the app");
throw new Error('Could not find root container to mount the app');
}
const root = ReactDOM.createRoot(container);
const root = createRoot(container);
root.render(
<React.StrictMode>
<Options />
</React.StrictMode>
);
<StrictMode>
<Options />
</StrictMode>
);

View File

@ -1,14 +1,13 @@
import * as React from "react";
import { useEffect, useState } from "react";
import type { FC } from "react";
import browser, { Tabs } from "webextension-polyfill";
import { getStorage } from "../utils/storage";
import { TabInfo } from "./components/TabInfo/TabInfo";
import { FooterActions } from "./components/FooterActions/FooterActions";
import styles from "./Popup.module.scss";
import {useEffect, useState} from 'react';
import type {FC} from 'react';
import browser, {Tabs} from 'webextension-polyfill';
import {getStorage} from '../utils/storage';
import {TabInfo} from './components/TabInfo/TabInfo';
import {FooterActions} from './components/FooterActions/FooterActions';
import styles from './Popup.module.scss';
function openWebPage(url: string): Promise<Tabs.Tab> {
return browser.tabs.create({ url });
return browser.tabs.create({url});
}
interface TabData {
@ -19,21 +18,21 @@ interface TabData {
const Popup: FC = () => {
const [tabInfo, setTabInfo] = useState<TabData | null>(null);
const [username, setUsername] = useState<string>("");
const [username, setUsername] = useState<string>('');
useEffect(() => {
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
browser.tabs.query({active: true, currentWindow: true}).then((tabs) => {
const tab = tabs[0];
if (tab) {
setTabInfo({
title: tab.title || "Unknown",
url: tab.url || "Unknown",
title: tab.title || 'Unknown',
url: tab.url || 'Unknown',
favIconUrl: tab.favIconUrl,
});
}
});
getStorage(["username"]).then(({ username: storedUsername }) => {
getStorage(['username']).then(({username: storedUsername}) => {
setUsername(storedUsername);
});
}, []);
@ -69,13 +68,15 @@ const Popup: FC = () => {
<FooterActions
onSettings={(): Promise<Tabs.Tab> =>
openWebPage("/Options/options.html")
openWebPage('/Options/options.html')
}
onGitHub={(): Promise<Tabs.Tab> =>
openWebPage("https://github.com/abhijithvijayan/web-extension-starter")
openWebPage(
'https://github.com/abhijithvijayan/web-extension-starter'
)
}
onSupport={(): Promise<Tabs.Tab> =>
openWebPage("https://www.buymeacoffee.com/abhijithvijayan")
openWebPage('https://www.buymeacoffee.com/abhijithvijayan')
}
/>
</section>

View File

@ -1,10 +1,9 @@
import * as React from "react";
import type { FC } from "react";
import { Button } from "../../../components/Button/Button";
import { SettingsIcon } from "../../../components/icons/SettingsIcon";
import { GitHubIcon } from "../../../components/icons/GitHubIcon";
import { HeartIcon } from "../../../components/icons/HeartIcon";
import styles from "./FooterActions.module.scss";
import type {FC} from 'react';
import {Button} from '../../../components/Button/Button';
import {SettingsIcon} from '../../../components/icons/SettingsIcon';
import {GitHubIcon} from '../../../components/icons/GitHubIcon';
import {HeartIcon} from '../../../components/icons/HeartIcon';
import styles from './FooterActions.module.scss';
interface FooterActionsProps {
onSettings: () => void;
@ -16,36 +15,34 @@ export const FooterActions: FC<FooterActionsProps> = ({
onSettings,
onGitHub,
onSupport,
}) => {
return (
<div className={styles.footer}>
<Button
variant="settings"
size="small"
className={styles.button}
onClick={onSettings}
>
<SettingsIcon />
<span>Settings</span>
</Button>
<Button
variant="github"
size="small"
className={styles.button}
onClick={onGitHub}
>
<GitHubIcon />
<span>GitHub</span>
</Button>
<Button
variant="support"
size="small"
className={styles.button}
onClick={onSupport}
>
<HeartIcon />
<span>Support</span>
</Button>
</div>
);
};
}) => (
<div className={styles.footer}>
<Button
variant="settings"
size="small"
className={styles.button}
onClick={onSettings}
>
<SettingsIcon />
<span>Settings</span>
</Button>
<Button
variant="github"
size="small"
className={styles.button}
onClick={onGitHub}
>
<GitHubIcon />
<span>GitHub</span>
</Button>
<Button
variant="support"
size="small"
className={styles.button}
onClick={onSupport}
>
<HeartIcon />
<span>Support</span>
</Button>
</div>
);

View File

@ -1,8 +1,7 @@
import * as React from "react";
import type { FC } from "react";
import { Card } from "../../../components/Card/Card";
import { Button } from "../../../components/Button/Button";
import styles from "./TabInfo.module.scss";
import type {FC} from 'react';
import {Card} from '../../../components/Card/Card';
import {Button} from '../../../components/Button/Button';
import styles from './TabInfo.module.scss';
interface TabInfoProps {
title: string;
@ -17,9 +16,7 @@ export const TabInfo: FC<TabInfoProps> = ({
favIconUrl,
onReload,
}) => {
const getInitial = (text: string): string => {
return text.charAt(0).toUpperCase();
};
const getInitial = (text: string): string => text.charAt(0).toUpperCase();
return (
<Card title="Current Tab">

View File

@ -1,17 +1,17 @@
import * as React from 'react';
import ReactDOM from 'react-dom/client';
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import Popup from './Popup';
const container = document.getElementById('popup-root');
if (!container) {
throw new Error("Could not find root container to mount the app");
throw new Error('Could not find root container to mount the app');
}
const root = ReactDOM.createRoot(container);
const root = createRoot(container);
root.render(
<React.StrictMode>
<Popup />
</React.StrictMode>
);
<StrictMode>
<Popup />
</StrictMode>
);

View File

@ -1,9 +1,13 @@
import * as React from "react";
import type { FC, ReactNode, ButtonHTMLAttributes } from "react";
import styles from "./Button.module.scss";
import type {FC, ReactNode, ButtonHTMLAttributes} from 'react';
import styles from './Button.module.scss';
type ButtonVariant = "primary" | "secondary" | "settings" | "github" | "support";
type ButtonSize = "small" | "medium" | "large";
type ButtonVariant =
| 'primary'
| 'secondary'
| 'settings'
| 'github'
| 'support';
type ButtonSize = 'small' | 'medium' | 'large';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
@ -13,8 +17,8 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
}
export const Button: FC<ButtonProps> = ({
variant = "primary",
size = "medium",
variant = 'primary',
size = 'medium',
fullWidth = false,
children,
className,
@ -28,7 +32,7 @@ export const Button: FC<ButtonProps> = ({
className,
]
.filter(Boolean)
.join(" ");
.join(' ');
return (
<button type="button" className={classNames} {...props}>

View File

@ -1,27 +1,22 @@
import * as React from "react";
import type { FC, ReactNode } from "react";
import styles from "./Card.module.scss";
import type {FC, ReactNode} from 'react';
import styles from './Card.module.scss';
interface CardProps {
title?: string;
size?: "default" | "large";
size?: 'default' | 'large';
children: ReactNode;
className?: string;
}
export const Card: FC<CardProps> = ({
title,
size = "default",
size = 'default',
children,
className,
}) => {
const classNames = [
styles.card,
size === "large" && styles.large,
className,
]
const classNames = [styles.card, size === 'large' && styles.large, className]
.filter(Boolean)
.join(" ");
.join(' ');
return (
<div className={classNames}>

View File

@ -1,8 +1,10 @@
import * as React from "react";
import type { FC, InputHTMLAttributes } from "react";
import styles from "./Checkbox.module.scss";
import type {FC, InputHTMLAttributes} from 'react';
import styles from './Checkbox.module.scss';
interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
interface CheckboxProps extends Omit<
InputHTMLAttributes<HTMLInputElement>,
'type'
> {
label: string;
}
@ -12,27 +14,16 @@ export const Checkbox: FC<CheckboxProps> = ({
checked,
onChange,
...props
}) => {
const handleWrapperClick = (e: React.MouseEvent<HTMLLabelElement>): void => {
if ((e.target as HTMLElement).tagName !== "INPUT") {
const input = e.currentTarget.querySelector("input");
if (input) {
input.click();
}
}
};
return (
<label htmlFor={id} className={styles.wrapper} onClick={handleWrapperClick}>
<input
type="checkbox"
id={id}
className={styles.checkbox}
checked={checked}
onChange={onChange}
{...props}
/>
<span className={styles.text}>{label}</span>
</label>
);
};
}) => (
<label htmlFor={id} className={styles.wrapper}>
<input
type="checkbox"
id={id}
className={styles.checkbox}
checked={checked}
onChange={onChange}
{...props}
/>
<span className={styles.text}>{label}</span>
</label>
);

View File

@ -1,24 +1,21 @@
import * as React from "react";
import type { FC, InputHTMLAttributes } from "react";
import styles from "./Input.module.scss";
import type {FC, InputHTMLAttributes} from 'react';
import styles from './Input.module.scss';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
}
export const Input: FC<InputProps> = ({ label, id, className, ...props }) => {
return (
<div className={styles.wrapper}>
{label && (
<label htmlFor={id} className={styles.label}>
{label}
</label>
)}
<input
id={id}
className={`${styles.input} ${className || ""}`.trim()}
{...props}
/>
</div>
);
};
export const Input: FC<InputProps> = ({label, id, className, ...props}) => (
<div className={styles.wrapper}>
{label && (
<label htmlFor={id} className={styles.label}>
{label}
</label>
)}
<input
id={id}
className={`${styles.input} ${className || ''}`.trim()}
{...props}
/>
</div>
);

View File

@ -1,11 +1,10 @@
import * as React from "react";
import type { FC } from "react";
import type {FC} from 'react';
interface IconProps {
size?: number;
}
export const GitHubIcon: FC<IconProps> = ({ size = 14 }) => (
export const GitHubIcon: FC<IconProps> = ({size = 14}) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>

View File

@ -1,11 +1,10 @@
import * as React from "react";
import type { FC } from "react";
import type {FC} from 'react';
interface IconProps {
size?: number;
}
export const HeartIcon: FC<IconProps> = ({ size = 14 }) => (
export const HeartIcon: FC<IconProps> = ({size = 14}) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg>

View File

@ -1,11 +1,10 @@
import * as React from "react";
import type { FC } from "react";
import type {FC} from 'react';
interface IconProps {
size?: number;
}
export const SettingsIcon: FC<IconProps> = ({ size = 14 }) => (
export const SettingsIcon: FC<IconProps> = ({size = 14}) => (
<svg
width={size}
height={size}

6
source/globals.d.ts vendored
View File

@ -1,5 +1,5 @@
// https://www.typescriptlang.org/tsconfig/#noUncheckedSideEffectImports
declare module "*.scss" {
const content: { [className: string]: string };
declare module '*.scss' {
const content: {[className: string]: string};
export default content;
}
}

View File

@ -15,7 +15,10 @@
/* Bundler-specific settings */
"moduleResolution": "bundler",
"isolatedModules": true,
"declaration": false
"declaration": false,
/* React JSX transform */
"jsx": "react-jsx"
},
"include": [
"source"

View File

@ -1,134 +1,135 @@
import { defineConfig } from "vite";
import path from "node:path";
import react from "@vitejs/plugin-react";
import process from "node:process";
import zipPack from "vite-plugin-zip-pack";
import { defineConfig } from 'vite';
import path from 'node:path';
import react from '@vitejs/plugin-react';
import process from 'node:process';
import zipPack from 'vite-plugin-zip-pack';
import checker from 'vite-plugin-checker';
import clean from 'vite-plugin-clean';
import WextManifest from "vite-plugin-wext-manifest";
import WextManifest from 'vite-plugin-wext-manifest';
export default defineConfig(({ mode }) => {
const isDevelopment = mode !== "production";
const sourcePath = path.resolve(__dirname, "source");
const destPath = path.resolve(__dirname, "extension");
const targetBrowser = process.env.TARGET_BROWSER || "chrome";
const isDevelopment = mode !== 'production';
const sourcePath = path.resolve(__dirname, 'source');
const destPath = path.resolve(__dirname, 'extension');
const targetBrowser = process.env.TARGET_BROWSER || 'chrome';
const getOutDir = () => path.resolve(destPath, targetBrowser);
const getOutDir = () => path.resolve(destPath, targetBrowser);
const getExtensionZipFileName = () => {
switch (targetBrowser) {
case 'opera': {
return `${targetBrowser}.crx`;
}
const getExtensionZipFileName = () => {
switch (targetBrowser) {
case 'opera': {
return `${targetBrowser}.crx`;
}
case 'firefox': {
return `${targetBrowser}.xpi`;
}
case 'firefox': {
return `${targetBrowser}.xpi`;
}
default: {
return `${targetBrowser}.zip`;
}
}
};
default: {
return `${targetBrowser}.zip`;
}
}
};
return {
root: sourcePath,
return {
root: sourcePath,
publicDir: path.resolve(sourcePath, "public"),
publicDir: path.resolve(sourcePath, 'public'),
resolve: {
alias: {
"@": path.resolve(sourcePath),
"~": path.resolve(__dirname, "node_modules"),
},
},
resolve: {
alias: {
'@': path.resolve(sourcePath),
'~': path.resolve(__dirname, 'node_modules'),
},
},
define: {
__DEV__: isDevelopment,
__TARGET_BROWSER__: JSON.stringify(targetBrowser),
},
define: {
__DEV__: isDevelopment,
__TARGET_BROWSER__: JSON.stringify(targetBrowser),
},
plugins: [
react(),
plugins: [
react(),
// delete previous built compressed file
clean({
targetFiles: [
path.resolve(destPath, getExtensionZipFileName())
],
}),
// delete previous built compressed file
clean({
targetFiles: [path.resolve(destPath, getExtensionZipFileName())],
}),
// Run typescript checker in worker thread
checker({
typescript: {
tsconfigPath: './tsconfig.json'
},
}),
// Run typescript checker in worker thread
checker({
typescript: {
tsconfigPath: './tsconfig.json',
},
}),
// Generate manifest.json for the browser
WextManifest({
manifestPath: "manifest.json",
usePackageJSONVersion: true,
}),
// Generate manifest.json for the browser
WextManifest({
manifestPath: 'manifest.json',
usePackageJSONVersion: true,
}),
!isDevelopment &&
zipPack({
inDir: getOutDir(),
outDir: destPath,
outFileName: getExtensionZipFileName(),
enableLogging: true,
}),
],
!isDevelopment &&
zipPack({
inDir: getOutDir(),
outDir: destPath,
outFileName: getExtensionZipFileName(),
enableLogging: true,
}),
],
build: {
outDir: getOutDir(),
build: {
outDir: getOutDir(),
emptyOutDir: !isDevelopment,
emptyOutDir: !isDevelopment,
sourcemap: isDevelopment ? "inline" : false,
sourcemap: isDevelopment ? 'inline' : false,
minify: mode === "production",
minify: mode === 'production',
rollupOptions: {
input: {
// For UI pages, use the HTML file as the entry.
// Vite will find the <script> tag inside and bundle it.
popup: path.resolve(sourcePath, 'Popup/popup.html'),
options: path.resolve(sourcePath, 'Options/options.html'),
// For script-only parts, use the TS file directly.
background: path.resolve(sourcePath, 'Background/index.ts'),
contentScript: path.resolve(sourcePath, 'ContentScript/index.ts'),
},
rollupOptions: {
input: {
// For UI pages, use the HTML file as the entry.
// Vite will find the <script> tag inside and bundle it.
popup: path.resolve(sourcePath, 'Popup/popup.html'),
options: path.resolve(sourcePath, 'Options/options.html'),
// For script-only parts, use the TS file directly.
background: path.resolve(sourcePath, 'Background/index.ts'),
contentScript: path.resolve(sourcePath, 'ContentScript/index.ts'),
},
output: {
// For main entry scripts (background, contentScript, etc.)
entryFileNames: "assets/js/[name].bundle.js",
output: {
// For main entry scripts (background, contentScript, etc.)
entryFileNames: 'assets/js/[name].bundle.js',
// For other assets like CSS
assetFileNames: (assetInfo) => {
if (!!assetInfo.name && /\.(css|s[ac]ss|less)$/.test(assetInfo.name)) {
return "assets/css/[name]-[hash].css";
}
// For other assets like CSS
assetFileNames: (assetInfo) => {
if (
!!assetInfo.name &&
/\.(css|s[ac]ss|less)$/.test(assetInfo.name)
) {
return 'assets/css/[name]-[hash].css';
}
// For other assets like fonts or images
return "assets/[name]-[hash].[ext]";
},
// For other assets like fonts or images
return 'assets/[name]-[hash].[ext]';
},
// For code-split chunks (if any)
chunkFileNames: "assets/js/[name]-[hash].chunk.js",
},
},
// For code-split chunks (if any)
chunkFileNames: 'assets/js/[name]-[hash].chunk.js',
},
},
terserOptions: {
mangle: true,
compress: {
drop_console: true,
drop_debugger: true,
},
format: {
comments: false,
},
},
},
};
terserOptions: {
mangle: true,
compress: {
drop_console: true,
drop_debugger: true,
},
format: {
comments: false,
},
},
},
};
});