Setup basic trading enhancements

This commit is contained in:
MusicalBean 2023-08-26 18:55:40 -07:00
parent 041527e7a4
commit 5f9f1faa93
33 changed files with 28599 additions and 1236 deletions

4
.gitignore vendored
View File

@ -170,7 +170,7 @@ typings/
# nuxt.js build output
.nuxt
# react / gatsby
# react / gatsby
public/
# vuepress build output
@ -201,4 +201,4 @@ dist/
.yarn/*
!.yarn/releases
!.yarn/plugins
.pnp.*
.pnp.*

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"tabWidth": 4,
"useTabs": false
}

27598
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
import Trader from "../trader";
export default class OwnsBadge {
static create(trader: Trader): HTMLSpanElement {
let badge = document.createElement("a");
badge.classList.add("et-badge");
if (trader.ownsList !== null) {
badge.href = trader.ownsList;
}
let recordings = trader.wantsCount === 1 ? "recording" : "recordings";
badge.innerHTML = `Owns <strong style="font-weight: semibold; color: #0f172a;">${trader.ownsCount} ${recordings}</strong> that I want`;
return badge;
}
}

View File

@ -0,0 +1,26 @@
export default class Star {
static create(): SVGElement {
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("height", "1em");
svg.setAttribute("viewBox", "0 0 576 512");
svg.style.position = "absolute";
svg.style.top = "0";
svg.style.left = "-28px";
svg.style.width = "20px";
svg.style.height = "20px";
let path = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
path.setAttribute(
"d",
"M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"
);
path.setAttribute("fill", "#f59e0b");
svg.appendChild(path);
return svg;
}
}

View File

@ -0,0 +1,18 @@
import Trader from "../trader";
export default class WantsBadge {
static create(trader: Trader): HTMLSpanElement {
let badge = document.createElement("a");
badge.classList.add("et-badge");
if (trader.wantsList !== null) {
badge.href = trader.wantsList;
}
let recordings = trader.wantsCount === 1 ? "recording" : "recordings";
badge.innerHTML = `Wants <strong style="font-weight: semibold; color: #0f172a;">${trader.wantsCount} ${recordings}</strong> that I own`;
return badge;
}
}

View File

@ -1,3 +1,10 @@
console.log('helloworld from content script');
import WhoOwnsEnhancer from "./page-enhancers/who-owns-enhancer";
import WhoWantsEnhancer from "./page-enhancers/who-wants-enhancer";
export {};
if (window.location.href.includes("who-owns.php")) {
WhoOwnsEnhancer.enhance(document);
}
if (window.location.href.includes("who-wants.php")) {
WhoWantsEnhancer.enhance(document);
}

View File

@ -0,0 +1,79 @@
import OwnsBadge from "../element-factories/owns-badge";
import Star from "../element-factories/star";
import WantsBadge from "../element-factories/wants-badge";
import Trader from "../trader";
import TraderRepository from "../trader-repository";
export default class ProfileLink {
constructor(
public element: HTMLAnchorElement,
public traderRepository: TraderRepository
) {}
get username(): string {
return this.element.innerText.trim();
}
get profile(): string {
return this.element.href;
}
get trader(): Trader | null {
return this.traderRepository.find(this.username);
}
get lastElement(): Element {
return this.element.nextElementSibling?.tagName === "SPAN"
? this.element.nextElementSibling!
: this.element;
}
addOwnsBadge() {
let trader = this.trader;
if (trader === null) {
throw new Error(
"Cannot add owns badge to a profile link that has no trader data"
);
}
let badge = OwnsBadge.create(this.trader!);
this.lastElement.insertAdjacentElement("afterend", badge);
}
addWantsBadge() {
let trader = this.trader;
if (trader === null) {
throw new Error(
"Cannot add wants badge to a profile link that has no trader data"
);
}
let badge = WantsBadge.create(this.trader!);
this.lastElement.insertAdjacentElement("afterend", badge);
}
addStar() {
let star = Star.create();
this.element.style.position = "relative";
this.element.style.fontWeight = "bold";
this.element.appendChild(star);
}
static getAll(page: Document, traderRepository: TraderRepository) {
return Array.from(
page.querySelectorAll('a[href*="/profile/view-profile.php?id="]')
).map(
(profileLink) =>
new ProfileLink(
profileLink as HTMLAnchorElement,
traderRepository
)
);
}
}

View File

@ -0,0 +1,22 @@
import ProfileLink from "../page-elements/profile-link";
import TraderRepository from "../trader-repository";
export default class WhoOwnsEnhancer {
static async enhance(page: Document) {
let repository = await TraderRepository.load();
ProfileLink.getAll(page, repository)
.filter((profileLink) => profileLink.trader !== null)
.filter((profileLink) => profileLink.trader!.wantsCount !== null)
.filter((profileLink) => profileLink.trader!.wantsCount! > 0)
.map((profileLink) => {
profileLink.addWantsBadge();
return profileLink;
})
.filter((profileLink) => profileLink.trader!.open)
.forEach((profileLink) => {
profileLink.addStar();
});
}
}

View File

@ -0,0 +1,22 @@
import ProfileLink from "../page-elements/profile-link";
import TraderRepository from "../trader-repository";
export default class WhoWantsEnhancer {
static async enhance(page: Document) {
let repository = await TraderRepository.load();
ProfileLink.getAll(page, repository)
.filter((profileLink) => profileLink.trader !== null)
.filter((profileLink) => profileLink.trader!.ownsCount !== null)
.filter((profileLink) => profileLink.trader!.ownsCount! > 0)
.map((profileLink) => {
profileLink.addOwnsBadge();
return profileLink;
})
.filter((profileLink) => profileLink.trader!.open)
.forEach((profileLink) => {
profileLink.addStar();
});
}
}

View File

@ -0,0 +1,127 @@
import { browser } from "webextension-polyfill-ts";
import Trader from "./trader";
export default class TraderRepository {
page: Document;
traders: Trader[];
constructor(traders: Trader[] = []) {
this.traders = traders;
}
static async load(): Promise<TraderRepository> {
if (!(await this.hasTradersInStorage())) {
await this.refreshGlobalTradersStorage();
}
let traders = await this.loadTradersFromStorage();
return new TraderRepository(traders!);
}
/**
* Fetches the traders who want my items from the Encora website.
*/
static async fetchTradersWhoWantMyItems(): Promise<Trader[]> {
const response = await fetch(
"https://encora.it/profile/trade_finder_theirs.php"
);
let text = await response.text();
let page = new DOMParser().parseFromString(text, "text/html");
let table = page.querySelector("table")!;
let rows = table.querySelectorAll("tr");
return Array.from(rows)
.map((row) => Trader.fromWantsRow(row as HTMLTableRowElement))
.filter((trader) => trader !== null) as Trader[];
}
/**
* Fetches the traders who own my wants from the Encora website.
*/
static async fetchTradersWhoOwnMyWants(): Promise<Trader[]> {
const response = await fetch(
"https://encora.it/profile/trade_finder.php"
);
let text = await response.text();
let page = new DOMParser().parseFromString(text, "text/html");
let table = page.querySelector("table")!;
let rows = table.querySelectorAll("tr");
return Array.from(rows)
.map((row) => Trader.fromOwnsRow(row as HTMLTableRowElement))
.filter((trader) => trader !== null) as Trader[];
}
/**
* Fetches the traders who want my items and the traders who own my wants
* from the Encora website and stores them in the global storage.
*/
static async refreshGlobalTradersStorage() {
let [wantsTraders, ownsTraders] = await Promise.all([
this.fetchTradersWhoWantMyItems(),
this.fetchTradersWhoOwnMyWants(),
]);
let traders = Trader.mergeLists(wantsTraders, ownsTraders).map(
(trader) => trader.toJSON()
);
browser.storage.local.set({ traders: traders });
}
/**
* Checks if the global storage has traders.
*/
static async hasTradersInStorage(): Promise<boolean> {
let storage = await browser.storage.local.get("traders");
return storage.traders != null;
}
/**
* Loads the traders from the global storage.
*/
static async loadTradersFromStorage(): Promise<Trader[]> {
let storage = await browser.storage.local.get("traders");
let traders = storage.traders;
if (traders == null) {
return [];
}
return traders.map((trader: any) => Trader.fromJSON(trader));
}
find(username: string): Trader | null {
return (
this.traders.find((trader) => trader.username === username) ?? null
);
}
static loadFromWhoOwnsPage(page: Document): TraderRepository {
let traders = Array.from(
page.querySelectorAll('a[href*="/profile/view-profile.php?id="]')
)
.map((profileLink) =>
Trader.fromProfileLink(profileLink as HTMLAnchorElement)
)
.filter((trader) => trader !== null);
return new TraderRepository(traders);
}
add(trader: Trader) {
this.traders.push(trader);
}
getAll(): Trader[] {
return this.traders;
}
getOpen(): Trader[] {
return this.traders.filter((trader) => trader.open);
}
}

View File

@ -0,0 +1,189 @@
interface RowData {
username: string;
profile: string;
open: boolean;
count: number;
list: string | null;
}
export default class Trader {
constructor(
public username: string,
public profile: string,
public open: boolean | null,
public ownsCount: number | null = null,
public ownsList: string | null = null,
public wantsCount: number | null = null,
public wantsList: string | null = null
) {}
/**
* Get the underlying trader data from a row in a "Trade finder" table.
*/
static parseRow(row: HTMLTableRowElement): RowData | null {
let cells = row.querySelectorAll("td");
if (cells.length < 2) {
return null;
}
let profileLink = cells[0].querySelector("a");
if (!profileLink) {
return null;
}
let username =
profileLink.querySelector("strong")?.innerText ??
profileLink.innerText;
let profile = profileLink.href;
let open =
cells[0]
.querySelector("span")
?.innerText.includes("Open to trades") ?? false;
let wantsLink = cells[1].querySelector("a");
let count = wantsLink
? parseInt(wantsLink.innerText.match(/\d+/)![0])
: 0;
let list = wantsLink ? wantsLink.href : null;
return {
username,
profile,
open,
count,
list,
};
}
/**
* Create a Trader from a row in a "Trade finder wants" table.
*/
static fromWantsRow(row: HTMLTableRowElement): Trader | null {
let data = Trader.parseRow(row);
if (data === null) {
return null;
}
return new Trader(
data.username,
data.profile,
data.open,
null,
null,
data.count,
data.list
);
}
/**
* Create a Trader from a row in a "Trade finder owns" table.
*/
static fromOwnsRow(row: HTMLTableRowElement): Trader | null {
let data = Trader.parseRow(row);
if (data === null) {
return null;
}
return new Trader(
data.username,
data.profile,
data.open,
data.count,
data.list,
null,
null
);
}
/**
* Create a Trader from a profile link.
*/
static fromProfileLink(profileLink: HTMLAnchorElement) {
let username = profileLink.innerText.trim();
let profile = profileLink.href;
let open = null;
if (profileLink.nextElementSibling?.nodeName === "SPAN") {
open = (<HTMLSpanElement>(
profileLink.nextElementSibling
)).innerText.includes("Open to trades");
}
return new Trader(username, profile, open, null, null);
}
/**
* Merge two traders together.
*/
merge(trader: Trader): Trader {
return new Trader(
this.username,
this.profile,
this.open ?? trader.open,
this.ownsCount ?? trader.ownsCount,
this.ownsList ?? trader.ownsList,
this.wantsCount ?? trader.wantsCount,
this.wantsList ?? trader.wantsList
);
}
/**
* Merge two lists of traders together.
*/
static mergeLists(list1: Trader[], list2: Trader[]): Trader[] {
let merged = list1.map((trader) => {
let trader2 = list2.find((t) => t.username === trader.username);
if (trader2 === undefined) {
return trader;
}
return trader.merge(trader2);
});
merged.push(
...list2.filter(
(trader) => !merged.some((t) => t.username === trader.username)
)
);
return merged;
}
/**
* Convert a Trader to a JSON object.
*/
toJSON() {
return {
username: this.username,
profile: this.profile,
open: this.open,
ownsCount: this.ownsCount,
ownsList: this.ownsList,
wantsCount: this.wantsCount,
wantsList: this.wantsList,
};
}
/**
* Convert a JSON object to a Trader.
*/
static fromJSON(json: any) {
return new Trader(
json.username,
json.profile,
json.open,
json.ownsCount,
json.ownsList,
json.wantsCount,
json.wantsList
);
}
}

View File

@ -1,34 +0,0 @@
import * as React from 'react';
import './styles.scss';
const Options: React.FC = () => {
return (
<div>
<form>
<p>
<label htmlFor="username">Your Name</label>
<br />
<input
type="text"
id="username"
name="username"
spellCheck="false"
autoComplete="off"
required
/>
</p>
<p>
<label htmlFor="logging">
<input type="checkbox" name="logging" /> Show the features enabled
on each page in the console
</label>
<p>cool cool cool</p>
</p>
</form>
</div>
);
};
export default Options;

View File

@ -1,6 +0,0 @@
import * as React from 'react';
import ReactDOM from 'react-dom';
import Options from './Options';
ReactDOM.render(<Options />, document.getElementById('options-root'));

View File

@ -1,10 +0,0 @@
@import "../styles/fonts";
@import "../styles/reset";
@import "../styles/variables";
@import "~webext-base-css/webext-base.css";
body {
color: $black;
background-color: $greyWhite;
}

View File

@ -1,55 +0,0 @@
import * as React from 'react';
import {browser, Tabs} from 'webextension-polyfill-ts';
import './styles.scss';
function openWebPage(url: string): Promise<Tabs.Tab> {
return browser.tabs.create({url});
}
const Popup: React.FC = () => {
return (
<section id="popup">
<h2>WEB-EXTENSION-STARTER</h2>
<button
id="options__button"
type="button"
onClick={(): Promise<Tabs.Tab> => {
return openWebPage('options.html');
}}
>
Options Page
</button>
<div className="links__holder">
<ul>
<li>
<button
type="button"
onClick={(): Promise<Tabs.Tab> => {
return openWebPage(
'https://github.com/abhijithvijayan/web-extension-starter'
);
}}
>
GitHub
</button>
</li>
<li>
<button
type="button"
onClick={(): Promise<Tabs.Tab> => {
return openWebPage(
'https://www.buymeacoffee.com/abhijithvijayan'
);
}}
>
Buy Me A Coffee
</button>
</li>
</ul>
</div>
</section>
);
};
export default Popup;

View File

@ -1,6 +0,0 @@
import * as React from 'react';
import ReactDOM from 'react-dom';
import Popup from './Popup';
ReactDOM.render(<Popup />, document.getElementById('popup-root'));

View File

@ -1,53 +0,0 @@
@import "../styles/fonts";
@import "../styles/reset";
@import "../styles/variables";
body {
color: $black;
background-color: $greyWhite;
}
#popup {
min-width: 350px;
padding: 30px 20px;
h2 {
font-size: 25px;
text-align: center;
}
#options__button {
width: 50%;
background: green;
color: white;
font-weight: 500;
border-radius: 15px;
padding: 5px 10px;
justify-content: center;
margin: 20px auto;
cursor: pointer;
opacity: 0.8;
display: flex;
}
.links__holder {
ul {
display: flex;
margin-top: 1em;
justify-content: space-around;
li {
button {
border-radius: 25px;
font-size: 20px;
font-weight: 600;
padding: 10px 17px;
background-color: rgba(0, 0, 255, 0.7);
color: white;
cursor: pointer;
}
}
}
}
}

View File

@ -0,0 +1,14 @@
.et-badge {
background-color: #e2e8f0;
color: #334155;
font-size: 0.6em;
border-radius: 5px;
padding: 2px 6px;
margin-left: 5px;
}
.et-badge:hover {
background-color: #cbd5e1;
color: #020617;
text-decoration: none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,30 +1,27 @@
{
"manifest_version": 2,
"name": "Sample WebExtension",
"version": "0.0.0",
"name": "Encora Tools",
"version": "0.0.1",
"icons": {
"16": "assets/icons/favicon-16.png",
"32": "assets/icons/favicon-32.png",
"48": "assets/icons/favicon-48.png",
"128": "assets/icons/favicon-128.png"
"48": "assets/icons/tools-48.png",
"96": "assets/icons/tools-96.png"
},
"description": "Sample description",
"homepage_url": "https://github.com/abhijithvijayan/web-extension-starter",
"short_name": "Sample Name",
"description": "Adds extra features to the Encora trading site.",
"homepage_url": "https://musicalbean.carrd.co",
"short_name": "Encora Tools",
"permissions": [
"activeTab",
"storage",
"http://*/*",
"https://*/*"
"webRequest"
],
"content_security_policy": "script-src 'self'; object-src 'self'",
"__chrome|firefox__author": "abhijithvijayan",
"__chrome|firefox__author": "musicalbean",
"__opera__developer": {
"name": "abhijithvijayan"
"name": "musicalbean"
},
"__firefox__applications": {
@ -36,26 +33,6 @@
"__chrome__minimum_chrome_version": "49",
"__opera__minimum_opera_version": "36",
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "assets/icons/favicon-16.png",
"32": "assets/icons/favicon-32.png",
"48": "assets/icons/favicon-48.png",
"128": "assets/icons/favicon-128.png"
},
"default_title": "tiny title",
"__chrome|opera__chrome_style": false,
"__firefox__browser_style": false
},
"__chrome|opera__options_page": "options.html",
"options_ui": {
"page": "options.html",
"open_in_tab": true,
"__chrome__chrome_style": false
},
"background": {
"scripts": [
"js/background.bundle.js"
@ -65,11 +42,13 @@
"content_scripts": [{
"matches": [
"http://*/*",
"https://*/*"
"*://*.encora.it/*"
],
"js": [
"js/contentScript.bundle.js"
],
"css": [
"assets/css/extension.css"
]
}]
}
}

View File

@ -1 +0,0 @@
@import url("https://fonts.googleapis.com/css?family=Nunito:400,600");

View File

@ -1,10 +0,0 @@
@import '~advanced-css-reset/dist/reset.css';
// Add your custom reset rules here
* {
margin: 0;
padding: 0;
border: 0;
outline: 0;
}

View File

@ -1,23 +0,0 @@
// colors
$black: #0d0d0d;
$greyWhite: #f3f3f3;
$skyBlue: #8892b0;
// fonts
$nunito: "Nunito", sans-serif;
// font weights
$thin: 100;
$exlight: 200;
$light: 300;
$regular: 400;
$medium: 500;
$semibold: 600;
$bold: 700;
$exbold: 800;
$exblack: 900;
// other variables
.d-none {
display: none !important;
}

View File

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Options</title>
</head>
<body>
<div id="options-root"></div>
</body>
</html>

View File

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=500" />
<title>Popup</title>
</head>
<body>
<div id="popup-root"></div>
</body>
</html>

View File

@ -1,211 +1,203 @@
const path = require('path');
const webpack = require('webpack');
const FilemanagerPlugin = require('filemanager-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const ExtensionReloader = require('webpack-extension-reloader');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WextManifestWebpackPlugin = require('wext-manifest-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const path = require("path");
const webpack = require("webpack");
const FilemanagerPlugin = require("filemanager-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const ExtensionReloader = require("webpack-extension-reloader");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const WextManifestWebpackPlugin = require("wext-manifest-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const viewsPath = path.join(__dirname, 'views');
const sourcePath = path.join(__dirname, 'source');
const destPath = path.join(__dirname, 'extension');
const nodeEnv = process.env.NODE_ENV || 'development';
const viewsPath = path.join(__dirname, "views");
const sourcePath = path.join(__dirname, "source");
const destPath = path.join(__dirname, "extension");
const nodeEnv = process.env.NODE_ENV || "development";
const targetBrowser = process.env.TARGET_BROWSER;
const extensionReloaderPlugin =
nodeEnv === 'development'
? new ExtensionReloader({
port: 9090,
reloadPage: true,
entries: {
// TODO: reload manifest on update
contentScript: 'contentScript',
background: 'background',
extensionPage: ['popup', 'options'],
},
})
: () => {
this.apply = () => {};
};
nodeEnv === "development"
? new ExtensionReloader({
port: 9090,
reloadPage: true,
entries: {
// TODO: reload manifest on update
contentScript: "contentScript",
background: "background",
extensionPage: ["popup", "options"],
},
})
: () => {
this.apply = () => {};
};
const getExtensionFileType = (browser) => {
if (browser === 'opera') {
return 'crx';
}
if (browser === "opera") {
return "crx";
}
if (browser === 'firefox') {
return 'xpi';
}
if (browser === "firefox") {
return "xpi";
}
return 'zip';
return "zip";
};
module.exports = {
devtool: false, // https://github.com/webpack/webpack/issues/1194#issuecomment-560382342
devtool: false, // https://github.com/webpack/webpack/issues/1194#issuecomment-560382342
stats: {
all: false,
builtAt: true,
errors: true,
hash: true,
},
mode: nodeEnv,
entry: {
manifest: path.join(sourcePath, 'manifest.json'),
background: path.join(sourcePath, 'Background', 'index.ts'),
contentScript: path.join(sourcePath, 'ContentScript', 'index.ts'),
popup: path.join(sourcePath, 'Popup', 'index.tsx'),
options: path.join(sourcePath, 'Options', 'index.tsx'),
},
output: {
path: path.join(destPath, targetBrowser),
filename: 'js/[name].bundle.js',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
alias: {
'webextension-polyfill-ts': path.resolve(
path.join(__dirname, 'node_modules', 'webextension-polyfill-ts')
),
stats: {
all: false,
builtAt: true,
errors: true,
hash: true,
},
},
module: {
rules: [
{
type: 'javascript/auto', // prevent webpack handling json with its own loaders,
test: /manifest\.json$/,
use: {
loader: 'wext-manifest-loader',
options: {
usePackageJSONVersion: true, // set to false to not use package.json version for manifest
},
mode: nodeEnv,
entry: {
manifest: path.join(sourcePath, "manifest.json"),
background: path.join(sourcePath, "Background", "index.ts"),
contentScript: path.join(sourcePath, "ContentScript", "index.ts"),
},
output: {
path: path.join(destPath, targetBrowser),
filename: "js/[name].bundle.js",
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
alias: {
"webextension-polyfill-ts": path.resolve(
path.join(__dirname, "node_modules", "webextension-polyfill-ts")
),
},
exclude: /node_modules/,
},
{
test: /\.(js|ts)x?$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader, // It creates a CSS file per JS file which contains CSS
},
{
loader: 'css-loader', // Takes the CSS files and returns the CSS with imports and url(...) for Webpack
options: {
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'autoprefixer',
{
// Options
},
module: {
rules: [
{
type: "javascript/auto", // prevent webpack handling json with its own loaders,
test: /manifest\.json$/,
use: {
loader: "wext-manifest-loader",
options: {
usePackageJSONVersion: true, // set to false to not use package.json version for manifest
},
],
],
},
},
exclude: /node_modules/,
},
{
test: /\.(js|ts)x?$/,
loader: "babel-loader",
exclude: /node_modules/,
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader, // It creates a CSS file per JS file which contains CSS
},
{
loader: "css-loader", // Takes the CSS files and returns the CSS with imports and url(...) for Webpack
options: {
sourceMap: true,
},
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"autoprefixer",
{
// Options
},
],
],
},
},
},
"resolve-url-loader", // Rewrites relative paths in url() statements
"sass-loader", // Takes the Sass/SCSS file and compiles to the CSS
],
},
},
'resolve-url-loader', // Rewrites relative paths in url() statements
'sass-loader', // Takes the Sass/SCSS file and compiles to the CSS
],
},
],
},
},
plugins: [
// Plugin to not generate js bundle for manifest entry
new WextManifestWebpackPlugin(),
// Generate sourcemaps
new webpack.SourceMapDevToolPlugin({filename: false}),
new ForkTsCheckerWebpackPlugin(),
// environmental variables
new webpack.EnvironmentPlugin(['NODE_ENV', 'TARGET_BROWSER']),
// delete previous build files
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [
path.join(process.cwd(), `extension/${targetBrowser}`),
path.join(
process.cwd(),
`extension/${targetBrowser}.${getExtensionFileType(targetBrowser)}`
),
],
cleanStaleWebpackAssets: false,
verbose: true,
}),
new HtmlWebpackPlugin({
template: path.join(viewsPath, 'popup.html'),
inject: 'body',
chunks: ['popup'],
hash: true,
filename: 'popup.html',
}),
new HtmlWebpackPlugin({
template: path.join(viewsPath, 'options.html'),
inject: 'body',
chunks: ['options'],
hash: true,
filename: 'options.html',
}),
// write css file(s) to build folder
new MiniCssExtractPlugin({filename: 'css/[name].css'}),
// copy static assets
new CopyWebpackPlugin({
patterns: [{from: 'source/assets', to: 'assets'}],
}),
// plugin to enable browser reloading in development mode
extensionReloaderPlugin,
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
format: {
comments: false,
},
},
extractComments: false,
}),
new OptimizeCSSAssetsPlugin({
cssProcessorPluginOptions: {
preset: ['default', {discardComments: {removeAll: true}}],
},
}),
new FilemanagerPlugin({
events: {
onEnd: {
archive: [
{
format: 'zip',
source: path.join(destPath, targetBrowser),
destination: `${path.join(destPath, targetBrowser)}.${getExtensionFileType(targetBrowser)}`,
options: {zlib: {level: 6}},
},
plugins: [
// Plugin to not generate js bundle for manifest entry
new WextManifestWebpackPlugin(),
// Generate sourcemaps
new webpack.SourceMapDevToolPlugin({ filename: false }),
new ForkTsCheckerWebpackPlugin(),
// environmental variables
new webpack.EnvironmentPlugin(["NODE_ENV", "TARGET_BROWSER"]),
// delete previous build files
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [
path.join(process.cwd(), `extension/${targetBrowser}`),
path.join(
process.cwd(),
`extension/${targetBrowser}.${getExtensionFileType(
targetBrowser
)}`
),
],
},
},
}),
cleanStaleWebpackAssets: false,
verbose: true,
}),
// write css file(s) to build folder
new MiniCssExtractPlugin({ filename: "css/[name].css" }),
// copy static assets
new CopyWebpackPlugin({
patterns: [{ from: "source/assets", to: "assets" }],
}),
// plugin to enable browser reloading in development mode
extensionReloaderPlugin,
],
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
format: {
comments: false,
},
},
extractComments: false,
}),
new OptimizeCSSAssetsPlugin({
cssProcessorPluginOptions: {
preset: [
"default",
{ discardComments: { removeAll: true } },
],
},
}),
new FilemanagerPlugin({
events: {
onEnd: {
archive: [
{
format: "zip",
source: path.join(destPath, targetBrowser),
destination: `${path.join(
destPath,
targetBrowser
)}.${getExtensionFileType(targetBrowser)}`,
options: { zlib: { level: 6 } },
},
],
},
},
}),
],
},
};

1062
yarn.lock

File diff suppressed because it is too large Load Diff