Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: use esbuild instead of webpack for building #500

Merged
merged 50 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
23ac24a
Change Awilix container injection mode
raducristianpopa Aug 12, 2024
91d2b30
use Cradle for types; don't import each service type
sidvishnoi Aug 12, 2024
cf71393
lint fix
sidvishnoi Aug 12, 2024
9fe6893
wip: try using esbuild
sidvishnoi Aug 12, 2024
88d4841
reset webpack config
sidvishnoi Aug 13, 2024
188b715
make content, background work with esbuild
sidvishnoi Aug 13, 2024
80665b5
build polyfill also
sidvishnoi Aug 13, 2024
15488e7
write manifest; use defines for dev
sidvishnoi Aug 13, 2024
1e44d11
make popup work; copy static assets
sidvishnoi Aug 13, 2024
fcfb59c
cleanups
sidvishnoi Aug 13, 2024
7372f16
clean on start
sidvishnoi Aug 13, 2024
3c1615e
extract config.defines
sidvishnoi Aug 13, 2024
76f712b
refactor: move read/write part out of processManifest
sidvishnoi Aug 13, 2024
e1b50ed
Refactor out esbuild configs; add zip plugin and typechecking
raducristianpopa Aug 13, 2024
6dced12
lint fixes
sidvishnoi Aug 13, 2024
609bf20
update build script usage (not finished; but CI will pass)
sidvishnoi Aug 13, 2024
2a736c1
lint fix
sidvishnoi Aug 13, 2024
e31dab7
fix glob pattern with zipPlugin
sidvishnoi Aug 13, 2024
00eb47d
Proposed solution for polyfill issue
raducristianpopa Aug 14, 2024
38534ca
remove webpack
sidvishnoi Aug 14, 2024
149604d
fix mangling; types and Nightly version number
sidvishnoi Aug 14, 2024
5f2e305
remove old webpack scripts
sidvishnoi Aug 14, 2024
5852caa
include esbuild/* in tsconfig
sidvishnoi Aug 14, 2024
1a61a71
fix format
sidvishnoi Aug 14, 2024
33600f9
lint fix
sidvishnoi Aug 14, 2024
363303f
Move polyfill preserver plugin to prod
raducristianpopa Aug 14, 2024
23d7fb8
Fix linting
raducristianpopa Aug 14, 2024
d1856e5
Trying web-ext for reloading
raducristianpopa Aug 14, 2024
193dfcb
Revert "Trying web-ext for reloading"
sidvishnoi Aug 14, 2024
053bf24
add live-reload esbuild plugin
sidvishnoi Aug 14, 2024
75ad248
use crypto-browserify correctly; rollup plugin to rescue
sidvishnoi Aug 14, 2024
d3bfbba
Remove `esbuild-plugin-clean`; add our own clean plugin
raducristianpopa Aug 16, 2024
24268fa
nits: consistent plugin names
raducristianpopa Aug 16, 2024
b17b5d5
Move `rimraf` to dev deps
raducristianpopa Aug 16, 2024
d85b26c
use `fm.rm` in clean plugin and simplify it
sidvishnoi Aug 16, 2024
94dce61
nit: use `node:` prefix on imports
sidvishnoi Aug 16, 2024
0c2b028
import global Buffer only in background
sidvishnoi Aug 16, 2024
4167508
nits: naming convention, remove opera/edge for now
raducristianpopa Aug 16, 2024
751eb46
Fix readme; add todo
raducristianpopa Aug 16, 2024
3e970b2
Add pnpm overrides
raducristianpopa Aug 16, 2024
6bd52e1
Move safe-buffer to deps
raducristianpopa Aug 16, 2024
0d0d5de
undo `ed25519` package import related changes
sidvishnoi Aug 16, 2024
e4a099a
Merge branch 'main' into use-esbuild
sidvishnoi Aug 16, 2024
0c8f844
Rename "release" channel to "stable"
raducristianpopa Aug 19, 2024
05dcb53
Update PR checks workflow; format
raducristianpopa Aug 19, 2024
3bb02c4
Add softprops to cspell
raducristianpopa Aug 19, 2024
20dd40c
Default to chrome on dev
raducristianpopa Aug 19, 2024
d7c7c87
change CLI syntax
sidvishnoi Aug 19, 2024
48ec449
specify min versions
sidvishnoi Aug 19, 2024
aa89ea7
style nits
sidvishnoi Aug 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:

- name: Build
shell: bash
run: pnpm build ${{ matrix.browser }}
run: pnpm build ${{ matrix.browser }} nightly

- name: Upload artifacts
uses: actions/[email protected]
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# TODO: To be updated

name: Release

concurrency: release
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sanity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

- name: Build
shell: bash
run: pnpm build ${{ matrix.browser}}
run: pnpm build ${{ matrix.browser}} nightly

test:
name: Test
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ pnpm i

All commands are run from the root of the project, from a terminal:

| Command | Action |
| :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pnpm dev <TARGET>` | Builds the extension for development, for a specified target (`chrome` or `firefox`). If the target is not specified the script will build the extension for a Chromium based browser. Output folder: `dev`. |
| `pnpm build <TARGET>` | Builds the extension for production usage, for a specified target (`chrome` or `firefox`). If the target is not specified the script will build the extension for all available targets. Output folder: `dist`. |
| `pnpm test` | Runs all test files using Jest. |
| Command | Action |
| :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `pnpm dev [target]` | Builds the extension for development, rebuilding on source code changes, for a specified target (`chrome` or `firefox`). If the target is not specified the script will build the extension for a Chromium based browser. Output folder: `dev`. |
| `pnpm build [TARGET] [CHANNEL]` | Builds the extension for production usage, for a specified target (`chrome` or `firefox`) and channel (`nightly`, `preview` or `stable`). If the target is not specified the script will build the extension for all available targets. If the channel is not specified the script will build the extension for the `nightly` channel. Output folder: `dist`. |
| `pnpm test` | Runs all test files using Jest. |

### Installing the extension from source, in Chromium based browsers (Chrome, Opera, Edge, Brave, Arc)
### Installing the extension from source, in Chromium based browsers (Chrome, Opera, Edge, Brave, Arc, Vivaldi)

1. <b>Build the extension with `pnpm build chrome`</b>

Expand Down
5 changes: 5 additions & 0 deletions cspell-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ GNAP
Deduplicator
crossorigin
iframes
unmangles
data-testid
nums

Expand All @@ -20,6 +21,9 @@ prettiercache
corepack
linkcode
endregion
metafile
iife
softprops

# packages and 3rd party tools/libraries
awilix
Expand All @@ -33,3 +37,4 @@ tailwindcss
raducristianpopa
sidvishnoi
dianafulga
jgoz
57 changes: 57 additions & 0 deletions esbuild/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import path from 'node:path'
import type { BuildOptions } from 'esbuild'
import type { Manifest } from 'webextension-polyfill'

export const TARGETS = ['chrome', 'firefox'] as const
export const CHANNELS = ['nightly', 'preview', 'stable'] as const

export const ROOT_DIR = path.resolve(__dirname, '..')
export const SRC_DIR = path.resolve(ROOT_DIR, 'src')
export const DEV_DIR = path.resolve(ROOT_DIR, 'dev')
export const DIST_DIR = path.resolve(ROOT_DIR, 'dist')

export type Target = (typeof TARGETS)[number]
export type Channel = (typeof CHANNELS)[number]
export type BuildArgs = {
target: Target
channel: Channel
dev: boolean
}

export const options: BuildOptions = {
entryPoints: [
{
in: path.join(SRC_DIR, 'background', 'index.ts'),
out: path.join('background', 'background')
},
{
in: path.join(SRC_DIR, 'content', 'index.ts'),
out: path.join('content', 'content')
},
{
in: path.join(SRC_DIR, 'content', 'polyfill.ts'),
out: path.join('polyfill', 'polyfill')
},
{
in: path.join(SRC_DIR, 'popup', 'index.tsx'),
out: path.join('popup', 'popup')
}
],
bundle: true,
legalComments: 'none',
target: 'es2020',
platform: 'browser',
format: 'iife',
write: true,
logLevel: 'info',
treeShaking: true
}

export type WebExtensionManifest = Manifest.WebExtensionManifest & {
background: Manifest.WebExtensionManifestBackgroundC3Type
}

export const SERVE_PORTS: Record<Target, number> = {
chrome: 7000,
firefox: 7002
}
88 changes: 88 additions & 0 deletions esbuild/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { readFile } from 'node:fs/promises'
import type { BuildOptions, Plugin as ESBuildPlugin } from 'esbuild'
import { SERVE_PORTS, type BuildArgs, type Target } from './config'
import { getPlugins } from './plugins'
import { typecheckPlugin } from '@jgoz/esbuild-plugin-typecheck'

export const getDevOptions = ({
outDir,
target,
channel
}: Omit<BuildArgs, 'dev'> & {
outDir: string
}): BuildOptions => {
return {
sourcemap: 'linked',
metafile: false,
minify: false,
plugins: getPlugins({ outDir, dev: true, target, channel }).concat([
typecheckPlugin({ buildMode: 'readonly', watch: true }),
liveReloadPlugin({ target })
]),
define: {
NODE_ENV: JSON.stringify('development'),
CONFIG_LOG_LEVEL: JSON.stringify('DEBUG'),
CONFIG_PERMISSION_HOSTS: JSON.stringify({
origins: ['http://*/*', 'https://*/*']
}),
CONFIG_ALLOWED_PROTOCOLS: JSON.stringify(['http:', 'https:']),
CONFIG_OPEN_PAYMENTS_REDIRECT_URL: JSON.stringify(
'https://webmonetization.org/welcome'
)
}
}
}

function liveReloadPlugin({ target }: { target: Target }): ESBuildPlugin {
const port = SERVE_PORTS[target]
const reloadScriptBackground = `
new EventSource("http://localhost:${port}/esbuild").addEventListener(
"change",
async (ev) => {
const browser = "browser" in globalThis ? globalThis.browser : globalThis.chrome;
const data = JSON.parse(ev.data);
if (
data.added.some(s => s.includes("background.js")) ||
data.updated.some(s => s.includes("background.js"))
) {
console.warn(">>>>>>>> reloading background...");
await browser.runtime.reload();
}
}
);`

const reloadScriptPopup = `
new EventSource("http://localhost:${port}/esbuild").addEventListener(
"change",
(ev) => {
const data = JSON.parse(ev.data);
if (
data.added.some(s => s.includes("popup.js")) ||
data.updated.some(s => s.includes("popup.js"))
) {
globalThis.location.reload();
}
}
);`

return {
name: 'live-reload',
setup(build) {
build.onLoad({ filter: /src\/background\/index\.ts$/ }, async (args) => {
const contents = await readFile(args.path, 'utf8')
return {
contents: reloadScriptBackground + '\n' + contents,
loader: 'ts' as const
}
})

build.onLoad({ filter: /src\/popup\/index\.tsx$/ }, async (args) => {
const contents = await readFile(args.path, 'utf8')
return {
contents: contents + '\n\n\n' + reloadScriptPopup,
loader: 'tsx' as const
}
})
}
}
}
174 changes: 174 additions & 0 deletions esbuild/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import path from 'node:path'
import fs from 'node:fs/promises'
import type { Plugin as ESBuildPlugin } from 'esbuild'
import { nodeBuiltin } from 'esbuild-node-builtin'
import esbuildStylePlugin from 'esbuild-style-plugin'
import { copy } from 'esbuild-plugin-copy'

import {
SRC_DIR,
ROOT_DIR,
type BuildArgs,
type WebExtensionManifest
} from './config'

export const getPlugins = ({
outDir,
target,
channel,
dev
}: BuildArgs & {
outDir: string
}): ESBuildPlugin[] => {
return [
cleanPlugin([outDir]),
// nodeBuiltIn (powered by rollup plugin) replaces crypto with an empty
// package. But we need it, and we use crypto-browserify in for our use
// case. The JSPM crypto package is too large and not tree shakeable, so we
// don't use it.
nodeBuiltin({ exclude: ['crypto'] }),
{
name: 'crypto-for-extension',
setup(build) {
build.onResolve({ filter: /^crypto$/ }, () => ({
path: require.resolve('crypto-browserify')
}))
}
},
ignorePackagePlugin([/@apidevtools[/|\\]json-schema-ref-parser/]),
esbuildStylePlugin({
extract: true,
postcss: {
plugins: [require('tailwindcss'), require('autoprefixer')]
}
}),
copy({
resolveFrom: ROOT_DIR,
assets: [
{
from: path.join(SRC_DIR, 'popup', 'index.html'),
to: path.join(outDir, 'popup', 'index.html')
},
{
from: path.join(SRC_DIR, '_locales/**/*'),
to: path.join(outDir, '_locales')
},
{
from: path.join(SRC_DIR, 'assets/**/*'),
to: path.join(outDir, 'assets')
}
],
watch: dev
}),
processManifestPlugin({ outDir, dev, target, channel })
]
}

// Based on https://github.com/Knowre-Dev/esbuild-plugin-ignore
function ignorePackagePlugin(ignores: RegExp[]): ESBuildPlugin {
return {
name: 'ignore-package',
setup(build) {
build.onResolve({ filter: /.*/, namespace: 'ignore' }, (args) => ({
path: args.path,
namespace: 'ignore'
}))
for (const ignorePattern of ignores) {
build.onResolve({ filter: ignorePattern }, (args) => {
return { path: args.path, namespace: 'ignore' }
})
}

build.onLoad({ filter: /.*/, namespace: 'ignore' }, () => ({
contents: ''
}))
}
}
}

function processManifestPlugin({
outDir,
target,
channel,
dev
}: BuildArgs & { outDir: string }): ESBuildPlugin {
return {
name: 'process-manifest',
setup(build) {
build.onEnd(async () => {
const src = path.join(SRC_DIR, 'manifest.json')
const dest = path.join(outDir, 'manifest.json')

const json = JSON.parse(
await fs.readFile(src, 'utf8')
) as WebExtensionManifest
// Transform manifest as targets have different expectations
// @ts-expect-error Only for IDE. No target accepts it
delete json['$schema']

if (channel === 'nightly') {
// Set version to YYYY.M.D
const now = new Date()
const [year, month, day] = [
now.getFullYear(),
now.getMonth() + 1,
now.getDate()
]
json.version = `${year}.${month}.${day}`
if (target !== 'firefox') {
json.version_name = `Nightly ${json.version}`
}
}

if (channel === 'preview') {
json.name = json.name + ' Preview'
} else if (channel === 'nightly') {
json.name = json.name + ' Nightly'
}

if (dev) {
if (
json.host_permissions &&
!json.host_permissions.includes('http://*/*')
) {
json.host_permissions.push('http://*/*')
}
json.content_scripts?.forEach((contentScript) => {
if (!contentScript.matches.includes('http://*/*')) {
contentScript.matches.push('http://*/*')
}
})
}

if (target === 'firefox') {
// @ts-expect-error Firefox doesn't support Service Worker in MV3 yet
json.background = {
scripts: [json.background.service_worker]
}
json.content_scripts?.forEach((contentScript) => {
// @ts-expect-error firefox doesn't support execution context yet
contentScript.world = undefined
})
delete json.minimum_chrome_version
} else {
delete json['browser_specific_settings']
}

await fs.writeFile(dest, JSON.stringify(json, null, 2))
})
}
}
}

function cleanPlugin(dirs: string[]): ESBuildPlugin {
return {
name: 'clean',
setup(build) {
build.onStart(async () => {
await Promise.all(
dirs.map((dir) => fs.rm(dir, { recursive: true, force: true }))
)
})
}
}
}
Loading
Loading