Skip to content

Commit 84e1a14

Browse files
fix(docs): merge together style docs from multiple CSS files (#5653)
This fixes a bug where although multiple stylesheets were being processed by the `ext-transforms-plugin` we were not properly merging the style documentation for all of those stylesheets, leading to a race condition where whichever file was processed last would set the style docs for the whole component. Not good! To fix the issue we just need to merge the style docs for the various stylesheets together (de-duping on the `name` property). This also adds a test project in `test/docs-readme` which exercises this functionality. STENCIL-1271
1 parent 2082368 commit 84e1a14

File tree

17 files changed

+254
-5
lines changed

17 files changed

+254
-5
lines changed

cspell-wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ stenciljs
112112
stnl
113113
stringification
114114
stringified
115+
styleurls
115116
subdir
116117
templating
117118
timespan

src/compiler/bundle/ext-transforms-plugin.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { hasError, isOutputTargetDistCollection, join, normalizeFsPath, relative } from '@utils';
1+
import { hasError, isOutputTargetDistCollection, join, mergeIntoWith, normalizeFsPath, relative } from '@utils';
22
import type { Plugin } from 'rollup';
33

44
import type * as d from '../../declarations';
@@ -163,7 +163,8 @@ export const extTransformsPlugin = (
163163

164164
// Set style docs
165165
if (cmp) {
166-
cmp.styleDocs = cssTransformResults.styleDocs;
166+
cmp.styleDocs ||= [];
167+
mergeIntoWith(cmp.styleDocs, cssTransformResults.styleDocs, (docs) => docs.name);
167168
}
168169

169170
// Track dependencies

src/utils/helpers.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,30 @@ export const unique = <T, K>(array: T[], predicate: (item: T) => K = (i) => i as
130130
});
131131
};
132132

133+
/**
134+
* Merge elements of an array into an existing array, using a predicate to
135+
* determine uniqueness and only adding elements when they are not present in
136+
* the first array.
137+
*
138+
* **Note**: this mutates the target array! This is intentional to avoid
139+
* unnecessary array allocation, but be sure that it's what you want!
140+
*
141+
* @param target the target array, to which new unique items should be added
142+
* @param newItems a list of new items, some (or all!) of which may be added
143+
* @param mergeWith a predicate function which reduces the items in `target`
144+
* and `newItems` to a value which can be equated with `===` for the purposes
145+
* of determining uniqueness
146+
*/
147+
export function mergeIntoWith<T1, T2>(target: T1[], newItems: T1[], mergeWith: (item: T1) => T2) {
148+
for (const item of newItems) {
149+
const maybeItem = target.find((existingItem) => mergeWith(existingItem) === mergeWith(item));
150+
if (!maybeItem) {
151+
// this is a new item that isn't present in `target` yet
152+
target.push(item);
153+
}
154+
}
155+
}
156+
133157
/**
134158
* A utility for building an object from an iterable very similar to
135159
* `Object.fromEntries`

src/utils/test/helpers.spec.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dashToPascalCase, isDef, isPromise, toCamelCase, toDashCase } from '../helpers';
1+
import { dashToPascalCase, isDef, isPromise, mergeIntoWith, toCamelCase, toDashCase } from '../helpers';
22

33
describe('util helpers', () => {
44
describe('isPromise', () => {
@@ -110,4 +110,31 @@ describe('util helpers', () => {
110110
expect(isDef(null)).toBe(false);
111111
});
112112
});
113+
114+
describe('mergeIntoWith', () => {
115+
it('should do nothing if all elements already present', () => {
116+
const target = [1, 2, 3];
117+
mergeIntoWith(target, [1, 2, 3], (x) => x);
118+
expect(target).toEqual([1, 2, 3]);
119+
});
120+
121+
it('should add new items', () => {
122+
const target = [1, 2, 3];
123+
mergeIntoWith(target, [1, 2, 3, 4, 5], (x) => x);
124+
expect(target).toEqual([1, 2, 3, 4, 5]);
125+
});
126+
127+
it('should merge in objects using the predicate', () => {
128+
const target = [{ id: 'foo' }, { id: 'bar' }, { id: 'boz' }, { id: 'baz' }];
129+
mergeIntoWith(target, [{ id: 'foo' }, { id: 'fab' }, { id: 'fib' }], (x) => x.id);
130+
expect(target).toEqual([
131+
{ id: 'foo' },
132+
{ id: 'bar' },
133+
{ id: 'boz' },
134+
{ id: 'baz' },
135+
{ id: 'fab' },
136+
{ id: 'fib' },
137+
]);
138+
});
139+
});
113140
});

test/docs-readme/package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/docs-readme/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "docs-readme-testbed",
3+
"version": "1.0.0",
4+
"description": "A test app for the docs-readme output target",
5+
"files": [
6+
"dist/"
7+
],
8+
"scripts": {
9+
"build": "node ../../bin/stencil build",
10+
"build.dev": "node ../../bin/stencil build --dev",
11+
"start": "node ../../bin/stencil build --dev --watch --serve",
12+
"test": "node ../../bin/stencil test --spec --e2e",
13+
"test.watch": "node ../../bin/stencil test --spec --e2e --watch",
14+
"generate": "node ../../bin/stencil generate"
15+
},
16+
"license": "MIT"
17+
}

test/docs-readme/readme.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# docs-readme test app
2+
3+
This directory contains a test application which exercises the `docs-readme`
4+
output target. This provides us with an end-to-end test of this functionality.
5+
6+
## Components
7+
8+
The components in here and what they test!
9+
10+
### `<styleurls-component>`
11+
12+
This tests that the docs from multiple `styleUrls` in the `@Component`
13+
decorator are pulled in to the docs output correctly.

test/docs-readme/src/components.d.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* eslint-disable */
2+
/* tslint:disable */
3+
/**
4+
* This is an autogenerated file created by the Stencil compiler.
5+
* It contains typing information for all components that exist in this project.
6+
*/
7+
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
8+
export namespace Components {
9+
interface StyleurlsComponent {
10+
}
11+
}
12+
declare global {
13+
interface HTMLStyleurlsComponentElement extends Components.StyleurlsComponent, HTMLStencilElement {
14+
}
15+
var HTMLStyleurlsComponentElement: {
16+
prototype: HTMLStyleurlsComponentElement;
17+
new (): HTMLStyleurlsComponentElement;
18+
};
19+
interface HTMLElementTagNameMap {
20+
"styleurls-component": HTMLStyleurlsComponentElement;
21+
}
22+
}
23+
declare namespace LocalJSX {
24+
interface StyleurlsComponent {
25+
}
26+
interface IntrinsicElements {
27+
"styleurls-component": StyleurlsComponent;
28+
}
29+
}
30+
export { LocalJSX as JSX };
31+
declare module "@stencil/core" {
32+
export namespace JSX {
33+
interface IntrinsicElements {
34+
"styleurls-component": LocalJSX.StyleurlsComponent & JSXBase.HTMLAttributes<HTMLStyleurlsComponentElement>;
35+
}
36+
}
37+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
:host {
2+
/**
3+
* @prop --one: Property One
4+
*/
5+
display: block;
6+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# styleurls-component
2+
3+
4+
5+
<!-- Auto Generated Below -->
6+
7+
8+
## CSS Custom Properties
9+
10+
| Name | Description |
11+
| ------- | ------------ |
12+
| `--one` | Property One |
13+
| `--two` | Property Two |
14+
15+
16+
----------------------------------------------
17+
18+
*Built with [StencilJS](https://stenciljs.com/)*
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Component, h } from '@stencil/core';
2+
3+
@Component({
4+
tag: 'styleurls-component',
5+
shadow: true,
6+
// CSS properties documented in both of these files should
7+
// show up in this component's README
8+
styleUrls: {
9+
one: 'one.scss',
10+
two: 'two.scss',
11+
},
12+
})
13+
export class StyleUrlsComponent {
14+
render() {
15+
return <div>Hello, World! I have multiple style URLs!</div>;
16+
}
17+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
:host {
2+
/**
3+
* @prop --two: Property Two
4+
*/
5+
display: block;
6+
}

test/docs-readme/src/index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html dir="ltr" lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" />
6+
<title>Stencil Component Starter</title>
7+
8+
<script type="module" src="/build/json-docs-testbed.esm.js"></script>
9+
<script nomodule src="/build/json-docs-testbed.js"></script>
10+
</head>
11+
<body>
12+
<my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>
13+
</body>
14+
</html>

test/docs-readme/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './components';

test/docs-readme/stencil.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Config } from '@stencil/core';
2+
3+
export const config: Config = {
4+
namespace: 'docs-readme-testbed',
5+
outputTargets: [
6+
{
7+
type: 'docs-readme',
8+
},
9+
{
10+
type: 'dist',
11+
},
12+
],
13+
};

test/docs-readme/tsconfig.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"compilerOptions": {
3+
"allowSyntheticDefaultImports": true,
4+
"allowUnreachableCode": false,
5+
"declaration": false,
6+
"experimentalDecorators": true,
7+
"lib": [
8+
"dom",
9+
"es2017"
10+
],
11+
"moduleResolution": "node",
12+
"module": "esnext",
13+
"target": "es2017",
14+
"noUnusedLocals": true,
15+
"noUnusedParameters": true,
16+
"jsx": "react",
17+
"jsxFactory": "h",
18+
"paths": {
19+
"@stencil/core/testing": [
20+
"../../src/testing/index.ts"
21+
],
22+
"@stencil/core": [
23+
"../../internal"
24+
],
25+
"@stencil/core/compiler": [
26+
"../../compiler"
27+
],
28+
"@stencil/core/internal": [
29+
"../../internal"
30+
]
31+
}
32+
},
33+
"include": [
34+
"src"
35+
],
36+
"exclude": [
37+
"node_modules"
38+
]
39+
40+
}

test/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
"TODO-STENCIL-389": "echo Remove the exit call below",
66
"analysis": "exit 0 && node ./.scripts/analysis.js",
77
"analysis.build-and-analyze": "npm run build && npm run analysis",
8-
"build": "npm run build.browser-compile && npm run build.hello-world && npm run build.hello-vdom && npm run build.todo && npm run build.end-to-end && npm run build.ionic && npm run build.docs-json",
8+
"build": "npm run build.browser-compile && npm run build.hello-world && npm run build.hello-vdom && npm run build.todo && npm run build.end-to-end && npm run build.ionic && npm run build.docs-json && npm run build.docs-readme",
99
"bundlers": "cd ./bundler && npm run start",
1010
"build.browser-compile": "cd ./browser-compile && npm run build",
1111
"build.end-to-end": "cd ./end-to-end && npm ci && npm run build",
1212
"build.hello-world": "cd ./hello-world && npm run build",
1313
"build.hello-vdom": "cd ./hello-vdom && npm run build",
1414
"build.ionic": "cd ./ionic-app && npm ci && npm run build",
1515
"build.todo": "cd ./todo-app && npm ci && npm run build",
16-
"build.docs-json": "cd ./docs-json && npm ci && npm run build"
16+
"build.docs-json": "cd ./docs-json && npm ci && npm run build",
17+
"build.docs-readme": "cd ./docs-readme && npm ci && npm run build"
1718
},
1819
"devDependencies": {
1920
"brotli": "^1.3.2"

0 commit comments

Comments
 (0)