From 84e1a14048bc34e64a866659d39376af605f8f9a Mon Sep 17 00:00:00 2001 From: Alice Pote Date: Mon, 15 Apr 2024 14:38:47 -0400 Subject: [PATCH] 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 --- cspell-wordlist.txt | 1 + src/compiler/bundle/ext-transforms-plugin.ts | 5 ++- src/utils/helpers.ts | 24 +++++++++++ src/utils/test/helpers.spec.ts | 29 +++++++++++++- test/docs-readme/package-lock.json | 13 ++++++ test/docs-readme/package.json | 17 ++++++++ test/docs-readme/readme.md | 13 ++++++ test/docs-readme/src/components.d.ts | 37 +++++++++++++++++ .../components/styleurls-component/one.scss | 6 +++ .../components/styleurls-component/readme.md | 18 +++++++++ .../styleurls-component.tsx | 17 ++++++++ .../components/styleurls-component/two.scss | 6 +++ test/docs-readme/src/index.html | 14 +++++++ test/docs-readme/src/index.ts | 1 + test/docs-readme/stencil.config.ts | 13 ++++++ test/docs-readme/tsconfig.json | 40 +++++++++++++++++++ test/package.json | 5 ++- 17 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 test/docs-readme/package-lock.json create mode 100644 test/docs-readme/package.json create mode 100644 test/docs-readme/readme.md create mode 100644 test/docs-readme/src/components.d.ts create mode 100644 test/docs-readme/src/components/styleurls-component/one.scss create mode 100644 test/docs-readme/src/components/styleurls-component/readme.md create mode 100644 test/docs-readme/src/components/styleurls-component/styleurls-component.tsx create mode 100644 test/docs-readme/src/components/styleurls-component/two.scss create mode 100644 test/docs-readme/src/index.html create mode 100644 test/docs-readme/src/index.ts create mode 100644 test/docs-readme/stencil.config.ts create mode 100644 test/docs-readme/tsconfig.json diff --git a/cspell-wordlist.txt b/cspell-wordlist.txt index 58f3046977f..7d3216e13a3 100644 --- a/cspell-wordlist.txt +++ b/cspell-wordlist.txt @@ -112,6 +112,7 @@ stenciljs stnl stringification stringified +styleurls subdir templating timespan diff --git a/src/compiler/bundle/ext-transforms-plugin.ts b/src/compiler/bundle/ext-transforms-plugin.ts index 2293fac4fc6..62479c33b4b 100644 --- a/src/compiler/bundle/ext-transforms-plugin.ts +++ b/src/compiler/bundle/ext-transforms-plugin.ts @@ -1,4 +1,4 @@ -import { hasError, isOutputTargetDistCollection, join, normalizeFsPath, relative } from '@utils'; +import { hasError, isOutputTargetDistCollection, join, mergeIntoWith, normalizeFsPath, relative } from '@utils'; import type { Plugin } from 'rollup'; import type * as d from '../../declarations'; @@ -163,7 +163,8 @@ export const extTransformsPlugin = ( // Set style docs if (cmp) { - cmp.styleDocs = cssTransformResults.styleDocs; + cmp.styleDocs ||= []; + mergeIntoWith(cmp.styleDocs, cssTransformResults.styleDocs, (docs) => docs.name); } // Track dependencies diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index c59e2c08882..b8812f68f58 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -130,6 +130,30 @@ export const unique = (array: T[], predicate: (item: T) => K = (i) => i as }); }; +/** + * Merge elements of an array into an existing array, using a predicate to + * determine uniqueness and only adding elements when they are not present in + * the first array. + * + * **Note**: this mutates the target array! This is intentional to avoid + * unnecessary array allocation, but be sure that it's what you want! + * + * @param target the target array, to which new unique items should be added + * @param newItems a list of new items, some (or all!) of which may be added + * @param mergeWith a predicate function which reduces the items in `target` + * and `newItems` to a value which can be equated with `===` for the purposes + * of determining uniqueness + */ +export function mergeIntoWith(target: T1[], newItems: T1[], mergeWith: (item: T1) => T2) { + for (const item of newItems) { + const maybeItem = target.find((existingItem) => mergeWith(existingItem) === mergeWith(item)); + if (!maybeItem) { + // this is a new item that isn't present in `target` yet + target.push(item); + } + } +} + /** * A utility for building an object from an iterable very similar to * `Object.fromEntries` diff --git a/src/utils/test/helpers.spec.ts b/src/utils/test/helpers.spec.ts index 0009b707000..f258d5c4022 100644 --- a/src/utils/test/helpers.spec.ts +++ b/src/utils/test/helpers.spec.ts @@ -1,4 +1,4 @@ -import { dashToPascalCase, isDef, isPromise, toCamelCase, toDashCase } from '../helpers'; +import { dashToPascalCase, isDef, isPromise, mergeIntoWith, toCamelCase, toDashCase } from '../helpers'; describe('util helpers', () => { describe('isPromise', () => { @@ -110,4 +110,31 @@ describe('util helpers', () => { expect(isDef(null)).toBe(false); }); }); + + describe('mergeIntoWith', () => { + it('should do nothing if all elements already present', () => { + const target = [1, 2, 3]; + mergeIntoWith(target, [1, 2, 3], (x) => x); + expect(target).toEqual([1, 2, 3]); + }); + + it('should add new items', () => { + const target = [1, 2, 3]; + mergeIntoWith(target, [1, 2, 3, 4, 5], (x) => x); + expect(target).toEqual([1, 2, 3, 4, 5]); + }); + + it('should merge in objects using the predicate', () => { + const target = [{ id: 'foo' }, { id: 'bar' }, { id: 'boz' }, { id: 'baz' }]; + mergeIntoWith(target, [{ id: 'foo' }, { id: 'fab' }, { id: 'fib' }], (x) => x.id); + expect(target).toEqual([ + { id: 'foo' }, + { id: 'bar' }, + { id: 'boz' }, + { id: 'baz' }, + { id: 'fab' }, + { id: 'fib' }, + ]); + }); + }); }); diff --git a/test/docs-readme/package-lock.json b/test/docs-readme/package-lock.json new file mode 100644 index 00000000000..297aa393f14 --- /dev/null +++ b/test/docs-readme/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "docs-readme-testbed", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "docs-readme-testbed", + "version": "1.0.0", + "license": "MIT" + } + } +} diff --git a/test/docs-readme/package.json b/test/docs-readme/package.json new file mode 100644 index 00000000000..076e0f6da38 --- /dev/null +++ b/test/docs-readme/package.json @@ -0,0 +1,17 @@ +{ + "name": "docs-readme-testbed", + "version": "1.0.0", + "description": "A test app for the docs-readme output target", + "files": [ + "dist/" + ], + "scripts": { + "build": "node ../../bin/stencil build", + "build.dev": "node ../../bin/stencil build --dev", + "start": "node ../../bin/stencil build --dev --watch --serve", + "test": "node ../../bin/stencil test --spec --e2e", + "test.watch": "node ../../bin/stencil test --spec --e2e --watch", + "generate": "node ../../bin/stencil generate" + }, + "license": "MIT" +} diff --git a/test/docs-readme/readme.md b/test/docs-readme/readme.md new file mode 100644 index 00000000000..1213e26fbb7 --- /dev/null +++ b/test/docs-readme/readme.md @@ -0,0 +1,13 @@ +# docs-readme test app + +This directory contains a test application which exercises the `docs-readme` +output target. This provides us with an end-to-end test of this functionality. + +## Components + +The components in here and what they test! + +### `` + +This tests that the docs from multiple `styleUrls` in the `@Component` +decorator are pulled in to the docs output correctly. \ No newline at end of file diff --git a/test/docs-readme/src/components.d.ts b/test/docs-readme/src/components.d.ts new file mode 100644 index 00000000000..ec67f55defb --- /dev/null +++ b/test/docs-readme/src/components.d.ts @@ -0,0 +1,37 @@ +/* eslint-disable */ +/* tslint:disable */ +/** + * This is an autogenerated file created by the Stencil compiler. + * It contains typing information for all components that exist in this project. + */ +import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +export namespace Components { + interface StyleurlsComponent { + } +} +declare global { + interface HTMLStyleurlsComponentElement extends Components.StyleurlsComponent, HTMLStencilElement { + } + var HTMLStyleurlsComponentElement: { + prototype: HTMLStyleurlsComponentElement; + new (): HTMLStyleurlsComponentElement; + }; + interface HTMLElementTagNameMap { + "styleurls-component": HTMLStyleurlsComponentElement; + } +} +declare namespace LocalJSX { + interface StyleurlsComponent { + } + interface IntrinsicElements { + "styleurls-component": StyleurlsComponent; + } +} +export { LocalJSX as JSX }; +declare module "@stencil/core" { + export namespace JSX { + interface IntrinsicElements { + "styleurls-component": LocalJSX.StyleurlsComponent & JSXBase.HTMLAttributes; + } + } +} diff --git a/test/docs-readme/src/components/styleurls-component/one.scss b/test/docs-readme/src/components/styleurls-component/one.scss new file mode 100644 index 00000000000..ce4748b5560 --- /dev/null +++ b/test/docs-readme/src/components/styleurls-component/one.scss @@ -0,0 +1,6 @@ +:host { + /** + * @prop --one: Property One + */ + display: block; +} diff --git a/test/docs-readme/src/components/styleurls-component/readme.md b/test/docs-readme/src/components/styleurls-component/readme.md new file mode 100644 index 00000000000..3e598321546 --- /dev/null +++ b/test/docs-readme/src/components/styleurls-component/readme.md @@ -0,0 +1,18 @@ +# styleurls-component + + + + + + +## CSS Custom Properties + +| Name | Description | +| ------- | ------------ | +| `--one` | Property One | +| `--two` | Property Two | + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/test/docs-readme/src/components/styleurls-component/styleurls-component.tsx b/test/docs-readme/src/components/styleurls-component/styleurls-component.tsx new file mode 100644 index 00000000000..45c9f2e3442 --- /dev/null +++ b/test/docs-readme/src/components/styleurls-component/styleurls-component.tsx @@ -0,0 +1,17 @@ +import { Component, h } from '@stencil/core'; + +@Component({ + tag: 'styleurls-component', + shadow: true, + // CSS properties documented in both of these files should + // show up in this component's README + styleUrls: { + one: 'one.scss', + two: 'two.scss', + }, +}) +export class StyleUrlsComponent { + render() { + return
Hello, World! I have multiple style URLs!
; + } +} diff --git a/test/docs-readme/src/components/styleurls-component/two.scss b/test/docs-readme/src/components/styleurls-component/two.scss new file mode 100644 index 00000000000..0f712520ee3 --- /dev/null +++ b/test/docs-readme/src/components/styleurls-component/two.scss @@ -0,0 +1,6 @@ +:host { + /** + * @prop --two: Property Two + */ + display: block; +} diff --git a/test/docs-readme/src/index.html b/test/docs-readme/src/index.html new file mode 100644 index 00000000000..d8da2edc058 --- /dev/null +++ b/test/docs-readme/src/index.html @@ -0,0 +1,14 @@ + + + + + + Stencil Component Starter + + + + + + + + diff --git a/test/docs-readme/src/index.ts b/test/docs-readme/src/index.ts new file mode 100644 index 00000000000..07635cbbc8e --- /dev/null +++ b/test/docs-readme/src/index.ts @@ -0,0 +1 @@ +export * from './components'; diff --git a/test/docs-readme/stencil.config.ts b/test/docs-readme/stencil.config.ts new file mode 100644 index 00000000000..209e5f7ff9f --- /dev/null +++ b/test/docs-readme/stencil.config.ts @@ -0,0 +1,13 @@ +import { Config } from '@stencil/core'; + +export const config: Config = { + namespace: 'docs-readme-testbed', + outputTargets: [ + { + type: 'docs-readme', + }, + { + type: 'dist', + }, + ], +}; diff --git a/test/docs-readme/tsconfig.json b/test/docs-readme/tsconfig.json new file mode 100644 index 00000000000..25f93da5947 --- /dev/null +++ b/test/docs-readme/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "declaration": false, + "experimentalDecorators": true, + "lib": [ + "dom", + "es2017" + ], + "moduleResolution": "node", + "module": "esnext", + "target": "es2017", + "noUnusedLocals": true, + "noUnusedParameters": true, + "jsx": "react", + "jsxFactory": "h", + "paths": { + "@stencil/core/testing": [ + "../../src/testing/index.ts" + ], + "@stencil/core": [ + "../../internal" + ], + "@stencil/core/compiler": [ + "../../compiler" + ], + "@stencil/core/internal": [ + "../../internal" + ] + } + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules" + ] + +} diff --git a/test/package.json b/test/package.json index 574a819e004..e74d73d0160 100644 --- a/test/package.json +++ b/test/package.json @@ -5,7 +5,7 @@ "TODO-STENCIL-389": "echo Remove the exit call below", "analysis": "exit 0 && node ./.scripts/analysis.js", "analysis.build-and-analyze": "npm run build && npm run analysis", - "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", + "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", "bundlers": "cd ./bundler && npm run start", "build.browser-compile": "cd ./browser-compile && npm run build", "build.end-to-end": "cd ./end-to-end && npm ci && npm run build", @@ -13,7 +13,8 @@ "build.hello-vdom": "cd ./hello-vdom && npm run build", "build.ionic": "cd ./ionic-app && npm ci && npm run build", "build.todo": "cd ./todo-app && npm ci && npm run build", - "build.docs-json": "cd ./docs-json && npm ci && npm run build" + "build.docs-json": "cd ./docs-json && npm ci && npm run build", + "build.docs-readme": "cd ./docs-readme && npm ci && npm run build" }, "devDependencies": { "brotli": "^1.3.2"