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

feat(define): don't modify string literals #9791

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
29da61c
fix(define): don't modify string literals
tony19 Aug 23, 2022
0781d42
test(define): verify define ignores literals
tony19 Aug 23, 2022
cf93c33
test(define): expand tests out of loop to easily identify failures
tony19 Aug 23, 2022
ed16db2
test(define): verify define ignores nested string of all quote types
tony19 Aug 23, 2022
589d30b
fix(define): don't modify string literals in ssr
tony19 Aug 23, 2022
603073c
test(define): verify define ignores literals in ssr mode
tony19 Aug 23, 2022
2eb1aea
test(define): include tests from #9621
tony19 Aug 23, 2022
91c47d4
fix(define): only strip literals if special constant found
tony19 Aug 23, 2022
2031d94
fix(define): remove unnecessary string replace for ssr
tony19 Aug 23, 2022
8974e65
fix(define): only start matching if replacement needed
tony19 Aug 23, 2022
46153b7
fix(define): return null early if no replacements needed
tony19 Aug 24, 2022
609df10
wip: test(define): more tests
tony19 Aug 24, 2022
f5277f0
wip: test(define): skip meta.env and process.env tests for now
tony19 Aug 24, 2022
6a06d5f
wip: test(define): refactor
tony19 Aug 24, 2022
d83ab82
wip: test(define): fix tests for for import.meta.env
tony19 Aug 24, 2022
7319275
fix(define): prevent replacement of import.meta.env during tests
tony19 Aug 24, 2022
d875495
test(define): fix possibly-undefined warning
tony19 Aug 24, 2022
89d8c29
test(define): tweaks
tony19 Aug 24, 2022
188fbdd
chore: escape import.meta.env in vue playground
tony19 Aug 24, 2022
e71a22c
fix(define): use object hook
tony19 Aug 24, 2022
22a9010
fix(define): simplify workaround for import.meta.env replacement
tony19 Aug 24, 2022
bb34dd1
chore(define): typo
tony19 Aug 25, 2022
cbff5c6
test(define): fix missing backticks in multiline tests
tony19 Aug 25, 2022
2b5bf2b
test(define): remove invalid test
tony19 Aug 25, 2022
75ff486
refactor(define): move string replacement code into utils
tony19 Sep 1, 2022
c5736ed
test(define): add playground tests
tony19 Aug 26, 2022
ae211b5
docs(define): describe new string replacement behavior
tony19 Sep 1, 2022
e41792a
chore: link to vitest issue in fixme comments
tony19 Sep 1, 2022
46a291b
test(define): add playground tests
tony19 Aug 26, 2022
bce6fe2
test(define): skip tests for 'process.env.NODE_ENV' until #9829 fix
tony19 Sep 1, 2022
b40fd91
perf(define): don't compare id if no replacements needed
tony19 Sep 1, 2022
2e9460d
chore: fix docs comment for replaceInCode
tony19 Sep 1, 2022
0383606
fix(client-inject): don't modify string literals
tony19 Sep 1, 2022
1303424
test(client-inject): add unit tests
tony19 Aug 26, 2022
59c2432
test: update snapshots for client-inject tests
tony19 Aug 26, 2022
60c8d4b
test(client-inject): verify replaces template literal expression
tony19 Aug 26, 2022
e3dd331
revert: "test(define): skip tests for 'process.env.NODE_ENV' until #9…
tony19 Sep 1, 2022
e8a3f24
Merge branch 'main' into fix/define-must-ignore-strings
tony19 Sep 1, 2022
87b7b16
Merge branch 'main' into fix/define-must-ignore-strings
tony19 Sep 6, 2022
43d21c2
chore(define): remove workaround for vitest#1941
tony19 Sep 6, 2022
39bd627
docs(define): update expected version
tony19 Sep 6, 2022
1df6fa4
test(util): verify replaceInCode ignores string literals/comments
tony19 Sep 6, 2022
60c4aee
Merge branch 'main' into fix/define-must-ignore-strings
tony19 Oct 23, 2022
308d640
Merge branch 'main' into fix/define-must-ignore-strings
tony19 Nov 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
129 changes: 125 additions & 4 deletions packages/vite/src/node/__tests__/plugins/define.spec.ts
Expand Up @@ -10,11 +10,18 @@ async function createDefinePluginTransform(
const config = await resolveConfig({ define }, build ? 'build' : 'serve')
const instance = definePlugin(config)
return async (code: string) => {
const result = await instance.transform.call({}, code, 'foo.ts', { ssr })
const transform =
instance.transform && 'handler' in instance.transform
? instance.transform.handler
: instance.transform
const result = await transform?.call({}, code, 'foo.ts', { ssr })
return result?.code || result
}
}

// FIXME: Use string concatenation to workaround Vite define-replacement of import\.meta.env in string literals
const importMetaEnv = 'import' + '.meta.env'
bluwy marked this conversation as resolved.
Show resolved Hide resolved

describe('definePlugin', () => {
test('replaces custom define', async () => {
const transform = await createDefinePluginTransform({
Expand All @@ -28,13 +35,127 @@ describe('definePlugin', () => {
)
})

test('replaces import.meta.env.SSR with false', async () => {
test(`replaces ${importMetaEnv}.SSR with false`, async () => {
const transform = await createDefinePluginTransform()
expect(await transform('const isSSR = import.meta.env.SSR ;')).toBe(
expect(await transform(`const isSSR = ${importMetaEnv}.SSR ;`)).toBe(
'const isSSR = false ;'
)
expect(await transform('const isSSR = import.meta.env.SSR;')).toBe(
expect(await transform(`const isSSR = ${importMetaEnv}.SSR;`)).toBe(
'const isSSR = false;'
)
})

// Specially defined constants found in packages/vite/src/node/plugins/define.ts
const specialDefines = {
'process.env.': '({}).',
'global.process.env.': '({}).',
'globalThis.process.env.': '({}).',
'process.env.NODE_ENV': '"test"',
'global.process.env.NODE_ENV': '"test"',
'globalThis.process.env.NODE_ENV': '"test"',
__vite_process_env_NODE_ENV: '"test"',
[importMetaEnv + '.']: '({}).',
[importMetaEnv]:
'{"BASE_URL":"/","MODE":"development","DEV":true,"PROD":false}',
'import.meta.hot': 'false'
}
const specialDefineKeys = Object.keys(specialDefines)

const specialDefinesSSR = {
...specialDefines,
// process.env is not replaced in SSR
'process.env.': null,
'global.process.env.': null,
'globalThis.process.env.': null,
'process.env.NODE_ENV': null,
'global.process.env.NODE_ENV': null,
'globalThis.process.env.NODE_ENV': null,
// __vite_process_env_NODE_ENV is a special variable that resolves to process.env.NODE_ENV, which is not replaced in SSR
__vite_process_env_NODE_ENV: 'process.env.NODE_ENV'
}

describe('ignores defined constants in string literals', async () => {
const singleQuotedDefines = specialDefineKeys
.map((define) => `let x = '${define}'`)
.join(';\n')
const doubleQuotedDefines = specialDefineKeys
.map((define) => `let x = "${define}"`)
.join(';\n')
const backtickedDefines = specialDefineKeys
.map((define) => `let x = \`${define}\``)
.join(';\n')
const singleQuotedDefinesMultilineNested = specialDefineKeys
.map((define) => `let x = \n'${define}'\n\``)
.join(';\n')
const doubleQuotedDefinesMultilineNested = specialDefineKeys
.map((define) => `let x = \n"${define}"\n\``)
.join(';\n')
const backtickedDefinesMultilineNested = specialDefineKeys
.map((define) => `let x = \`\n${define}\n\``)
.join(';\n')

const inputs = [
['double-quoted', doubleQuotedDefines],
['single-quoted', singleQuotedDefines],
['backticked', backtickedDefines],
['multiline nested double-quoted', doubleQuotedDefinesMultilineNested],
['multiline nested single-quoted', singleQuotedDefinesMultilineNested],
['multiline nested backticked', backtickedDefinesMultilineNested]
]

describe('non-SSR', async () => {
const transform = await createDefinePluginTransform()
test.each(inputs)('%s', async (label, input) => {
// transform() returns null when no replacement is made
expect(await transform(input)).toBe(null)
})
})

describe('SSR', async () => {
const ssrTransform = await createDefinePluginTransform({}, true, true)
test.each(inputs)('%s', async (label, input) => {
// transform() returns null when no replacement is made
expect(await ssrTransform(input)).toBe(null)
})
})
})

describe('replaces defined constants in template literal expressions', async () => {
describe('non-SSR', async () => {
const transform = await createDefinePluginTransform()

test.each(specialDefineKeys)('%s', async (key) => {
const result = await transform('let x = `${' + key + '}`')
expect(result).toBe('let x = `${' + specialDefines[key] + '}`')
})

// multiline tests
test.each(specialDefineKeys)('%s', async (key) => {
const result = await transform('let x = `\n${' + key + '}\n`')
expect(result).toBe('let x = `\n${' + specialDefines[key] + '}\n`')
})
})
describe('SSR', async () => {
const ssrTransform = await createDefinePluginTransform({}, true, true)

test.each(specialDefineKeys)('%s', async (key) => {
const result = await ssrTransform('let x = `${' + key + '}`')
expect(result).toBe(
specialDefinesSSR[key]
? 'let x = `${' + specialDefinesSSR[key] + '}`'
: null
)
})

// multiline tests
test.each(specialDefineKeys)('%s', async (key) => {
const result = await ssrTransform('let x = `\n${' + key + '}\n`')
expect(result).toBe(
specialDefinesSSR[key]
? 'let x = `\n${' + specialDefinesSSR[key] + '}\n`'
: null
)
})
})
})
})
21 changes: 13 additions & 8 deletions packages/vite/src/node/plugins/define.ts
@@ -1,4 +1,5 @@
import MagicString from 'magic-string'
import { stripLiteral } from 'strip-literal'
import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
import { transformStableResult } from '../utils'
Expand Down Expand Up @@ -45,12 +46,16 @@ export function definePlugin(config: ResolvedConfig): Plugin {
...config.env,
SSR: !!config.build.ssr
}

// FIXME: Use string concatenation to workaround Vite define-replacement of import\.meta.env in string literals
const importMetaEnv = 'import' + '.meta.env'

for (const key in env) {
importMetaKeys[`import.meta.env.${key}`] = JSON.stringify(env[key])
importMetaKeys[`${importMetaEnv}.${key}`] = JSON.stringify(env[key])
}
Object.assign(importMetaFallbackKeys, {
'import.meta.env.': `({}).`,
'import.meta.env': JSON.stringify(config.env),
[importMetaEnv + '.']: `({}).`,
[importMetaEnv]: JSON.stringify(config.env),
'import.meta.hot': `false`
})
}
Expand Down Expand Up @@ -123,17 +128,17 @@ export function definePlugin(config: ResolvedConfig): Plugin {
return null
}

if (ssr && !isBuild) {
tony19 marked this conversation as resolved.
Show resolved Hide resolved
// ssr + dev, simple replace
return code.replace(pattern, (_, match) => {
return '' + replacements[match]
})
const maybeNeedsReplacement = new RegExp(pattern).test(code)
if (!maybeNeedsReplacement) {
return null
}

const s = new MagicString(code)
let hasReplaced = false
let match: RegExpExecArray | null

code = stripLiteral(code)

while ((match = pattern.exec(code))) {
hasReplaced = true
const start = match.index
Expand Down
2 changes: 2 additions & 0 deletions playground/react/App.jsx
@@ -1,5 +1,6 @@
import { useState } from 'react'
import Dummy from './components/Dummy?qs-should-not-break-plugin-react'
import DefineVariable from './components/DefineVariable'
import Button from 'jsx-entry'

function App() {
Expand Down Expand Up @@ -27,6 +28,7 @@ function App() {
</header>

<Dummy />
<DefineVariable />
<Button>button</Button>
</div>
)
Expand Down
6 changes: 6 additions & 0 deletions playground/react/__tests__/react.spec.ts
Expand Up @@ -37,3 +37,9 @@ test.runIf(isServe)(
])
}
)

test('defined/reserved words are preserved in string literals', async () => {
expect(await page.textContent('.define-variable')).toBe(
'import' + '.meta.env' // FIXME: Workaround Vite replacement of import\0.meta.env
)
})
3 changes: 3 additions & 0 deletions playground/react/components/DefineVariable.tsx
@@ -0,0 +1,3 @@
export default function DefineVariable() {
return <div class="define-variable">import.meta.env</div>
}
3 changes: 3 additions & 0 deletions playground/vue/DefineVariable.vue
@@ -0,0 +1,3 @@
<template>
<div class="define-variable">import.meta.env</div>
</template>
2 changes: 2 additions & 0 deletions playground/vue/Main.vue
Expand Up @@ -22,6 +22,7 @@
<ReactivityTransform :foo="time" />
<SetupImportTemplate />
<WorkerTest />
<DefineVariable />
</template>

<script setup lang="ts">
Expand All @@ -39,6 +40,7 @@ import AsyncComponent from './AsyncComponent.vue'
import ReactivityTransform from './ReactivityTransform.vue'
import SetupImportTemplate from './setup-import-template/SetupImportTemplate.vue'
import WorkerTest from './worker.vue'
import DefineVariable from './DefineVariable.vue'
import { ref } from 'vue'

const time = ref('loading...')
Expand Down
8 changes: 8 additions & 0 deletions playground/vue/__tests__/vue.spec.ts
Expand Up @@ -263,3 +263,11 @@ describe('vue worker', () => {
expect(await page.textContent('.vue-worker')).toMatch('worker load!')
})
})

describe('defined/reserved words', () => {
test('are preserved in string literals', async () => {
expect(await page.textContent('.define-variable')).toBe(
'import' + '.meta.env' // FIXME: Workaround Vite replacement of import\0.meta.env
)
})
})