1
+ import { exec as execCb } from 'node:child_process' ;
2
+ import * as path from 'node:path' ;
3
+ import { promisify } from 'node:util' ;
1
4
import {
5
+ DecorationOptions ,
2
6
Disposable ,
3
7
ExtensionContext ,
8
+ Position ,
9
+ Range ,
4
10
Uri ,
5
11
ViewColumn ,
6
12
WebviewPanel ,
7
13
window ,
8
14
workspace ,
9
- Range ,
10
- DecorationOptions ,
11
- Position ,
12
15
} from 'vscode' ;
13
- import * as path from 'node:path' ;
14
- import { execSync } from 'node:child_process' ;
16
+
17
+ const exec = promisify ( execCb ) ;
15
18
16
19
const SHOPIFY_CLI_COMMAND = 'shopify theme profile' ;
17
20
@@ -41,71 +44,71 @@ export class LiquidProfiler {
41
44
borderRadius : '3px' ,
42
45
} ) ;
43
46
44
- private _panel : WebviewPanel | undefined ;
45
- private _disposables : Disposable [ ] = [ ] ;
46
- private _decorations = new Map < string , DecorationOptions [ ] > ( ) ;
47
- private _context : ExtensionContext ;
47
+ private panel : WebviewPanel | undefined ;
48
+ private disposables : Disposable [ ] = [ ] ;
49
+ private decorations = new Map < string , DecorationOptions [ ] > ( ) ;
50
+ private context : ExtensionContext ;
48
51
49
52
constructor ( context : ExtensionContext ) {
50
- this . _context = context ;
53
+ this . context = context ;
51
54
}
52
55
53
56
public async showProfileForUrl ( url : string ) {
54
- const profile = LiquidProfiler . getProfileContents ( url ) ;
57
+ const profile = await fetchProfileContents ( url ) ;
55
58
await this . processAndShowDecorations ( profile ) ;
56
59
await this . showWebviewPanelForProfile ( profile , url ) ;
57
60
}
58
61
59
62
private async showWebviewPanelForProfile ( profile : string , url : string ) {
60
63
const column = ViewColumn . Beside ;
61
64
62
- if ( this . _panel ) {
63
- this . _panel . reveal ( column ) ;
64
- this . _panel . title = `Liquid Profile: ${ url } ` ;
65
- this . _panel . webview . html = '' ;
65
+ if ( this . panel ) {
66
+ this . panel . reveal ( column ) ;
67
+ this . panel . title = `Liquid Profile: ${ url } ` ;
68
+ this . panel . webview . html = '' ;
66
69
} else {
67
- this . _panel = window . createWebviewPanel ( 'liquidProfile' , `Liquid Profile: ${ url } ` , column , {
70
+ this . panel = window . createWebviewPanel ( 'liquidProfile' , `Liquid Profile: ${ url } ` , column , {
68
71
enableScripts : true ,
69
72
// Allow files in the user's workspace (.tmp directory) to be used as local resources
70
73
localResourceRoots : [
71
74
...( workspace . workspaceFolders
72
75
? workspace . workspaceFolders . map ( ( folder ) => folder . uri )
73
76
: [ ] ) ,
74
- Uri . file ( this . _context . asAbsolutePath ( path . join ( 'dist' , 'node' , 'speedscope' ) ) ) ,
77
+ Uri . file ( this . context . asAbsolutePath ( path . join ( 'dist' , 'node' , 'speedscope' ) ) ) ,
75
78
] ,
76
79
} ) ;
77
- this . _panel . onDidDispose ( ( ) => this . dispose ( ) , null , this . _disposables ) ;
80
+ this . panel . onDidDispose ( ( ) => this . dispose ( ) , null , this . disposables ) ;
78
81
}
79
82
80
- this . _panel . webview . html = await this . _getSpeedscopeHtml ( profile ) ;
83
+ this . panel . webview . html = await this . getSpeedscopeHtml ( profile ) ;
81
84
}
82
85
83
- private _getSpeedscopeWebviewUri ( fileName : string ) : Uri {
86
+ private getSpeedscopeWebviewUri ( fileName : string ) : Uri {
84
87
const filePath = path . join ( 'dist' , 'node' , 'speedscope' , fileName ) ;
85
- return this . _panel ! . webview . asWebviewUri ( Uri . file ( this . _context . asAbsolutePath ( filePath ) ) ) ;
88
+ return this . panel ! . webview . asWebviewUri ( Uri . file ( this . context . asAbsolutePath ( filePath ) ) ) ;
86
89
}
87
90
88
- private async _getSpeedscopeHtml ( profileContents : string ) {
91
+ private async getSpeedscopeHtml ( profileContents : string ) {
89
92
const indexHtmlPath = Uri . file (
90
- this . _context . asAbsolutePath ( path . join ( 'dist' , 'node' , 'speedscope' , 'index.html' ) ) ,
93
+ this . context . asAbsolutePath ( path . join ( 'dist' , 'node' , 'speedscope' , 'index.html' ) ) ,
91
94
) ;
92
95
let htmlContent = Buffer . from ( await workspace . fs . readFile ( indexHtmlPath ) ) . toString ( 'utf8' ) ;
93
96
94
97
// Convert local resource paths to vscode-resource URIs, and replace the paths in the HTML content
95
- const cssUri = this . _getSpeedscopeWebviewUri ( 'source-code-pro.52b1676f.css' ) ;
98
+ const cssUri = this . getSpeedscopeWebviewUri ( 'source-code-pro.52b1676f.css' ) ;
96
99
htmlContent = htmlContent . replace ( 'source-code-pro.52b1676f.css' , cssUri . toString ( ) ) ;
97
100
98
- const resetCssUri = this . _getSpeedscopeWebviewUri ( 'reset.8c46b7a1.css' ) ;
101
+ const resetCssUri = this . getSpeedscopeWebviewUri ( 'reset.8c46b7a1.css' ) ;
99
102
htmlContent = htmlContent . replace ( 'reset.8c46b7a1.css' , resetCssUri . toString ( ) ) ;
100
103
101
- const jsUri = this . _getSpeedscopeWebviewUri ( 'speedscope.6f107512.js' ) ;
104
+ const jsUri = this . getSpeedscopeWebviewUri ( 'speedscope.6f107512.js' ) ;
102
105
htmlContent = htmlContent . replace ( 'speedscope.6f107512.js' , jsUri . toString ( ) ) ;
103
106
104
107
// Put the profile JSON in a tmp file, and replace the profile URL in the HTML content.
105
108
const tmpDir = workspace . workspaceFolders ?. [ 0 ] . uri . fsPath ;
106
109
const tmpFile = path . join ( tmpDir ! , '.tmp' , 'profile.json' ) ;
107
110
await workspace . fs . writeFile ( Uri . file ( tmpFile ) , Buffer . from ( profileContents ) ) ;
108
- const tmpUri = this . _panel ! . webview . asWebviewUri ( Uri . file ( tmpFile ) ) ;
111
+ const tmpUri = this . panel ! . webview . asWebviewUri ( Uri . file ( tmpFile ) ) ;
109
112
htmlContent = htmlContent . replace (
110
113
'<body>' ,
111
114
`<body><script>window.location.hash = "profileURL=${ encodeURIComponent (
@@ -117,14 +120,14 @@ export class LiquidProfiler {
117
120
}
118
121
119
122
public dispose ( ) {
120
- this . _panel ?. dispose ( ) ;
121
- while ( this . _disposables . length ) {
122
- const disposable = this . _disposables . pop ( ) ;
123
+ this . panel ?. dispose ( ) ;
124
+ while ( this . disposables . length ) {
125
+ const disposable = this . disposables . pop ( ) ;
123
126
if ( disposable ) {
124
127
disposable . dispose ( ) ;
125
128
}
126
129
}
127
- this . _panel = undefined ;
130
+ this . panel = undefined ;
128
131
}
129
132
130
133
/**
@@ -174,7 +177,7 @@ export class LiquidProfiler {
174
177
console . log ( '[Liquid Profiler] Processing profile results for decorations' ) ;
175
178
176
179
// Clear existing decorations
177
- this . _decorations . clear ( ) ;
180
+ this . decorations . clear ( ) ;
178
181
const visibleEditorsToClear = window . visibleTextEditors ;
179
182
for ( const editor of visibleEditorsToClear ) {
180
183
editor . setDecorations ( this . fileDecorationType , [ ] ) ;
@@ -215,7 +218,7 @@ export class LiquidProfiler {
215
218
} ;
216
219
217
220
// Store the file-level decoration.
218
- this . _decorations . set ( uri . fsPath , [ decoration ] ) ;
221
+ this . decorations . set ( uri . fsPath , [ decoration ] ) ;
219
222
220
223
const visibleEditors = window . visibleTextEditors ;
221
224
// Store the paths it's been applied to in a set.
@@ -279,9 +282,9 @@ export class LiquidProfiler {
279
282
} ;
280
283
281
284
// Store the decoration in a map where the key is the file path and the value is an array of decorations
282
- const fileDecorations = this . _decorations . get ( uri . fsPath ) || [ ] ;
285
+ const fileDecorations = this . decorations . get ( uri . fsPath ) || [ ] ;
283
286
fileDecorations . push ( decoration ) ;
284
- this . _decorations . set ( uri . fsPath , fileDecorations ) ;
287
+ this . decorations . set ( uri . fsPath , fileDecorations ) ;
285
288
} catch ( err ) {
286
289
console . error (
287
290
`[Liquid Profiler] Error creating line decoration for ${ frame . file } :${ frame . line } :` ,
@@ -294,15 +297,15 @@ export class LiquidProfiler {
294
297
const visibleEditors = window . visibleTextEditors ;
295
298
for ( const editor of visibleEditors ) {
296
299
// Get stored decorations for this file
297
- const lineDecorations = this . _decorations . get ( editor . document . uri . fsPath ) || [ ] ;
300
+ const lineDecorations = this . decorations . get ( editor . document . uri . fsPath ) || [ ] ;
298
301
editor . setDecorations ( this . lineDecorationType , lineDecorations ) ;
299
302
}
300
303
301
304
// Add listener for active editor changes
302
- this . _context . subscriptions . push (
305
+ this . context . subscriptions . push (
303
306
window . onDidChangeActiveTextEditor ( ( editor ) => {
304
307
if ( editor ) {
305
- const decorations = this . _decorations . get ( editor . document . uri . fsPath ) ;
308
+ const decorations = this . decorations . get ( editor . document . uri . fsPath ) ;
306
309
if ( decorations ) {
307
310
editor . setDecorations ( this . lineDecorationType , decorations ) ;
308
311
} else {
@@ -328,28 +331,30 @@ export class LiquidProfiler {
328
331
// Slow: Red
329
332
return '#f44336' ;
330
333
}
334
+ }
331
335
332
- private static getProfileContents ( url : string ) {
333
- try {
334
- console . log ( '[Liquid Profiler] Attempting to load preview for URL:' , url ) ;
335
- const result = execSync ( `${ SHOPIFY_CLI_COMMAND } --url=${ url } --json` , { stdio : 'pipe' } ) ;
336
- // Remove all characters leading up to the first {
337
- const content = result . toString ( ) . replace ( / ^ [ ^ { ] + / , '' ) ;
338
- console . log ( `[Liquid Profiler] Successfully retrieved preview content ${ content } ` ) ;
339
- return content ;
340
- } catch ( error ) {
341
- console . error ( '[Liquid Profiler] Error loading preview:' , error ) ;
342
- if ( error instanceof Error ) {
343
- // If there's stderr output, it will be in error.stderr
344
- const errorMessage = ( error as any ) . stderr ?. toString ( ) || error . message ;
345
- console . error ( '[Liquid Profiler] Error details:' , errorMessage ) ;
346
- return `<div style="color: red; padding: 20px;">
336
+ export async function fetchProfileContents ( url : string ) {
337
+ try {
338
+ console . log ( '[Liquid Profiler] Attempting to load preview for URL:' , url ) ;
339
+ const { stdout : result , stderr } = await exec ( `${ SHOPIFY_CLI_COMMAND } --url=${ url } --json` ) ;
340
+ if ( stderr ) console . error ( stderr ) ;
341
+
342
+ // Remove all characters leading up to the first {
343
+ const content = result . toString ( ) . replace ( / ^ [ ^ { ] + / , '' ) ;
344
+ console . log ( `[Liquid Profiler] Successfully retrieved preview content ${ content } ` ) ;
345
+ return content ;
346
+ } catch ( error ) {
347
+ console . error ( '[Liquid Profiler] Error loading preview:' , error ) ;
348
+ if ( error instanceof Error ) {
349
+ // If there's stderr output, it will be in error.stderr
350
+ const errorMessage = ( error as any ) . stderr ?. toString ( ) || error . message ;
351
+ console . error ( '[Liquid Profiler] Error details:' , errorMessage ) ;
352
+ return `<div style="color: red; padding: 20px;">
347
353
<h3>Error loading preview:</h3>
348
354
<pre>${ errorMessage } </pre>
349
355
</div>` ;
350
- }
351
- console . error ( '[Liquid Profiler] Unexpected error type:' , typeof error ) ;
352
- return '<div style="color: red; padding: 20px;">An unexpected error occurred</div>' ;
353
356
}
357
+ console . error ( '[Liquid Profiler] Unexpected error type:' , typeof error ) ;
358
+ return '<div style="color: red; padding: 20px;">An unexpected error occurred</div>' ;
354
359
}
355
360
}
0 commit comments