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

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

wants to merge 44 commits into from

Conversation

tony19
Copy link
Contributor

@tony19 tony19 commented Aug 23, 2022

Description

The define and client-inject plugins indiscriminately replace strings within string literals, which can create invalid JavaScript syntax (e.g., console.log("mode is process.env.NODE_ENV") is transformed into console.log("mode is "process.env.NODE_ENV""). This PR strips the literals from the code string that the plugins scan, thereby leaving the literals unchanged, which is the same solution used in several other Vite plugins.

fix #9790
fix #3304
fix #8663
fix #6295
fix #9829

Additional context

This fix is similar to #9621, which was closed due to a preference for a regex fix for performance.

I did experiment with tweaking the plugin's regex to exclude string literals, but it's not robust enough to cover multiline strings:

const PUBLIC_TEST = "import.meta.env"  // ✅ ignored

const PUBLIC_TEST = `
import.meta.env.PUBLIC_TEST  // ❌ still matches
`

Plus, based on the description in #8054, it's impossible to handle this properly with regex.


What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

Before submitting the PR, please make sure you do the following

  • Read the Contributing Guidelines.
  • Read the Pull Request Guidelines and follow the Commit Convention.
  • Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
  • Provide a description in this PR that addresses what the PR is solving, or reference the issue that it solves (e.g. fixes #123).
  • Ideally, include relevant tests that fail without this PR but pass with it.

@tony19 tony19 changed the title Fix/define must ignore strings fix(define): don't modify string literals Aug 23, 2022
@@ -37,4 +37,22 @@ describe('definePlugin', () => {
'const isSSR = false;'
)
})

test('ignores import\0.meta.env inside strings', async () => {
Copy link
Contributor Author

@tony19 tony19 Aug 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vitest (or Vite) is replacing import.meta.env with process.env in unit tests, which coincidentally is the bug I'm trying to fix (so meta 🤣). I'm wondering if it's actually the same bug.

I inserted \0 in this test's strings to prevent that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is such a deep level of programming irony. Got me laughing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is because this PR doesn't strip literals in SSR here, which we may want to do as well. Otherwise we're using Vitest linked to local Vite, so theoretically this line should be fixed too.

Since we're already parsing in SSR dev (though I heard Anthony is experimenting something), another parse/tokenize here maybe isn't too bad, unless we could share a cache via this.parse perhaps.

@bluwy
Copy link
Member

bluwy commented Aug 23, 2022

This fix is similar to #9621. But this using strip-literal means we're only tokenizing with acorn instead of a full parse. My opinion with either PRs is that I think this may be the only way for correctness in builds. And it's not too bad as we only need to tokenize on builds.

@patak-dev
Copy link
Member

The difference with other places where we are using stripLiteral, is that we only use it after checking that there is a possible match (like checking for import.meta.url here). Maybe we should do the first match and then only do a stripLiteral to disard it? In this way, people that don't use define, won't pay for tokenizing every file.

Another thought is if we should combine some of the plugins that depends on stripLiteral so we avoid doing several passes on different plugins.

Let's add this one to be discussed with the team when we get the chance.

@patak-dev patak-dev added the p3-minor-bug An edge case that only affects very specific usage (priority) label Aug 23, 2022
Shinigami92
Shinigami92 previously approved these changes Aug 25, 2022
@patak-dev
Copy link
Member

We discussed this PR in today's meeting @tony19, and we think that we should redefine the define feature as you did here to avoid replacing in strings.

Regarding the implementation, we discussed the performance implications and Evan had the idea to explore using Define from esbuild (directly in the esbuild plugin). So we should first look if that is a possible path forward (thus avoiding the tokenizer and regex scheme).

@shigma
Copy link
Contributor

shigma commented Sep 14, 2022

@patak-dev Using define from esbuild would be a very good idea. Currently vite is not really consistent with esbuild behavior (although documentation says so). Vite generate definitions into a single file and use references as replacements, which means:

  • object definitions will share the same references
  • users cannot provide context-aware definitions

In my case, I use resolve(__filename, '../client') in my source code (I want it to run on both node and browser), and trying to define __filename as import.meta.url, but it doesn't work. Using define from esbuild would certainly help.

@JulianCataldo
Copy link

I don't know why, but with me, this bugs occurs only with debian/ubuntu, not macOS

withastro/astro#5074

@tony19
Copy link
Contributor Author

tony19 commented Oct 23, 2022

@patak-dev I tried using esbuild's define option, but only valid identifiers are allowed, so these defines would cause an esbuild error because the identifiers end with .:

'process.env.': `({}).`,
'global.process.env.': `({}).`,
'globalThis.process.env.': `({}).`

'import.meta.env.': `({}).`,

This is the error I'm seeing:

vite v3.2.0-beta.3 building for production...
✓ 16 modules transformed.
[vite:esbuild-transpile] Transform failed with 8 errors:
error: The define key "globalThis.process.env." contains invalid identifier ""
error: Invalid define value (must be an entity name or valid JSON syntax): ({}).
error: The define key "import.meta.env." contains invalid identifier ""
error: Invalid define value (must be an entity name or valid JSON syntax): ({}).
error: The define key "process.env." contains invalid identifier ""
...

The define key "globalThis.process.env." contains invalid identifier ""

Invalid define value (must be an entity name or valid JSON syntax): ({}).

The define key "import.meta.env." contains invalid identifier ""

Invalid define value (must be an entity name or valid JSON syntax): ({}).

The define key "process.env." contains invalid identifier ""

Invalid define value (must be an entity name or valid JSON syntax): ({}).

The define key "global.process.env." contains invalid identifier ""

Invalid define value (must be an entity name or valid JSON syntax): ({}).

error during build:
Error: Transform failed with 8 errors:
error: The define key "globalThis.process.env." contains invalid identifier ""
error: Invalid define value (must be an entity name or valid JSON syntax): ({}).
error: The define key "import.meta.env." contains invalid identifier ""
error: Invalid define value (must be an entity name or valid JSON syntax): ({}).
error: The define key "process.env." contains invalid identifier ""
...
    at failureErrorWithLog (/Users/tony/src/vite/node_modules/.pnpm/esbuild@0.15.9/node_modules/esbuild/lib/main.js:1566:15)
    at /Users/tony/src/vite/node_modules/.pnpm/esbuild@0.15.9/node_modules/esbuild/lib/main.js:805:29
    at responseCallbacks.<computed> (/Users/tony/src/vite/node_modules/.pnpm/esbuild@0.15.9/node_modules/esbuild/lib/main.js:671:9)
    at handleIncomingPacket (/Users/tony/src/vite/node_modules/.pnpm/esbuild@0.15.9/node_modules/esbuild/lib/main.js:726:9)
    at Socket.readFromStdout (/Users/tony/src/vite/node_modules/.pnpm/esbuild@0.15.9/node_modules/esbuild/lib/main.js:647:7)
    at Socket.emit (node:events:513:28)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at Socket.Readable.push (node:internal/streams/readable:228:10)
    at Pipe.onStreamRead (node:internal/stream_base_commons:190:23)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I could workaround the error by removing those "invalid identifiers" from esbuild's config, and then just use our own string replacement mechanism (with strip-literal) to handle those. This hybrid solution would still require scanning the code (more than once), so it's still suboptimal.

@patak-dev
Copy link
Member

@patak-dev I tried using esbuild's define option, but only valid identifiers are allowed, so these defines would cause an esbuild error because the identifiers end with .:

I think we were using process.env. to avoid false positives with the current regex matching. We could go with the same rules as esbuild in Vite 4. For 4.0, we could still automatically remove the . and issue a warning that this is no longer valid and the extra dot needs to be removed, then in ~4.2 we do a hard switch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p3-significant High priority enhancement (priority)
Projects
Archived in project
9 participants