-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add custom elements manifest #31673
base: master
Are you sure you want to change the base?
Add custom elements manifest #31673
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "prerelease", | ||
"comment": "Add a custom elements manifest", | ||
"packageName": "@fluentui/web-components", | ||
"email": "[email protected]", | ||
"dependentChangeType": "patch" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { tagNameFix, typescriptTypeTextSanitize } from "./custom-elements-manifest.plugins.js"; | ||
import { customElementVsCodePlugin } from "custom-element-vs-code-integration"; | ||
|
||
export default { | ||
/** Globs to analyze */ | ||
globs: ["src/**/*.ts"], | ||
/** Globs to exclude */ | ||
exclude: [ | ||
"*.js", | ||
"*.ts", | ||
"src/helpers.stories.ts", | ||
"src/helpers.tests.ts", | ||
"src/index-rollup.ts", | ||
"src/utils/benchmark-wrapper.ts", | ||
"src/**/*.bench.ts", | ||
"src/**/*.spec.ts", | ||
"src/**/*.stories.ts", | ||
"src/**/define.ts", | ||
"src/**/index.ts", | ||
"src/**/*.md" | ||
], | ||
/** Directory to output CEM to */ | ||
outdir: "dist", | ||
/** Run in dev mode, provides extra logging */ | ||
dev: false, | ||
/** Enable special handling for fast */ | ||
fast: true, | ||
plugins: [ | ||
tagNameFix(), | ||
typescriptTypeTextSanitize(), | ||
customElementVsCodePlugin({ | ||
outdir: "dist" | ||
}) | ||
], | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { readFileSync } from "fs"; | ||
import path from "path"; | ||
import ts from "typescript"; | ||
import { fileURLToPath } from 'url'; | ||
|
||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can get dirname from |
||
|
||
const getTagNameFromCommentInDefinitionFile = function (definitionPathName) { | ||
const indexFilePath = path.resolve(__dirname, `./${definitionPathName}`); | ||
let name; | ||
|
||
try { | ||
let sourceFile = ts.createSourceFile( | ||
indexFilePath, | ||
readFileSync(indexFilePath).toString(), | ||
ts.ScriptTarget.ES2015, | ||
/*setParentNodes */ true | ||
); | ||
|
||
if (Array.isArray(sourceFile.statements)) { | ||
sourceFile.statements.forEach((statement) => { | ||
if (Array.isArray(statement.jsDoc) && statement.jsDoc[0].tags !== undefined) { | ||
statement.jsDoc.forEach((jsDoc) => { | ||
if (Array.isArray(jsDoc.tags)) { | ||
jsDoc.tags.forEach((tag) => { | ||
if (typeof tag.comment === "string" && tag.comment.startsWith("HTML Element:")) { | ||
name = tag.comment.match(/<(.*)>/)[1].replace("\\", ""); | ||
} | ||
}); | ||
} | ||
}) | ||
} | ||
}) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: to improve readability, maybe test negative cases and return? Something like: if (!Array.isArray(...)) {
return;
}
sourceFile.statements.forEach(...); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree with @marchbox's suggestion these nested ifs could be easier to read. Can |
||
} catch (err) { | ||
// do nothing | ||
} | ||
|
||
return name; | ||
} | ||
|
||
const resolveDefinitionFilePath = function(filePathName) { | ||
return filePathName.split("/").map((pathItem) => { | ||
if (pathItem.endsWith(".ts")) { | ||
const splitPathItem = pathItem.split("."); | ||
splitPathItem.splice(1, 0, "definition"); | ||
return splitPathItem.join("."); | ||
} | ||
|
||
return pathItem; | ||
}).join("/"); | ||
} | ||
|
||
const checkIsUnresolvedTypeScriptType = function(type) { | ||
/** | ||
* Due to TypeScript types being PascalCase, and all other default | ||
* types being lowercase, we determine if this is a typescript type by checking | ||
* the first letter. | ||
*/ | ||
return type[0] === type[0].toUpperCase() && isNaN(type[0]); | ||
} | ||
|
||
const resolveTypeToValues = function(CEM, type) { | ||
let values = ""; | ||
|
||
CEM.modules.forEach((cemModule) => { | ||
if (cemModule.kind === "javascript-module") { | ||
cemModule.declarations.forEach((declaration) => { | ||
if (declaration.name === type) { | ||
const sanitizedType = declaration.type?.text; | ||
const matches = sanitizedType | ||
.match(/((?:.*):(?:.*))/gm) | ||
.map((match) => { | ||
return match.match(/(?:(?:')(.*)(?:')|(\d+))/)[0]; | ||
}); | ||
values = matches.reduce((accum, match) => { | ||
return `${accum}${accum === "" ? "" : " | "}${match}`; | ||
}, values) | ||
} | ||
}) | ||
} | ||
}); | ||
|
||
return values; | ||
} | ||
|
||
/** | ||
* @return {import('@custom-elements-manifest/analyzer').Plugin} | ||
* | ||
* This plugin adds the tagName after the manifest has been processed | ||
* See: https://github.com/webcomponents/custom-elements-manifest/blob/main/schema.json | ||
*/ | ||
export function tagNameFix() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just so I understand, we need this plugin because we dynamically prefix our component names? And then this goes thru and yoinks the name from the comment. What is the |
||
return { | ||
name: "fluentTagName", | ||
packageLinkPhase({customElementsManifest, context}){ | ||
customElementsManifest.modules.map((item) => { | ||
item.declarations.forEach((declaration) => { | ||
if (declaration.customElement) { | ||
const name = getTagNameFromCommentInDefinitionFile( | ||
resolveDefinitionFilePath(item.path) | ||
); | ||
|
||
if (typeof name === "undefined") { | ||
console.error(`no tag name for ${item.path}`); | ||
} else { | ||
declaration.tagName = name; | ||
} | ||
} | ||
}) | ||
}); | ||
}, | ||
}; | ||
} | ||
|
||
/** | ||
* @return {import('@custom-elements-manifest/analyzer').Plugin} | ||
* | ||
* This plugin changes the types to use pipe syntax so that the vscode plugin can | ||
* correctly interpret the possible values, eg. | ||
* from: | ||
* { | ||
* "name": "heading-level", | ||
* "type": { | ||
* "text": "HeadingLevel" | ||
* }, | ||
* "fieldName": "headinglevel" | ||
* } | ||
* | ||
* to: | ||
* { | ||
* "name": "heading-level", | ||
* "type": { | ||
* "text": "1 | 2 | 3 | 4 | 5 | 6" | ||
* }, | ||
* "fieldName": "headinglevel" | ||
* }, | ||
*/ | ||
export function typescriptTypeTextSanitize() { | ||
return { | ||
name: "typescriptTypeTextSanitize", | ||
packageLinkPhase({customElementsManifest, context}){ | ||
customElementsManifest.modules.map((item) => { | ||
item.declarations.forEach((declaration) => { | ||
if (declaration.customElement && Array.isArray(declaration.attributes)) { | ||
declaration.attributes.forEach((attribute) => { | ||
if (typeof attribute.type?.text === "string") { | ||
const possibleTypes = attribute.type.text.split("|").map((item) => { | ||
return item.trim(); | ||
}).map((possibleType) => { | ||
if (checkIsUnresolvedTypeScriptType(possibleType)) { | ||
return resolveTypeToValues(customElementsManifest, possibleType); | ||
} | ||
|
||
return possibleType; | ||
}).join(" | "); | ||
|
||
attribute.type.text = possibleTypes; | ||
} | ||
}); | ||
} | ||
}) | ||
}); | ||
}, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🕵🏾♀️ visual regressions to review in the fluentuiv9 Visual Regression Report
Avatar Converged 2 screenshots