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: allow any JS identifier in define, not ASCII-only #5972

Merged
merged 12 commits into from May 5, 2022
8 changes: 4 additions & 4 deletions docs/config/index.md
Expand Up @@ -123,11 +123,11 @@ export default defineConfig(async ({ command, mode }) => {

Define global constant replacements. Entries will be defined as globals during dev and statically replaced during build.

- Starting from `2.0.0-beta.70`, string values will be used as raw expressions, so if defining a string constant, it needs to be explicitly quoted (e.g. with `JSON.stringify`).

- Replacements are performed only when the match is surrounded by word boundaries (`\b`).
:::warning String constants
Starting from `2.0.0-beta.70`, string values will be used as raw expressions, so if defining a string constant, it needs to be explicitly quoted (e.g. with `JSON.stringify`).
:::

Because it's implemented as straightforward text replacements without any syntax analysis, we recommend using `define` for CONSTANTS only.
Because it's implemented as straightforward [RegExp-based](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/define.ts#L62-L76) text replacements without any syntax analysis, we recommend using `define` for CONSTANTS only.
jonaskuske marked this conversation as resolved.
Show resolved Hide resolved

For example, `process.env.FOO` and `__APP_VERSION__` are good fits. But `process` or `global` should not be put into this option. Variables can be shimmed or polyfilled instead.

Expand Down
8 changes: 8 additions & 0 deletions packages/playground/define/__tests__/define.spec.ts
Expand Up @@ -20,4 +20,12 @@ test('string', async () => {
expect(await page.textContent('.spread-array')).toBe(
JSON.stringify([...defines.__STRING__])
)
expect(await page.textContent('.dollar-identifier')).toBe(
String(defines.$DOLLAR)
)
expect(await page.textContent('.unicode-identifier')).toBe(
String(defines.ÖUNICODE_LETTERɵ)
)
expect(await page.textContent('.no-identifier-substring')).toBe(String(true))
expect(await page.textContent('.no-property')).toBe(String(true))
})
13 changes: 13 additions & 0 deletions packages/playground/define/index.html
Expand Up @@ -9,6 +9,10 @@ <h1>Define</h1>
<p>process as property: <code class="process-as-property"></code></p>
<p>spread object: <code class="spread-object"></code></p>
<p>spread array: <code class="spread-array"></code></p>
<p>dollar identifier: <code class="dollar-identifier"></code></p>
<p>unicode identifier: <code class="unicode-identifier"></code></p>
<p>no property: <code class="no-property"></code></p>
<p>no identifier substring: <span class="no-identifier-substring"></span></p>

<script type="module">
text('.exp', __EXP__)
Expand All @@ -25,6 +29,15 @@ <h1>Define</h1>
})
)
text('.spread-array', JSON.stringify([...`"${__STRING__}"`]))
text('.dollar-identifier', $DOLLAR)
text('.unicode-identifier', ÖUNICODE_LETTERɵ)

// make sure these kinds of use are NOT replaced:
const obj = { [`${'_'}_EXP__`]: true }
text('.no-property', obj.__EXP__)

window[`${'_'}_EXP__SUBSTR__`] = true
text('.no-identifier-substring', __EXP__SUBSTR__)

function text(el, text) {
document.querySelector(el).textContent = text
Expand Down
4 changes: 3 additions & 1 deletion packages/playground/define/vite.config.js
Expand Up @@ -15,6 +15,8 @@ module.exports = {
}
}
},
'process.env.SOMEVAR': '"SOMEVAR"'
'process.env.SOMEVAR': '"SOMEVAR"',
$DOLLAR: 456,
ÖUNICODE_LETTERɵ: 789
}
}
10 changes: 7 additions & 3 deletions packages/vite/src/node/plugins/define.ts
Expand Up @@ -61,14 +61,18 @@ export function definePlugin(config: ResolvedConfig): Plugin {

const pattern = new RegExp(
// Do not allow preceding '.', but do allow preceding '...' for spread operations
'(?<!(?<!\\.\\.)\\.)\\b(' +
'(?<!(?<!\\.\\.)\\.)' +
// Must follow beginning of a line or a char that can't be part of an identifier
'(?<=^|[^\\p{L}\\p{N}_$])(' +
Object.keys(replacements)
.map((str) => {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')
})
.join('|') +
')\\b',
'g'
// Must be followed by: end of a line, a char that can't be part of an identifier or
// anything following a dot (handles cases where replacement includes a trailing dot)
')(?=$|[^\\p{L}\\p{N}_$]|(?<=\\.).)',
'gu'
)

return [replacements, pattern]
Expand Down