mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
docs(docs-infra): lift circular imports (#63186)
This commit also adds adev on the cyclic import check. PR Close #63186
This commit is contained in:
committed by
Jessica Janiuk
parent
cab3adfdd1
commit
9b539a18ba
@@ -21,6 +21,7 @@ ng_project(
|
||||
"//adev:node_modules/@angular/common",
|
||||
"//adev:node_modules/@angular/core",
|
||||
"//adev:node_modules/@angular/material",
|
||||
"//adev:node_modules/@angular/platform-browser",
|
||||
"//adev:node_modules/@angular/router",
|
||||
"//adev:node_modules/rxjs",
|
||||
"//adev/shared-docs/components/breadcrumb",
|
||||
|
||||
@@ -39,6 +39,7 @@ import {fromEvent} from 'rxjs';
|
||||
import {Breadcrumb} from '../../breadcrumb/breadcrumb.component';
|
||||
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
|
||||
import {ExampleViewer} from '../example-viewer/example-viewer.component';
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
|
||||
const TOC_HOST_ELEMENT_NAME = 'docs-table-of-contents';
|
||||
export const ASSETS_EXAMPLES_PATH = 'assets/content/examples';
|
||||
@@ -74,6 +75,7 @@ export class DocViewer {
|
||||
private readonly environmentInjector = inject(EnvironmentInjector);
|
||||
private readonly injector = inject(Injector);
|
||||
private readonly appRef = inject(ApplicationRef);
|
||||
private readonly sanitizer = inject(DomSanitizer);
|
||||
|
||||
protected animateContent = false;
|
||||
private readonly pendingTasks = inject(PendingTasks);
|
||||
@@ -215,7 +217,7 @@ export class DocViewer {
|
||||
|
||||
return tabs.map((tab) => ({
|
||||
name: tab.getAttribute('path') ?? tab.getAttribute('header') ?? '',
|
||||
content: tab.innerHTML,
|
||||
sanitizedContent: this.sanitizer.bypassSecurityTrustHtml(tab.innerHTML),
|
||||
visibleLinesRange: tab.getAttribute('visibleLines') ?? undefined,
|
||||
}));
|
||||
}
|
||||
@@ -235,7 +237,9 @@ export class DocViewer {
|
||||
return {
|
||||
title,
|
||||
name: path,
|
||||
content: content?.outerHTML,
|
||||
sanitizedContent: content?.outerHTML
|
||||
? this.sanitizer.bypassSecurityTrustHtml(content.outerHTML)
|
||||
: '',
|
||||
visibleLinesRange: visibleLines,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
}
|
||||
|
||||
@if (view() === CodeExampleViewMode.MULTI_FILE) {
|
||||
<mat-tab-group
|
||||
#codeTabs
|
||||
animationDuration="0ms"
|
||||
mat-stretch-tabs="false"
|
||||
>
|
||||
<mat-tab-group #codeTabs animationDuration="0ms" mat-stretch-tabs="false">
|
||||
@for (tab of tabs(); track tab) {
|
||||
<mat-tab [label]="tab.name"></mat-tab>
|
||||
}
|
||||
@@ -87,7 +83,9 @@
|
||||
[class.docs-example-viewer-multi-file]="view() === CodeExampleViewMode.MULTI_FILE"
|
||||
>
|
||||
<button docs-copy-source-code></button>
|
||||
<docs-viewer [docContent]="snippetCode()?.content" />
|
||||
@if (snippetCode()?.sanitizedContent; as content) {
|
||||
<div [innerHTML]="content"></div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (exampleComponent) {
|
||||
|
||||
+15
-15
@@ -53,9 +53,9 @@ describe('ExampleViewer', () => {
|
||||
'metadata',
|
||||
getMetadata({
|
||||
files: [
|
||||
{name: 'file.ts', content: ''},
|
||||
{name: 'file.html', content: ''},
|
||||
{name: 'file.css', content: ''},
|
||||
{name: 'file.ts', sanitizedContent: ''},
|
||||
{name: 'file.html', sanitizedContent: ''},
|
||||
{name: 'file.css', sanitizedContent: ''},
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -73,9 +73,9 @@ describe('ExampleViewer', () => {
|
||||
'metadata',
|
||||
getMetadata({
|
||||
files: [
|
||||
{name: 'file.ts', content: 'typescript file'},
|
||||
{name: 'file.html', content: 'html file'},
|
||||
{name: 'file.css', content: 'css file'},
|
||||
{name: 'file.ts', sanitizedContent: 'typescript file'},
|
||||
{name: 'file.html', sanitizedContent: 'html file'},
|
||||
{name: 'file.css', sanitizedContent: 'css file'},
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -93,9 +93,9 @@ describe('ExampleViewer', () => {
|
||||
'metadata',
|
||||
getMetadata({
|
||||
files: [
|
||||
{name: 'example.ts', content: 'typescript file'},
|
||||
{name: 'example.html', content: 'html file'},
|
||||
{name: 'another-example.ts', content: 'css file'},
|
||||
{name: 'example.ts', sanitizedContent: 'typescript file'},
|
||||
{name: 'example.html', sanitizedContent: 'html file'},
|
||||
{name: 'another-example.ts', sanitizedContent: 'css file'},
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -124,7 +124,7 @@ describe('ExampleViewer', () => {
|
||||
files: [
|
||||
{
|
||||
name: 'example.ts',
|
||||
content: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
|
||||
sanitizedContent: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
|
||||
visibleLinesRange: '[1]',
|
||||
},
|
||||
],
|
||||
@@ -148,7 +148,7 @@ describe('ExampleViewer', () => {
|
||||
files: [
|
||||
{
|
||||
name: 'example.ts',
|
||||
content: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
|
||||
sanitizedContent: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
|
||||
visibleLinesRange: '[1]',
|
||||
},
|
||||
],
|
||||
@@ -243,9 +243,9 @@ describe('ExampleViewer', () => {
|
||||
files: [
|
||||
{
|
||||
name: 'example.ts',
|
||||
content: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
|
||||
sanitizedContent: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
|
||||
},
|
||||
{name: 'example.css', content: ''},
|
||||
{name: 'example.css', sanitizedContent: ''},
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -279,8 +279,8 @@ const getMetadata = (value: Partial<ExampleMetadata> = {}): ExampleMetadata => {
|
||||
return {
|
||||
id: 1,
|
||||
files: [
|
||||
{name: 'example.ts', content: ''},
|
||||
{name: 'example.css', content: ''},
|
||||
{name: 'example.ts', sanitizedContent: ''},
|
||||
{name: 'example.css', sanitizedContent: ''},
|
||||
],
|
||||
preview: false,
|
||||
...value,
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
computed,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
inject,
|
||||
Injector,
|
||||
input,
|
||||
@@ -29,7 +28,6 @@ import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-co
|
||||
import {ExampleMetadata, Snippet} from '../../../interfaces/index';
|
||||
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {DocViewer} from '../docs-viewer/docs-viewer.component';
|
||||
|
||||
export enum CodeExampleViewMode {
|
||||
SNIPPET = 'snippet',
|
||||
@@ -43,7 +41,7 @@ export const HIDDEN_CLASS_NAME = 'hidden';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-example-viewer',
|
||||
imports: [CommonModule, forwardRef(() => DocViewer), CopySourceCodeButton, MatTabsModule],
|
||||
imports: [CommonModule, CopySourceCodeButton, MatTabsModule],
|
||||
templateUrl: './example-viewer.component.html',
|
||||
styleUrls: ['./example-viewer.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -80,7 +78,7 @@ export class ExampleViewer {
|
||||
this.exampleMetadata()?.files.map((file) => ({
|
||||
name:
|
||||
file.title ?? (this.shouldDisplayFullName() ? file.name : this.getFileExtension(file.name)),
|
||||
code: file.content,
|
||||
code: file.sanitizedContent,
|
||||
})),
|
||||
);
|
||||
view = computed(() =>
|
||||
|
||||
@@ -26,6 +26,7 @@ ts_project(
|
||||
),
|
||||
deps = [
|
||||
"//adev:node_modules/@angular/core",
|
||||
"//adev:node_modules/@angular/platform-browser",
|
||||
"//adev:node_modules/@angular/router",
|
||||
"//adev:node_modules/@types/node",
|
||||
"//adev:node_modules/@webcontainer/api",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {SafeHtml} from '@angular/platform-browser';
|
||||
|
||||
/**
|
||||
* Map of the examples, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component
|
||||
@@ -21,7 +22,7 @@ export interface Snippet {
|
||||
/** Name of the file. */
|
||||
name: string;
|
||||
/** Content of code snippet */
|
||||
content: string;
|
||||
sanitizedContent: SafeHtml;
|
||||
/** Text in following format `start-end`. Start and end are numbers, based on them provided range of lines will be displayed in collapsed mode */
|
||||
visibleLinesRange?: string;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {Subject, filter, take} from 'rxjs';
|
||||
|
||||
import {EditorFile} from '../code-mirror-editor.service';
|
||||
import type {EditorFile} from '../code-mirror-editor.service';
|
||||
import {TsVfsWorkerActions} from '../workers/enums/actions';
|
||||
import {AutocompleteRequest} from '../workers/interfaces/autocomplete-request';
|
||||
import {AutocompleteItem, AutocompleteResponse} from '../workers/interfaces/autocomplete-response';
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {Diagnostic, linter} from '@codemirror/lint';
|
||||
import {TsVfsWorkerActions} from '../workers/enums/actions';
|
||||
import {Signal} from '@angular/core';
|
||||
import {EditorFile} from '../code-mirror-editor.service';
|
||||
import type {EditorFile} from '../code-mirror-editor.service';
|
||||
import {ActionMessage} from '../workers/interfaces/message';
|
||||
import {DiagnosticsRequest} from '../workers/interfaces/diagnostics-request';
|
||||
import {Subject, filter, take} from 'rxjs';
|
||||
|
||||
@@ -13,7 +13,7 @@ import {Subject, filter, take} from 'rxjs';
|
||||
|
||||
import ts from 'typescript';
|
||||
|
||||
import {EditorFile} from '../code-mirror-editor.service';
|
||||
import type {EditorFile} from '../code-mirror-editor.service';
|
||||
import {TsVfsWorkerActions} from '../workers/enums/actions';
|
||||
import {DisplayTooltipRequest} from '../workers/interfaces/display-tooltip-request';
|
||||
import {DisplayTooltipResponse} from '../workers/interfaces/display-tooltip-response';
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
export const DEV_SERVER_READY_MSG = 'Watch mode enabled. Watching for file changes...';
|
||||
export const OUT_OF_MEMORY_MSG = 'Out of memory';
|
||||
@@ -15,15 +15,11 @@ import {FakeWebContainer, FakeWebContainerProcess, TutorialType} from '@angular/
|
||||
import {AlertManager} from './alert-manager.service';
|
||||
import {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
|
||||
import {LoadingStep} from './enums/loading-steps';
|
||||
import {
|
||||
DEV_SERVER_READY_MSG,
|
||||
NodeRuntimeSandbox,
|
||||
OUT_OF_MEMORY_MSG,
|
||||
PACKAGE_MANAGER,
|
||||
} from './node-runtime-sandbox.service';
|
||||
import {NodeRuntimeSandbox, PACKAGE_MANAGER} from './node-runtime-sandbox.service';
|
||||
import {NodeRuntimeState} from './node-runtime-state.service';
|
||||
import {TerminalHandler} from './terminal/terminal-handler.service';
|
||||
import {TypingsLoader} from './typings-loader.service';
|
||||
import {DEV_SERVER_READY_MSG, OUT_OF_MEMORY_MSG} from './node-runtime-errors';
|
||||
|
||||
describe('NodeRuntimeSandbox', () => {
|
||||
let testBed: TestBed;
|
||||
|
||||
@@ -19,9 +19,7 @@ import {LoadingStep} from './enums/loading-steps';
|
||||
import {ErrorType, NodeRuntimeState} from './node-runtime-state.service';
|
||||
import {TerminalHandler} from './terminal/terminal-handler.service';
|
||||
import {TypingsLoader} from './typings-loader.service';
|
||||
|
||||
export const DEV_SERVER_READY_MSG = 'Watch mode enabled. Watching for file changes...';
|
||||
export const OUT_OF_MEMORY_MSG = 'Out of memory';
|
||||
import {DEV_SERVER_READY_MSG, OUT_OF_MEMORY_MSG} from './node-runtime-errors';
|
||||
|
||||
const enum PROCESS_EXIT_CODE {
|
||||
SUCCESS = 0, // process exited successfully
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ErrorType, NodeRuntimeState} from './node-runtime-state.service';
|
||||
import {OUT_OF_MEMORY_MSG} from './node-runtime-sandbox.service';
|
||||
import {OUT_OF_MEMORY_MSG} from './node-runtime-errors';
|
||||
|
||||
describe('NodeRuntimeState', () => {
|
||||
let service: NodeRuntimeState;
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
*/
|
||||
|
||||
import {Injectable, signal} from '@angular/core';
|
||||
import {isFirefox, isIos} from '@angular/docs';
|
||||
import {isIos} from '@angular/docs';
|
||||
|
||||
import {LoadingStep} from './enums/loading-steps';
|
||||
import {OUT_OF_MEMORY_MSG} from './node-runtime-sandbox.service';
|
||||
import {OUT_OF_MEMORY_MSG} from './node-runtime-errors';
|
||||
|
||||
export const MAX_RECOMMENDED_WEBCONTAINERS_INSTANCES = 3;
|
||||
export const WEBCONTAINERS_COUNTER_KEY = 'numberOfWebcontainers';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Animation} from '../animation';
|
||||
import type {Animation} from '../animation';
|
||||
|
||||
/**
|
||||
* Animation plugin interface.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {ParentComponent} from './parent.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-child',
|
||||
template: 'The child!',
|
||||
})
|
||||
export class ChildComponent {
|
||||
private parent = inject(ParentComponent);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {ChildComponent} from './child.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-parent',
|
||||
imports: [ChildComponent],
|
||||
template: '<app-child/>',
|
||||
})
|
||||
export class ParentComponent {}
|
||||
@@ -3,9 +3,30 @@
|
||||
A component, directive, or pipe that is referenced by this component would require the compiler to add an import that would lead to a cycle of imports.
|
||||
For example, consider a scenario where a `ParentComponent` references a `ChildComponent` in its template:
|
||||
|
||||
<docs-code header="parent.component.ts" path="adev/src/content/examples/errors/cyclic-imports/parent.component.ts"/>
|
||||
<docs-code header="parent.component.ts" language="angular-ts">
|
||||
import {Component} from '@angular/core';
|
||||
import {ChildComponent} from './child.component';
|
||||
|
||||
<docs-code header="child.component.ts" path="adev/src/content/examples/errors/cyclic-imports/child.component.ts"/>
|
||||
@Component({
|
||||
selector: 'app-parent',
|
||||
imports: [ChildComponent],
|
||||
template: '<app-child/>',
|
||||
})
|
||||
export class ParentComponent {}
|
||||
</docs-code>
|
||||
|
||||
<docs-code header="child.component.ts" language="angular-ts">
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {ParentComponent} from './parent.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-child',
|
||||
template: 'The child!',
|
||||
})
|
||||
export class ChildComponent {
|
||||
private parent = inject(ParentComponent);
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
There is already an import from `child.component.ts` to `parent.component.ts` since the `ChildComponent` references the `ParentComponent` in its constructor.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
baseDir: '../',
|
||||
glob: `./**/*.ts`,
|
||||
glob: `../{packages,adev}/**/*.ts`,
|
||||
resolveModule: resolveModule,
|
||||
ignoreTypeOnlyChecks: true,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user