Skip to content

Commit 896022d

Browse files
committed
fix(dev-server): fix updating inline styles hmr
1 parent e67f98d commit 896022d

12 files changed

Lines changed: 166 additions & 21 deletions

File tree

.vscode/launch.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@
8484
],
8585
"protocol": "inspector"
8686
},
87+
{
88+
"type": "node",
89+
"request": "launch",
90+
"name": "Hello VDom Watch",
91+
"args": [
92+
"${workspaceFolder}/bin/stencil",
93+
"build",
94+
"--dev",
95+
"--serve",
96+
"--watch",
97+
"--config",
98+
"${workspaceFolder}/test/hello-vdom/stencil.config.ts"
99+
],
100+
"protocol": "inspector"
101+
},
87102
{
88103
"type": "node",
89104
"request": "launch",

src/compiler/build/compiler-ctx.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export class CompilerContext implements d.CompilerCtx {
4040
rootTsFiles: string[] = [];
4141
tsService: d.TsService = null;
4242
cachedGlobalStyle: string;
43+
styleModeNames = new Set();
4344

4445
constructor(config: d.Config) {
4546
const cacheFs = (config.enableCache && config.sys.fs != null) ? new InMemoryFileSystem(config.sys.fs, config.sys.path) : null;

src/compiler/style/cached-styles.ts

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as d from '../../declarations';
22
import { getCssImports } from './css-imports';
3+
import { getStyleId } from './component-styles';
34

45

56
export async function getComponentStylesCache(config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx, cmp: d.ComponentCompilerMeta, styleMeta: d.StyleCompiler, commentOriginalSelector: boolean) {
@@ -141,19 +142,21 @@ async function hasChangedImportContent(config: d.Config, compilerCtx: d.Compiler
141142
function getComponentStyleInputKey(cmp: d.ComponentCompilerMeta) {
142143
const input: string[] = [];
143144

144-
cmp.styles.forEach(styleMeta => {
145-
input.push(styleMeta.modeName);
145+
if (Array.isArray(cmp.styles)) {
146+
cmp.styles.forEach(styleMeta => {
147+
input.push(styleMeta.modeName);
146148

147-
if (styleMeta.styleStr) {
148-
input.push(styleMeta.styleStr);
149-
}
149+
if (typeof styleMeta.styleStr === 'string') {
150+
input.push(styleMeta.styleStr);
151+
}
150152

151-
if (styleMeta.externalStyles) {
152-
styleMeta.externalStyles.forEach(s => {
153-
input.push(s.absolutePath);
154-
});
155-
}
156-
});
153+
if (styleMeta.externalStyles) {
154+
styleMeta.externalStyles.forEach(s => {
155+
input.push(s.absolutePath);
156+
});
157+
}
158+
});
159+
}
157160

158161
return input.join(',');
159162
}
@@ -172,3 +175,54 @@ export function setComponentStylesCache(compilerCtx: d.CompilerCtx, cmp: d.Compo
172175
function getComponentStylesCacheKey(cmp: d.ComponentCompilerMeta, modeName: string) {
173176
return `${cmp.sourceFilePath}#${cmp.tagName}#${modeName}`;
174177
}
178+
179+
180+
export function hasComponentDecoratorStyleChanges(config: d.Config, compilerCtx: d.CompilerCtx, cmp: d.ComponentCompilerMeta) {
181+
const cacheKey = cmp.tagName;
182+
const currentInputHash = getComponentDecoratorStyleHash(config, cmp);
183+
184+
const lastInputHash = compilerCtx.lastComponentStyleInput.get(cacheKey);
185+
if (lastInputHash === currentInputHash) {
186+
return false;
187+
}
188+
189+
return true;
190+
}
191+
192+
193+
export function updateLastStyleComponetInputs(config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) {
194+
if (config.watch) {
195+
compilerCtx.moduleMap.forEach(m => {
196+
if (Array.isArray(m.cmps)) {
197+
m.cmps.forEach(cmp => {
198+
const cacheKey = cmp.tagName;
199+
const currentInputHash = getComponentDecoratorStyleHash(config, cmp);
200+
201+
if (cmp.styles == null || cmp.styles.length === 0) {
202+
compilerCtx.styleModeNames.forEach(modeName => {
203+
const lastInputHash = compilerCtx.lastComponentStyleInput.get(cacheKey);
204+
if (lastInputHash !== currentInputHash) {
205+
buildCtx.stylesUpdated.push({
206+
styleTag: cmp.tagName,
207+
styleText: '',
208+
styleMode: modeName
209+
});
210+
211+
const cacheKey = getComponentStylesCacheKey(cmp, modeName);
212+
compilerCtx.cachedStyleMeta.delete(cacheKey);
213+
214+
const styleId = getStyleId(cmp, modeName, false);
215+
compilerCtx.lastBuildStyles.delete(styleId);
216+
}
217+
});
218+
}
219+
compilerCtx.lastComponentStyleInput.set(cacheKey, currentInputHash);
220+
});
221+
}
222+
});
223+
}
224+
}
225+
226+
function getComponentDecoratorStyleHash(config: d.Config, cmp: d.ComponentCompilerMeta) {
227+
return config.sys.generateContentHash(getComponentStyleInputKey(cmp), 8);
228+
}

src/compiler/style/component-styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ async function setStyleText(config: d.Config, compilerCtx: d.CompilerCtx, buildC
250250
}
251251

252252

253-
function getStyleId(cmp: d.ComponentCompilerMeta, modeName: string, isScopedStyles: boolean) {
253+
export function getStyleId(cmp: d.ComponentCompilerMeta, modeName: string, isScopedStyles: boolean) {
254254
return `${cmp.tagName}${modeName}${isScopedStyles ? '.sc' : ''}`;
255255
}
256256

src/compiler/style/generate-styles.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as d from '../../declarations';
22
import { generateComponentStyles } from './component-styles';
33
import { generateGlobalStyles } from './global-styles';
4+
import { hasComponentDecoratorStyleChanges, updateLastStyleComponetInputs } from './cached-styles';
5+
46

57
export async function generateStyles(config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) {
6-
if (canSkipGenerateStyles(compilerCtx, buildCtx)) {
8+
if (canSkipGenerateStyles(config, compilerCtx, buildCtx)) {
79
return;
810
}
911

@@ -14,11 +16,13 @@ export async function generateStyles(config: d.Config, compilerCtx: d.CompilerCt
1416
generateComponentStyles(config, compilerCtx, buildCtx),
1517
]);
1618

19+
updateLastStyleComponetInputs(config, compilerCtx, buildCtx);
20+
1721
timeSpan.finish(`generate styles finished`);
1822
}
1923

2024

21-
function canSkipGenerateStyles(compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) {
25+
function canSkipGenerateStyles(config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) {
2226
if (buildCtx.requiresFullBuild) {
2327
return false;
2428
}
@@ -42,7 +46,9 @@ function canSkipGenerateStyles(compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx)
4246
// see if any of the changed scripts are module files
4347
const moduleFile = compilerCtx.moduleMap.get(filePath);
4448
if (moduleFile != null && moduleFile.cmps != null) {
45-
return moduleFile.cmps.some(cmp => cmp.hasStyle);
49+
return moduleFile.cmps.some(cmp => {
50+
return cmp.hasStyle || hasComponentDecoratorStyleChanges(config, compilerCtx, cmp);
51+
});
4652
}
4753
// not a module with a component
4854
return false;

src/compiler/style/test/style.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,66 @@ describe('component-styles', () => {
4646
expect(content).toContain('\\\\2014 \\\\00A0');
4747
});
4848

49+
it('should build one component w/ out inline style, and re-compile when adding inline styles', async () => {
50+
compiler.config.watch = true;
51+
await compiler.fs.writeFiles({
52+
[path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a' }) export class CmpA {}`,
53+
});
54+
await compiler.fs.commit();
55+
56+
let r = await compiler.build();
57+
expect(r.diagnostics).toHaveLength(0);
58+
expect(r.styleBuildCount).toBe(0);
59+
60+
const rebuildListener = compiler.once('buildFinish');
61+
62+
await compiler.fs.writeFiles({
63+
[path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a', styles: 'body { color: green; }' }) export class CmpA {}`,
64+
}, { clearFileCache: true });
65+
await compiler.fs.commit();
66+
67+
compiler.trigger('fileUpdate', path.join(root, 'src', 'cmp-a.tsx'));
68+
69+
r = await rebuildListener;
70+
expect(r.diagnostics).toHaveLength(0);
71+
expect(r.hmr.inlineStylesUpdated).toHaveLength(1);
72+
expect(r.hmr.inlineStylesUpdated[0].styleText).toBe(`body { color: green; }`);
73+
74+
const content = await compiler.fs.readFile(path.join(root, 'www', 'build', 'cmp-a.entry.js'));
75+
expect(content).toContain(`color: green`);
76+
expect(r.styleBuildCount).toBe(1);
77+
});
78+
79+
it('should build one component w/ inline style, and re-compile when removing inline styles', async () => {
80+
compiler.config.watch = true;
81+
await compiler.fs.writeFiles({
82+
[path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a', styles: 'body { color: green; }' }) export class CmpA {}`,
83+
});
84+
await compiler.fs.commit();
85+
86+
let r = await compiler.build();
87+
expect(r.diagnostics).toHaveLength(0);
88+
expect(r.styleBuildCount).toBe(1);
89+
90+
const rebuildListener = compiler.once('buildFinish');
91+
92+
await compiler.fs.writeFiles({
93+
[path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a' }) export class CmpA {}`,
94+
}, { clearFileCache: true });
95+
await compiler.fs.commit();
96+
97+
compiler.trigger('fileUpdate', path.join(root, 'src', 'cmp-a.tsx'));
98+
99+
r = await rebuildListener;
100+
expect(r.diagnostics).toHaveLength(0);
101+
expect(r.hmr.inlineStylesUpdated).toHaveLength(1);
102+
expect(r.hmr.inlineStylesUpdated[0].styleText).toBe(``);
103+
104+
const content = await compiler.fs.readFile(path.join(root, 'www', 'build', 'cmp-a.entry.js'));
105+
expect(content).not.toContain(`color: green`);
106+
expect(r.styleBuildCount).toBe(0);
107+
});
108+
49109
it('should build one component w/ inline style, and re-compile on module file changes', async () => {
50110
compiler.config.watch = true;
51111
await compiler.fs.writeFiles({
@@ -71,6 +131,9 @@ describe('component-styles', () => {
71131

72132
r = await rebuildListener;
73133
expect(r.diagnostics).toHaveLength(0);
134+
expect(r.diagnostics).toHaveLength(0);
135+
expect(r.hmr.inlineStylesUpdated).toHaveLength(1);
136+
expect(r.hmr.inlineStylesUpdated[0].styleText).toContain(`color: green`);
74137

75138
content = await compiler.fs.readFile(path.join(root, 'www', 'build', 'cmp-a.entry.js'));
76139
expect(content).toContain(`color: green`);

src/compiler/transformers/static-to-meta/component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import ts from 'typescript';
1717
import { normalizePath, unique } from '@utils';
1818

1919

20-
export function parseStaticComponentMeta(config: d.Config, transformCtx: ts.TransformationContext, typeChecker: ts.TypeChecker, cmpNode: ts.ClassDeclaration, moduleFile: d.Module, nodeMap: d.NodeMap, transformOpts: d.TransformOptions) {
20+
export function parseStaticComponentMeta(config: d.Config, compilerCtx: d.CompilerCtx, transformCtx: ts.TransformationContext, typeChecker: ts.TypeChecker, cmpNode: ts.ClassDeclaration, moduleFile: d.Module, nodeMap: d.NodeMap, transformOpts: d.TransformOptions) {
2121
if (cmpNode.members == null) {
2222
return cmpNode;
2323
}
@@ -46,7 +46,7 @@ export function parseStaticComponentMeta(config: d.Config, transformCtx: ts.Tran
4646
listeners: parseStaticListeners(staticMembers),
4747
events: parseStaticEvents(staticMembers),
4848
watchers: parseStaticWatchers(staticMembers),
49-
styles: parseStaticStyles(config, tagName, moduleFile.sourceFilePath, isCollectionDependency, staticMembers),
49+
styles: parseStaticStyles(config, compilerCtx, tagName, moduleFile.sourceFilePath, isCollectionDependency, staticMembers),
5050
legacyConnect: getStaticValue(staticMembers, 'connectProps') || [],
5151
legacyContext: getStaticValue(staticMembers, 'contextProps') || [],
5252
internal: isInternal(docs),

src/compiler/transformers/static-to-meta/styles.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { normalizeStyles } from '../../style/normalize-styles';
55
import ts from 'typescript';
66

77

8-
export function parseStaticStyles(config: d.Config, tagName: string, componentFilePath: string, isCollectionDependency: boolean, staticMembers: ts.ClassElement[]) {
8+
export function parseStaticStyles(config: d.Config, compilerCtx: d.CompilerCtx, tagName: string, componentFilePath: string, isCollectionDependency: boolean, staticMembers: ts.ClassElement[]) {
99
const styles: d.StyleCompiler[] = [];
1010

1111
let parsedStyleStr: string = getStaticValue(staticMembers, 'styles');
@@ -22,6 +22,7 @@ export function parseStaticStyles(config: d.Config, tagName: string, componentFi
2222
compiledStyleTextScopedCommented: null,
2323
externalStyles: []
2424
});
25+
compilerCtx.styleModeNames.add(DEFAULT_STYLE_MODE);
2526
}
2627
}
2728

@@ -51,6 +52,7 @@ export function parseStaticStyles(config: d.Config, tagName: string, componentFi
5152
compiledStyleTextScopedCommented: null,
5253
externalStyles: externalStyles
5354
});
55+
compilerCtx.styleModeNames.add(modeName);
5456
}
5557
});
5658
}

src/compiler/transformers/static-to-meta/visitor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function convertStaticToMeta(config: d.Config, compilerCtx: d.CompilerCtx
1414

1515
function visitNode(node: ts.Node): ts.VisitResult<ts.Node> {
1616
if (ts.isClassDeclaration(node)) {
17-
return parseStaticComponentMeta(config, transformCtx, typeChecker, node, moduleFile, compilerCtx.nodeMap, transformOpts);
17+
return parseStaticComponentMeta(config, compilerCtx, transformCtx, typeChecker, node, moduleFile, compilerCtx.nodeMap, transformOpts);
1818
} else if (ts.isImportDeclaration(node)) {
1919
return parseImport(config, compilerCtx, buildCtx, moduleFile, dirPath, node);
2020
}

src/declarations/compiler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface CompilerCtx {
3939
rollupCacheLazy: any;
4040
rollupCacheNative: any;
4141
rootTsFiles: string[];
42+
styleModeNames: Set<string>;
4243
tsService: TsService;
4344

4445
reset(): void;

0 commit comments

Comments
 (0)