Implement a Compiler

Standard compilers are integrated into Bit using a Bit Aspect. For example, Babel is integrated using the Babel Aspect and TypeScript is integrated using the TypeScript Aspect. These Aspects implement the Compiler interface. A compiler implementation should be able to provide compilation in the workspace as well as compilation during build.

The easiest way to start implementing a compiler is by using the 'compiler' template:

bit create compiler extensions/my-compiler
CopiedCopy

Alternatively, you can import an example compiler component (and edit it in your own workspace):

bit import teambit.compilation/examples/extensions/babel-compiler
CopiedCopy

Implement compilation in the workspace

For component compilation in the workspace, implement the transpileFile API.

transpileFile receives from the Compiler service, the content (fileContent) and relative paths (options), of each of the component's files (one at a time). To prevent the Compiler service from passing unsupported files (files that cannot be compiled by your compiler), implement the isFileSupported API.

transpileFile should return an array of objects that contain the compiled content and relative paths (with their new filename extensions). The output returned from transpileFile is written by the Compiler service into the dist directory of the component's corresponding package (in the workspace node_modules directory).

To instruct the Compiler service to copy unsupported files to the dist directory, implement the shouldCopyNonSupportedFiles field and set it to true.

transpileFile( fileContent: string, options: TranspileFileParams ): TranspileFileOutput {
  // ...
  return return [{ outputText: compiledContent, outputPath: compiledRelPath }];
}

isFileSupported(filePath: string): boolean {
   // ..
  return isFileExtSupported
}

shouldCopyNonSupportedFiles = true;
CopiedCopy

Implement Compilation during Build

For component compilation during build, implement the build API.

build receives (from the Builder service) the entire Build Context. However, our compiler only needs the seedersCapsules as they are Component Capsules that are intended to be built, while the rest are dependencies that do not need to go through the compilation process.

The build implementation should compile all the relevant files by iterating over each Capsule, and again over each supported file in these Capsules (note that Capsules contain the filepaths, not their content). Use the isFileSupported method, implemented in the 'Compilation in the Workspace' section, to filter out unsupported files.

The returned artifacts array selects the artifacts (in this case, the compiled code and copied unsupported files) to persist in the component snapshot. It does so by selecting all files under its dist directory (in the the compiled component's Capsule. This is supplemented with the artifacts description (generatedBy and name).

async build(context: BuildContext): Promise<BuiltTaskResult> {
    const capsules = context.capsuleNetwork.seedersCapsules;
    const componentsResults = await Promise.all(capsules.map(async (capsule) => {
      // ...
      return { component: capsule.component, errors } as ComponentResult;
      })
    );
    return {
      artifacts: [
        {
          generatedBy: this.id,
          name: 'compiler output',
          globPatterns: [`${this.distDir}/**`],
        },
      ],
      componentsResults,
    };
  }
CopiedCopy

It is recommended to expose an API that returns you compiler's build task, for Envs to use. Without it, Envs will be forced to use the Compiler aspect directly.

createTask() {
  return this.compiler.createTask('MyCompiler', this);
}
CopiedCopy

Expose your Compiler's Output Paths

Implement the getDistPathBySrcPath API to share with other aspects (e.g, the Tester aspect) the dist paths.

distDir = 'dist';

getDistPathBySrcPath(srcPath: string) {
  // ...
  const absoluteDistPath = path.join(distDir, distPath)
  return absoluteDistPath;
  }
CopiedCopy

Expose a 'CreateCompiler' API for Envs to Use

Update the compiler's main runtime file (my-compiler.main.runtime.ts) to do the following:

  1. Expose the createCompiler function for Envs to use.
  2. Update the main file property of the component's package.json, via the getPackageJsonProps service handler.
import { MainRuntime } from '@teambit/cli';
import { CompilerAspect, CompilerMain } from '@teambit/compiler';
import { MyCompiler } from './my-compiler.compiler';
import { MyCompilerAspect } from './my-compiler.aspect';

export class MyCompilerMain {
  constructor(private compiler: CompilerMain) {}

  static dependencies = [CompilerAspect];

  static runtime = MainRuntime;

  createCompiler(): MyCompiler {
    return new MyCompiler(MyCompilerAspect.id, this.compiler);
  }

  getPackageJsonProps() {
    return {
      main: 'dist/{main}.js',
    };
  }

  static async provider([compiler]: [CompilerMain]) {
    return new MyCompiler(compiler);
  }
}

MyCompilerAspect.addRuntime(MyCompilerMain);
CopiedCopy

Using the Compiler Implementation in an Env

See here to learn how to configure you Env to use your compiler implementation.