-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(atomic): add SVG transformer (#4867)
Update the Lit build process to be support SVG imports ## Explanation We are essentially creating a compiler which takes a list of TypeScript files (Lit components and dependencies) and compiles them to their corresponding JavaScript keeping the same file structure as in `/src`. [Typescript doc ](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#a-minimal-compiler) To that compiler, I have added a SVG custom transformer. ### Example The following typescript file ```ts import Tick from '../../../images/checkbox.svg'; () => console.log(Tick); ``` was transpiled to ```js import Tick from '../../../images/checkbox.svg'; () => console.log(Tick); ``` With the SVG transformer, we have ```js const Tick = "<svg viewBox=\"0 0 12 9\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M1.5 5L4.6 7.99999L11 1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n</svg>"; () => console.log(Tick); ``` ### Pseudo-code 1. Load the TypeScript configuration file (tsconfig.lit.json) 2. Create a default CompilerHost which uses the file system to get files (using `program.emit` function). 3. To that program, provide a customTransformers which will essentially visit recursively tree nodes and check for `svg` import statements. 4. When a SVG import statement is encountered, replace it with a "create variable statement" using the `createVariableStatement` typescript built-in method. Ensure to right-hand side of the assignation is the content of the .svg file https://coveord.atlassian.net/browse/KIT-3865 --------- Co-authored-by: GitHub Actions Bot <> Co-authored-by: Louis Bompart <[email protected]>
- Loading branch information
1 parent
9cac893
commit a331579
Showing
4 changed files
with
192 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import {dirname, basename} from 'path'; | ||
import {argv} from 'process'; | ||
import { | ||
readConfigFile, | ||
getLineAndCharacterOfPosition, | ||
sys, | ||
parseJsonConfigFileContent, | ||
getPreEmitDiagnostics, | ||
createProgram, | ||
flattenDiagnosticMessageText, | ||
} from 'typescript'; | ||
import svgTransformer from './svg-transform.mjs'; | ||
|
||
const args = argv.slice(2); | ||
const configArg = args.find((arg) => arg.startsWith('--config=')); | ||
if (configArg === undefined) { | ||
throw new Error('Missing --config=[PATH] argument'); | ||
} | ||
const tsConfigPath = configArg.split('=')[1]; | ||
|
||
function loadTsConfig(configPath) { | ||
const configFile = readConfigFile(configPath, sys.readFile); | ||
if (configFile.error) { | ||
throw new Error( | ||
`Error loading tsconfig file: ${configFile.error.messageText}` | ||
); | ||
} | ||
return parseJsonConfigFileContent( | ||
configFile.config, | ||
sys, | ||
dirname(configPath) | ||
); | ||
} | ||
|
||
function emit(program) { | ||
const targetSourceFile = undefined; | ||
const cancellationToken = undefined; | ||
const writeFile = undefined; | ||
const emitOnlyDtsFiles = false; | ||
const customTransformers = { | ||
before: [svgTransformer], | ||
}; | ||
|
||
return program.emit( | ||
targetSourceFile, | ||
cancellationToken, | ||
writeFile, | ||
emitOnlyDtsFiles, | ||
customTransformers | ||
); | ||
} | ||
|
||
/** | ||
* Compiles TypeScript files using a custom transformer. | ||
* | ||
* This function mimics the behavior of running `tsc -p tsconfig.json` but applies a custom SVG transformer | ||
* to all TypeScript files. It loads the TypeScript configuration from the specified `tsconfig.json` file, | ||
* creates a TypeScript program, and emits the compiled JavaScript files with the custom transformer applied. | ||
* | ||
* Info: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#a-minimal-compiler | ||
*/ | ||
function compileWithTransformer() { | ||
console.log('Using tsconfig:', basename(tsConfigPath)); | ||
const {options, fileNames} = loadTsConfig(tsConfigPath); | ||
const program = createProgram(fileNames, options); | ||
const emitResult = emit(program); | ||
|
||
const allDiagnostics = getPreEmitDiagnostics(program).concat( | ||
emitResult.diagnostics | ||
); | ||
|
||
allDiagnostics.forEach((diagnostic) => { | ||
if (diagnostic.file) { | ||
const {line, character} = getLineAndCharacterOfPosition( | ||
diagnostic.file, | ||
diagnostic.start | ||
); | ||
const message = flattenDiagnosticMessageText( | ||
diagnostic.messageText, | ||
'\n' | ||
); | ||
|
||
console.log( | ||
`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}` | ||
); | ||
} else { | ||
console.error(flattenDiagnosticMessageText(diagnostic.messageText, '\n')); | ||
} | ||
}); | ||
|
||
let exitCode = emitResult.emitSkipped ? 1 : 0; | ||
console.log(`Process exiting with code '${exitCode}'.`); | ||
process.exit(exitCode); | ||
} | ||
|
||
compileWithTransformer(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import {readFileSync} from 'fs'; | ||
import {basename, dirname, join, resolve} from 'path'; | ||
import { | ||
NodeFlags, | ||
isImportDeclaration, | ||
visitEachChild, | ||
visitNode, | ||
} from 'typescript'; | ||
|
||
/** | ||
* Creates a TypeScript variable statement for an SVG import. | ||
* | ||
* This function generates a TypeScript variable statement that assigns the SVG content as a string literal | ||
* to a variable. It is used as part of a custom TypeScript transformer to inline SVG content in the transpiled | ||
* JavaScript files. | ||
* | ||
* @example | ||
* The following TypeScript source file: | ||
* ```ts | ||
* // src/components/component.ts | ||
* import Tick from '../../../images/checkbox.svg'; | ||
* () => console.log(Tick); | ||
* ``` | ||
* | ||
* Will be transpiled to (note that the SVG import statement has been replaced with the SVG content): | ||
* ```js | ||
* // dist/components/component.js | ||
* const Tick = "<svg viewBox=\"0 0 12 9\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> ... </svg>"; | ||
* () => console.log(Tick); | ||
* ``` | ||
* | ||
* @param {NodeFactory} factory - The TypeScript factory object used to create AST nodes. | ||
* @param {string} svgContent - The content of the SVG file as a string. | ||
* @param {string} variableName - The name of the variable to which the SVG content will be assigned. | ||
* @returns {VariableStatement} A TypeScript variable statement that assigns the SVG content to the variable. | ||
* @throws If the variable name is not defined. | ||
*/ | ||
function createStatement(factory, svgContent, variableName) { | ||
const bindingName = undefined; | ||
const exclamationToken = undefined; | ||
const modifiers = []; | ||
const { | ||
createVariableStatement, | ||
createVariableDeclarationList, | ||
createVariableDeclaration, | ||
createStringLiteral, | ||
} = factory; | ||
|
||
if (variableName === undefined) { | ||
throw new Error( | ||
`Variable name is not defined for the import statement ${node.getText()}` | ||
); | ||
} | ||
|
||
return createVariableStatement( | ||
modifiers, | ||
createVariableDeclarationList( | ||
[ | ||
createVariableDeclaration( | ||
variableName, | ||
bindingName, | ||
exclamationToken, | ||
createStringLiteral(svgContent) | ||
), | ||
], | ||
NodeFlags.Const | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* Custom SVG transformer to handle .svg imports. | ||
*/ | ||
export default function svgTransformer(context) { | ||
const {factory} = context; | ||
|
||
function visit(node) { | ||
if (isImportDeclaration(node)) { | ||
const importPath = node.moduleSpecifier.text; | ||
if (importPath.endsWith('.svg')) { | ||
console.log('Replacing SVG import:', basename(importPath)); | ||
const dir = dirname(node.getSourceFile().fileName); | ||
const svgPath = resolve(dir, importPath); | ||
const svgContent = readFileSync(svgPath, 'utf8'); | ||
const variableName = node.importClause?.name?.escapedText; | ||
|
||
return createStatement(factory, svgContent, variableName); | ||
} | ||
} | ||
return visitEachChild(node, visit, context); | ||
} | ||
|
||
return (sourceFile) => visitNode(sourceFile, visit); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters