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): handle replacement with esbuild #11151

Merged
merged 18 commits into from Oct 26, 2023
Merged

feat(define): handle replacement with esbuild #11151

merged 18 commits into from Oct 26, 2023

Conversation

bluwy
Copy link
Member

@bluwy bluwy commented Dec 2, 2022

Description

Use esbuild to handle define replacements

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

supersedes and closes #9791


define replacements are now replaced with esbuild. This matches the semantics of define vs replace, where define replace identifiers with a certain value (AST-informed), and replace does a simple find-and-replace (not AST-informed).

This may break if you specify define keys that are not single identifiers, and values that doesn't match esbuild's requirements. This former restriction is a breaking change, but the latter restriction is already documented today. We also (used to) pass define to esbuild for the optimizer too.

To be consistent with esbuild behavior, expressions must either be a JSON object (null, boolean, number, string, array, or object) or a single identifier.

Configurations that didn't follow this rule before used to indirectly work (especially in SSR), and they will not work with this PR. If they still need the previous behaviour, they can use https://www.npmjs.com/package/@rollup/plugin-replace that does replace instead.

If you've hit with parsing errors because you wrote import.meta.env.* in markdown before, this will now fix it.

Additional context

Tests are mostly copied from #9791 (Thanks Tony 😬)


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.

bluwy and others added 2 commits December 2, 2022 16:19
@bluwy bluwy added the p3-significant High priority enhancement (priority) label Dec 2, 2022
expect(await transform('const version = __APP_VERSION__;')).toBe(
'const version = "1.0";'
'const version = "1.0";\n'
Copy link
Member Author

Choose a reason for hiding this comment

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

Esbuild transform tend to semi-format the entire code so this is changed here. That also means we can't do stable sourcemaps transform.

Comment on lines 52 to 51
'import.meta.env.': `({}).`,
'import.meta.env': JSON.stringify(config.env),
Copy link
Member Author

Choose a reason for hiding this comment

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

This change is fine as esbuild will share a single reference to import.meta.env object as a variable, so it shouldn't increase the bundle size by much after bundling

Copy link
Member Author

Choose a reason for hiding this comment

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

Update: not exactly fine, as in the bundle size will technically increase, but multiple usages of import.meta.env should be deduplicated now. There isn't a way to fix the former with esbuild define restriction

Comment on lines 47 to 52
expect(await page.textContent('.import-meta-env-undefined')).toBe(
isBuild ? '({}).UNDEFINED' : 'import.meta.env.UNDEFINED'
)
expect(await page.textContent('.process-env-undefined')).toBe(
isBuild ? '({}).UNDEFINED' : 'process.env.UNDEFINED'
)
Copy link
Member Author

Choose a reason for hiding this comment

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

This test isn't relevant for esbuild transform anymore.

@bluwy
Copy link
Member Author

bluwy commented Dec 2, 2022

So close 😢 Turns out esbuild strips out comments by default and there's no way to prevent that. That means /* @vite-ignore */ get strips out which makes the worker tests fail.

This currently also happens when the user uses a TypeScript file instead as esbuild transpiles it. Interestingly comments within import(/* @vite-ignore */) are not stripped.

Blocked by evanw/esbuild#221

@bluwy
Copy link
Member Author

bluwy commented Jan 6, 2023

I realize this is a breaking change as define config not supported by esbuild, e.g. "foo.": "foo.bar.", will not work with this PR. That means we might have to implement another round of replacements, or break this intentionally. I'm slightly leaning towards the former though.

Setting this aside for now as the complexity is growing.

@patak-dev
Copy link
Member

Adding this one to the Vite 5 milestone, if we move forward with it I think we should break compat. @bluwy maybe we could start issuing a warning for these cases in 4.3 or 4.4 to give time for folks to update their configs?

@xrado
Copy link

xrado commented Sep 13, 2023

is this going into v5 ?

@bluwy bluwy marked this pull request as ready for review October 12, 2023 13:31
@@ -38,7 +38,6 @@ export default defineConfig({
'import.meta.env.VITE_NUMBER': 5173,
'import.meta.env.VITE_STRING': JSON.stringify('string'),
'import.meta.env.VITE_OBJECT_STRING': '{ "foo": "bar" }',
'import.meta.env.VITE_TEMPLATE_LITERAL': '`template literal`',
Copy link
Member Author

Choose a reason for hiding this comment

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

Removed this handling as it seems that this isn't a valid specifier for esbuild. We also documented this in the docs:

To be consistent with esbuild behavior, expressions must either be a JSON object (null, boolean, number, string, array, or object) or a single identifier.

This was added in #13425

@bluwy

This comment was marked as outdated.

@vite-ecosystem-ci

This comment was marked as outdated.

@bluwy
Copy link
Member Author

bluwy commented Oct 23, 2023

/ecosystem-ci run

@vite-ecosystem-ci
Copy link

📝 Ran ecosystem CI on 9d1772d: Open

suite result latest scheduled
analogjs success success
astro success success
histoire success success
ladle success success
laravel failure failure
marko failure failure
nuxt failure success
nx failure failure
previewjs success success
qwik success success
rakkas failure success
sveltekit failure failure
unocss success success
vike failure failure
vite-plugin-pwa failure failure
vite-plugin-react success success
vite-plugin-react-pages failure success
vite-plugin-react-swc success success
vite-plugin-svelte failure success
vite-plugin-vue failure success
vite-setup-catalogue success success
vitepress success success
vitest failure failure

docs/guide/migration.md Outdated Show resolved Hide resolved
Comment on lines +157 to +160
// may contain values with raw identifiers, making it non-JSON-serializable,
// we replace it with a temporary marker and then replace it back after to
// workaround it. This means that esbuild is unable to optimize the `import.meta.env`
// access, but that's a tradeoff for now.
Copy link
Member

Choose a reason for hiding this comment

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

What is an example of may contain values with raw identifiers?
What is the cost of esbuild is unable to optimize?

Copy link
Member Author

Choose a reason for hiding this comment

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

What is an example of may contain values with raw identifiers?

We have this in plugin-legacy:

'import.meta.env.LEGACY':
env.command === 'serve' || config.build?.ssr
? false
: legacyEnvVarMarker,

Where legacyEnvVarMarker is an __VITE_IS_LEGACY__ identifier to be replaced after transform in renderChunk. That means when we create a define for import.meta.env, the value is { ..., LEGACY: __VITE_IS_LEGACY__ } which is not valid JSON for esbuild. So we need to work-around it here.

What is the cost of esbuild is unable to optimize?

Here's an esbuild example. As you can notice, replacing with a JSON-object vs an identifier, JSON-objects can be deduplicated as a single variable. And perhaps among future optimizations where esbuild could treeshake object key access (now it doesn't).

Copy link
Member

Choose a reason for hiding this comment

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

And you think this would be to breaking to limit Vite define to the esbuild one?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure I understand. We would have to continue making our plugins work, and the issue is only specific to import.meta.env, so a workaround is needed for import.meta.env specifically. The rest user-facing define rules still need to follow esbuild's rule.

@antfu
Copy link
Member

antfu commented Oct 24, 2023

I wonder if we could have both resolve.define and resolve.replace, where resolve.replace would be using @rollup/plugin-replace the same as the previous behavior. I think text replace is still a common need for a bundler to have (in docs we could encourage the use of define)

@bluwy
Copy link
Member Author

bluwy commented Oct 24, 2023

Yeah I've also thought about that, but I think we could hold it for later if there's a common need for it. So far I've not seen many frameworks needing to use replace, and for users define should be good enough.

@bluwy
Copy link
Member Author

bluwy commented Oct 24, 2023

Something is wrong with the macos test. It's consistently failing with a RollupError: Cannot call "addWatchFile" after the build has finished. error somehow. Probably best to hold a while before merging.

@bluwy
Copy link
Member Author

bluwy commented Oct 25, 2023

Update: The CI fail is unrelated and fixed at #14751

Copy link
Member

@patak-dev patak-dev left a comment

Choose a reason for hiding this comment

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

I think it is a good idea to merge this one soon if we want it in Vite 5 to get more time to test it

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