Skip to content

Commit ad48037

Browse files
committed
feat: implement build system with StandardBuild and GroupedBuild classes, integrating piscina for worker management
1 parent 26943b6 commit ad48037

7 files changed

Lines changed: 196 additions & 66 deletions

File tree

builder/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"chokidar": "^5.0.0",
4141
"esbuild": "^0.28.0",
4242
"eslint": "^10.2.1",
43+
"piscina": "^5.1.4",
4344
"tldts": "^7.0.28",
4445
"tsx": "^4.21.0",
4546
"typescript": "^6.0.3",

builder/source/build-core.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as Zod from 'zod'
2+
import * as Process from 'node:process'
3+
import { FetchAdShieldDomains, type TASDomainContainer } from './references/index.js'
4+
import { SafeInitCwd } from './utils/safe-init-cwd.js'
5+
6+
export type BuildOptions = {
7+
Minify: boolean
8+
BuildType: 'production' | 'development',
9+
SubscriptionUrl: string,
10+
Version?: string
11+
}
12+
13+
export class Build {
14+
protected Options: {
15+
Minify: boolean
16+
BuildType: 'production' | 'development'
17+
SubscriptionUrl: URL
18+
Version?: string
19+
} | undefined = undefined
20+
protected ProjectRoot = SafeInitCwd({ Cwd: Process.cwd(), InitCwd: Process.env.INIT_CWD })
21+
protected FetchedDomains: TASDomainContainer = new Map()
22+
23+
constructor(Option: BuildOptions) {
24+
this.Options = Zod.strictObject({
25+
Minify: Zod.boolean(),
26+
BuildType: Zod.enum(['production', 'development']),
27+
SubscriptionUrl: Zod.string().transform(Value => new URL(Value)).default(new URL('https://cdn.jsdelivr.net/npm/@filteringdev/tinyshield@latest/dist/tinyShield.user.js')),
28+
Version: Zod.string().optional()
29+
}).parse(Option)
30+
}
31+
32+
async Init() {
33+
console.log('[build-core.ts] this.FetchDomains is empty. Fetching domains started.')
34+
this.FetchedDomains = await FetchAdShieldDomains()
35+
console.log('[build-core.ts] Fetching domains completed.')
36+
}
37+
38+
protected CopyStateFrom(From: Build): void {
39+
this.Options = From.Options
40+
this.ProjectRoot = From.ProjectRoot
41+
this.FetchedDomains = From.FetchedDomains
42+
}
43+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as ESBuild from 'esbuild'
2+
import * as Piscina from 'piscina'
3+
import { CreateBanner } from './banner/index.js'
4+
import type { BuildOptions } from './build-core.js'
5+
6+
type WorkerData = {
7+
Config: BuildOptions
8+
ProjectRoot: string
9+
OutfileName: string
10+
}
11+
12+
export default async function Worker({ FileName, Domains }: { FileName: Set<string>, Domains: Set<string> }) {
13+
const WorkerData: WorkerData = Piscina.workerData as WorkerData
14+
15+
const Banner = CreateBanner({
16+
Version: WorkerData.Config.Version!,
17+
BuildType: WorkerData.Config.BuildType ?? 'production',
18+
Domains: Domains,
19+
Name: 'tinyShield',
20+
Namespace: 'https://github.com/FilteringDev/tinyShield',
21+
DownloadURL: new URL(WorkerData.Config.SubscriptionUrl),
22+
UpdateURL: new URL(WorkerData.Config.SubscriptionUrl),
23+
HomepageURL: new URL('https://github.com/FilteringDev/tinyShield'),
24+
SupportURL: new URL('https://github.com/FilteringDev/tinyShield/issues'),
25+
License: 'MPL-2.0',
26+
Author: 'PiQuark6046 and contributors',
27+
Description: {
28+
en: 'tinyShield allows AdGuard, uBlock Origin, Brave and ABP to resist against Ad-Shield quickly.',
29+
ko: 'tinyShield는 AdGuard, uBlock Origin, Brave 와 ABP가 애드쉴드에 빠르게 저항할 수 있도록 합니다.',
30+
ja: 'tinyShieldを使うと、AdGuard, uBlock Origin, Brave, およびABPがAd-Shieldに素早く対抗できます。'
31+
}
32+
})
33+
34+
await ESBuild.build({
35+
entryPoints: [WorkerData.ProjectRoot + '/userscript/source/index.ts'],
36+
bundle: true,
37+
minify: WorkerData.Config.Minify,
38+
outfile: `${WorkerData.ProjectRoot}/dist/grouped/tinyShield-${[...FileName][0]}.user.js`,
39+
banner: {
40+
js: Banner
41+
},
42+
target: ['es2024', 'chrome119', 'firefox142', 'safari26']
43+
})
44+
}

builder/source/build-grouped.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as Piscina from 'piscina'
2+
import * as Path from 'node:path'
3+
import * as Process from 'node:process'
4+
import PackageJson from '@npmcli/package-json'
5+
import { Build, type BuildOptions } from './build-core.js'
6+
7+
export class GroupedBuild extends Build {
8+
constructor(FromOrOption: Build | BuildOptions, Option?: BuildOptions) {
9+
if (FromOrOption instanceof Build) {
10+
super(Option!)
11+
this.CopyStateFrom(FromOrOption)
12+
return
13+
}
14+
15+
super(FromOrOption)
16+
}
17+
18+
private TransformDomainSetToFileName(DomainSet: Set<Set<string>>): Set<Set<string>> {
19+
const Result = new Set<Set<string>>()
20+
for (const Value of DomainSet) {
21+
const FileNameSet = new Set<string>()
22+
for (const Domain of Value) {
23+
FileNameSet.add(Domain.endsWith('.*') ? Domain.replaceAll(/\.\*$/g, '.tld') : Domain)
24+
}
25+
Result.add(FileNameSet)
26+
}
27+
return Result
28+
}
29+
30+
async Build() {
31+
if (!this.Options) {
32+
throw new Error('Build options are not initialized.')
33+
}
34+
35+
this.Options.Version ??= (await PackageJson.load(this.ProjectRoot)).content.version ?? '0.0.0'
36+
37+
const FileNameList: Set<Set<string>> = this.TransformDomainSetToFileName(new Set<Set<string>>(this.FetchedDomains.get('EachDomain')))
38+
const MatchingDomains: Set<Set<string>> = new Set<Set<string>>(this.FetchedDomains.get('EachDomainFull'))
39+
40+
const Workerpool = new Piscina.Piscina({
41+
filename: Path.resolve(this.ProjectRoot, 'builder/source/build-grouped-worker.ts'),
42+
execArgv: [...Process.execArgv, '--import=tsx'],
43+
workerData: { Config: { ...this.Options, SubscriptionUrl: this.Options.SubscriptionUrl.href }, ProjectRoot: this.ProjectRoot }
44+
})
45+
46+
const WorkerResults: Promise<void>[] = []
47+
for (let I = 0; I < FileNameList.size; I++) {
48+
WorkerResults.push(Workerpool.run({ FileName: [...FileNameList][I], Domains: [...MatchingDomains][I] }))
49+
}
50+
await Promise.all(WorkerResults)
51+
}
52+
}

builder/source/build.ts

Lines changed: 40 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,49 @@
11
import * as ESBuild from 'esbuild'
2-
import * as Zod from 'zod'
3-
import * as Process from 'node:process'
4-
import * as TLDTS from 'tldts'
52
import PackageJson from '@npmcli/package-json'
6-
import { FetchAdShieldDomains } from './references/index.js'
7-
import { CustomDefinedMatches } from './references/custom-defined.js'
8-
import { ConvertWildcardSuffixToRegexPattern } from './utils/wildcard-suffix-converter.js'
93
import { CreateBanner } from './banner/index.js'
10-
import { SafeInitCwd } from './utils/safe-init-cwd.js'
4+
import { Build, type BuildOptions } from './build-core.js'
115

12-
export type BuildOptions = {
13-
Minify: boolean
14-
BuildType: 'production' | 'development',
15-
SubscriptionUrl: string,
16-
Version?: string
17-
}
18-
19-
export async function Build(OptionsParam?: BuildOptions): Promise<void> {
20-
const Options = await Zod.strictObject({
21-
Minify: Zod.boolean(),
22-
BuildType: Zod.enum(['production', 'development']),
23-
SubscriptionUrl: Zod.string().transform(Value => new URL(Value)).default(new URL('https://cdn.jsdelivr.net/npm/@filteringdev/tinyshield@latest/dist/tinyShield.user.js')),
24-
Version: Zod.string().optional()
25-
}).parseAsync(OptionsParam)
26-
27-
const FetchedDomains = await FetchAdShieldDomains()
28-
let MatchingDomains: Set<string> = new Set<string>(FetchedDomains.get('Normal'))
29-
CustomDefinedMatches.forEach(Domain => MatchingDomains.add(Domain))
30-
31-
MatchingDomains = new Set<string>([...MatchingDomains].map(Domain => TLDTS.parse(Domain).domain ?? Domain).filter((D): D is string => D !== null))
32-
for (const Domain of MatchingDomains) {
33-
if (Domain.endsWith('.*')) {
34-
MatchingDomains.delete(Domain)
35-
ConvertWildcardSuffixToRegexPattern(Domain).forEach(GeneratedPattern => MatchingDomains.add(GeneratedPattern))
6+
export class StandardBuild extends Build {
7+
constructor(FromOrOption: Build | BuildOptions, Option?: BuildOptions) {
8+
if (FromOrOption instanceof Build) {
9+
super(Option!)
10+
this.CopyStateFrom(FromOrOption)
11+
return
3612
}
13+
14+
super(FromOrOption)
3715
}
3816

39-
let ProjectRoot = SafeInitCwd({ Cwd: Process.cwd(), InitCwd: Process.env.INIT_CWD })
40-
41-
const Banner = CreateBanner({
42-
Version: Options.Version ?? (await PackageJson.load(ProjectRoot)).content.version ?? '0.0.0',
43-
BuildType: Options.BuildType ?? 'production',
44-
Domains: MatchingDomains,
45-
Name: 'tinyShield',
46-
Namespace: 'https://github.com/FilteringDev/tinyShield',
47-
DownloadURL: Options.SubscriptionUrl,
48-
UpdateURL: Options.SubscriptionUrl,
49-
HomepageURL: new URL('https://github.com/FilteringDev/tinyShield'),
50-
SupportURL: new URL('https://github.com/FilteringDev/tinyShield/issues'),
51-
License: 'MPL-2.0',
52-
Author: 'PiQuark6046 and contributors',
53-
Description: {
54-
en: 'tinyShield allows AdGuard, uBlock Origin, Brave and ABP to resist against Ad-Shield quickly.',
55-
ko: 'tinyShield는 AdGuard, uBlock Origin, Brave 와 ABP가 애드쉴드에 빠르게 저항할 수 있도록 합니다.',
56-
ja: 'tinyShieldを使うと、AdGuard, uBlock Origin, Brave, およびABPがAd-Shieldに素早く対抗できます。'
57-
}
58-
})
17+
async Build() {
18+
let MatchingDomains: Set<string> = new Set<string>(this.FetchedDomains.get('Full'))
19+
const Banner = CreateBanner({
20+
Version: this.Options?.Version ?? (await PackageJson.load(this.ProjectRoot)).content.version ?? '0.0.0',
21+
BuildType: this.Options!.BuildType ?? 'production',
22+
Domains: MatchingDomains,
23+
Name: 'tinyShield',
24+
Namespace: 'https://github.com/FilteringDev/tinyShield',
25+
DownloadURL: this.Options!.SubscriptionUrl,
26+
UpdateURL: this.Options!.SubscriptionUrl,
27+
HomepageURL: new URL('https://github.com/FilteringDev/tinyShield'),
28+
SupportURL: new URL('https://github.com/FilteringDev/tinyShield/issues'),
29+
License: 'MPL-2.0',
30+
Author: 'PiQuark6046 and contributors',
31+
Description: {
32+
en: 'tinyShield allows AdGuard, uBlock Origin, Brave and ABP to resist against Ad-Shield quickly.',
33+
ko: 'tinyShield는 AdGuard, uBlock Origin, Brave 와 ABP가 애드쉴드에 빠르게 저항할 수 있도록 합니다.',
34+
ja: 'tinyShieldを使うと、AdGuard, uBlock Origin, Brave, およびABPがAd-Shieldに素早く対抗できます。'
35+
}
36+
})
5937

60-
await ESBuild.build({
61-
entryPoints: [ProjectRoot + '/userscript/source/index.ts'],
62-
bundle: true,
63-
minify: Options.Minify,
64-
outfile: `${ProjectRoot}/dist/tinyShield${Options.BuildType === 'development' ? '.dev' : ''}.user.js`,
65-
banner: {
66-
js: Banner
67-
},
68-
target: ['es2024', 'chrome119', 'firefox142', 'safari26']
69-
})
38+
await ESBuild.build({
39+
entryPoints: [this.ProjectRoot + '/userscript/source/index.ts'],
40+
bundle: true,
41+
minify: this.Options!.Minify,
42+
outfile: `${this.ProjectRoot}/dist/tinyShield${this.Options!.BuildType === 'development' ? '.dev' : ''}.user.js`,
43+
banner: {
44+
js: Banner
45+
},
46+
target: ['es2024', 'chrome119', 'firefox142', 'safari26']
47+
})
48+
}
7049
}

builder/source/buildci.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import * as Zod from 'zod'
22
import * as Process from 'node:process'
33
import { ParseArgumentsAndOptions, FilterArgumentsForOptions } from '@typescriptprime/parsing'
4-
import { Build, BuildOptions } from './build.js'
4+
import { StandardBuild } from './build.js'
5+
import { GroupedBuild } from './build-grouped.js'
6+
import { Build } from './build-core.js'
7+
import type { BuildOptions } from './build-core.js'
58

69
let ParsedArgv = (await ParseArgumentsAndOptions<BuildOptions>(FilterArgumentsForOptions(Process.argv))).Options
710
let Options = await Zod.strictObject({
@@ -10,4 +13,9 @@ let Options = await Zod.strictObject({
1013
SubscriptionUrl: Zod.string()
1114
}).parseAsync(ParsedArgv)
1215

13-
await Build(Options)
16+
const CoreBuildInstance = new Build(Options)
17+
await CoreBuildInstance.Init()
18+
await new StandardBuild(CoreBuildInstance, Options).Build()
19+
console.log('StandardBuild completed successfully.')
20+
await new GroupedBuild(CoreBuildInstance, Options).Build()
21+
console.log('GroupedBuild completed successfully.')

builder/source/debug.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import * as Process from 'node:process'
44
import * as Crypto from 'node:crypto'
55
import * as Fs from 'node:fs'
66
import { RunDebugServer } from './utils/http-server.js'
7-
import { Build } from './build.js'
7+
import { StandardBuild } from './build.js'
88
import { SafeInitCwd } from './utils/safe-init-cwd.js'
9+
import { Build } from './build-core.js'
910

1011
let ProjectRoot = SafeInitCwd({ Cwd: Process.cwd(), InitCwd: Process.env.INIT_CWD })
1112
const WatchingGlob: string[] = [];
@@ -21,19 +22,21 @@ const Watcher = Chokidar.watch([...WatchingGlob], {
2122
let BuildCooldownTimer: NodeJS.Timeout | null = null
2223
let ShouldPreventHTTPResponse = false
2324
let Version: number = 0
25+
let RandomPort = Crypto.randomInt(8000, 8999)
26+
const CoreBuildInstance = new Build({ Version: `0.0.${Version}`, Minify: false, BuildType: 'development', SubscriptionUrl: `http://localhost:${RandomPort}/tinyShield.dev.user.js` })
27+
await CoreBuildInstance.Init()
2428
Watcher.on('all', async (WatcherEvent, WatcherPath) => {
2529
if (BuildCooldownTimer) {
2630
clearTimeout(BuildCooldownTimer)
2731
}
2832
BuildCooldownTimer = setTimeout(async () => {
2933
console.log(`Detected file change (${WatcherEvent}):`, WatcherPath)
3034
ShouldPreventHTTPResponse = true
31-
await Build({ Version: `0.0.${Version}`, Minify: false, BuildType: 'development', SubscriptionUrl: `http://localhost:${RandomPort}/tinyShield.dev.user.js` })
35+
await new StandardBuild(CoreBuildInstance, { Version: `0.0.${Version}`, Minify: false, BuildType: 'development', SubscriptionUrl: `http://localhost:${RandomPort}/tinyShield.dev.user.js` }).Build()
3236
Version++
3337
ShouldPreventHTTPResponse = false
3438
}, 1500)
3539
})
3640

37-
let RandomPort = Crypto.randomInt(8000, 8999)
3841
RunDebugServer(RandomPort, ['tinyShield.dev.user.js'], ShouldPreventHTTPResponse)
3942
console.log(`Debug HTTP server running on http://localhost:${RandomPort}/tinyShield.dev.user.js`)

0 commit comments

Comments
 (0)