Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 3 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,19 @@ To get started clone the repo and get the web application started.
1. Run `git clone git@github.com:firefox-devtools/profiler.git`
2. Run `cd profiler`
3. Run `yarn install`, this will install all of the dependencies.
4. Run `yarn start`, this will start up the webpack server.
4. Run `yarn start`, this will start up the development server.
5. Point your browser to [http://localhost:4242](http://localhost:4242).
6. If port `4242` is taken, then you can run the web app on a different port: `FX_PROFILER_PORT=1234 yarn start`

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:

```js
module.exports = function (config, serverConfig) {
config.cache = false;
serverConfig.open = true;
};
```

This project uses [TypeScript](https://www.typescriptlang.org/).

## Using GitHub Codespaces

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

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.

## Loading in profiles for development
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.

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:
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:

#### 1. Record a profile:

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

Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test_script:
- yarn versions
# run tests
- yarn test-all
- yarn build-prod:quiet
- yarn build-prod

cache:
- node_modules
Expand Down
209 changes: 209 additions & 0 deletions esbuild.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import esbuild from 'esbuild';
import copy from 'esbuild-plugin-copy';
import { wasmLoader } from 'esbuild-plugin-wasm';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const isProduction = process.env.NODE_ENV === 'production';

function generateHtmlPlugin(options) {
return {
name: 'firefox-profiler-generate-html',
setup(build) {
const { outdir, publicPath } = build.initialOptions;
build.initialOptions.metafile = true;
build.onEnd(async (result) => {
await generateHTML(result.metafile, { ...options, outdir, publicPath });
});
},
};
}

const baseConfig = {
bundle: true,
absWorkingDir: __dirname,
loader: {
'.png': 'file',
'.jpg': 'file',
'.svg': 'file',
'.worker.js': 'file',
},
alias: {
'firefox-profiler': './src',
'firefox-profiler-res': './res',
},
plugins: [wasmLoader()],
};

const templateHTML = fs.readFileSync(
path.join(__dirname, 'res/index.html'),
'utf8'
);

export const mainBundleConfig = {
...baseConfig,
format: 'esm',
platform: 'browser',
target: 'es2022',
sourcemap: true,
minify: isProduction,
splitting: true,
entryPoints: ['src/index.tsx'],
outdir: 'dist',
metafile: true,
publicPath: '/',
entryNames: '[name]-[hash]',
define: {
'process.env.L10N': process.env.L10N
? JSON.stringify(process.env.L10N)
: 'undefined',
AVAILABLE_STAGING_LOCALES: process.env.L10N
? JSON.stringify(fs.readdirSync('./locales'))
: 'undefined',
// no need to define NODE_ENV:
// esbuild automatically defines NODE_ENV based on the value for "minify"
},
external: ['zlib'],
plugins: [
wasmLoader(),
copy({
resolveFrom: __dirname,
assets: [
{ from: ['res/_headers'], to: ['dist'] },
{ from: ['res/_redirects'], to: ['dist'] },
{ from: ['res/contribute.json'], to: ['dist'] },
{ from: ['res/robots.txt'], to: ['dist'] },
{ from: ['res/service-worker-compat.js'], to: ['dist'] },
{ from: ['res/img/favicon.png'], to: ['dist/res/img'] },
{ from: ['docs-user/**/*'], to: ['dist/docs'] },
{ from: ['locales/**/*'], to: ['dist/locales'] },
],
}),
generateHtmlPlugin({
filename: 'index.html',
entryPoint: 'src/index.tsx',
templateHTML,
}),
],
};

// Common build configuration for node-based tools
export const nodeBaseConfig = {
...baseConfig,
platform: 'node',
target: 'node16',
format: 'cjs',
external: ['fs', 'path', 'crypto', 'zlib'],
};

async function buildAll() {
// Clean dist directory
if (fs.existsSync('dist')) {
fs.rmSync('dist', { recursive: true });
}

const builds = [];

// Main app build
builds.push(esbuild.build(mainBundleConfig));

// Node tools (if requested)
if (process.argv.includes('--node-tools')) {
// Symbolicator CLI
builds.push(
esbuild.build({
...nodeBaseConfig,
entryPoints: ['src/symbolicator-cli/index.ts'],
outfile: 'dist/symbolicator-cli.js',
})
);
}

// Wait for all builds to complete
const buildResults = await Promise.all(builds);

// Save metafile for analysis
if (buildResults[0].metafile) {
fs.writeFileSync(
'dist/metafile.json',
JSON.stringify(buildResults[0].metafile, null, 2)
);
console.log('📊 Metafile saved to dist/metafile.json');
}

console.log('✅ Build completed');
}

async function generateHTML(metafileJson, options) {
const { entryPoint, templateHTML, filename, outdir, publicPath } = options;

const htmlOutputPath = outdir + '/' + filename;

function convertPath(oldPath) {
const prefixToStrip = outdir + '/';

if (!oldPath || !oldPath.startsWith || !oldPath.startsWith(prefixToStrip)) {
throw new Error(
`Unexpected path ${oldPath} which seems to be outside the outdir (which is set to ${outdir})`
);
}

const relativePath = oldPath.slice(prefixToStrip.length);
if (publicPath) {
// e.g. publicPath === '/'
return publicPath + relativePath;
}
return relativePath;
}

if (!metafileJson || !metafileJson.outputs) {
throw new Error('No outputs detected');
}

const [mainBundlePath, mainBundle] = Object.entries(
metafileJson.outputs
).find(([_bundlePath, bundle]) => bundle.entryPoint === entryPoint);

const extraHeadTags = [];

// Main JS bundle
extraHeadTags.push(
`<script src="${convertPath(mainBundlePath)}" type="module" async></script>`
);

// Main Stylesheet
if (mainBundle.cssBundle) {
extraHeadTags.push(
`<link rel="stylesheet" href="${convertPath(mainBundle.cssBundle)}">`
);
}

// Preload startup chunks
const startupChunks = mainBundle.imports.filter(
(imp) => imp.kind === 'import-statement' // as opposed to 'dynamic-import'
);
for (const startupChunk of startupChunks) {
extraHeadTags.push(
`<link rel="modulepreload" href="${convertPath(startupChunk.path)}">`
);
}

// Insert tags before </head>
const extraHeadStr = extraHeadTags.map((s) => ' ' + s).join('\n');
const html = templateHTML.replace(
'</head>',
'\n' + extraHeadStr + '\n </head>'
);

// Write the final HTML
fs.writeFileSync(htmlOutputPath, html);
}

// Run build if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
buildAll().catch(console.error);
}
2 changes: 1 addition & 1 deletion netlify.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[context.l10n]
command = "yarn build-l10n-prod:quiet"
command = "yarn build-l10n-prod"
42 changes: 13 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,14 @@
}
},
"scripts": {
"build:clean": "rimraf dist && mkdirp dist",
"build:quiet": "yarn build:clean && cross-env NODE_ENV=development webpack",
"build": "yarn build:quiet --progress",
"build-prod:quiet": "yarn build:clean && yarn build-photon && cross-env NODE_ENV=production webpack",
"build-prod": "yarn build-prod:quiet --progress",
"build-l10n": "yarn build:clean && cross-env NODE_ENV=development L10N=1 webpack --progress",
"build-l10n-prod:quiet": "yarn build:clean && yarn build-photon && cross-env NODE_ENV=production L10N=1 webpack",
"build-l10n-prod": "yarn build-l10n-prod:quiet --progress",
"build-photon": "webpack --config res/photon/webpack.config.js",
"build-symbolicator-cli": "yarn build-symbolicator-cli:quiet --progress",
"build-symbolicator-cli:quiet": "yarn build:clean && cross-env NODE_ENV=production webpack --config src/symbolicator-cli/webpack.config.js",
"build": "cross-env NODE_ENV=development node esbuild.mjs",
"build-prod": "cross-env NODE_ENV=production node esbuild.mjs && yarn build-sw && yarn build-photon",
"build-l10n": "cross-env NODE_ENV=development L10N=1 node esbuild.mjs",
"build-l10n-prod": "cross-env NODE_ENV=production L10N=1 node esbuild.mjs && yarn build-sw && yarn build-photon",
"build-photon": "cross-env NODE_ENV=production node res/photon/esbuild.mjs",
"build-sw": "workbox generateSW workbox-config.js",
"build-symbolicator-cli": "cross-env NODE_ENV=production node src/symbolicator-cli/esbuild.mjs",
"build-prod:quiet": "yarn build-prod",
"lint": "node bin/output-fixing-commands.js run-p lint-js lint-css prettier-run",
"lint-fix": "run-p lint-fix-js lint-fix-css prettier-fix",
"lint-js": "node bin/output-fixing-commands.js eslint . --report-unused-disable-directives --cache --cache-strategy content",
Expand All @@ -37,11 +34,10 @@
"serve-static": "ws -d dist/ -s index.html -p 4243",
"start": "yarn build:clean && cross-env NODE_ENV=development node server.js",
"start-prod": "yarn build-prod && yarn serve-static",
"start-l10n": "yarn build:clean && cross-env NODE_ENV=development L10N=1 node server.js",
"start-l10n": "cross-env NODE_ENV=development L10N=1 node server.mjs",
"start-l10n-prod": "yarn build-l10n-prod && yarn serve-static",
"start-examples": "ws -d examples/ -s index.html -p 4244",
"start-docs": "ws -d docs-user/ -p 3000",
"start-photon": "node res/photon/server",
"test": "node bin/output-fixing-commands.js cross-env LC_ALL=C TZ=UTC NODE_ENV=test jest",
"test-all": "run-p --max-parallel 4 ts license-check lint test test-alex test-lockfile",
"test-build-coverage": "yarn test --coverage --coverageReporters=html",
Expand Down Expand Up @@ -133,18 +129,15 @@
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"alex": "^11.0.1",
"autoprefixer": "^10.4.21",
"babel-jest": "^30.2.0",
"babel-loader": "^10.0.0",
"babel-plugin-module-resolver": "^5.0.2",
"browserslist": "^4.27.0",
"caniuse-lite": "^1.0.30001751",
"circular-dependency-plugin": "^5.2.1",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "^10.1.0",
"css-loader": "^7.1.2",
"cssnano": "^7.1.1",
"devtools-license-check": "^0.9.0",
"esbuild": "^0.25.9",
"esbuild-plugin-copy": "^2.1.1",
"esbuild-plugin-wasm": "^1.1.0",
"eslint": "^9.37.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-alias": "^1.1.2",
Expand All @@ -157,36 +150,27 @@
"espree": "^10.4.0",
"fake-indexeddb": "^6.2.4",
"fetch-mock": "^12.5.5",
"file-loader": "^6.2.0",
"glob": "^11.0.3",
"globals": "^16.4.0",
"html-webpack-plugin": "^5.6.4",
"husky": "^4.3.8",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"jest-extended": "^6.0.0",
"json-loader": "^0.5.7",
"local-web-server": "^5.4.0",
"lockfile-lint": "^4.14.1",
"mkdirp": "^3.0.1",
"npm-run-all2": "^8.0.4",
"open": "^10.2.0",
"patch-package": "^8.0.1",
"postcss": "^8.5.6",
"postcss-loader": "^8.2.0",
"postinstall-postinstall": "^2.1.0",
"prettier": "^3.6.2",
"raw-loader": "^4.0.2",
"rimraf": "^5.0.10",
"style-loader": "^4.0.0",
"stylelint": "^16.25.0",
"stylelint-config-idiomatic-order": "^10.0.0",
"stylelint-config-standard": "^39.0.1",
"typescript": "^5.9.3",
"webpack": "^5.102.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2",
"workbox-webpack-plugin": "^7.3.0",
"workbox-cli": "^7.3.0",
"yargs": "^18.0.0"
},
"resolutions": {
Expand Down
6 changes: 0 additions & 6 deletions postcss.config.js

This file was deleted.

1 change: 1 addition & 0 deletions res/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<meta name="viewport" content="initial-scale=1" />
<title>Firefox Profiler</title>
<link rel="preload" href="/locales/en-US/app.ftl" as="fetch" />
<link rel="icon" type="image/png" href="/res/img/favicon.png" />
</head>
<body style="background-color: #363959; /* ink-70 */">
<svg id="svg-filters"></svg>
Expand Down
Loading
Loading