diff --git a/packages/atomic/project.json b/packages/atomic/project.json index a0f7b8a898b..b3d2d24cc16 100644 --- a/packages/atomic/project.json +++ b/packages/atomic/project.json @@ -34,7 +34,7 @@ "commands": [ "node --max_old_space_size=6144 ../../node_modules/@stencil/core/bin/stencil build", "node ./scripts/stencil-proxy.mjs", - "tsc -p tsconfig.lit.json", + "node ./scripts/build.mjs --config=tsconfig.lit.json", "esbuild src/autoloader/index.ts --format=esm --outfile=dist/atomic/autoloader/index.esm.js", "esbuild src/autoloader/index.ts --format=cjs --outfile=dist/atomic/autoloader/index.cjs.js" ], diff --git a/packages/atomic/scripts/build.mjs b/packages/atomic/scripts/build.mjs new file mode 100644 index 00000000000..3ea239f5a7c --- /dev/null +++ b/packages/atomic/scripts/build.mjs @@ -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(); diff --git a/packages/atomic/scripts/svg-transform.mjs b/packages/atomic/scripts/svg-transform.mjs new file mode 100644 index 00000000000..cc83d15e544 --- /dev/null +++ b/packages/atomic/scripts/svg-transform.mjs @@ -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 = " ... "; + * () => 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); +} diff --git a/packages/atomic/scripts/watch.mjs b/packages/atomic/scripts/watch.mjs index e2613acc523..00499cfd5b8 100644 --- a/packages/atomic/scripts/watch.mjs +++ b/packages/atomic/scripts/watch.mjs @@ -5,7 +5,7 @@ function rebuild() { const commands = [ 'node --max_old_space_size=6144 ../../node_modules/@stencil/core/bin/stencil build', 'node ./scripts/stencil-proxy.mjs', - 'tsc -p tsconfig.lit.json', + 'node ./scripts/build.mjs --config=tsconfig.lit.json', 'esbuild src/autoloader/index.ts --format=esm --outfile=dist/atomic/autoloader/index.esm.js', 'esbuild src/autoloader/index.ts --format=cjs --outfile=dist/atomic/autoloader/index.cjs.js', ];