build method
Generates the outputs for a given BuildStep.
Implementation
@override
Future<void> build(BuildStep buildStep) async {
final inputId = buildStep.inputId;
// Determine which worker file to compile
AssetId? workerFile;
if (inputId.path == 'lib/worker.dart') {
// Manual worker.dart always takes priority
workerFile = inputId;
log.fine('Using manual worker.dart');
} else if (inputId.path == 'lib/worker_generated.g.dart') {
// Only use worker_generated.g.dart if worker.dart doesn't exist
final manualWorker = AssetId(inputId.package, 'lib/worker.dart');
if (await buildStep.canRead(manualWorker)) {
log.info(
'Skipping worker_generated.g.dart compilation because manual worker.dart exists');
return;
}
workerFile = inputId;
// make sure the generated worker file is materialized on disk for the compiler to read,
// it might have been generated in-memory and not exist on disk yet, which would cause the compiler to fail with a missing file error.
await _materializeAsset(
buildStep,
await _resolvePackageRoot(buildStep, workerFile),
workerFile.path,
workerFile,
);
log.fine('Using generated worker_generated.g.dart');
}
if (workerFile == null) {
return;
}
// Determine output filename based on input
final outputBasename = inputId.path == 'lib/worker.dart'
? 'worker.dart'
: 'worker_generated.dart';
log.info('Compiling worker for web platform: ${workerFile.path} → web/$outputBasename.js');
final stopwatch = Stopwatch()..start();
// Read the worker source (validates it exists and triggers rebuild on changes)
final workerSource = await buildStep.readAsString(workerFile);
// Create temporary directory for compilation output
final tempDir = await Directory.systemTemp.createTemp('worker_build_');
final tempOutputPath = p.join(tempDir.path, 'worker.dart.js');
try {
// Resolve package root so the compiler can see generated source outputs.
final packageRoot = await _resolvePackageRoot(buildStep, workerFile);
final inputPath = p.join(packageRoot, workerFile.path);
final generatedImports = _collectGeneratedImports(
workerSource, workerFile.package, workerFile.path);
for (final assetPath in generatedImports) {
await _materializeAsset(
buildStep,
packageRoot,
assetPath,
workerFile,
);
}
// Run dart compile js with production flags
final result = await Process.run(
'dart',
[
'compile',
'js',
'--no-source-maps', // Source maps can be enabled with flag later
'-o',
tempOutputPath,
inputPath,
],
runInShell: true,
workingDirectory: packageRoot,
);
if (result.exitCode != 0) {
log.severe('Worker compilation failed:');
log.severe('stdout: ${result.stdout}');
log.severe('stderr: ${result.stderr}');
throw Exception(
'Failed to compile worker: exit code ${result.exitCode}');
}
stopwatch.stop();
// Read the compiled JavaScript file from temp directory
final compiledJs = await File(tempOutputPath).readAsString();
final fileSize = compiledJs.length;
log.info(
'Worker compiled successfully in ${stopwatch.elapsedMilliseconds}ms');
log.info('Output size: ${_formatSize(fileSize)}');
// Write the compiled JavaScript through build system
// This ensures proper integration with build_runner
await buildStep.writeAsString(
AssetId(workerFile.package, 'web/$outputBasename.js'),
compiledJs,
);
// Check if source map was generated
final sourceMapPath = '$tempOutputPath.map';
final sourceMapFile = File(sourceMapPath);
if (await sourceMapFile.exists()) {
final sourceMap = await sourceMapFile.readAsString();
await buildStep.writeAsString(
AssetId(workerFile.package, 'web/$outputBasename.js.map'),
sourceMap,
);
log.info('Source map written: $outputBasename.js.map');
}
} catch (e, stack) {
log.severe('Error compiling worker', e, stack);
rethrow;
} finally {
// Clean up temporary directory
try {
await tempDir.delete(recursive: true);
} catch (e) {
log.warning('Failed to delete temp directory: ${tempDir.path}', e);
}
}
}