Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(scan): handle local scripts with lang=ts #5850

Merged
merged 3 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions packages/playground/vue/__tests__/vue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@ test(':slotted', async () => {
expect(await getColor('.slotted')).toBe('red')
})

test('scan deps from <script setup lang="ts">', async () => {
expect(await page.textContent('.scan')).toBe('ok')
describe('dep scan', () => {
test('scan deps from <script setup lang="ts">', async () => {
expect(await page.textContent('.scan')).toBe('ok')
})

test('find deps on initial scan', () => {
serverLogs.forEach((log) => {
expect(log).not.toMatch('new dependencies found')
})
})
})

describe('pre-processors', () => {
Expand Down
62 changes: 37 additions & 25 deletions packages/vite/src/node/optimizer/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,42 +273,29 @@ function esbuildScanPlugin(
contextMatch &&
(contextMatch[1] || contextMatch[2] || contextMatch[3])
if (
(path.endsWith('.vue') && setupRE.test(raw)) ||
(path.endsWith('.vue') && setupRE.test(openTag)) ||
(path.endsWith('.svelte') && context !== 'module')
) {
// append imports in TS to prevent esbuild from removing them
// since they may be used in the template
const localContent =
content +
(loader.startsWith('ts') ? extractImportPaths(content) : '')
localScripts[path] = {
loader,
contents: content
contents: localContent
}
js += `import '${virtualModulePrefix}${path}';\n`
} else {
js += content + '\n'
}
}
}
// empty singleline & multiline comments to avoid matching comments
const code = js
.replace(multilineCommentsRE, '/* */')
.replace(singlelineCommentsRE, '')

if (
loader.startsWith('ts') &&
(path.endsWith('.svelte') ||
(path.endsWith('.vue') && setupRE.test(raw)))
) {
// when using TS + (Vue + <script setup>) or Svelte, imports may seem
// unused to esbuild and dropped in the build output, which prevents
// esbuild from crawling further.
// the solution is to add `import 'x'` for every source to force
// esbuild to keep crawling due to potential side effects.
let m
while ((m = importsRE.exec(code)) != null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === importsRE.lastIndex) {
importsRE.lastIndex++
}
js += `\nimport ${m[1]}`
}

// `<script>` in Svelte has imports that can be used in the template
// so we handle them here too
if (loader.startsWith('ts') && path.endsWith('.svelte')) {
js += extractImportPaths(js)
}

// This will trigger incorrectly if `export default` is contained
Expand Down Expand Up @@ -488,6 +475,31 @@ async function transformGlob(
return s.toString()
}

/**
* when using TS + (Vue + `<script setup>`) or Svelte, imports may seem
* unused to esbuild and dropped in the build output, which prevents
* esbuild from crawling further.
* the solution is to add `import 'x'` for every source to force
* esbuild to keep crawling due to potential side effects.
*/
function extractImportPaths(code: string) {
// empty singleline & multiline comments to avoid matching comments
code = code
.replace(multilineCommentsRE, '/* */')
.replace(singlelineCommentsRE, '')

let js = ''
let m
while ((m = importsRE.exec(code)) != null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === importsRE.lastIndex) {
importsRE.lastIndex++
}
js += `\nimport ${m[1]}`
}
return js
}

export function shouldExternalizeDep(
resolvedId: string,
rawId: string
Expand Down
47 changes: 44 additions & 3 deletions scripts/jestPerTestSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import {
ViteDevServer,
UserConfig,
PluginOption,
ResolvedConfig
ResolvedConfig,
Logger
} from 'vite'
import { Page } from 'playwright-chromium'
// eslint-disable-next-line node/no-extraneous-import
import { RollupWatcher, RollupWatcherEvent } from 'rollup'
import { RollupError, RollupWatcher, RollupWatcherEvent } from 'rollup'

const isBuildTest = !!process.env.VITE_TEST_BUILD

Expand All @@ -25,6 +26,7 @@ declare global {
const page: Page | undefined

const browserLogs: string[]
const serverLogs: string[]
const viteTestUrl: string | undefined
const watcher: RollupWatcher | undefined
let beforeAllError: Error | null // error caught in beforeAll, useful if you want to test error scenarios on build
Expand All @@ -34,6 +36,7 @@ declare const global: {
page?: Page

browserLogs: string[]
serverLogs: string[]
viteTestUrl?: string
watcher?: RollupWatcher
beforeAllError: Error | null
Expand Down Expand Up @@ -87,6 +90,8 @@ beforeAll(async () => {
}
}

const serverLogs: string[] = []

const options: UserConfig = {
root: rootDir,
logLevel: 'silent',
Expand All @@ -105,9 +110,12 @@ beforeAll(async () => {
build: {
// skip transpilation during tests to make it faster
target: 'esnext'
}
},
customLogger: createInMemoryLogger(serverLogs)
}

global.serverLogs = serverLogs

if (!isBuildTest) {
process.env.VITE_INLINE = 'inline-serve'
server = await (await createServer(options)).listen()
Expand Down Expand Up @@ -153,6 +161,7 @@ beforeAll(async () => {

afterAll(async () => {
global.page?.off('console', onConsole)
global.serverLogs = []
await global.page?.close()
await server?.close()
const beforeAllErr = getBeforeAllError()
Expand Down Expand Up @@ -221,3 +230,35 @@ export async function notifyRebuildComplete(
})
return watcher.removeListener('event', callback)
}

function createInMemoryLogger(logs: string[]): Logger {
const loggedErrors = new WeakSet<Error | RollupError>()
const warnedMessages = new Set<string>()

const logger: Logger = {
hasWarned: false,
hasErrorLogged: (err) => loggedErrors.has(err),
clearScreen: () => {},
info(msg) {
logs.push(msg)
},
warn(msg) {
logs.push(msg)
logger.hasWarned = true
},
warnOnce(msg) {
if (warnedMessages.has(msg)) return
logs.push(msg)
logger.hasWarned = true
warnedMessages.add(msg)
},
error(msg, opts) {
logs.push(msg)
if (opts?.error) {
loggedErrors.add(opts.error)
}
}
}

return logger
}