Source maps
A minified stack trace says a.b is not a function at app-D4wpJuT4.js:1:48213. Source maps turn that back into your file, your line, your function and the code around it.
Your build uploads its maps to LaraBug, tagged with a release. The SDK sends the same release with every error. LaraBug maps each frame with the maps of that release, so the maps never have to be public.
Vite
Install the plugin:
npm install --save-dev @larabug/vite
Add it to vite.config.js, next to laravel-vite-plugin in a Laravel app:
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import larabug from '@larabug/vite'
export default defineConfig({
plugins: [
laravel({ input: ['resources/js/app.js'] }),
larabug(),
],
})
Give the build your application's ingest key, or its DSN, from the application settings:
LARABUG_KEY=lbi_your_ingest_key
On vite build the plugin:
- Picks a release:
LARABUG_RELEASEwhen set, otherwise the commit being built. - Writes hidden source maps, so the scripts carry no
sourceMappingURLcomment. - Puts the release in your bundle, where
@larabug/browserpicks it up for every error. - Uploads every map with the url of its script, then deletes the maps from the output.
vite dev is left alone. A failed upload never fails the build unless you pass failOnError: true.
Webpack and Laravel Mix
Two things have to line up: the bundle has to carry the release, and the maps have to be uploaded under that same release. DefinePlugin sets the release, and hidden-source-map writes the maps without pointing browsers at them:
const { execSync } = require('child_process')
const webpack = require('webpack')
const release = process.env.LARABUG_RELEASE || execSync('git rev-parse HEAD').toString().trim()
module.exports = {
mode: 'production',
devtool: 'hidden-source-map',
plugins: [new webpack.DefinePlugin({ __LARABUG_RELEASE__: JSON.stringify(release) })],
}
Laravel Mix takes the same settings through webpackConfig:
const { execSync } = require('child_process')
const release = process.env.LARABUG_RELEASE || execSync('git rev-parse HEAD').toString().trim()
mix.js('resources/js/app.js', 'public/js')
.webpackConfig((webpack) => ({
devtool: 'hidden-source-map',
plugins: [new webpack.DefinePlugin({ __LARABUG_RELEASE__: JSON.stringify(release) })],
}))
Then upload after the build, with the same release, and remove the maps so they are not served:
npx @larabug/sourcemaps upload --release "$(git rev-parse HEAD)" --dir public/js --url-prefix "~/js" --remove-source-maps
The uploader reads the key from LARABUG_KEY the same way the Vite plugin does. npx @larabug/sourcemaps list --release <release> prints what LaraBug holds for a release.
Matching files
Every map is uploaded with the url of the script it belongs to, and a frame uses the map whose url matches its file:
- A path starting with
~/, such as~/build/assets/app-D4wpJuT4.js, matches that path on any host. One upload covers production, staging and every preview domain. - A full url, such as
https://cdn.example.com/build/assets/app-D4wpJuT4.js, matches that host only.
A query string on the script, such as a cache buster, is ignored.
Naming the build yourself
Without a bundler plugin, set release when you start the client, and upload with the same string:
init({
dsn: import.meta.env.VITE_LARABUG_DSN,
release: import.meta.env.VITE_APP_VERSION,
})
An error without a release cannot be matched to any map, and the issue page says so.
On the issue page
A JavaScript error mapped to its source shows the original file, line and function of each frame, with the code around it when the build included sourcesContent, as Vite and webpack do by default. A toggle shows the minified frames the browser reported. Frames from scripts with no map, such as a third party widget, stay minified and are marked as such.
Issues group on the original line. An error that was already grouped before you uploaded maps stays in the issue it was in.
Maps uploaded after an error arrived still apply: the errors of that release are filled in once the upload lands.
Limits and retention
- One map can be up to 50 MB, and one release can hold up to 1000 maps.
- LaraBug keeps the maps of your newest 10 releases, counted by when they were last uploaded. Errors keep their original frames after the maps are gone.
- Uploading the same file for the same release again replaces it.
The application settings list every release and its files under Source maps, where you can delete a file or a whole release.
The upload API
The plugin and the uploader call two endpoints, which you can call from any tool:
curl -X POST https://www.larabug.com/api/sourcemaps \
-H "X-LaraBug-Ingest-Key: lbi_your_ingest_key" \
-F release=v1.4.0 \
-F minified_url="~/build/assets/app-D4wpJuT4.js" \
-F sourcemap=@public/build/assets/app-D4wpJuT4.js.map
It answers 201 for a new file, 200 for a replaced one, 413 for a map over the size limit, 422 for a file that is not a version 3 source map, and 401 or 403 for credentials it does not accept. GET /api/sourcemaps/releases/{release} lists the files a release has.