blob: a781e68723ad90a1874246307c011c30d396c06c [file] [log] [blame] [edit]
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const {uglify} = require('rollup-plugin-uglify');
const commonjs = require('@rollup/plugin-commonjs');
const nodeResolve = require('@rollup/plugin-node-resolve');
const path = require('path');
const replace = require('rollup-plugin-re');
const sourcemaps = require('rollup-plugin-sourcemaps');
const json = require('@rollup/plugin-json');
const {SourceMapConsumer, SourceMapGenerator} = require('source-map');
const ROOT_DIR = path.dirname(path.dirname(__dirname)); // The repo root.
const OUT_SYMLINK = path.join(ROOT_DIR, 'ui/out');
// Plugin to embed minimal source maps directly into bundles
function embedMinimalSourceMap() {
return {
name: 'embed-minimal-sourcemap',
async generateBundle(options, bundle) {
for (const chunk of Object.values(bundle)) {
if (!chunk.fileName || !chunk.fileName.endsWith('_bundle.js') || !chunk.map) {
continue;
}
try {
// Create minimal source map from Rollup's full map
const consumer = await new SourceMapConsumer(chunk.map);
const generator = new SourceMapGenerator({
file: chunk.map.file,
});
// Track which lines we've seen to only add one mapping per line
const seenLines = new Set();
// Clean source paths
const cleanSourcePath = (source) => {
let cleaned = source.replace('../../../out/ui/', '');
cleaned = cleaned.replace('../../node_modules/', 'node_modules/');
return cleaned;
};
consumer.eachMapping((mapping) => {
if (!mapping.source) return;
// Only add first mapping per generated line
const lineKey = mapping.generatedLine;
if (seenLines.has(lineKey)) return;
seenLines.add(lineKey);
const cleanSource = cleanSourcePath(mapping.source);
generator.addMapping({
generated: {
line: mapping.generatedLine,
column: 0, // First column only
},
original: {
line: mapping.originalLine,
column: mapping.originalColumn,
},
source: cleanSource,
// name intentionally omitted to strip name references
});
});
consumer.destroy();
const minimalMap = JSON.parse(generator.toString());
// Remove sourcesContent to reduce size
delete minimalMap.sourcesContent;
// Remove names array (should be empty anyway since we didn't add names)
delete minimalMap.names;
// Embed the minimal map at the end of the bundle using a registry
// Use 'self' instead of 'window' for worker compatibility
// Each bundle registers its map with its filename as the key
chunk.code += `\n;(self.__SOURCEMAPS=self.__SOURCEMAPS||{})['${chunk.fileName}']=${JSON.stringify(minimalMap)};`;
if (process.env.VERBOSE) {
console.log(`Embedded minimal source map into ${chunk.fileName}`);
}
} catch (err) {
console.error(`Error creating minimal source map for ${chunk.fileName}:`, err.message);
// Don't fail the build, just skip embedding
}
}
},
};
}
function defBundle(tsRoot, bundle, distDir) {
return {
input: `${OUT_SYMLINK}/${tsRoot}/${bundle}/index.js`,
output: {
name: bundle,
format: 'iife',
esModule: false,
file: `${OUT_SYMLINK}/${distDir}/${bundle}_bundle.js`,
sourcemap: true,
},
watch: {
exclude: ['out/**'],
buildDelay: 250,
},
plugins: [
replace({
patterns:
process.env['IS_MEMORY64_ONLY'] != 'true'
? [
{
test: './trace_processor_32_stub',
replace: '../gen/trace_processor',
},
]
: [],
}),
nodeResolve({
mainFields: ['browser'],
browser: true,
preferBuiltins: false,
}),
commonjs({
strictRequires: true,
}),
json(),
replace({
patterns: [
// Protobufjs's inquire() uses eval but that's not really needed in
// the browser. https://github.com/protobufjs/protobuf.js/issues/593
{test: /eval\(.*\(moduleName\);/g, replace: 'undefined;'},
// Immer entry point has a if (process.env.NODE_ENV === 'production')
// but |process| is not defined in the browser. Bypass.
// https://github.com/immerjs/immer/issues/557
{test: /process\.env\.NODE_ENV/g, replace: "'production'"},
],
}),
// Translate source maps to point back to the .ts sources.
sourcemaps(),
// Embed minimal source map for error reporting
embedMinimalSourceMap(),
].concat(maybeUglify()),
onwarn: function (warning, warn) {
if (warning.code === 'CIRCULAR_DEPENDENCY') {
// Ignore circular dependency warnings coming from third party code.
if (warning.message.includes('node_modules')) {
return;
}
// Treat all other circular dependency warnings as errors.
throw new Error(
`Circular dependency detected in ${warning.importer}:\n\n ${warning.cycle.join('\n ')}`,
);
}
// Call the default warning handler for all remaining warnings.
warn(warning);
},
};
}
function defServiceWorkerBundle() {
return {
input: `${OUT_SYMLINK}/tsc/service_worker/service_worker.js`,
output: {
name: 'service_worker',
format: 'iife',
esModule: false,
file: `${OUT_SYMLINK}/dist/service_worker.js`,
sourcemap: true,
},
plugins: [
nodeResolve({
mainFields: ['browser'],
browser: true,
preferBuiltins: false,
}),
commonjs(),
sourcemaps(),
],
};
}
function maybeUglify() {
const minifyEnv = process.env['MINIFY_JS'];
if (!minifyEnv) return [];
const opts =
minifyEnv === 'preserve_comments' ? {output: {comments: 'all'}} : undefined;
return [uglify(opts)];
}
const maybeBigtrace = process.env['ENABLE_BIGTRACE']
? [defBundle('tsc/bigtrace', 'bigtrace', 'dist_version/bigtrace')]
: [];
const maybeOpenPerfettoTrace = process.env['ENABLE_OPEN_PERFETTO_TRACE']
? [defBundle('tsc', 'open_perfetto_trace', 'dist/open_perfetto_trace')]
: [];
module.exports = [
defBundle('tsc', 'frontend', 'dist_version'),
defBundle('tsc', 'engine', 'dist_version'),
defBundle('tsc', 'traceconv', 'dist_version'),
defBundle('tsc', 'chrome_extension', 'chrome_extension'),
defServiceWorkerBundle(),
]
.concat(maybeBigtrace)
.concat(maybeOpenPerfettoTrace);