@@ -17,6 +17,10 @@ const defaultHotOptions = {
1717 // true, or no component will be hot reloaded (but there are a lot of edge
1818 // cases that HMR can't support correctly with accessors)
1919 acceptAccessors : true ,
20+ // only inject CSS instead of recreating components when only CSS changes
21+ injectCss : true ,
22+ // to mitigate FOUC between dispose (remove stylesheet) and accept
23+ cssEjectDelay : 100 ,
2024}
2125
2226const domAdapter = require . resolve ( '../runtime/proxy-adapter-dom.js' )
@@ -50,7 +54,9 @@ const resolveImportAdapter = (
5054const renderApplyHmr = ( {
5155 id,
5256 cssId,
53- options,
57+ nonCssHash,
58+ hotOptions, // object
59+ options, // serialized
5460 hotApi,
5561 adapterPath,
5662 importAdapterName,
@@ -78,18 +84,52 @@ export default $2
7884 compileData: ${ compileData } ,
7985 compileOptions: ${ compileOptions } ,
8086 cssId: ${ quote ( cssId ) } ,
87+ nonCssHash: ${ quote ( nonCssHash ) } ,
8188 })
8289 : $2;
90+ ${
91+ // NOTE when doing CSS only voodoo, we have to inject the stylesheet as soon
92+ // as the component is loaded because Svelte normally do that when a component
93+ // is instantiated, but we might already have instances in the large when a
94+ // component is loaded with HMR
95+ hotOptions . injectCss && cssId
96+ ? `
97+ if (typeof add_css !== 'undefined' && !document.getElementById(${ quote (
98+ cssId
99+ ) } )) add_css();`
100+ : ``
101+ }
83102`
84103
85- const parseCssId = code => {
104+ // https://github.com/darkskyapp/string-hash/blob/master/index.js
105+ // (via https://github.com/sveltejs/svelte/blob/91d758e35b2b2154512ddd11e6b6d9d65708a99e/src/compiler/compile/utils/hash.ts#L2)
106+ const stringHashcode = str => {
107+ let hash = 5381
108+ let i = str . length
109+ while ( i -- ) hash = ( ( hash << 5 ) - hash ) ^ str . charCodeAt ( i )
110+ return ( hash >>> 0 ) . toString ( 36 )
111+ }
112+
113+ const parseCssId = ( code , parseHash ) => {
86114 // the regex matching is very pretty conservative 'cause I don't want to
87115 // match something else by error... I'm probably make it more lax if I have
88116 // to fix it 3 times in a single week ¯\_(ツ)_/¯
89117 let match = / ^ f u n c t i o n a d d _ c s s \( \) \{ [ \s \S ] * ?^ } / m. exec ( code )
90- if ( ! match ) return null
118+ if ( ! match ) return { }
119+ const codeExceptCSS =
120+ code . slice ( 0 , match . index ) + code . slice ( match . index + match [ 0 ] . length )
121+
91122 match = / \b s t y l e \. i d \s * = \s * ( [ ' " ] ) ( [ ^ ' " ] * ) \1/ . exec ( match [ 0 ] )
92- return match ? match [ 2 ] : null
123+ const cssId = match ? match [ 2 ] : null
124+
125+ if ( ! parseHash || ! cssId ) return { cssId }
126+
127+ const cssHash = cssId . split ( '-' ) [ 1 ]
128+ const nonCssHash = stringHashcode (
129+ codeExceptCSS . replace ( new RegExp ( '\\b' + cssHash + '\\b' , 'g' ) , '' )
130+ )
131+
132+ return { cssId, nonCssHash }
93133}
94134
95135// meta can be 'import.meta' or 'module'
@@ -139,9 +179,13 @@ const createMakeHot = (hotApi, { meta = 'import.meta', walk } = {}) => {
139179
140180 const { adapterPath, importAdapterName } = resolveImportAdapter ( hotOptions )
141181
182+ const { cssId, nonCssHash } = parseCssId ( compiledCode , hotOptions . injectCss )
183+
142184 const replacement = renderApplyHmr ( {
143185 id,
144- cssId : parseCssId ( compiledCode ) ,
186+ cssId,
187+ nonCssHash,
188+ hotOptions,
145189 options,
146190 hotApi,
147191 adapterPath,
0 commit comments