Skip to content

Commit 0455be4

Browse files
committed
WIP: Migrate to esbuild.
1 parent e1e8477 commit 0455be4

28 files changed

Lines changed: 1356 additions & 2343 deletions

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ jobs:
5353
resource_class: large
5454
steps:
5555
- checkout-and-dependencies
56-
- run: yarn build-prod:quiet
57-
- run: yarn build-symbolicator-cli:quiet
56+
- run: yarn build-prod
57+
- run: yarn build-symbolicator-cli
5858

5959
licence-check:
6060
executor: node

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ src/types/libdef/npm
33
docs-user/js
44
docs-user/css
55
src/test/fixtures/upgrades
6-
res/zee-worker.js
6+
res/zee.worker.js
77
dist
88
coverage
99
taskcluster/

CONTRIBUTING.md

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,10 @@ 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 Gitpod
@@ -58,11 +49,11 @@ Alternatively, you can also develop the Firefox Profiler online in a pre-configu
5849

5950
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/firefox-devtools/profiler)
6051

61-
Gitpod will automatically install all dependencies; start the webpack server for you; and open the web app in a new browser tab. Please look at our [gitpod documentation](./docs-user/gitpod.md) for more information.
52+
Gitpod will automatically install all dependencies; start the development server for you; and open the web app in a new browser tab. Please look at our [gitpod documentation](./docs-user/gitpod.md) for more information.
6253

6354
## Loading in profiles for development
6455

65-
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:
56+
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:
6657

6758
#### 1. Record a profile:
6859

__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

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test_script:
2828
- yarn versions
2929
# run tests
3030
- yarn test-all
31-
- yarn build-prod:quiet
31+
- yarn build-prod
3232

3333
cache:
3434
- node_modules

esbuild.mjs

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

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"

0 commit comments

Comments
 (0)