build method

  1. @override
Future<void> build(
  1. BuildStep buildStep
)

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);
    }
  }
}