Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/slick-bats-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@patternfly/pfe-tools": minor
---
**TypeScript**: Add static version transformer. This adds a runtime-only
static `version` field to custom element classes.

```js
import '@patternfly/elements/pf-button/pf-button.js';
const PFE_VERSION =
await customElements.whenDefined('pf-button')
.then(PfButton => PfButton.version);
```
3 changes: 2 additions & 1 deletion tools/pfe-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"./test/render-to-string.js": "./test/render-to-string.js",
"./test/stub-logger.js": "./test/stub-logger.js",
"./test/utils.js": "./test/utils.js",
"./typescript/transformers/css-imports.cjs": "./typescript/transformers/css-imports.cjs"
"./typescript/transformers/css-imports.cjs": "./typescript/transformers/css-imports.cjs",
"./typescript/transformers/static-version.cjs": "./typescript/transformers/static-version.cjs"
},
"contributors": [
"Kyle Buchanan <[email protected]> (https://github.com/kylebuch8)",
Expand Down
65 changes: 27 additions & 38 deletions tools/pfe-tools/typescript/transformers/css-imports.cjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// @ts-check
const ts = require('typescript/lib/typescript');
const ts = require('typescript');
const fs = require('node:fs');
const path = require('node:path');
const { pathToFileURL } = require('node:url');

const SEEN_SOURCES = new WeakSet();

/**
* @param {import('typescript').CoreTransformationContext} ctx
* @param {import('typescript').SourceFile} sourceFile
* @param {ts.CoreTransformationContext} ctx
* @param {ts.SourceFile} sourceFile
*/
function createLitCssImportStatement(ctx, sourceFile) {
if (SEEN_SOURCES.has(sourceFile)) {
Expand Down Expand Up @@ -45,8 +44,8 @@ function createLitCssImportStatement(ctx, sourceFile) {
}

/**
* @param {import('typescript').CoreTransformationContext} ctx
* @param {string} stylesheet
* @param {ts.CoreTransformationContext} ctx
* @param {ts.SourceFile} sourceFile
* @param {string} [name]
*/
function createLitCssTaggedTemplateLiteral(ctx, stylesheet, name) {
Expand Down Expand Up @@ -87,18 +86,14 @@ function minifyCss(stylesheet, filePath) {
}
}

/**
* @param node
* @param{import('typescript').ImportDeclaration} node
*/
/** @param {ts.ImportDeclaration} node */
function getImportSpecifier(node) {
return node.moduleSpecifier.getText().replace(/^'(.*)'$/, '$1');
}

/**
* @param node
* @param{import('typescript').Node} node
* @returns {node is import('typescript').ImportDeclaration}
* @param {ts.Node} node
* @returns {node is ts.ImportDeclaration}
*/
function isCssImportNode(node) {
if (ts.isImportDeclaration(node) && !node.importClause?.isTypeOnly) {
Expand All @@ -115,11 +110,7 @@ const cssImportSpecImporterMap = new Map();
/** map from (abspath to import spec) to (abspaths to manually written transformed module) */
const cssImportFakeEmitMap = new Map();

// abspath to file
/**
* @param node
* @param{import('typescript').ImportDeclaration} node
*/
/** @param {ts.ImportDeclaration} node */
function getImportAbsPathOrBareSpec(node) {
const specifier = getImportSpecifier(node);
if (!specifier.startsWith('.')) {
Expand All @@ -131,9 +122,7 @@ function getImportAbsPathOrBareSpec(node) {
}
}

/**
* @param {import('typescript').SourceFile} sourceFile
*/
/** @param {ts.SourceFile} sourceFile */
function cacheCssImportSpecsAbsolute(sourceFile) {
sourceFile.forEachChild(node => {
if (isCssImportNode(node)) {
Expand All @@ -151,13 +140,16 @@ function cacheCssImportSpecsAbsolute(sourceFile) {
* If the inline option is set, remove the import specifier and print the css
* object in place, except if that module is imported elsewhere in the project,
* in which case leave a `.css.js` import
* @param {import('typescript').Program} program
* @param root0
* @param root0.inline
* @param root0.minify
* @returns {import('typescript').TransformerFactory<import('typescript').SourceFile>}
* @param {ts.Program} program
* @param opts
* @param {boolean} opts.inline
* @param {boolean} opts.minify
* @returns {ts.TransformerFactory<ts.SourceFile>}
*/
module.exports = function(program, { inline = false, minify = false } = {}) {
module.exports = function(program, {
inline = false,
minify = false,
} = {}) {
return ctx => {
for (const sourceFileName of program.getRootFileNames()) {
const sourceFile = program.getSourceFile(sourceFileName);
Expand All @@ -166,10 +158,7 @@ module.exports = function(program, { inline = false, minify = false } = {}) {
}
}

/**
* @param node
* @param{import('typescript').Node} node
*/
/** @param {ts.Node} node */
function rewriteOrInlineVisitor(node) {
if (isCssImportNode(node)) {
const { fileName } = node.getSourceFile();
Expand Down Expand Up @@ -210,21 +199,21 @@ module.exports = function(program, { inline = false, minify = false } = {}) {
return sourceFile => {
const children = sourceFile.getChildren();
const litImportBindings =
/** @type{import('typescript').ImportDeclaration}*/(children.find(x =>
(children.find(/** @returns {x is ts.ImportDeclaration} */x =>
!ts.isTypeOnlyImportOrExportDeclaration(x)
&& !ts.isNamespaceImport(x)
&& ts.isImportDeclaration(x)
&& x.moduleSpecifier.getText() === 'lit'
&& x.importClause?.namedBindings
&& !ts.isNamespaceImport(x)
&& ts.isImportDeclaration(x)
&& x.moduleSpecifier.getText() === 'lit'
&& !!x.importClause?.namedBindings
))?.importClause?.namedBindings;

const hasStyleImports = children.find(x =>
ts.isImportDeclaration(x) && x.moduleSpecifier.getText().endsWith('.css'));

if (hasStyleImports) {
if (litImportBindings
&& ts.isNamedImports(litImportBindings)
&& !litImportBindings.elements?.some(x => x.getText() === 'css')) {
&& ts.isNamedImports(litImportBindings)
&& !litImportBindings.elements?.some(x => x.getText() === 'css')) {
ctx.factory.updateNamedImports(
litImportBindings,
[
Expand Down
84 changes: 84 additions & 0 deletions tools/pfe-tools/typescript/transformers/static-version.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const ts = require('typescript');
const fs = require('node:fs');
const path = require('node:path');

/**
* @param {ts.ModifierLike} mod
* @returns {mod is ts.ExportKeyword}
*/
const isExportKeyword = mod =>
mod.kind === ts.SyntaxKind.ExportKeyword;

/**
* @param {ts.ModifierLike} mod
* @returns {mod is ts.Decorator}
*/
const isCustomElementDecorator = mod =>
ts.isDecorator(mod)
&& ts.isCallExpression(mod.expression)
&& ts.isIdentifier(mod.expression.expression)
&& mod.expression.expression.escapedText === 'customElement';

/**
* @param {ts.Node} node
* @returns {node is ts.ClassDeclaration}
*/
const isExportCustomElementClass = node =>
ts.isClassDeclaration(node)
&& !!node.modifiers?.some(isExportKeyword)
&& !!node.modifiers?.some(isCustomElementDecorator);

/** @param {string} dir */
function findPackageDir(dir) {
if (fs.existsSync(path.join(dir, 'package.json'))) {
return dir;
}
const parentDir = path.resolve(dir, '..');
if (dir === parentDir) {
return null;
}
return findPackageDir(parentDir);
}

/** @param {string} filePath */
function getNearestPackageJson(filePath) {
const parentDir = path.dirname(filePath);
const packageDir = findPackageDir(parentDir);
if (packageDir) {
const filePath = path.normalize(`${packageDir}/package.json`);
return require(filePath);
} else {
return null;
}
}

/** @returns {ts.TransformerFactory<ts.SourceFile>} */
module.exports = () => ctx => {
return sourceFile => ts.visitEachChild(
sourceFile,
function addVersionVisitor(node) {
if (isExportCustomElementClass(node)) {
const { fileName } = node.getSourceFile();
const packageJson = getNearestPackageJson(fileName);
if (packageJson?.version) {
return ctx.factory.createClassDeclaration(
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
node.members.concat(ctx.factory.createPropertyDeclaration(
[ctx.factory.createModifier(ts.SyntaxKind.StaticKeyword)],
'version',
undefined,
undefined,
ctx.factory.createStringLiteral(packageJson.version)
))
);
}
}
return node;
},
ctx
);
};

3 changes: 3 additions & 0 deletions tsconfig.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"transform": "@patternfly/pfe-tools/typescript/transformers/css-imports.cjs",
"inline": true
},
{
"transform": "@patternfly/pfe-tools/typescript/transformers/static-version.cjs"
},
{
"name": "typescript-lit-html-plugin"
},
Expand Down