Skip to content

Commit

Permalink
fix(compiler): reapply changes to style import transformer (#5125) (#…
Browse files Browse the repository at this point in the history
…5131)

This reverts commit cf4a701.

fixes: #5016
STENCIL-999
  • Loading branch information
christian-bromann committed Dec 6, 2023
1 parent 4303d6a commit 735d45a
Show file tree
Hide file tree
Showing 12 changed files with 417 additions and 75 deletions.
89 changes: 74 additions & 15 deletions src/compiler/transformers/add-static-style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ts from 'typescript';
import type * as d from '../../declarations';
import { scopeCss } from '../../utils/shadow-css';
import { getScopeId } from '../style/scope-css';
import { createStaticGetter } from './transform-utils';
import { createStaticGetter, getExternalStyles } from './transform-utils';

/**
* Adds static "style" getter within the class
Expand Down Expand Up @@ -91,7 +91,7 @@ const getMultipleModeStyle = (
// import generated from @Component() styleUrls option
// import myTagIosStyle from './import-path.css';
// static get style() { return { ios: myTagIosStyle }; }
const styleUrlIdentifier = createStyleIdentifierFromUrl(cmp, style);
const styleUrlIdentifier = createStyleIdentifier(cmp, style);
const propUrlIdentifier = ts.factory.createPropertyAssignment(style.modeName, styleUrlIdentifier);
styleModes.push(propUrlIdentifier);
}
Expand All @@ -107,18 +107,18 @@ const getSingleStyle = (cmp: d.ComponentCompilerMeta, style: d.StyleCompiler, co
return createStyleLiteral(cmp, style, commentOriginalSelector);
}

if (typeof style.styleIdentifier === 'string') {
// direct import already written in the source code
if (Array.isArray(style.externalStyles) && style.externalStyles.length > 0) {
// import generated from @Component() styleUrls option
// import myTagStyle from './import-path.css';
// static get style() { return myTagStyle; }
return ts.factory.createIdentifier(style.styleIdentifier);
return createStyleIdentifier(cmp, style);
}

if (Array.isArray(style.externalStyles) && style.externalStyles.length > 0) {
// import generated from @Component() styleUrls option
if (typeof style.styleIdentifier === 'string') {
// direct import already written in the source code
// import myTagStyle from './import-path.css';
// static get style() { return myTagStyle; }
return createStyleIdentifierFromUrl(cmp, style);
return ts.factory.createIdentifier(style.styleIdentifier);
}

return null;
Expand All @@ -134,16 +134,75 @@ const createStyleLiteral = (cmp: d.ComponentCompilerMeta, style: d.StyleCompiler
return ts.factory.createStringLiteral(style.styleStr);
};

const createStyleIdentifierFromUrl = (cmp: d.ComponentCompilerMeta, style: d.StyleCompiler) => {
style.styleIdentifier = dashToPascalCase(cmp.tagName);
style.styleIdentifier = style.styleIdentifier.charAt(0).toLowerCase() + style.styleIdentifier.substring(1);

/**
* Helper method to create a style identifier for a component using {@link createStyleIdentifierFromUrl}.
* The method ensures that duplicate styles are removed and that the order of the styles is preserved.
* It also ensures that the style identifier is unique.
*
* @param cmp the metadata associated with the component being evaluated
* @param style style meta data
* @returns an assignment expression to be applied to the `style` property of a component class (e.g. `_myComponentCssStyle + _myComponentIosCssStyle` based on the example)
*/
export const createStyleIdentifier = (cmp: d.ComponentCompilerMeta, style: d.StyleCompiler) => {
const externalStyles = getExternalStyles(style);
/**
* Set a styleIdentifier which will be propagated to the component and
* later picked up by rollup when it injects the parsed CSS directly into
* the component, see `compilerCtx.worker.transformCssToEsm` in
* `src/compiler/bundle/ext-transforms-plugin.ts`
*/
style.styleIdentifier = dashToPascalCase(cmp.tagName.charAt(0).toLowerCase() + cmp.tagName.substring(1));
if (style.modeName !== DEFAULT_STYLE_MODE) {
style.styleIdentifier += dashToPascalCase(style.modeName);
}

style.styleIdentifier += 'Style';
style.externalStyles = [style.externalStyles[0]];
return createIdentifierFromStyleIdentifier(style.styleIdentifier, Object.keys(externalStyles));
};

/**
* Creates an expression to be assigned to the `style` property of a component class. For example
* given the following component:
*
* ```ts
* @Component({
* styleUrls: ['my-component.css', 'my-component.ios.css']
* tag: 'cmp',
* })
* export class MyComponent {
* // ...
* }
* ```
*
* it would generate the following expression:
*
* ```ts
* import MyComponentStyle0 from './my-component.css';
* import MyComponentStyle1 from './my-component.ios.css';
* export class MyComponent {
* // ...
* }
* MyComponent.style = MyComponentStyle0 + MyComponentStyle1;
* ```
*
* Note: style imports are made in [`createEsmStyleImport`](src/compiler/transformers/style-imports.ts).
*
* @param styleIdentifier identifier to be used for the style
* @param externalStyleIds numeric ids of the external styles
* @returns an assignment expression to be applied to the `style` property of a component class (e.g. `_myComponentCssStyle + _myComponentIosCssStyle` based on the example)
*/
const createIdentifierFromStyleIdentifier = (
styleIdentifier: string,
externalStyleIds: string[],
): ts.Identifier | ts.BinaryExpression => {
const id = externalStyleIds[0];

if (externalStyleIds.length === 1) {
return ts.factory.createIdentifier(styleIdentifier + id);
}

return ts.factory.createIdentifier(style.styleIdentifier);
return ts.factory.createBinaryExpression(
createIdentifierFromStyleIdentifier(styleIdentifier, [id]),
ts.SyntaxKind.PlusToken,
createIdentifierFromStyleIdentifier(styleIdentifier, externalStyleIds.slice(1)),
);
};
21 changes: 4 additions & 17 deletions src/compiler/transformers/component-native/native-static-style.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { dashToPascalCase, DEFAULT_STYLE_MODE } from '@utils';
import { DEFAULT_STYLE_MODE } from '@utils';
import ts from 'typescript';

import type * as d from '../../../declarations';
import { scopeCss } from '../../../utils/shadow-css';
import { getScopeId } from '../../style/scope-css';
import { createStyleIdentifier } from '../add-static-style';
import { createStaticGetter } from '../transform-utils';

export const addNativeStaticStyle = (classMembers: ts.ClassElement[], cmp: d.ComponentCompilerMeta) => {
Expand Down Expand Up @@ -43,7 +44,7 @@ const addMultipleModeStyleGetter = (
// import generated from @Component() styleUrls option
// import myTagIosStyle from './import-path.css';
// static get style() { return { "ios": myTagIosStyle }; }
const styleUrlIdentifier = createStyleIdentifierFromUrl(cmp, style);
const styleUrlIdentifier = createStyleIdentifier(cmp, style);
const propUrlIdentifier = ts.factory.createPropertyAssignment(style.modeName, styleUrlIdentifier);
styleModes.push(propUrlIdentifier);
}
Expand Down Expand Up @@ -74,7 +75,7 @@ const addSingleStyleGetter = (
// import generated from @Component() styleUrls option
// import myTagStyle from './import-path.css';
// static get style() { return myTagStyle; }
const styleUrlIdentifier = createStyleIdentifierFromUrl(cmp, style);
const styleUrlIdentifier = createStyleIdentifier(cmp, style);
classMembers.push(createStaticGetter('style', styleUrlIdentifier));
}
};
Expand All @@ -88,17 +89,3 @@ const createStyleLiteral = (cmp: d.ComponentCompilerMeta, style: d.StyleCompiler

return ts.factory.createStringLiteral(style.styleStr);
};

const createStyleIdentifierFromUrl = (cmp: d.ComponentCompilerMeta, style: d.StyleCompiler) => {
style.styleIdentifier = dashToPascalCase(cmp.tagName);
style.styleIdentifier = style.styleIdentifier.charAt(0).toLowerCase() + style.styleIdentifier.substring(1);

if (style.modeName !== DEFAULT_STYLE_MODE) {
style.styleIdentifier += dashToPascalCase(style.modeName);
}

style.styleIdentifier += 'Style';
style.externalStyles = [style.externalStyles[0]];

return ts.factory.createIdentifier(style.styleIdentifier);
};
173 changes: 132 additions & 41 deletions src/compiler/transformers/style-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,37 @@ import ts from 'typescript';

import type * as d from '../../declarations';
import { serializeImportPath } from './stencil-import-path';
import { retrieveTsModifiers } from './transform-utils';
import { getExternalStyles, retrieveTsModifiers } from './transform-utils';

/**
* This function adds imports (either in ESM or CJS syntax) for styles that are
* imported from the component's styleUrls option. For example, if a component
* has the following:
*
* ```ts
* @Component({
* styleUrls: ['my-component.css', 'my-component.ios.css']
* })
* export class MyComponent {
* // ...
* }
* ```
*
* then this function will add the following import statement:
*
* ```ts
* import MyComponentStyle0 from './my-component.css';
* import MyComponentStyle1 from './my-component.ios.css';
* ```
*
* Note that import identifier are used in [`addStaticStyleGetterWithinClass`](src/compiler/transformers/add-static-style.ts)
* to attach them to a components static style property.
*
* @param transformOpts transform options configured for the current output target transpilation
* @param tsSourceFile the TypeScript source file that is being updated
* @param moduleFile component file to update
* @returns an updated source file with the added import statements
*/
export const updateStyleImports = (
transformOpts: d.TransformOptions,
tsSourceFile: ts.SourceFile,
Expand All @@ -17,6 +46,14 @@ export const updateStyleImports = (
return updateEsmStyleImports(transformOpts, tsSourceFile, moduleFile);
};

/**
* Iterate over all components defined in given module, collect import
* statements to be added and update source file with them.
* @param transformOpts transform options configured for the current output target transpilation
* @param tsSourceFile the TypeScript source file that is being updated
* @param moduleFile component file to update
* @returns update source file with added import statements
*/
const updateEsmStyleImports = (
transformOpts: d.TransformOptions,
tsSourceFile: ts.SourceFile,
Expand All @@ -32,7 +69,7 @@ const updateEsmStyleImports = (
updateSourceFile = true;
if (style.externalStyles.length > 0) {
// add style imports built from @Component() styleUrl option
styleImports.push(createEsmStyleImport(transformOpts, tsSourceFile, cmp, style));
styleImports.push(...createStyleImport(transformOpts, tsSourceFile, cmp, style));
} else {
// update existing esm import of a style identifier
statements = updateEsmStyleImportPath(transformOpts, tsSourceFile, statements, cmp, style);
Expand Down Expand Up @@ -86,22 +123,105 @@ const updateEsmStyleImportPath = (
return statements;
};

const createEsmStyleImport = (
/**
* Add import or require statement for each style
* e.g. `import CMP__import_path_css from './import-path.css';`
*
* @param transformOpts transform options configured for the current output target transpilation
* @param tsSourceFile the TypeScript source file that is being updated
* @param cmp component meta data
* @param style style meta data
* @param moduleType module type (either 'esm' or 'cjs')
* @returns an set or import or require statements to add to the source file
*/
const createStyleImport = <ModuleType extends 'esm' | 'cjs'>(
transformOpts: d.TransformOptions,
tsSourceFile: ts.SourceFile,
cmp: d.ComponentCompilerMeta,
style: d.StyleCompiler,
/**
* default to 'esm' if not provided, behavior defined in `updateStyleImports`
*/
moduleType: ModuleType = 'esm' as ModuleType,
) => {
const importName = ts.factory.createIdentifier(style.styleIdentifier);
const importPath = getStyleImportPath(transformOpts, tsSourceFile, cmp, style, style.externalStyles[0].absolutePath);

return ts.factory.createImportDeclaration(
undefined,
ts.factory.createImportClause(false, importName, undefined),
ts.factory.createStringLiteral(importPath),
);
type ImportDeclarationOrVariableStatementType = ModuleType extends 'esm'
? ts.ImportDeclaration
: ts.VariableStatement;
const imports: ImportDeclarationOrVariableStatementType[] = [];

for (const [i, externalStyle] of Object.entries(getExternalStyles(style))) {
/**
* Concat styleId and absolutePath to get a unique identifier for each style.
*
* For example:
* ```ts
* @Component({
* styleUrls: {
* md: './foo/bar.css',
* ios: './bar/foo.css'
* },
* tag: 'cmp-a'
* })
* ```
*
* it would create the following identifiers:
* ```ts
* import CmpAStyle0 from './foo/bar.css';
* import CmpAStyle1 from './bar/foo.css';
* ```
*
* Attention: if you make changes to how this identifier is created you also need
* to update this in [`createStyleIdentifierFromUrl`](`src/compiler/transformers/add-static-style.ts`).
*/
const styleIdentifier = `${style.styleIdentifier}${i}`;
const importIdentifier = ts.factory.createIdentifier(styleIdentifier);
const importPath = getStyleImportPath(transformOpts, tsSourceFile, cmp, style, externalStyle);

if (moduleType === 'esm') {
imports.push(
ts.factory.createImportDeclaration(
undefined,
ts.factory.createImportClause(false, importIdentifier, undefined),
ts.factory.createStringLiteral(importPath),
) as ImportDeclarationOrVariableStatementType,
);
} else if (moduleType === 'cjs') {
imports.push(
ts.factory.createVariableStatement(
undefined,
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
importIdentifier,
undefined,
undefined,
ts.factory.createCallExpression(
ts.factory.createIdentifier('require'),
[],
[ts.factory.createStringLiteral(importPath)],
),
),
],
ts.NodeFlags.Const,
),
) as ImportDeclarationOrVariableStatementType,
);
} else {
throw new Error(`Invalid module type: ${moduleType}`);
}
}

return imports;
};

/**
* Iterate over all components defined in given module, collect require
* statements to be added and update source file with them.
* @param transformOpts transform options configured for the current output target transpilation
* @param tsSourceFile the TypeScript source file that is being updated
* @param moduleFile component file to update
* @returns update source file with added import statements
*/
const updateCjsStyleRequires = (
transformOpts: d.TransformOptions,
tsSourceFile: ts.SourceFile,
Expand All @@ -113,7 +233,7 @@ const updateCjsStyleRequires = (
cmp.styles.forEach((style) => {
if (typeof style.styleIdentifier === 'string' && style.externalStyles.length > 0) {
// add style imports built from @Component() styleUrl option
styleRequires.push(createCjsStyleRequire(transformOpts, tsSourceFile, cmp, style));
styleRequires.push(...createStyleImport(transformOpts, tsSourceFile, cmp, style));
}
});
});
Expand All @@ -125,35 +245,6 @@ const updateCjsStyleRequires = (
return tsSourceFile;
};

const createCjsStyleRequire = (
transformOpts: d.TransformOptions,
tsSourceFile: ts.SourceFile,
cmp: d.ComponentCompilerMeta,
style: d.StyleCompiler,
) => {
const importName = ts.factory.createIdentifier(style.styleIdentifier);
const importPath = getStyleImportPath(transformOpts, tsSourceFile, cmp, style, style.externalStyles[0].absolutePath);

return ts.factory.createVariableStatement(
undefined,
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
importName,
undefined,
undefined,
ts.factory.createCallExpression(
ts.factory.createIdentifier('require'),
[],
[ts.factory.createStringLiteral(importPath)],
),
),
],
ts.NodeFlags.Const,
),
);
};

const getStyleImportPath = (
transformOpts: d.TransformOptions,
tsSourceFile: ts.SourceFile,
Expand Down
Loading

0 comments on commit 735d45a

Please sign in to comment.