Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ export class PartialComponentLinkerVersion1<
version: string,
): LinkedDefinition {
const meta = this.toR3ComponentMeta(metaObj, version);
return compileComponentFromMetadata(meta, constantPool, makeBindingParser());
return compileComponentFromMetadata(meta, constantPool, makeBindingParser(), {
namespaceCssVariables: false,
});
}

/**
Expand Down
31 changes: 31 additions & 0 deletions packages/compiler-cli/linker/test/file_linker/file_linker_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,37 @@ describe('FileLinker', () => {
expect(compileSpy).toHaveBeenCalled();
expect(compileSpy.calls.mostRecent().args[1].getNode('ngImport')).toBe(ngImport);
});

it('should not namespace CSS variables in partially compiled component styles', () => {
const source = `
ɵɵngDeclareComponent({
minVersion: '0.0.0-PLACEHOLDER',
version: '0.0.0-PLACEHOLDER',
ngImport: core,
type: SomeComp,
template: '',
isInline: true,
styles: [':host { color: var(--brand); --global--accent: red; }'],
});
`;
const {fileLinker} = createFileLinker(source);
const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true);
const call = (sourceFile.statements[0] as ts.ExpressionStatement)
.expression as ts.CallExpression;

const result = fileLinker.linkPartialDeclaration(
'ɵɵngDeclareComponent',
[call.arguments[0]],
new MockDeclarationScope(),
);
const linkedDeclaration = ts
.createPrinter()
.printNode(ts.EmitHint.Unspecified, result, sourceFile);

expect(linkedDeclaration).toContain('color: var(--brand)');
expect(linkedDeclaration).toContain('--accent: red');
expect(linkedDeclaration).not.toContain('%NS%');
});
});

describe('block syntax support', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,3 @@
/****************************************************************************************************
* PARTIAL FILE: encapsulation_default.js
****************************************************************************************************/
import { Component, NgModule } from '@angular/core';
import * as i0 from "@angular/core";
export class MyComponent {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: false, selector: "my-component", ngImport: i0, template: '...', isInline: true, styles: ["div.foo { color: red; }", ":host p:nth-child(even) { --webkit-transition: 1s linear all; }"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
type: Component,
args: [{ selector: 'my-component', template: '...', standalone: false, styles: ["div.foo { color: red; }", ":host p:nth-child(even) { --webkit-transition: 1s linear all; }"] }]
}] });
export class MyModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{
type: NgModule,
args: [{ declarations: [MyComponent] }]
}] });

/****************************************************************************************************
* PARTIAL FILE: encapsulation_default.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyComponent {
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, false, never>;
}
export declare class MyModule {
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyComponent], never, never>;
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
}

/****************************************************************************************************
* PARTIAL FILE: encapsulation_emulated.js
****************************************************************************************************/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,72 @@
"cases": [
{
"description": "should pass in the component metadata styles into the component definition and shim them using style encapsulation",
"inputFiles": [
"encapsulation_default.ts"
],
"inputFiles": ["encapsulation_default.ts"],
"compilationModeFilter": ["full compile", "declaration-only emit", "instruction compile"],
"expectations": [
{
"failureMessage": "Incorrect template",
"files": ["encapsulation_default.js"]
}
]
},
{
"description": "should preserve CSS variable names when linking partially compiled component styles",
"inputFiles": ["encapsulation_default.ts"],
"compilationModeFilter": ["linked compile"],
"expectations": [
{
"failureMessage": "Incorrect template",
"files": [
"encapsulation_default.js"
{
"expected": "encapsulation_default_linked.js",
"generated": "encapsulation_default.js"
}
]
}
]
},
{
"description": "should pass in the component metadata styles into the component definition and shim them using style encapsulation",
"inputFiles": [
"encapsulation_emulated.ts"
],
"inputFiles": ["encapsulation_emulated.ts"],
"expectations": [
{
"failureMessage": "Incorrect template",
"files": [
"encapsulation_emulated.js"
]
"files": ["encapsulation_emulated.js"]
}
]
},
{
"description": "should pass in styles, but skip shimming the styles if the view encapsulation signals not to",
"inputFiles": [
"encapsulation_none.ts"
],
"inputFiles": ["encapsulation_none.ts"],
"expectations": [
{
"failureMessage": "Incorrect template",
"files": [
"encapsulation_none.js"
]
"files": ["encapsulation_none.js"]
}
]
},
{
"description": "should pass in the component metadata styles into the component definition but skip shimming when style encapsulation is set to shadow dom",
"inputFiles": [
"encapsulation_shadow_dom.ts"
],
"inputFiles": ["encapsulation_shadow_dom.ts"],
"expectations": [
{
"failureMessage": "Incorrect template",
"files": [
"encapsulation_shadow_dom.js"
]
"files": ["encapsulation_shadow_dom.js"]
}
]
},
{
"description": "should emit external runtime styles component feature for file-based styles when 'externalRuntimeStyles' option is enabled",
"inputFiles": [
"external_runtime_files.ts"
],
"inputFiles": ["external_runtime_files.ts"],
"angularCompilerOptions": {
"externalRuntimeStyles": true
},
"compilationModeFilter": [
"full compile",
"declaration-only emit"
],
"compilationModeFilter": ["full compile", "declaration-only emit"],
"expectations": [
{
"failureMessage": "Incorrect template",
"files": [
"external_runtime_files.js"
]
"files": ["external_runtime_files.js"]
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
styles: ["div.foo[_ngcontent-%COMP%] { color: red; }", "[_nghost-%COMP%] p[_ngcontent-%COMP%]:nth-child(even) { --webkit-transition: 1s linear all; }"]
7 changes: 5 additions & 2 deletions packages/compiler/src/jit_compiler_facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,17 +380,20 @@ export class CompilerFacadeImpl implements CompilerFacade {
sourceMapUrl,
);
const meta = convertDeclareComponentFacadeToMetadata(declaration, typeSourceSpan, sourceMapUrl);
return this.compileComponentFromMeta(angularCoreEnv, sourceMapUrl, meta);
return this.compileComponentFromMeta(angularCoreEnv, sourceMapUrl, meta, {
namespaceCssVariables: false,
});
}

private compileComponentFromMeta(
angularCoreEnv: CoreEnvironment,
sourceMapUrl: string,
meta: R3ComponentMetadata<R3TemplateDependency>,
options: {namespaceCssVariables?: boolean} = {},
): any {
const constantPool = new ConstantPool();
const bindingParser = makeBindingParser();
const res = compileComponentFromMetadata(meta, constantPool, bindingParser);
const res = compileComponentFromMetadata(meta, constantPool, bindingParser, options);
return this.jitExpression(
res.expression,
angularCoreEnv,
Expand Down
5 changes: 4 additions & 1 deletion packages/compiler/src/render3/view/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export function compileComponentFromMetadata(
meta: R3ComponentMetadata<R3TemplateDependency>,
constantPool: ConstantPool,
bindingParser: BindingParser,
options: {namespaceCssVariables?: boolean} = {},
): R3CompiledExpression {
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
addFeatures(definitionMap, meta);
Expand Down Expand Up @@ -269,7 +270,9 @@ export function compileComponentFromMetadata(
let hasStyles = !!meta.externalStyles?.length;
// e.g. `styles: [str1, str2]`
if (meta.styles && meta.styles.length) {
const namespacedStyles = meta.styles.map((s) => namespaceCssVariables(s));
const namespacedStyles = meta.styles.map((s) =>
namespaceCssVariables(s, options.namespaceCssVariables !== false),
);
const styleValues =
meta.encapsulation == core.ViewEncapsulation.Emulated
? compileStyles(namespacedStyles, CONTENT_ATTR, HOST_ATTR)
Expand Down
10 changes: 7 additions & 3 deletions packages/compiler/src/shadow_css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1054,16 +1054,18 @@ const _cssRbraceInPlaceholderReGlobal = new RegExp(RBRACE_IN_PLACEHOLDER, 'g');
const _cssVariableRe = /(var\(\s*|@property\s+)?(--(?:[a-zA-Z0-9_-]|[^\x00-\x7F])+)(\s*:)?/g;

/**
* Transforms CSS variables within a stylesheet to include a namespace placeholder.
* Transforms CSS variables within a stylesheet, optionally adding a namespace placeholder.
*
* E.g. `--foo: bar;` becomes `--%NS%foo: bar;`
* E.g. `color: var(--foo);` becomes `color: var(--%NS%foo);`
* E.g. `@property --foo` becomes `@property --%NS%foo`
*
* If a variable is prefixed with `--global--`, it is NOT namespaced and the prefix is removed.
* E.g. `--global--mycolor: red;` becomes `--mycolor: red;`
*
* @param shouldNamespace Whether to add the namespace placeholder to non-global variables.
*/
export function namespaceCssVariables(cssText: string): string {
export function namespaceCssVariables(cssText: string, shouldNamespace = true): string {
return cssText.replace(_cssVariableRe, (match, prefix, varName, trailingColon) => {
// Check for a leading `var(`, `@property`, or trailing `:` to approximate whether we're
// operating on a real CSS variable, not another piece of syntax that resembles it.
Expand All @@ -1077,7 +1079,9 @@ export function namespaceCssVariables(cssText: string): string {
if (!prefix && !trailingColon) {
return match;
}
return (prefix ?? '') + namespaceCssVariable(varName) + (trailingColon ?? '');
return (
(prefix ?? '') + namespaceCssVariable(varName, shouldNamespace) + (trailingColon ?? '')
);
});
}

Expand Down
7 changes: 5 additions & 2 deletions packages/compiler/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,11 @@ export function getJitStandaloneDefaultForVersion(version: string): boolean {
* Namespaces a CSS variable name and validates its syntax.
*
* @param varName The CSS variable name starting with `--`.
* @param shouldNamespace Whether to add the namespace placeholder to the variable.
* @throws An Error if the CSS variable is invalid (e.g. has a single hyphen after "--global").
* @returns The namespaced CSS variable name.
*/
export function namespaceCssVariable(varName: string): string {
export function namespaceCssVariable(varName: string, shouldNamespace = true): string {
// TODO: Enforce this in v23. This is a breaking change.
// Enable tests under platform-browser and compiler matching "has a single hyphen".
// g3-only-start
Expand All @@ -171,7 +172,9 @@ export function namespaceCssVariable(varName: string): string {

if (varName.startsWith('--global--')) {
return '--' + varName.substring('--global--'.length);
} else {
} else if (shouldNamespace) {
return '--%NS%' + varName.substring('--'.length);
} else {
return varName;
}
}
7 changes: 7 additions & 0 deletions packages/compiler/test/shadow_css/shadow_css_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,13 @@ describe('ShadowCss', () => {
expect(namespaceCssVariables(input)).toEqualCss(expected);
});

it('should preserve variable names while removing `--global--` when namespacing is disabled', () => {
const input = '.foo { --my-color: red; color: var(--global--my-color); }';
const expected = '.foo { --my-color: red; color: var(--my-color); }';

expect(namespaceCssVariables(input, false)).toEqualCss(expected);
});

it('should handle multiple variables with mixed namespacing', () => {
const input = `
.foo {
Expand Down
15 changes: 15 additions & 0 deletions packages/core/test/render3/jit/declare_component_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,21 @@ describe('component declaration jit compilation', () => {
});
});

it('should not namespace CSS variables in partial component styles', () => {
const def = ɵɵngDeclareComponent({
version: '22.1.0',
type: TestClass,
template: '<div></div>',
styles: [':host { color: var(--brand); --global--accent: red; }'],
changeDetection: ChangeDetectionStrategy.Eager,
}) as ComponentDef<TestClass>;

expectComponentDef(def, {
styles: ['[_nghost-%COMP%] { color: var(--brand); --accent: red; }'],
encapsulation: ViewEncapsulation.Emulated,
});
});

it('should compile components with view encapsulation', () => {
const def = ɵɵngDeclareComponent({
version: '18.0.0',
Expand Down