Skip to content

Commit

Permalink
feat: skip hmr when script is merely formatted
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Jan 6, 2024
1 parent 2c2326c commit dbadc58
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 13 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"vite": "^5.0.11"
},
"devDependencies": {
"@babel/types": "^7.23.6",
"@jridgewell/gen-mapping": "^0.3.3",
"@jridgewell/trace-mapping": "^0.3.20",
"@sxzz/eslint-config": "^3.7.6",
Expand Down
21 changes: 20 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 85 additions & 4 deletions src/core/handleHotUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import {
import {
getResolvedScript,
invalidateScript,
resolveScript,
setResolvedScript,
} from './script'
import type { SFCBlock, SFCDescriptor } from 'vue/compiler-sfc'
import type { ResolvedOptions } from '.'

import type * as t from '@babel/types'

const debug = _debug('vite:hmr')

// eslint-disable-next-line unicorn/better-regex
const directRequestRE = /(?:\?|&)direct\b/
const directRequestRE = /[&?]direct\b/

/**
* Vite-specific HMR handling
Expand All @@ -40,6 +42,8 @@ export async function handleHotUpdate(
const mainModule = getMainModule(modules)
const templateModule = modules.find((m) => /type=template/.test(m.url))

// trigger resolveScript for descriptor so that we'll have the AST ready
resolveScript('vite', descriptor, options, false)
const scriptChanged = hasScriptChanged(prevDescriptor, descriptor)
if (scriptChanged) {
affectedModules.add(getScriptModule(modules) || mainModule)
Expand Down Expand Up @@ -192,11 +196,88 @@ export function isOnlyTemplateChanged(
)
}

function deepEqual(obj1: any, obj2: any, excludeProps: string[] = []): boolean {
// Check if both objects are of the same type
if (typeof obj1 !== typeof obj2) {
return false
}

// Check if both objects are primitive types or null
if (obj1 == null || obj2 == null || typeof obj1 !== 'object') {
return obj1 === obj2
}

// Get the keys of the objects
const keys1 = Object.keys(obj1)
const keys2 = Object.keys(obj2)

// Check if the number of keys is the same
if (keys1.length !== keys2.length) {
return false
}

// Iterate through the keys and recursively compare the values
for (const key of keys1) {
// Check if the current key should be excluded
if (excludeProps.includes(key)) {
continue
}

if (!deepEqual(obj1[key], obj2[key], excludeProps)) {
return false
}
}

// If all comparisons passed, the objects are deep equal
return true
}

function isEqualAst(prev?: t.Statement[], next?: t.Statement[]): boolean {
if (typeof prev === 'undefined' || typeof next === 'undefined') {
return prev === next
}

// deep equal, but ignore start/end/loc/range/leadingComments/trailingComments/innerComments
if (prev.length !== next.length) {
return false
}

for (const [i, prevNode] of prev.entries()) {
const nextNode = next[i]
if (
!deepEqual(prevNode, nextNode, [
'start',
'end',
'loc',
'range',
'leadingComments',
'trailingComments',
'innerComments',
])
) {
return false
}
}

return true
}

function hasScriptChanged(prev: SFCDescriptor, next: SFCDescriptor): boolean {
if (!isEqualBlock(prev.script, next.script)) {
// check for scriptAst/scriptSetupAst changes
// note that the next ast is not available yet, so we need to trigger parsing
const prevScript = getResolvedScript(prev, false)
const nextScript = getResolvedScript(next, false)

if (
!isEqualBlock(prev.script, next.script) &&
!isEqualAst(prevScript?.scriptAst, nextScript?.scriptAst)
) {
return true
}
if (!isEqualBlock(prev.scriptSetup, next.scriptSetup)) {
if (
!isEqualBlock(prev.scriptSetup, next.scriptSetup) &&
!isEqualAst(prevScript?.scriptSetupAst, nextScript?.scriptSetupAst)
) {
return true
}

Expand Down
9 changes: 7 additions & 2 deletions src/core/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,16 @@ async function genScriptCode(
let scriptCode = `const ${scriptIdentifier} = {}`
let map: RawSourceMap | undefined

const script = resolveScript(pluginContext, descriptor, options, ssr)
const script = resolveScript(
pluginContext.framework,
descriptor,
options,
ssr,
)
if (script) {
// If the script is js/ts and has no external src, it can be directly placed
// in the main module.
if (canInlineMain(pluginContext, descriptor, options)) {
if (canInlineMain(pluginContext.framework, descriptor, options)) {
if (!options.compiler.version) {
// if compiler-sfc exposes no version, it's < 3.3 and doesn't support
// genDefaultAs option.
Expand Down
9 changes: 4 additions & 5 deletions src/core/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function isUseInlineTemplate(
export const scriptIdentifier = `_sfc_main`

export function resolveScript(
pluginContext: UnpluginContextMeta,
framework: UnpluginContextMeta['framework'],
descriptor: SFCDescriptor,
options: ResolvedOptions,
ssr: boolean,
Expand All @@ -74,7 +74,7 @@ export function resolveScript(
inlineTemplate: isUseInlineTemplate(options, descriptor),
templateOptions: resolveTemplateCompilerOptions(descriptor, options, ssr),
sourceMap: options.sourceMap,
genDefaultAs: canInlineMain(pluginContext, descriptor, options)
genDefaultAs: canInlineMain(framework, descriptor, options)
? scriptIdentifier
: undefined,
})
Expand Down Expand Up @@ -103,7 +103,7 @@ export function resolveScript(
// If the script is js/ts and has no external src, it can be directly placed
// in the main module. Skip for build
export function canInlineMain(
pluginContext: UnpluginContextMeta,
framework: UnpluginContextMeta['framework'],
descriptor: SFCDescriptor,
options: ResolvedOptions,
): boolean {
Expand All @@ -116,8 +116,7 @@ export function canInlineMain(
}
if (
lang === 'ts' &&
(options.devServer ||
['esbuild', 'rspack'].includes(pluginContext.framework))
(options.devServer || ['esbuild', 'rspack'].includes(framework))
) {
return true
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function compile(
ssr: boolean,
) {
const filename = descriptor.filename
resolveScript(pluginContext, descriptor, options, ssr)
resolveScript(pluginContext.framework, descriptor, options, ssr)
const result = options.compiler.compileTemplate({
...resolveTemplateCompilerOptions(descriptor, options, ssr)!,
source: code,
Expand Down

0 comments on commit dbadc58

Please sign in to comment.