Skip to content

Commit 03f4d99

Browse files
committed
Use esbuild instead of webpack.
1 parent ee509b8 commit 03f4d99

21 files changed

Lines changed: 692 additions & 2225 deletions

CONTRIBUTING.md

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,30 +37,21 @@ To get started clone the repo and get the web application started.
3737
1. Run `git clone git@github.com:firefox-devtools/profiler.git`
3838
2. Run `cd profiler`
3939
3. Run `yarn install`, this will install all of the dependencies.
40-
4. Run `yarn start`, this will start up the webpack server.
40+
4. Run `yarn start`, this will start up the development server.
4141
5. Point your browser to [http://localhost:4242](http://localhost:4242).
4242
6. If port `4242` is taken, then you can run the web app on a different port: `FX_PROFILER_PORT=1234 yarn start`
4343

44-
Other [webpack](https://webpack.js.org/configuration/) and [webpack server](https://webpack.js.org/configuration/dev-server/) options can be set in a `webpack.local-config.js` file at the repo root. For example, if you want to disable caching and the server to automatically open the home page, put in there the following code:
45-
46-
```js
47-
module.exports = function (config, serverConfig) {
48-
config.cache = false;
49-
serverConfig.open = true;
50-
};
51-
```
52-
5344
This project uses [TypeScript](https://www.typescriptlang.org/).
5445

5546
## Using GitHub Codespaces
5647

5748
Alternatively, you can also develop the Firefox Profiler online in a pre-configured development environment using [GitHub Codespaces](https://github.com/features/codespaces).
5849

59-
GitHub Codespaces will automatically install all dependencies, start the webpack server for you, and forward port 4242 so you can access the web app. Please look at our [GitHub Codespaces documentation](./docs-developer/codespaces.md) for more information.
50+
GitHub Codespaces will automatically install all dependencies, start the development server for you, and forward port 4242 so you can access the web app. Please look at our [GitHub Codespaces documentation](./docs-developer/codespaces.md) for more information.
6051

6152
## Loading in profiles for development
6253

63-
The web app doesn't include any performance profiles by default, so you'll need to load some in. Make sure the local Webpack web server is running, and then try one of the following:
54+
The web app doesn't include any performance profiles by default, so you'll need to load some in. Make sure the local development server is running, and then try one of the following:
6455

6556
#### 1. Record a profile:
6657

__mocks__/gecko-profiler-demangle.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// This module replaces the wasm-pack generated module 'gecko-profiler-demangle'
22
// in our tests.
33
// The reason for this replacement is the fact that wasm-pack (or rather,
4-
// wasm-bindgen), when targeting the browser + webpack, generates an ES6 module
4+
// wasm-bindgen), when targeting the browser + bundlers, generates an ES6 module
55
// that node cannot deal with. Most importantly, it uses the syntax
66
// "import * as wasm from './gecko_profiler_demangle_bg';" in order to load
7-
// the wasm module, which is currently only supported by webpack.
7+
// the wasm module, which is currently only supported by bundlers.
88
// The long-term path to make this work correctly is to wait for node to
99
// support ES6 modules (and WASM as ES6 modules) natively [1]. It's possible
1010
// that in the medium term, wasm-bindgen will get support for outputting JS
11-
// files which work in both webpack and in node natively [2].
11+
// files which work in both bundlers and in node natively [2].
1212
// [1] https://medium.com/@giltayar/native-es-modules-in-nodejs-status-and-future-directions-part-i-ee5ea3001f71
1313
// [2] https://github.com/rustwasm/wasm-bindgen/issues/233
1414

esbuild.mjs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import esbuild from 'esbuild';
2+
import copy from 'esbuild-plugin-copy';
3+
import { wasmLoader } from 'esbuild-plugin-wasm';
4+
import path from 'path';
5+
import fs from 'fs';
6+
import { fileURLToPath } from 'url';
7+
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = path.dirname(__filename);
10+
11+
const isProduction = process.env.NODE_ENV === 'production';
12+
13+
function generateHtmlPlugin(options) {
14+
return {
15+
name: 'firefox-profiler-generate-html',
16+
setup(build) {
17+
const { outdir, publicPath } = build.initialOptions;
18+
build.initialOptions.metafile = true;
19+
build.onEnd(async (result) => {
20+
await generateHTML(result.metafile, { ...options, outdir, publicPath });
21+
});
22+
},
23+
};
24+
}
25+
26+
const baseConfig = {
27+
bundle: true,
28+
minify: isProduction,
29+
absWorkingDir: __dirname,
30+
loader: {
31+
'.png': 'file',
32+
'.jpg': 'file',
33+
'.svg': 'file',
34+
'.worker.js': 'file',
35+
},
36+
alias: {
37+
'firefox-profiler': './src',
38+
'firefox-profiler-res': './res',
39+
},
40+
};
41+
42+
const templateHTML = fs.readFileSync(
43+
path.join(__dirname, 'res/index.html'),
44+
'utf8'
45+
);
46+
47+
export const mainBundleConfig = {
48+
...baseConfig,
49+
format: 'esm',
50+
platform: 'browser',
51+
target: 'es2022',
52+
sourcemap: true,
53+
splitting: true,
54+
entryPoints: ['src/index.tsx'],
55+
outdir: 'dist',
56+
metafile: true,
57+
publicPath: '/',
58+
entryNames: '[name]-[hash]',
59+
define: {
60+
'process.env.L10N': process.env.L10N
61+
? JSON.stringify(process.env.L10N)
62+
: 'undefined',
63+
AVAILABLE_STAGING_LOCALES: process.env.L10N
64+
? JSON.stringify(fs.readdirSync('./locales'))
65+
: 'undefined',
66+
// no need to define NODE_ENV:
67+
// esbuild automatically defines NODE_ENV based on the value for "minify"
68+
},
69+
external: ['zlib'],
70+
plugins: [
71+
wasmLoader(),
72+
copy({
73+
resolveFrom: __dirname,
74+
assets: [
75+
{ from: ['res/_headers'], to: ['dist'] },
76+
{ from: ['res/_redirects'], to: ['dist'] },
77+
{ from: ['res/contribute.json'], to: ['dist'] },
78+
{ from: ['res/robots.txt'], to: ['dist'] },
79+
{ from: ['res/service-worker-compat.js'], to: ['dist'] },
80+
{ from: ['res/img/favicon.png'], to: ['dist/res/img'] },
81+
{ from: ['docs-user/**/*'], to: ['dist/docs'] },
82+
{ from: ['locales/**/*'], to: ['dist/locales'] },
83+
],
84+
}),
85+
generateHtmlPlugin({
86+
filename: 'index.html',
87+
entryPoint: 'src/index.tsx',
88+
templateHTML,
89+
}),
90+
],
91+
};
92+
93+
// Common build configuration for node-based tools
94+
export const nodeBaseConfig = {
95+
...baseConfig,
96+
platform: 'node',
97+
target: 'node16',
98+
splitting: false,
99+
format: 'cjs',
100+
bundle: true,
101+
external: ['fs', 'path', 'crypto', 'zlib'],
102+
plugins: [
103+
wasmLoader({
104+
mode: 'embedded',
105+
}),
106+
],
107+
};
108+
109+
async function buildAll() {
110+
// Clean dist directory
111+
if (fs.existsSync('dist')) {
112+
fs.rmSync('dist', { recursive: true });
113+
}
114+
115+
const builds = [];
116+
117+
// Main app build
118+
builds.push(esbuild.build(mainBundleConfig));
119+
120+
// Node tools (if requested)
121+
if (process.argv.includes('--node-tools')) {
122+
// Symbolicator CLI
123+
builds.push(
124+
esbuild.build({
125+
...nodeBaseConfig,
126+
entryPoints: ['src/symbolicator-cli/index.ts'],
127+
outfile: 'dist/symbolicator-cli.js',
128+
})
129+
);
130+
}
131+
132+
// Wait for all builds to complete
133+
const buildResults = await Promise.all(builds);
134+
135+
// Save metafile data to a file, for example to allow visualizing bundle size.
136+
if (buildResults[0].metafile) {
137+
fs.writeFileSync(
138+
'dist/metafile.json',
139+
JSON.stringify(buildResults[0].metafile, null, 2)
140+
);
141+
console.log('📊 Metafile saved to dist/metafile.json');
142+
}
143+
144+
console.log('✅ Build completed');
145+
}
146+
147+
async function generateHTML(metafileJson, options) {
148+
const { entryPoint, templateHTML, filename, outdir, publicPath } = options;
149+
150+
const htmlOutputPath = outdir + '/' + filename;
151+
152+
function convertPath(oldPath) {
153+
const prefixToStrip = outdir + '/';
154+
155+
if (!oldPath || !oldPath.startsWith || !oldPath.startsWith(prefixToStrip)) {
156+
throw new Error(
157+
`Unexpected path ${oldPath} which seems to be outside the outdir (which is set to ${outdir})`
158+
);
159+
}
160+
161+
const relativePath = oldPath.slice(prefixToStrip.length);
162+
if (publicPath) {
163+
// e.g. publicPath === '/'
164+
return publicPath + relativePath;
165+
}
166+
return relativePath;
167+
}
168+
169+
if (!metafileJson || !metafileJson.outputs) {
170+
throw new Error('No outputs detected');
171+
}
172+
173+
const [mainBundlePath, mainBundle] = Object.entries(
174+
metafileJson.outputs
175+
).find(([_bundlePath, bundle]) => bundle.entryPoint === entryPoint);
176+
177+
const extraHeadTags = [];
178+
179+
// Main JS bundle
180+
extraHeadTags.push(
181+
`<script src="${convertPath(mainBundlePath)}" type="module" async></script>`
182+
);
183+
184+
// Main Stylesheet
185+
if (mainBundle.cssBundle) {
186+
extraHeadTags.push(
187+
`<link rel="stylesheet" href="${convertPath(mainBundle.cssBundle)}">`
188+
);
189+
}
190+
191+
// Preload startup chunks
192+
const startupChunks = mainBundle.imports.filter(
193+
(imp) => imp.kind === 'import-statement' // as opposed to 'dynamic-import'
194+
);
195+
for (const startupChunk of startupChunks) {
196+
extraHeadTags.push(
197+
`<link rel="modulepreload" href="${convertPath(startupChunk.path)}">`
198+
);
199+
}
200+
201+
// Insert tags before </head>
202+
const extraHeadStr = extraHeadTags.map((s) => ' ' + s).join('\n');
203+
const html = templateHTML.replace(
204+
'</head>',
205+
'\n' + extraHeadStr + '\n </head>'
206+
);
207+
208+
// Write the final HTML
209+
fs.writeFileSync(htmlOutputPath, html);
210+
}
211+
212+
// Run build if called directly
213+
if (import.meta.url === `file://${process.argv[1]}`) {
214+
buildAll().catch(console.error);
215+
}

netlify.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[context.l10n]
2-
command = "yarn build-l10n-prod:quiet"
2+
command = "yarn build-l10n-prod"

package.json

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,15 @@
1313
"./src/utils/gz.ts": "./src/utils/gz.browser.ts"
1414
},
1515
"scripts": {
16-
"build:clean": "rimraf dist && mkdirp dist",
17-
"build:quiet": "yarn build:clean && cross-env NODE_ENV=development webpack",
18-
"build": "yarn build:quiet --progress",
19-
"build-prod:quiet": "yarn build:clean && yarn build-photon && cross-env NODE_ENV=production webpack && yarn build-sw",
20-
"build-prod": "yarn build:clean && yarn build-photon && cross-env NODE_ENV=production webpack --progress && yarn build-sw",
21-
"build-l10n": "yarn build:clean && cross-env NODE_ENV=development L10N=1 webpack --progress",
22-
"build-l10n-prod:quiet": "yarn build:clean && yarn build-photon && cross-env NODE_ENV=production L10N=1 webpack && yarn build-sw",
23-
"build-l10n-prod": "yarn build:clean && yarn build-photon && cross-env NODE_ENV=production L10N=1 webpack --progress && yarn build-sw",
24-
"build-photon": "webpack --config res/photon/webpack.config.js",
16+
"build": "cross-env NODE_ENV=development node esbuild.mjs",
17+
"build-prod": "cross-env NODE_ENV=production node esbuild.mjs && yarn build-sw && yarn build-photon",
18+
"build-l10n": "cross-env NODE_ENV=development L10N=1 node esbuild.mjs",
19+
"build-l10n-prod": "cross-env NODE_ENV=production L10N=1 node esbuild.mjs && yarn build-sw && yarn build-photon",
20+
"build-photon": "cross-env NODE_ENV=production node res/photon/esbuild.mjs",
2521
"build-sw": "workbox generateSW workbox-config.js",
26-
"build-symbolicator-cli": "yarn build-symbolicator-cli:quiet --progress",
27-
"build-symbolicator-cli:quiet": "yarn build:clean && cross-env NODE_ENV=production webpack --config src/symbolicator-cli/webpack.config.js",
22+
"build-symbolicator-cli": "cross-env NODE_ENV=production node src/symbolicator-cli/esbuild.mjs",
23+
"build-prod:quiet": "yarn build-prod",
24+
"build-symbolicator-cli:quiet": "yarn build-symbolicator-cli",
2825
"lint": "node bin/output-fixing-commands.js run-p lint-js lint-css prettier-run",
2926
"lint-fix": "run-p lint-fix-js lint-fix-css prettier-fix",
3027
"lint-js": "node bin/output-fixing-commands.js eslint . --report-unused-disable-directives --cache --cache-strategy content",
@@ -39,13 +36,12 @@
3936
"preinstall": "node bin/pre-install.js",
4037
"publish": "rimraf public_html && cp -r dist public_html",
4138
"serve-static": "ws -d dist/ -s index.html -p 4243",
42-
"start": "yarn build:clean && cross-env NODE_ENV=development node server.js",
39+
"start": "cross-env NODE_ENV=development node server.mjs",
4340
"start-prod": "yarn build-prod && yarn serve-static",
44-
"start-l10n": "yarn build:clean && cross-env NODE_ENV=development L10N=1 node server.js",
41+
"start-l10n": "cross-env NODE_ENV=development L10N=1 node server.mjs",
4542
"start-l10n-prod": "yarn build-l10n-prod && yarn serve-static",
4643
"start-examples": "ws -d examples/ -s index.html -p 4244",
4744
"start-docs": "ws -d docs-user/ -p 3000",
48-
"start-photon": "node res/photon/server",
4945
"test": "node bin/output-fixing-commands.js cross-env LC_ALL=C TZ=UTC NODE_ENV=test jest",
5046
"test-node": "node bin/output-fixing-commands.js cross-env LC_ALL=C TZ=UTC NODE_ENV=test JEST_ENVIRONMENT=node jest",
5147
"test-all": "run-p --max-parallel 4 ts license-check lint test test-alex test-lockfile",
@@ -139,18 +135,15 @@
139135
"@typescript-eslint/eslint-plugin": "^8.51.0",
140136
"@typescript-eslint/parser": "^8.51.0",
141137
"alex": "^11.0.1",
142-
"autoprefixer": "^10.4.23",
143138
"babel-jest": "^30.2.0",
144-
"babel-loader": "^10.0.0",
145139
"babel-plugin-module-resolver": "^5.0.2",
146140
"browserslist": "^4.28.1",
147141
"caniuse-lite": "^1.0.30001762",
148-
"circular-dependency-plugin": "^5.2.1",
149-
"copy-webpack-plugin": "^13.0.1",
150142
"cross-env": "^10.1.0",
151-
"css-loader": "^7.1.2",
152-
"cssnano": "^7.1.2",
153143
"devtools-license-check": "^0.9.0",
144+
"esbuild": "^0.27.0",
145+
"esbuild-plugin-copy": "^2.1.1",
146+
"esbuild-plugin-wasm": "^1.1.0",
154147
"eslint": "^9.39.2",
155148
"eslint-config-prettier": "^10.1.8",
156149
"eslint-import-resolver-alias": "^1.1.2",
@@ -162,33 +155,25 @@
162155
"eslint-plugin-testing-library": "^7.15.3",
163156
"fake-indexeddb": "^6.2.5",
164157
"fetch-mock": "^12.6.0",
165-
"file-loader": "^6.2.0",
166158
"globals": "^17.0.0",
167-
"html-webpack-plugin": "^5.6.5",
168159
"husky": "^4.3.8",
169160
"jest": "^30.2.0",
170161
"jest-environment-jsdom": "^30.2.0",
171162
"jest-extended": "^7.0.0",
172-
"json-loader": "^0.5.7",
173163
"local-web-server": "^5.4.0",
174164
"lockfile-lint": "^4.14.1",
175165
"mkdirp": "^3.0.1",
176166
"npm-run-all2": "^8.0.4",
177167
"open": "^11.0.0",
178168
"patch-package": "^8.0.1",
179169
"postcss": "^8.5.6",
180-
"postcss-loader": "^8.2.0",
181170
"postinstall-postinstall": "^2.1.0",
182171
"prettier": "^3.7.4",
183172
"rimraf": "^6.1.2",
184-
"style-loader": "^4.0.0",
185173
"stylelint": "^16.26.1",
186174
"stylelint-config-idiomatic-order": "^10.0.0",
187175
"stylelint-config-standard": "^39.0.1",
188176
"typescript": "^5.9.3",
189-
"webpack": "^5.104.1",
190-
"webpack-cli": "^6.0.1",
191-
"webpack-dev-server": "^5.2.2",
192177
"workbox-cli": "^7.4.0",
193178
"yargs": "^18.0.0"
194179
},

postcss.config.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

res/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<meta name="viewport" content="initial-scale=1" />
99
<title>Firefox Profiler</title>
1010
<link rel="preload" href="/locales/en-US/app.ftl" as="fetch" />
11+
<link rel="icon" type="image/png" href="/res/img/favicon.png" />
1112
</head>
1213
<body style="background-color: #363959; /* ink-70 */">
1314
<svg id="svg-filters"></svg>

0 commit comments

Comments
 (0)