Skip to content

Commit 673b8e9

Browse files
committed
build: fix chips drag-drop example not working and improve example-module generation
Loading the chips-drag-drop example in the docs site is currently not possible, as the example files fail to load. This is because the example id does not match up with the directory that the example is stored within. This highlights an issue as the docs site assumes a certain folder structure. This is not ideal and an implementation detail specific to the Angular Components repository. The docs site should not need to make any assumptions about where code lives, but instead should just retrieve this information through the example information. That way we are free to change layout/file conventions in the components repository without having to worry about it breaking the docs site. This is implemented as part of this commit. Also a few sanity checks that enforce consistency (for improved code health) have been added. The new concept for examples also allows us to remove empty CSS files for examples without CSS.
1 parent 49de56c commit 673b8e9

24 files changed

+97
-38
lines changed

src/components-examples/cdk/stepper/cdk-linear-stepper-with-form/cdk-linear-stepper-with-form-example.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {FormBuilder, FormGroup, Validators} from '@angular/forms';
44

55
/** @title A custom CDK linear stepper with forms */
66
@Component({
7-
selector: 'cdk-linear-stepper-with-form',
7+
selector: 'cdk-linear-stepper-with-form-example',
88
templateUrl: './cdk-linear-stepper-with-form-example.html',
99
styleUrls: ['./cdk-linear-stepper-with-form-example.css']
1010
})

src/components-examples/example-data.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,13 @@ export class ExampleData {
2727
return;
2828
}
2929

30-
const {componentName, additionalFiles, additionalComponents, title} =
30+
const {componentName, files, selector, primaryFile, additionalComponents, title} =
3131
EXAMPLE_COMPONENTS[example];
3232
const exampleName = example.replace(/(?:^\w|\b\w)/g, letter => letter.toUpperCase());
3333

34-
// TODO(tinayuangao): Do not hard-code extensions
35-
this.exampleFiles = ['html', 'ts', 'css'].map(extension => `${example}-example.${extension}`);
36-
this.selectorName = this.indexFilename = `${example}-example`;
37-
38-
this.exampleFiles.push(...additionalFiles);
34+
this.exampleFiles = files;
35+
this.selectorName = selector;
36+
this.indexFilename = primaryFile;
3937
this.description = title || exampleName.replace(/[\-]+/g, ' ') + ' Example';
4038
this.componentNames = [componentName, ...additionalComponents];
4139
}

src/components-examples/material-experimental/column-resize/index.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
import {NgModule} from '@angular/core';
2-
import {OptInColumnResizeExampleModule} from './opt-in/opt-in-column-resize-example-module';
32
import {
4-
DefaultEnabledColumnResizeExampleModule,
5-
} from './default-enabled/default-enabled-column-resize-example-module';
3+
DefaultEnabledColumnResizeFlexExampleModule
4+
} from './default-enabled-column-resize-flex/default-enabled-column-resize-flex-example-module';
5+
import {
6+
DefaultEnabledColumnResizeExampleModule
7+
} from './default-enabled-column-resize/default-enabled-column-resize-example-module';
68
import {
7-
DefaultEnabledColumnResizeFlexExampleModule,
8-
} from './default-enabled-flex/default-enabled-column-resize-flex-example-module';
9+
OptInColumnResizeExampleModule
10+
} from './opt-in-column-resize/opt-in-column-resize-example-module';
911

1012
export {
1113
DefaultEnabledColumnResizeExample
12-
} from './default-enabled/default-enabled-column-resize-example';
14+
} from './default-enabled-column-resize/default-enabled-column-resize-example';
1315
export {
1416
DefaultEnabledColumnResizeExampleModule
15-
} from './default-enabled/default-enabled-column-resize-example-module';
17+
} from './default-enabled-column-resize/default-enabled-column-resize-example-module';
1618

1719
export {
1820
DefaultEnabledColumnResizeFlexExample
19-
} from './default-enabled-flex/default-enabled-column-resize-flex-example';
21+
} from './default-enabled-column-resize-flex/default-enabled-column-resize-flex-example';
2022
export {
2123
DefaultEnabledColumnResizeFlexExampleModule
22-
} from './default-enabled-flex/default-enabled-column-resize-flex-example-module';
24+
} from './default-enabled-column-resize-flex/default-enabled-column-resize-flex-example-module';
2325

2426
export {
2527
OptInColumnResizeExample
26-
} from './opt-in/opt-in-column-resize-example';
28+
} from './opt-in-column-resize/opt-in-column-resize-example';
2729
export {
2830
OptInColumnResizeExampleModule
29-
} from './opt-in/opt-in-column-resize-example-module';
31+
} from './opt-in-column-resize/opt-in-column-resize-example-module';
3032

3133

3234
@NgModule({

src/components-examples/material-experimental/mdc-form-field/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import {ReactiveFormsModule} from '@angular/forms';
44
import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field';
55
import {MatIconModule} from '@angular/material/icon';
66
import {
7-
FormFieldCustomControlExample,
7+
MdcFormFieldCustomControlExample,
88
MyTelInput
99
} from './mdc-form-field-custom-control/form-field-custom-control-example';
1010

1111
export {
12-
FormFieldCustomControlExample,
12+
MdcFormFieldCustomControlExample,
1313
MyTelInput,
1414
};
1515

1616
const EXAMPLES = [
17-
FormFieldCustomControlExample,
17+
MdcFormFieldCustomControlExample,
1818
];
1919

2020
@NgModule({

src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import {Subject} from 'rxjs';
77

88
/** @title Form field with custom telephone number input control. */
99
@Component({
10-
selector: 'form-field-custom-control-example',
10+
selector: 'mdc-form-field-custom-control-example',
1111
templateUrl: 'form-field-custom-control-example.html',
1212
styleUrls: ['form-field-custom-control-example.css'],
1313
})
14-
export class FormFieldCustomControlExample {}
14+
export class MdcFormFieldCustomControlExample {}
1515

1616
/** Data structure for holding telephone number. */
1717
export class MyTel {

src/components-examples/material/chips/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {MatChipsModule} from '@angular/material/chips';
77
import {MatFormFieldModule} from '@angular/material/form-field';
88
import {MatIconModule} from '@angular/material/icon';
99
import {ChipsAutocompleteExample} from './chips-autocomplete/chips-autocomplete-example';
10-
import {ChipsDragDropExample} from './chips-drag-and-drop/chips-drag-drop-example';
10+
import {ChipsDragDropExample} from './chips-drag-drop/chips-drag-drop-example';
1111
import {ChipsInputExample} from './chips-input/chips-input-example';
1212
import {ChipsOverviewExample} from './chips-overview/chips-overview-example';
1313
import {ChipsStackedExample} from './chips-stacked/chips-stacked-example';

tools/example-module/example-module.template

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
/**
22
******************************************************************************
33
* DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED.
4-
******************************************************************************/
4+
******************************************************************************
5+
*/
56

67
export interface LiveExample {
78
/** Title of the example. */
89
title: string;
910
/** Name of the example component. */
1011
componentName: string;
12+
/** Selector to match the component of this example. */
13+
selector: string;
14+
/** Name of the primary file of this example. */
15+
primaryFile: string;
16+
/** List of files which are part of the example. */
17+
files: string[];
18+
/** Path to the directory containing the example. */
19+
packagePath: string;
1120
/** List of additional components which are part of the example. */
1221
additionalComponents: string[];
13-
/** List of additional files which are part of the example. */
14-
additionalFiles: string[];
1522
/** NgModule that declares this example. */
1623
module: NgModuleInfo;
1724
}
@@ -27,4 +34,4 @@ export interface NgModuleInfo {
2734
importSpecifier: string;
2835
}
2936

30-
export const EXAMPLE_COMPONENTS: {[key: string]: LiveExample} = ${exampleComponents};
37+
export const EXAMPLE_COMPONENTS: {[id: string]: LiveExample} = ${exampleComponents};

tools/example-module/generate-example-module.ts

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ interface ExampleMetadata {
88
componentName: string;
99
/** Path to the source file that declares this example. */
1010
sourcePath: string;
11-
/** Module import that can be used to load this example. */
12-
importPath: string;
11+
/** Path to the directory containing this example. */
12+
packagePath: string;
13+
/** Selector to match the component of this example. */
14+
selector: string;
1315
/** Unique id for this example. */
1416
id: string;
1517
/** Title of the example. */
1618
title: string;
1719
/** Additional components for this example. */
1820
additionalComponents: string[];
19-
/** Additional files for this example. */
20-
additionalFiles: string[];
21+
/** Files for this example. */
22+
files: string[];
2123
/** Reference to the module that declares this example. */
2224
module: ExampleModule;
2325
}
@@ -37,11 +39,18 @@ interface AnalyzedExamples {
3739
function inlineExampleModuleTemplate(parsedData: AnalyzedExamples): string {
3840
const {exampleMetadata} = parsedData;
3941
const exampleComponents = exampleMetadata.reduce((result, data) => {
42+
if (result[data.id] !== undefined) {
43+
throw Error(`Multiple examples with the same id have been discovered: ${data.id}`);
44+
}
45+
4046
result[data.id] = {
47+
packagePath: data.packagePath,
4148
title: data.title,
4249
componentName: data.componentName,
43-
additionalFiles: data.additionalFiles,
50+
files: data.files,
51+
selector: data.selector,
4452
additionalComponents: data.additionalComponents,
53+
primaryFile: path.basename(data.sourcePath),
4554
module: {
4655
name: data.module.name,
4756
importSpecifier: data.module.packagePath,
@@ -73,13 +82,14 @@ function analyzeExamples(sourceFiles: string[], baseDir: string): AnalyzedExampl
7382
for (const sourceFile of sourceFiles) {
7483
const relativePath = path.relative(baseDir, sourceFile).replace(/\\/g, '/');
7584
const importPath = relativePath.replace(/\.ts$/, '');
85+
const packagePath = path.dirname(relativePath);
7686

7787
// Collect all individual example modules.
7888
if (path.basename(sourceFile) === 'index.ts') {
7989
exampleModules.push(...parseExampleModuleFile(sourceFile).map(name => ({
8090
name,
8191
importPath,
82-
packagePath: path.dirname(relativePath),
92+
packagePath,
8393
})));
8494
}
8595

@@ -96,25 +106,46 @@ function analyzeExamples(sourceFiles: string[], baseDir: string): AnalyzedExampl
96106
const exampleId = convertToDashCase(primaryComponent.componentName.replace('Example', ''));
97107
const example: ExampleMetadata = {
98108
sourcePath: relativePath,
109+
packagePath,
99110
id: exampleId,
111+
selector: primaryComponent.selector,
100112
componentName: primaryComponent.componentName,
101113
title: primaryComponent.title.trim(),
102-
additionalFiles: [],
103114
additionalComponents: [],
115+
files: [],
104116
module: null,
105-
importPath,
106117
};
118+
119+
// For consistency, we expect the example component selector to match
120+
// the id of the example.
121+
const expectedSelector = `${exampleId}-example`;
122+
if (primaryComponent.selector !== expectedSelector) {
123+
throw Error(`Example ${exampleId} uses selector: ${primaryComponent.selector}, ` +
124+
`but expected: ${expectedSelector}`);
125+
}
126+
127+
example.files.push(path.basename(relativePath));
128+
if (primaryComponent.templateUrl) {
129+
example.files.push(primaryComponent.templateUrl);
130+
}
131+
if (primaryComponent.styleUrls) {
132+
example.files.push(...primaryComponent.styleUrls);
133+
}
134+
107135
if (secondaryComponents.length) {
108136
for (const meta of secondaryComponents) {
109137
example.additionalComponents.push(meta.componentName);
110138
if (meta.templateUrl) {
111-
example.additionalFiles.push(meta.templateUrl);
139+
example.files.push(meta.templateUrl);
112140
}
113141
if (meta.styleUrls) {
114-
example.additionalFiles.push(...meta.styleUrls);
142+
example.files.push(...meta.styleUrls);
115143
}
116144
}
117145
}
146+
147+
// Ensure referenced files actually exist in the example.
148+
example.files.forEach(f => assertReferencedExampleFileExists(baseDir, packagePath, f));
118149
exampleMetadata.push(example);
119150
} else {
120151
throw Error(`Could not find a primary example component in ${sourceFile}. ` +
@@ -132,12 +163,32 @@ function analyzeExamples(sourceFiles: string[], baseDir: string): AnalyzedExampl
132163
throw Error(`Could not determine example module for: ${example.id}`);
133164
}
134165

166+
const actualPath = path.dirname(example.sourcePath);
167+
const expectedPath = path.posix.join(parentModule.packagePath, example.id);
168+
169+
// Ensures that example ids match with the directory they are stored in. This is not
170+
// necessary for the docs site, but it helps with consistency and makes it easy to
171+
// determine an id for an example. We also ensures for consistency that example
172+
// folders are direct siblings of the module file.
173+
if (actualPath !== expectedPath) {
174+
throw Error(`Example is stored in: ${actualPath}, but expected: ${expectedPath}`);
175+
}
176+
135177
example.module = parentModule;
136178
});
137179

138180
return {exampleMetadata};
139181
}
140182

183+
/** Asserts that the given file exists for the specified example. */
184+
function assertReferencedExampleFileExists(baseDir: string, examplePackagePath: string,
185+
relativePath: string) {
186+
if (!fs.existsSync(path.join(baseDir, examplePackagePath, relativePath))) {
187+
throw Error(
188+
`Example "${examplePackagePath}" references "${relativePath}", but file does not exist.`);
189+
}
190+
}
191+
141192
/**
142193
* Generates the example module from the given source files and writes it to a specified output
143194
* file.

tools/example-module/parse-example-file.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ interface ParsedMetadata {
44
isPrimary: boolean;
55
componentName: string;
66
title: string;
7+
selector: string;
78
templateUrl: string;
89
styleUrls: string[];
910
}

0 commit comments

Comments
 (0)