Skip to content

Build integration

The Vite plugin, the Angular builder and the editor plugin behind `templateMD`.

templateMD inside @Component is a build-time transform. This page is what it does, where it runs, and what it costs.

Why a transform is needed

Angular's compiler accepts only what it knows: a @Component argument must be a literal object, its values must be statically evaluable, and an unknown key is dropped before anything reaches the runtime. A Markdown string imported at build time is none of those, so templateMD cannot work as metadata on its own.

Given this:

ts
@Component({
  selector: 'app-button-page',
  templateMD,
  styleUrl: './button.page.css',
  components: [AppButton],
})
export class ButtonPage {
  count = 0;
}

the transform produces the page you would otherwise write by hand:

ts
@Component({
  selector: 'app-button-page',
  template: '',
  styleUrl: './button.page.css',
})
export class ButtonPage {
  #blasdocPage = blasdocPageInit(this, { templateMD, components: [AppButton] });
  count = 0;
}

Every edit preserves line numbers. Removed properties are blanked rather than deleted, the initialiser goes on the class's opening-brace line, and the import is appended at the end where ESM hoists it. A stack trace still lands on the line you wrote.

Where it runs

Neither Angular toolchain takes its sources from the bundler — @angular/build runs ngtsc over its own program, and @analogjs/vite-plugin-angular keeps one too — so a Vite transform hook alone is read and discarded by both. The seam they share is ts.sys.readFile, and that is where the transform is installed.

The Angular CLI

One word in angular.json:

json
{
  "build": {
    "builder": "@blasdoc/build:application",
    "options": { }
  },
  "serve": {
    "builder": "@blasdoc/build:dev-server",
    "options": { "buildTarget": "app:build" }
  }
}

The options are Angular's own and pass through untouched — it is @angular/build:application with the transform installed first. Nx reads the same builders, so a project.json says the same thing.

Vite, Vitest, Analog

ts
import { blasdoc } from '@blasdoc/core/build';
 
export default defineConfig({ plugins: [blasdoc(), angular()] });

The same plugin also resolves .md imports to their string.

The editor

The Angular Language Service runs its own compiler over the file as written, so it reports NG2001: @Component is missing a template on a page that builds and runs. Tell it to leave that one alone:

json
{
  "angular.suppressAngularDiagnosticCodes": "-992001"
}

Nothing is lost: ng build still fails on a component that genuinely has no template. Blasdoc also ships a TypeScript language-service plugin, which fixes it properly wherever the Angular Language Service runs inside tsserver:

json
{
  "compilerOptions": {
    "plugins": [{ "name": "@blasdoc/build" }]
  }
}

The VS Code extension runs in its own process with local plugin loading disabled, which is why the setting above is the answer there.

What it costs

  • Blasdoc owns a build target in a project that wants templateMD.

  • The Angular compilation runs in the build process rather than in a worker thread, which costs some build time.

  • A project on plain ng build with no wrapper has no seam to install it into.

Using the transform directly

ts
import {
  installAngularPageTransform,
  transformAngularPage,
  editorAngularPage,
} from '@blasdoc/core/build';
FunctionUse
transformAngularPage(code, file)the build transform; returns the rewritten module or null
editorAngularPage(code, file)the smallest edit that satisfies the compiler, same byte length
installAngularPageTransform()patches ts.sys.readFile for whatever runs next