|
1 | 1 | import type { Options, Plugin } from 'prettier'; |
2 | 2 | import type { builders } from 'prettier/doc'; |
3 | | -import * as html from 'prettier/plugins/html'; |
4 | | -import { format } from 'prettier/standalone'; |
5 | 3 |
|
6 | 4 | interface HtmlNode { |
7 | 5 | type?: 'element' | 'text' | 'ieConditionalComment'; |
@@ -86,46 +84,70 @@ function recursivelyMapDoc( |
86 | 84 | return callback(doc); |
87 | 85 | } |
88 | 86 |
|
89 | | -const modifiedHtml = { ...html } as Plugin; |
90 | | -if (modifiedHtml.printers) { |
91 | | - const previousPrint = modifiedHtml.printers.html.print; |
92 | | - modifiedHtml.printers.html.print = (path, options, print, args) => { |
93 | | - const node = getHtmlNode( |
94 | | - path as Parameters<NonNullable<Plugin['printers']>['html']['print']>[0], |
95 | | - ); |
96 | | - |
97 | | - const rawPrintingResult = previousPrint(path, options, print, args); |
98 | | - |
99 | | - if ( |
100 | | - node?.type === 'ieConditionalComment' || |
101 | | - node?.kind === 'ieConditionalComment' |
102 | | - ) { |
103 | | - const printingResult = recursivelyMapDoc(rawPrintingResult, (doc) => { |
104 | | - if (typeof doc === 'object' && doc.type === 'line') { |
105 | | - return doc.soft ? '' : ' '; |
106 | | - } |
107 | | - |
108 | | - return doc; |
109 | | - }); |
110 | | - |
111 | | - return printingResult; |
112 | | - } |
| 87 | +function createModifiedHtmlPlugin(html: Plugin): Plugin { |
| 88 | + const modifiedHtml = { ...html }; |
| 89 | + if (modifiedHtml.printers) { |
| 90 | + const previousPrint = modifiedHtml.printers.html.print; |
| 91 | + modifiedHtml.printers.html.print = (path, options, print, args) => { |
| 92 | + const node = getHtmlNode( |
| 93 | + path as Parameters<NonNullable<Plugin['printers']>['html']['print']>[0], |
| 94 | + ); |
| 95 | + |
| 96 | + const rawPrintingResult = previousPrint(path, options, print, args); |
| 97 | + |
| 98 | + if ( |
| 99 | + node?.type === 'ieConditionalComment' || |
| 100 | + node?.kind === 'ieConditionalComment' |
| 101 | + ) { |
| 102 | + const printingResult = recursivelyMapDoc(rawPrintingResult, (doc) => { |
| 103 | + if (typeof doc === 'object' && doc.type === 'line') { |
| 104 | + return doc.soft ? '' : ' '; |
| 105 | + } |
| 106 | + |
| 107 | + return doc; |
| 108 | + }); |
| 109 | + |
| 110 | + return printingResult; |
| 111 | + } |
113 | 112 |
|
114 | | - return rawPrintingResult; |
115 | | - }; |
| 113 | + return rawPrintingResult; |
| 114 | + }; |
| 115 | + } |
| 116 | + |
| 117 | + return modifiedHtml; |
116 | 118 | } |
117 | 119 |
|
| 120 | +const importPrettier = async () => { |
| 121 | + const [{ format }, html] = await Promise.all([ |
| 122 | + import('prettier/standalone'), |
| 123 | + import('prettier/plugins/html'), |
| 124 | + ]); |
| 125 | + |
| 126 | + return { format, html: createModifiedHtmlPlugin(html as Plugin) }; |
| 127 | +}; |
| 128 | + |
| 129 | +/** |
| 130 | + * Prettier is only loaded the first time `pretty` is called, so importing |
| 131 | + * `render` doesn't load it when `pretty` is never used. This also keeps |
| 132 | + * bundlers that externalize `prettier` (e.g. Next.js with Turbopack) from |
| 133 | + * trying to resolve it at runtime unless it's actually needed. |
| 134 | + */ |
| 135 | +let prettierPromise: ReturnType<typeof importPrettier> | undefined; |
| 136 | + |
118 | 137 | const defaults: Options = { |
119 | 138 | endOfLine: 'lf', |
120 | 139 | tabWidth: 2, |
121 | | - plugins: [modifiedHtml], |
122 | 140 | bracketSameLine: true, |
123 | 141 | parser: 'html', |
124 | 142 | }; |
125 | 143 |
|
126 | | -export const pretty = (str: string, options: Options = {}) => { |
| 144 | +export const pretty = async (str: string, options: Options = {}) => { |
| 145 | + prettierPromise ??= importPrettier(); |
| 146 | + const { format, html } = await prettierPromise; |
| 147 | + |
127 | 148 | return format(str.replaceAll('\0', ''), { |
128 | 149 | ...defaults, |
| 150 | + plugins: [html], |
129 | 151 | ...options, |
130 | 152 | }); |
131 | 153 | }; |
0 commit comments