Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: vitejs/rolldown-vite
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: vitejs/rolldown-vite
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: rolldown-v6
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.

Commits on Mar 3, 2025

  1. feat: use rolldown in the dep optimizer

    Co-authored-by: underfin <likui.underfin@gmail.com>
    sapphi-red and underfin committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    43a0867 View commit details
  2. feat: use rolldown for build

    Co-authored-by: underfin <likui.underfin@gmail.com>
    sapphi-red and underfin committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    a3fe122 View commit details
  3. chore: skip plugin-legacy build

    Co-authored-by: underfin <likui.underfin@gmail.com>
    sapphi-red and underfin committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    7f4463b View commit details
  4. chore: skip typecheck for now

    Co-authored-by: underfin <likui.underfin@gmail.com>
    sapphi-red and underfin committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    f71fd52 View commit details
  5. Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    eaefc7f View commit details
  6. feat: add experimental.enableNativePlugin (#41)

    2cf0eb2 fe71cbc
    
    Co-authored-by: IWANABETHATGUY <iwanabethatguy@qq.com>
    IWANABETHATGUY authored and sapphi-red committed Mar 3, 2025

    Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    ab4a853 View commit details
  7. Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    f332074 View commit details
  8. Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    2b00a1e View commit details
  9. Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    632754e View commit details
  10. Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    e46cd0e View commit details
  11. feat: support native define

    Co-authored-by: IWANABETHATGUY <iwanabethatguy@qq.com>
    sapphi-red and IWANABETHATGUY committed Mar 3, 2025

    Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    23cdb63 View commit details
  12. feat: use filter for plugins (#49) (#50) (#51) (#52) (#53)

    Co-authored-by: IWANABETHATGUY <iwanabethatguy@qq.com>
    sapphi-red and IWANABETHATGUY committed Mar 3, 2025

    Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    c702f87 View commit details
  13. Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    965adf0 View commit details
  14. chore: bump rolldown

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    a12ad02 View commit details
  15. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    f9df06f View commit details
  16. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    4e8b874 View commit details
  17. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    917ee43 View commit details
  18. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    f59ea65 View commit details
  19. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    fafbd68 View commit details
  20. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    3d12009 View commit details
  21. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    32fe623 View commit details
  22. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    6b7c1a6 View commit details
  23. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    2d624e1 View commit details
  24. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    158c2e7 View commit details
  25. feat: oxc tranformer (#60)

    Co-authored-by: IWANABETHATGUY <iwanabethatguy@qq.com>
    2 people authored and sapphi-red committed Mar 3, 2025

    Partially verified

    This commit is signed with the committer’s verified signature.
    sapphi-red’s contribution has been verified via GPG key.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    b2cac64 View commit details
  26. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    bb0814d View commit details
  27. chore: enable continuous release under rolldown-vite (#61)

    chore: revert package name change + use non-compact continuous release URLs
    
    chore: update lockfile
    yyx990803 authored and sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    f0a94ae View commit details
  28. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    d1a9320 View commit details
  29. feat: export transformWithOxc (#62)

    underfin authored and sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    78ecbb5 View commit details
  30. feat: add oxc jsxInclude and jsxExclude (#63)

    Co-authored-by: 翠 / green <green@sapphi.red>
    underfin and sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    a6ad54c View commit details
  31. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    189f697 View commit details
  32. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    9e84e43 View commit details
  33. chore: bump rolldown

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    cb0c0cb View commit details
  34. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    1bfef08 View commit details
  35. chore: fix type errors

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    1d82e0f View commit details
  36. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    bc8b43e View commit details
  37. chore: tweak tests

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    1934b36 View commit details
  38. chore: bump rolldown

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    cbfbc57 View commit details
  39. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    ae1cfbf View commit details
  40. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    8d98006 View commit details
  41. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    b3fa0f7 View commit details
  42. chore: fix LICENSE

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    918e169 View commit details
  43. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    9255bc3 View commit details
  44. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    a102494 View commit details
  45. chore: reduce diff

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    42e45aa View commit details
  46. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    0a5c1b0 View commit details
  47. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    56f0967 View commit details
  48. chore: add comment

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    7961722 View commit details
  49. Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    b9f60b1 View commit details
  50. chore: bump rolldown

    sapphi-red committed Mar 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    sapphi-red 翠 / green
    Copy the full SHA
    4db4558 View commit details
Showing with 4,913 additions and 2,720 deletions.
  1. +2 −2 .github/workflows/ci.yml
  2. +3 −3 .github/workflows/preview-release.yml
  3. +1 −1 docs/_data/blog.data.ts
  4. +1 −0 eslint.config.js
  5. +13 −0 justfile
  6. +0 −1 packages/plugin-legacy/package.json
  7. +33 −44 packages/vite/LICENSE.md
  8. +1 −0 packages/vite/index.cjs
  9. +11 −8 packages/vite/package.json
  10. +3 −1 packages/vite/rollup.config.ts
  11. +11 −11 packages/vite/rollup.dts.config.ts
  12. +7 −6 packages/vite/src/node/__tests__/build.spec.ts
  13. +2 −2 packages/vite/src/node/__tests__/environment.spec.ts
  14. +3 −4 packages/vite/src/node/__tests__/plugins/assetImportMetaUrl.spec.ts
  15. +4 −3 packages/vite/src/node/__tests__/plugins/css.spec.ts
  16. +1 −1 packages/vite/src/node/__tests__/plugins/define.spec.ts
  17. +7 −3 packages/vite/src/node/__tests__/plugins/import.spec.ts
  18. +27 −35 ...src/node/__tests__/plugins/modulePreloadPolyfill/__snapshots__/modulePreloadPolyfill.spec.ts.snap
  19. +1 −1 packages/vite/src/node/__tests__/plugins/modulePreloadPolyfill/modulePreloadPolyfill.spec.ts
  20. +2 −2 packages/vite/src/node/__tests__/plugins/terser.spec.ts
  21. +23 −21 packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts
  22. +1 −1 packages/vite/src/node/__tests_dts__/plugin.ts
  23. +221 −107 packages/vite/src/node/build.ts
  24. +302 −88 packages/vite/src/node/config.ts
  25. +5 −5 packages/vite/src/node/constants.ts
  26. +33 −13 packages/vite/src/node/idResolver.ts
  27. +5 −3 packages/vite/src/node/index.ts
  28. +1 −1 packages/vite/src/node/logger.ts
  29. +0 −347 packages/vite/src/node/optimizer/esbuildDepPlugin.ts
  30. +156 −177 packages/vite/src/node/optimizer/index.ts
  31. +291 −0 packages/vite/src/node/optimizer/pluginConverter.ts
  32. +363 −0 packages/vite/src/node/optimizer/rolldownDepPlugin.ts
  33. +294 −360 packages/vite/src/node/optimizer/scan.ts
  34. +17 −4 packages/vite/src/node/plugin.ts
  35. +8 −4 packages/vite/src/node/plugins/asset.ts
  36. +116 −105 packages/vite/src/node/plugins/assetImportMetaUrl.ts
  37. +11 −11 packages/vite/src/node/plugins/completeSystemWrap.ts
  38. +309 −247 packages/vite/src/node/plugins/css.ts
  39. +21 −35 packages/vite/src/node/plugins/define.ts
  40. +1 −1 packages/vite/src/node/plugins/dynamicImportVars.ts
  41. +25 −19 packages/vite/src/node/plugins/esbuild.ts
  42. +390 −357 packages/vite/src/node/plugins/html.ts
  43. +13 −7 packages/vite/src/node/plugins/importAnalysis.ts
  44. +78 −32 packages/vite/src/node/plugins/importAnalysisBuild.ts
  45. +3 −2 packages/vite/src/node/plugins/importMetaGlob.ts
  46. +96 −27 packages/vite/src/node/plugins/index.ts
  47. +3 −0 packages/vite/src/node/plugins/json.ts
  48. +21 −13 packages/vite/src/node/plugins/loadFallback.ts
  49. +2 −1 packages/vite/src/node/plugins/manifest.ts
  50. +0 −18 packages/vite/src/node/plugins/metadata.ts
  51. +665 −0 packages/vite/src/node/plugins/oxc.ts
  52. +1 −1 packages/vite/src/node/plugins/reporter.ts
  53. +347 −21 packages/vite/src/node/plugins/resolve.ts
  54. +105 −105 packages/vite/src/node/plugins/splitVendorChunk.ts
  55. +195 −154 packages/vite/src/node/plugins/worker.ts
  56. +88 −78 packages/vite/src/node/plugins/workerImportMetaUrl.ts
  57. +2 −1 packages/vite/src/node/publicUtils.ts
  58. +1 −1 packages/vite/src/node/server/hmr.ts
  59. +1 −1 packages/vite/src/node/server/index.ts
  60. +1 −1 packages/vite/src/node/server/middlewares/error.ts
  61. +1 −1 packages/vite/src/node/server/middlewares/indexHtml.ts
  62. +1 −1 packages/vite/src/node/server/middlewares/transform.ts
  63. +1 −1 packages/vite/src/node/server/mixedModuleGraph.ts
  64. +1 −1 packages/vite/src/node/server/moduleGraph.ts
  65. +26 −9 packages/vite/src/node/server/pluginContainer.ts
  66. +2 −2 packages/vite/src/node/server/send.ts
  67. +26 −23 packages/vite/src/node/server/sourcemap.ts
  68. +1 −1 packages/vite/src/node/server/transformRequest.ts
  69. +16 −7 packages/vite/src/node/server/ws.ts
  70. +3 −1 packages/vite/src/node/ssr/__tests__/ssrLoadModule.spec.ts
  71. +1 −1 packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
  72. +1 −1 packages/vite/src/node/ssr/ssrManifestPlugin.ts
  73. +5 −5 packages/vite/src/node/ssr/ssrTransform.ts
  74. +1 −1 packages/vite/src/node/typeUtils.ts
  75. +7 −4 packages/vite/src/node/utils.ts
  76. +1 −1 packages/vite/src/node/watch.ts
  77. +3 −3 packages/vite/src/types/alias.d.ts
  78. +28 −0 packages/vite/types/internal/esbuildOptions.d.ts
  79. +5 −1 packages/vite/types/metadata.d.ts
  80. +2 −1 playground/assets/__tests__/assets.spec.ts
  81. +2 −1 playground/assets/index.html
  82. +6 −3 playground/backend-integration/__tests__/backend-integration.spec.ts
  83. +2 −2 playground/css-codesplit/__tests__/css-codesplit-consistent.spec.ts
  84. +1 −1 playground/css-codesplit/__tests__/css-codesplit.spec.ts
  85. +21 −5 playground/css-codesplit/vite.config.js
  86. +5 −5 playground/css/__tests__/css.spec.ts
  87. +2 −0 playground/dynamic-import/__tests__/dynamic-import.spec.ts
  88. +1 −0 playground/external/src/main.js
  89. +7 −0 playground/external/src/require-polyfill.js
  90. +7 −6 playground/js-sourcemap/__tests__/js-sourcemap.spec.ts
  91. +34 −13 playground/js-sourcemap/vite.config.js
  92. +5 −5 playground/lib/__tests__/lib.spec.ts
  93. +1 −1 playground/minify/__tests__/minify.spec.ts
  94. +3 −0 playground/minify/vite.config.js
  95. +2 −1 playground/optimize-deps/dep-incompatible/index.js
  96. +11 −35 playground/optimize-deps/vite.config.js
  97. +4 −2 playground/resolve/__tests__/resolve.spec.ts
  98. +2 −3 playground/resolve/browser-field/relative.js
  99. +10 −8 playground/resolve/index.html
  100. +1 −1 playground/tsconfig-json-load-error/__tests__/tsconfig-json-load-error.spec.ts
  101. +10 −8 playground/vitestSetup.ts
  102. +8 −8 playground/worker/__tests__/es/worker-es.spec.ts
  103. +7 −5 playground/worker/__tests__/iife/worker-iife.spec.ts
  104. +4 −4 playground/worker/__tests__/relative-base/worker-relative-base.spec.ts
  105. +7 −7 playground/worker/__tests__/sourcemap-hidden/worker-sourcemap-hidden.spec.ts
  106. +7 −7 playground/worker/__tests__/sourcemap-inline/worker-sourcemap-inline.spec.ts
  107. +7 −7 playground/worker/__tests__/sourcemap/worker-sourcemap.spec.ts
  108. +4 −4 playground/worker/vite.config-es.js
  109. +4 −4 playground/worker/vite.config-iife.js
  110. +234 −10 pnpm-lock.yaml
  111. +14 −2 vitest.config.e2e.ts
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -173,8 +173,8 @@ jobs:
- name: Check formatting
run: pnpm prettier --write --log-level=warn . && git diff --exit-code

- name: Typecheck
run: pnpm run typecheck
# - name: Typecheck
# run: pnpm run typecheck

- name: Test docs
run: pnpm run test-docs
6 changes: 3 additions & 3 deletions .github/workflows/preview-release.yml
Original file line number Diff line number Diff line change
@@ -10,14 +10,14 @@ permissions:
on:
push:
branches:
- main
- rolldown-v6
pull_request:
types: [opened, synchronize, labeled]

jobs:
preview:
if: >
github.repository == 'vitejs/vite' &&
github.repository == 'rolldown/vite' &&
(github.event_name == 'push' ||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'trigger: preview')))
runs-on: ubuntu-latest
@@ -35,4 +35,4 @@ jobs:
working-directory: ./packages/vite
run: pnpm build

- run: pnpm dlx pkg-pr-new@0.0 publish --compact --pnpm ./packages/vite
- run: pnpm dlx pkg-pr-new@0.0 publish --pnpm ./packages/vite
2 changes: 1 addition & 1 deletion docs/_data/blog.data.ts
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ interface Post {
}

declare const data: Post[]
export { data }
export { type data }

export default createContentLoader('blog/*.md', {
// excerpt: true,
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -91,6 +91,7 @@ export default tseslint.config(
{
allowModules: [
'vite',
'esbuild',
'less',
'sass',
'sass-embedded',
13 changes: 13 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
build-vite:
pnpm --filter vite run build-bundle

test-serve:
pnpm run test-serve

test-build:
pnpm run test-build

test: test-serve test-build

fmt:
pnpm --filter vite run format
1 change: 0 additions & 1 deletion packages/plugin-legacy/package.json
Original file line number Diff line number Diff line change
@@ -24,7 +24,6 @@
},
"scripts": {
"dev": "unbuild --stub",
"build": "unbuild && pnpm run patch-cjs",
"patch-cjs": "tsx ../../scripts/patchCJS.ts",
"prepublishOnly": "npm run build"
},
77 changes: 33 additions & 44 deletions packages/vite/LICENSE.md
Original file line number Diff line number Diff line change
@@ -360,15 +360,11 @@ Repository: lukeed/polka

---------------------------------------

## @rollup/plugin-alias, @rollup/plugin-commonjs, @rollup/plugin-dynamic-import-vars, @rollup/pluginutils
## @rollup/plugin-alias, @rollup/plugin-dynamic-import-vars, @rollup/pluginutils
License: MIT
By: Johannes Stein
Repository: rollup/plugins

License: MIT
By: Rich Harris
Repository: rollup/plugins

License: MIT
By: LarsDenBakker
Repository: rollup/plugins
@@ -584,38 +580,6 @@ Repository: git+https://github.com/paulmillr/chokidar.git
---------------------------------------

## commondir, shell-quote
License: MIT
By: James Halliday
Repositories: http://github.com/substack/node-commondir.git, http://github.com/ljharb/shell-quote.git

> The MIT License
>
> Copyright (c) 2013 James Halliday (mail@substack.net)
>
> Permission is hereby granted, free of charge,
> to any person obtaining a copy of this software and
> associated documentation files (the "Software"), to
> deal in the Software without restriction, including
> without limitation the rights to use, copy, modify,
> merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom
> the Software is furnished to do so,
> subject to the following conditions:
>
> The above copyright notice and this permission notice
> shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------

## connect
License: MIT
By: TJ Holowaychuk, Douglas Christopher Wilson, Jonathan Ong, Tim Caswell
@@ -1298,13 +1262,6 @@ Repository: micromatch/is-glob
---------------------------------------

## is-reference
License: MIT
By: Rich Harris
Repository: git+https://github.com/Rich-Harris/is-reference.git

---------------------------------------

## isexe, which
License: ISC
By: Isaac Z. Schlueter
@@ -2147,6 +2104,38 @@ Repository: kevva/shebang-command
---------------------------------------

## shell-quote
License: MIT
By: James Halliday
Repository: http://github.com/ljharb/shell-quote.git

> The MIT License
>
> Copyright (c) 2013 James Halliday (mail@substack.net)
>
> Permission is hereby granted, free of charge,
> to any person obtaining a copy of this software and
> associated documentation files (the "Software"), to
> deal in the Software without restriction, including
> without limitation the rights to use, copy, modify,
> merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom
> the Software is furnished to do so,
> subject to the following conditions:
>
> The above copyright notice and this permission notice
> shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------

## sirv
License: MIT
By: Luke Edwards
1 change: 1 addition & 0 deletions packages/vite/index.cjs
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ const asyncFunctions = [
'createServer',
'preview',
'transformWithEsbuild',
'transformWithOxc',
'resolveConfig',
'optimizeDeps',
'formatPostcssSourceMap',
19 changes: 11 additions & 8 deletions packages/vite/package.json
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"type": "module",
"license": "MIT",
"author": "Evan You",
"description": "Native-ESM powered web dev build tool",
"description": "Vite on Rolldown preview",
"bin": {
"vite": "bin/vite.js"
},
@@ -85,9 +85,10 @@
},
"//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!",
"dependencies": {
"esbuild": "^0.25.0",
"@oxc-project/runtime": "^0.53.0",
"lightningcss": "^1.29.1",
"postcss": "^8.5.3",
"rollup": "^4.30.1"
"rolldown": "1.0.0-beta.3-commit.62fba31"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
@@ -96,6 +97,7 @@
"@ampproject/remapping": "^2.3.0",
"@babel/parser": "^7.26.9",
"@jridgewell/trace-mapping": "^0.3.25",
"@oxc-project/types": "^0.53.0",
"@polka/compression": "^1.0.0-next.25",
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-commonjs": "^28.0.2",
@@ -117,12 +119,12 @@
"dotenv": "^16.4.7",
"dotenv-expand": "^12.0.1",
"es-module-lexer": "^1.6.0",
"esbuild": "^0.24.2",
"escape-html": "^1.0.3",
"estree-walker": "^3.0.3",
"etag": "^1.8.1",
"http-proxy": "^1.18.1",
"launch-editor-middleware": "^2.10.0",
"lightningcss": "^1.29.1",
"magic-string": "^0.30.17",
"mlly": "^1.7.4",
"mrmime": "^2.0.1",
@@ -137,6 +139,7 @@
"postcss-load-config": "^6.0.1",
"postcss-modules": "^6.0.1",
"resolve.exports": "^2.0.3",
"rollup": "^4.23.0",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-esbuild": "^6.2.1",
"rollup-plugin-license": "^3.6.0",
@@ -155,9 +158,9 @@
},
"peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"esbuild": "^0.25.0",
"jiti": ">=1.21.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
@@ -170,6 +173,9 @@
"@types/node": {
"optional": true
},
"esbuild": {
"optional": true
},
"jiti": {
"optional": true
},
@@ -188,9 +194,6 @@
"sugarss": {
"optional": true
},
"lightningcss": {
"optional": true
},
"terser": {
"optional": true
},
4 changes: 3 additions & 1 deletion packages/vite/rollup.config.ts
Original file line number Diff line number Diff line change
@@ -111,6 +111,8 @@ const nodeConfig = defineConfig({
/^vite\//,
'fsevents',
'rollup/parseAst',
'rolldown/parseAst',
'rolldown/experimental',
/^tsx\//,
/^#/,
...Object.keys(pkg.dependencies),
@@ -190,7 +192,7 @@ const moduleRunnerConfig = defineConfig({
external: [
'fsevents',
'lightningcss',
'rollup/parseAst',
'rolldown/parseAst',
...Object.keys(pkg.dependencies),
],
plugins: [
22 changes: 11 additions & 11 deletions packages/vite/rollup.dts.config.ts
Original file line number Diff line number Diff line change
@@ -16,7 +16,8 @@ const pkg = JSON.parse(
const external = [
/^node:*/,
/^vite\//,
'rollup/parseAst',
'rolldown/parseAst',
'rolldown/experimental',
...Object.keys(pkg.dependencies),
...Object.keys(pkg.peerDependencies),
...Object.keys(pkg.devDependencies),
@@ -46,17 +47,16 @@ const identifierWithTrailingDollarRE = /\b(\w+)\$\d+\b/g
* the module that imports the identifer as a named import alias
*/
const identifierReplacements: Record<string, Record<string, string>> = {
rollup: {
Plugin$1: 'rollup.Plugin',
PluginContext$1: 'rollup.PluginContext',
MinimalPluginContext$1: 'rollup.MinimalPluginContext',
TransformPluginContext$1: 'rollup.TransformPluginContext',
TransformResult$1: 'rollup.TransformResult',
rolldown: {
Plugin$1: 'rolldown.Plugin',
PluginContext$1: 'rolldown.PluginContext',
MinimalPluginContext$1: 'rolldown.MinimalPluginContext',
TransformPluginContext$1: 'rolldown.TransformPluginContext',
TransformResult$1: 'rolldown.TransformResult',
},
esbuild: {
TransformResult$2: 'esbuild_TransformResult',
TransformOptions$1: 'esbuild_TransformOptions',
BuildOptions$1: 'esbuild_BuildOptions',
'rolldown/experimental': {
TransformOptions$1: 'rolldown_experimental_TransformOptions',
TransformResult$2: 'rolldown_experimental_TransformResult',
},
'node:https': {
Server$1: 'HttpsServer',
13 changes: 7 additions & 6 deletions packages/vite/src/node/__tests__/build.spec.ts
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import type {
RollupLog,
RollupOptions,
RollupOutput,
} from 'rollup'
} from 'rolldown'
import type { LibraryFormats, LibraryOptions } from '../build'
import {
build,
@@ -131,13 +131,13 @@ describe('build', () => {
{
"changed": [
"index",
"_foo",
"_bar",
"_foo",
"_baz.css",
],
"unchanged": [
"_foo.css",
"_bar.css",
"_foo.css",
"undefined",
],
}
@@ -861,11 +861,11 @@ test.for([true, false])(
([client, ssr, custom1, custom2] as RollupOutput[]).map(
(o) => o.output[0].code.split('\n').length,
),
).toEqual([2, 5, 2, 5])
).toEqual([1, 5, 1, 5])
},
)

test('adjust worker build error for worker.format', async () => {
test.skip('adjust worker build error for worker.format', async () => {
try {
await build({
root: resolve(__dirname, 'fixtures/worker-dynamic'),
@@ -886,7 +886,8 @@ test('adjust worker build error for worker.format', async () => {
expect.unreachable()
})

describe('onRollupLog', () => {
// rolldown does not append plugin name to the message automatically
describe.skip('onRollupLog', () => {
const pluginName = 'rollup-plugin-test'
const msgInfo = 'This is the INFO message.'
const msgWarn = 'This is the WARN message.'
4 changes: 2 additions & 2 deletions packages/vite/src/node/__tests__/environment.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path'
import { describe, expect, onTestFinished, test } from 'vitest'
import type { RollupOutput } from 'rollup'
import type { RolldownOutput } from 'rolldown'
import { createServer } from '../server'
import type { InlineConfig } from '../config'
import { createBuilder } from '../build'
@@ -167,7 +167,7 @@ describe('custom environment conditions', () => {
const results: Record<string, unknown> = {}
for (const key of ['ssr', 'worker', 'custom1', 'custom1_2']) {
const output = await builder.build(builder.environments[key])
const chunk = (output as RollupOutput).output[0]
const chunk = (output as RolldownOutput).output[0]
const mod = await import(
path.join(
import.meta.dirname,
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { describe, expect, test } from 'vitest'
import { parseAst } from 'rollup/parseAst'
import { assetImportMetaUrlPlugin } from '../../plugins/assetImportMetaUrl'
import { resolveConfig } from '../../config'
import { PartialEnvironment } from '../../baseEnvironment'
@@ -10,9 +9,9 @@ async function createAssetImportMetaurlPluginTransform() {
const environment = new PartialEnvironment('client', config)

return async (code: string) => {
// @ts-expect-error transform should exist
const result = await instance.transform.call(
{ environment, parse: parseAst },
// @ts-expect-error transform.handler should exist
const result = await instance.transform.handler.call(
{ environment },
code,
'foo.ts',
)
7 changes: 4 additions & 3 deletions packages/vite/src/node/__tests__/plugins/css.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, test } from 'vitest'
import type { Plugin } from 'rolldown'
import { resolveConfig } from '../../config'
import type { InlineConfig } from '../../config'
import {
@@ -229,15 +230,15 @@ async function createCssPluginTransform(inlineConfig: InlineConfig = {}) {
const config = await resolveConfig(inlineConfig, 'serve')
const environment = new PartialEnvironment('client', config)

const { transform, buildStart } = cssPlugin(config)
const { transform, buildStart } = cssPlugin(config) as Plugin

// @ts-expect-error buildStart is function
await buildStart.call({})

return {
async transform(code: string, id: string) {
// @ts-expect-error transform is function
return await transform.call(
// @ts-expect-error transform.handler is function
return await transform.handler.call(
{
addWatchFile() {
return
2 changes: 1 addition & 1 deletion packages/vite/src/node/__tests__/plugins/define.spec.ts
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ describe('definePlugin', () => {
// assert that the default behavior is to replace import.meta.hot with undefined
const transform = await createDefinePluginTransform()
expect(await transform('const hot = import.meta.hot;')).toBe(
'const hot = void 0;\n',
'const hot = undefined;\n',
)
// assert that we can specify a user define to preserve import.meta.hot
const overrideTransform = await createDefinePluginTransform({
10 changes: 7 additions & 3 deletions packages/vite/src/node/__tests__/plugins/import.spec.ts
Original file line number Diff line number Diff line change
@@ -73,9 +73,13 @@ describe('transformCjsImport', () => {
'',
config,
),
).toBe(
'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' +
`const react = ((m) => m?.__esModule ? m : { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m })(__vite__cjsImport0_react)`,
).toMatchInlineSnapshot(
`
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; const react = ((m) => m?.__esModule ? m : {
...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {},
default: m
})(__vite__cjsImport0_react)"
`,
)
})

Original file line number Diff line number Diff line change
@@ -7,41 +7,33 @@ exports[`load > doesn't load modulepreload polyfill when format is cjs 1`] = `

exports[`load > loads modulepreload polyfill 1`] = `
"(function polyfill() {
const relList = document.createElement("link").relList;
if (relList && relList.supports && relList.supports("modulepreload")) {
return;
}
for (const link of document.querySelectorAll('link[rel="modulepreload"]')) {
processPreload(link);
}
new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type !== "childList") {
continue;
}
for (const node of mutation.addedNodes) {
if (node.tagName === "LINK" && node.rel === "modulepreload")
processPreload(node);
}
}
}).observe(document, { childList: true, subtree: true });
function getFetchOpts(link) {
const fetchOpts = {};
if (link.integrity) fetchOpts.integrity = link.integrity;
if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
if (link.crossOrigin === "use-credentials")
fetchOpts.credentials = "include";
else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
else fetchOpts.credentials = "same-origin";
return fetchOpts;
}
function processPreload(link) {
if (link.ep)
return;
link.ep = true;
const fetchOpts = getFetchOpts(link);
fetch(link.href, fetchOpts);
}
const relList = document.createElement("link").relList;
if (relList && relList.supports && relList.supports("modulepreload")) return;
for (const link of document.querySelectorAll("link[rel=\\"modulepreload\\"]")) processPreload(link);
new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type !== "childList") continue;
for (const node of mutation.addedNodes) if (node.tagName === "LINK" && node.rel === "modulepreload") processPreload(node);
}
}).observe(document, {
childList: true,
subtree: true
});
function getFetchOpts(link) {
const fetchOpts = {};
if (link.integrity) fetchOpts.integrity = link.integrity;
if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include";
else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
else fetchOpts.credentials = "same-origin";
return fetchOpts;
}
function processPreload(link) {
if (link.ep) return;
link.ep = true;
const fetchOpts = getFetchOpts(link);
fetch(link.href, fetchOpts);
}
})();
"
`;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it } from 'vitest'
import type { ModuleFormat, RollupOutput } from 'rollup'
import type { ModuleFormat, RollupOutput } from 'rolldown'
import { build } from '../../../build'
import { modulePreloadPolyfillId } from '../../../plugins/modulePreloadPolyfill'

4 changes: 2 additions & 2 deletions packages/vite/src/node/__tests__/plugins/terser.spec.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, test } from 'vitest'
import { build } from 'vite'
import type { RollupOutput } from 'rollup'
import type { RolldownOutput } from 'rolldown'
import type { TerserOptions } from '../../plugins/terser'

const __dirname = resolve(fileURLToPath(import.meta.url), '..')
@@ -32,7 +32,7 @@ describe('terser', () => {
},
},
],
})) as RollupOutput
})) as RolldownOutput
return result.output[0].code
}

Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ async function createWorkerImportMetaUrlPluginTransform() {

return async (code: string) => {
// @ts-expect-error transform should exist
const result = await instance.transform.call(
const result = await instance.transform.handler.call(
{ environment, parse: parseAst },
code,
'foo.ts',
@@ -27,7 +27,7 @@ describe('workerImportMetaUrlPlugin', async () => {
expect(
await transform('new Worker(new URL("./worker.js", import.meta.url))'),
).toMatchInlineSnapshot(
`"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url))"`,
`"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url))"`,
)
})

@@ -37,7 +37,7 @@ describe('workerImportMetaUrlPlugin', async () => {
'new SharedWorker(new URL("./worker.js", import.meta.url))',
),
).toMatchInlineSnapshot(
`"new SharedWorker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url))"`,
`"new SharedWorker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url))"`,
)
})

@@ -47,7 +47,7 @@ describe('workerImportMetaUrlPlugin', async () => {
'new Worker(new URL("./worker.js", import.meta.url), { type: "module", name: "worker1" })',
),
).toMatchInlineSnapshot(
`"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { type: "module", name: "worker1" })"`,
`"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { type: "module", name: "worker1" })"`,
)
})

@@ -57,7 +57,7 @@ describe('workerImportMetaUrlPlugin', async () => {
'new Worker(new URL("./worker.js", import.meta.url), { "type": "module", "name": "worker1" })',
),
).toMatchInlineSnapshot(
`"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { "type": "module", "name": "worker1" })"`,
`"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { "type": "module", "name": "worker1" })"`,
)
})

@@ -67,7 +67,7 @@ describe('workerImportMetaUrlPlugin', async () => {
'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: "worker" + id })',
),
).toMatchInlineSnapshot(
`"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url), { name: "worker" + id })"`,
`"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url), { name: "worker" + id })"`,
)
})

@@ -77,7 +77,7 @@ describe('workerImportMetaUrlPlugin', async () => {
'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: `worker-${id}` })',
),
).toMatchInlineSnapshot(
`"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", import.meta.url), { name: \`worker-\${id}\` })"`,
`"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url), { name: \`worker-\${id}\` })"`,
)
})

@@ -87,7 +87,7 @@ describe('workerImportMetaUrlPlugin', async () => {
'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: "worker" + id, type: "module" })',
),
).toMatchInlineSnapshot(
`"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { name: "worker" + id, type: "module" })"`,
`"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { name: "worker" + id, type: "module" })"`,
)
})

@@ -97,7 +97,7 @@ describe('workerImportMetaUrlPlugin', async () => {
'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: `worker-${id}`, type: "module" })',
),
).toMatchInlineSnapshot(
`"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { name: \`worker-\${id}\`, type: "module" })"`,
`"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { name: \`worker-\${id}\`, type: "module" })"`,
)
})

@@ -107,7 +107,7 @@ describe('workerImportMetaUrlPlugin', async () => {
'const worker = new Worker(new URL("./worker.js", import.meta.url), { name: genName(), type: "module"})',
),
).toMatchInlineSnapshot(
`"const worker = new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), { name: genName(), type: "module"})"`,
`"const worker = new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { name: genName(), type: "module"})"`,
)
})

@@ -122,15 +122,17 @@ const worker = new Worker(new URL("./worker.js", import.meta.url), {
worker.addEventListener('message', (ev) => text('.simple-worker-url', JSON.stringify(ev.data)))
`),
).toMatchInlineSnapshot(`"
const worker = new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url), {
name: genName(),
type: "module",
},
)
).toMatchInlineSnapshot(`
"
const worker = new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), {
name: genName(),
type: "module",
},
)
worker.addEventListener('message', (ev) => text('.simple-worker-url', JSON.stringify(ev.data)))
"`)
worker.addEventListener('message', (ev) => text('.simple-worker-url', JSON.stringify(ev.data)))
"
`)
})

test('trailing comma', async () => {
@@ -145,7 +147,7 @@ new Worker(
`),
).toMatchInlineSnapshot(`"
new Worker(
new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", import.meta.url),
new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url),
{
type: 'module'
}, // },
@@ -207,14 +209,14 @@ new Worker(
`(() => { new Worker(new URL('./worker', import.meta.url)); repro({ test: "foo", }); })();`,
),
).toMatchInlineSnapshot(
`"(() => { new Worker(new URL(/* @vite-ignore */ "/worker?worker_file&type=classic", import.meta.url)); repro({ test: "foo", }); })();"`,
`"(() => { new Worker(new URL(/* @vite-ignore */ "/worker?worker_file&type=classic", '' + import.meta.url)); repro({ test: "foo", }); })();"`,
)
expect(
await transform(
`repro(new Worker(new URL('./worker', import.meta.url)), { type: "module" })`,
),
).toMatchInlineSnapshot(
`"repro(new Worker(new URL(/* @vite-ignore */ "/worker?worker_file&type=classic", import.meta.url)), { type: "module" })"`,
`"repro(new Worker(new URL(/* @vite-ignore */ "/worker?worker_file&type=classic", '' + import.meta.url)), { type: "module" })"`,
)
})
})
2 changes: 1 addition & 1 deletion packages/vite/src/node/__tests_dts__/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* This is a development only file for testing types.
*/
import type { Plugin as RollupPlugin } from 'rollup'
import type { Plugin as RollupPlugin } from 'rolldown'
import type { Equal, ExpectExtends, ExpectTrue } from '@type-challenges/utils'
import type { Plugin, PluginContextExtension } from '../plugin'
import type { ROLLUP_HOOKS } from '../constants'
328 changes: 221 additions & 107 deletions packages/vite/src/node/build.ts

Large diffs are not rendered by default.

390 changes: 302 additions & 88 deletions packages/vite/src/node/config.ts

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions packages/vite/src/node/constants.ts
Original file line number Diff line number Diff line change
@@ -19,10 +19,10 @@ export const ROLLUP_HOOKS = [
'banner',
'footer',
'augmentChunkHash',
'outputOptions',
'renderDynamicImport',
'resolveFileUrl',
'resolveImportMeta',
// 'outputOptions',
// 'renderDynamicImport',
// 'resolveFileUrl',
// 'resolveImportMeta',
'intro',
'outro',
'closeBundle',
@@ -32,7 +32,7 @@ export const ROLLUP_HOOKS = [
'watchChange',
'resolveDynamicImport',
'resolveId',
'shouldTransformCachedModule',
// 'shouldTransformCachedModule',
'transform',
'onLog',
] satisfies RollupPluginHooks[]
46 changes: 33 additions & 13 deletions packages/vite/src/node/idResolver.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { PartialResolvedId } from 'rollup'
import type { PartialResolvedId } from 'rolldown'
import aliasPlugin from '@rollup/plugin-alias'
import type { ResolvedConfig } from './config'
import type { EnvironmentPluginContainer } from './server/pluginContainer'
import { createEnvironmentPluginContainer } from './server/pluginContainer'
import { resolvePlugin } from './plugins/resolve'
import { oxcResolvePlugin, resolvePlugin } from './plugins/resolve'
import type { InternalResolveOptions } from './plugins/resolve'
import type { Environment } from './environment'
import type { PartialEnvironment } from './baseEnvironment'
import type { Plugin } from './plugin'

export type ResolveIdFn = (
environment: PartialEnvironment,
@@ -59,18 +60,36 @@ export function createIdResolver(
pluginContainer = await createEnvironmentPluginContainer(
environment as Environment,
[
// @ts-expect-error the aliasPlugin uses rollup types
aliasPlugin({ entries: environment.config.resolve.alias }),
resolvePlugin({
root: config.root,
isProduction: config.isProduction,
isBuild: config.command === 'build',
asSrc: true,
preferRelative: false,
tryIndex: true,
...options,
// Ignore sideEffects and other computations as we only need the id
idOnly: true,
}),
...(config.experimental.enableNativePlugin
? (oxcResolvePlugin(
{
root: config.root,
isProduction: config.isProduction,
isBuild: config.command === 'build',
asSrc: true,
preferRelative: false,
tryIndex: true,
...options,
// Ignore sideEffects and other computations as we only need the id
idOnly: true,
},
environment.config,
) as Plugin[])
: [
resolvePlugin({
root: config.root,
isProduction: config.isProduction,
isBuild: config.command === 'build',
asSrc: true,
preferRelative: false,
tryIndex: true,
...options,
// Ignore sideEffects and other computations as we only need the id
idOnly: true,
}),
]),
],
undefined,
false,
@@ -93,6 +112,7 @@ export function createIdResolver(
if (!pluginContainer) {
pluginContainer = await createEnvironmentPluginContainer(
environment as Environment,
// @ts-expect-error the aliasPlugin uses rollup types
[aliasPlugin({ entries: environment.config.resolve.alias })],
undefined,
false,
8 changes: 5 additions & 3 deletions packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type * as Rollup from 'rollup'
import type * as Rollup from 'rolldown'

export type { Rollup }
export { parseAst, parseAstAsync } from 'rollup/parseAst'
export { parseAst, parseAstAsync } from 'rolldown/parseAst'
export {
defineConfig,
loadConfigFromFile,
@@ -19,6 +19,7 @@ export { createIdResolver } from './idResolver'

export { formatPostcssSourceMap, preprocessCSS } from './plugins/css'
export { transformWithEsbuild } from './plugins/esbuild'
export { transformWithOxc } from './plugins/oxc'
export { buildErrorMessage } from './server/middlewares/error'

export {
@@ -137,7 +138,8 @@ export type {
StylusPreprocessorOptions,
} from './plugins/css'
export type { JsonOptions } from './plugins/json'
export type { TransformOptions as EsbuildTransformOptions } from 'esbuild'
export type { EsbuildTransformOptions } from 'types/internal/esbuildOptions'
export type { OxcOptions } from './plugins/oxc'
export type { ESBuildOptions, ESBuildTransformResult } from './plugins/esbuild'
export type { Manifest, ManifestChunk } from './plugins/manifest'
export type { ResolveOptions, InternalResolveOptions } from './plugins/resolve'
2 changes: 1 addition & 1 deletion packages/vite/src/node/logger.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

import readline from 'node:readline'
import colors from 'picocolors'
import type { RollupError } from 'rollup'
import type { RollupError } from 'rolldown'
import type { ResolvedServerUrls } from './server'

export type LogType = 'error' | 'warn' | 'info'
347 changes: 0 additions & 347 deletions packages/vite/src/node/optimizer/esbuildDepPlugin.ts

This file was deleted.

333 changes: 156 additions & 177 deletions packages/vite/src/node/optimizer/index.ts

Large diffs are not rendered by default.

291 changes: 291 additions & 0 deletions packages/vite/src/node/optimizer/pluginConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import { dirname } from 'node:path'
import type * as esbuild from 'esbuild'
import type {
ImportKind,
LoadResult,
PluginContext,
ResolveIdResult,
Plugin as RolldownPlugin,
RolldownPluginOption,
} from 'rolldown'

type MaybePromise<T> = T | Promise<T>
type EsbuildOnResolveCallback = (
args: esbuild.OnResolveArgs,
) => MaybePromise<esbuild.OnResolveResult | null | undefined>
type EsbuildOnLoadCallback = (
args: esbuild.OnLoadArgs,
) => MaybePromise<esbuild.OnLoadResult | null | undefined>
type ResolveIdHandler = (
this: PluginContext,
id: string,
importer: string | undefined,
opts: { kind: ImportKind },
) => MaybePromise<ResolveIdResult>
type LoadHandler = (this: PluginContext, id: string) => MaybePromise<LoadResult>

export function convertEsbuildPluginToRolldownPlugin(
esbuildPlugin: esbuild.Plugin,
): RolldownPlugin {
const onStartCallbacks: Array<() => void> = []
const onEndCallbacks: Array<(buildResult: esbuild.BuildResult) => void> = []
const onDisposeCallbacks: Array<() => void> = []
let resolveIdHandlers: ResolveIdHandler[]
let loadHandlers: LoadHandler[]

let isSetupDone = false
const setup = async (
plugins: RolldownPluginOption[],
platform: 'browser' | 'node' | 'neutral',
) => {
const onResolveCallbacks: Array<
[options: esbuild.OnResolveOptions, callback: EsbuildOnResolveCallback]
> = []
const onLoadCallbacks: Array<
[options: esbuild.OnLoadOptions, callback: EsbuildOnLoadCallback]
> = []

const pluginBuild: esbuild.PluginBuild = {
initialOptions: new Proxy(
{
platform,
plugins:
plugins?.flatMap((p) =>
p && 'name' in p
? [
{
name: p.name,
// eslint-disable-next-line @typescript-eslint/no-empty-function
setup() {},
},
]
: [],
) ?? [],
},
{
get(target, p, _receiver) {
if (p in target) return (target as any)[p]
throw new Error('Not implemented')
},
},
) as esbuild.BuildOptions,
resolve() {
throw new Error('Not implemented')
},
onStart(callback) {
onStartCallbacks.push(callback)
},
onEnd(callback) {
onEndCallbacks.push(callback)
},
onResolve(options, callback) {
onResolveCallbacks.push([options, callback])
},
onLoad(options, callback) {
onLoadCallbacks.push([options, callback])
},
onDispose(callback) {
onDisposeCallbacks.push(callback)
},
get esbuild(): esbuild.PluginBuild['esbuild'] {
throw new Error('Not implemented')
},
set esbuild(_: unknown) {
throw new Error('Not implemented')
},
}

await esbuildPlugin.setup(pluginBuild)

resolveIdHandlers = onResolveCallbacks.map(([options, callback]) =>
createResolveIdHandler(options, callback),
)
loadHandlers = onLoadCallbacks.map(([options, callback]) =>
createLoadHandler(options, callback),
)
isSetupDone = true
}

return {
name: esbuildPlugin.name,
async options(inputOptions) {
await setup(
inputOptions.plugins as RolldownPluginOption[],
inputOptions.platform ?? 'node',
)
},
async buildStart(inputOptions) {
// options hook is not called for scanner
if (!isSetupDone) {
// inputOptions.plugins is not available for buildStart hook
// put a dummy plugin to tell that this is a scan
await setup(
[{ name: 'vite:dep-scan' }],
inputOptions.platform ?? 'node',
)
}

for (const cb of onStartCallbacks) {
cb()
}
},
generateBundle() {
const buildResult = new Proxy(
{},
{
get(_target, _prop) {
throw new Error('Not implemented')
},
},
) as esbuild.BuildResult
for (const cb of onEndCallbacks) {
cb(buildResult)
}
},
async resolveId(id, importer, opts) {
for (const handler of resolveIdHandlers) {
const result = await handler.call(this, id, importer, opts)
if (result) {
return result
}
}
},
async load(id) {
for (const handler of loadHandlers) {
const result = await handler.call(this, id)
if (result) {
return result
}
}
},
closeBundle() {
if (!this.meta.watchMode) {
for (const cb of onDisposeCallbacks) {
cb()
}
}
},
closeWatcher() {
for (const cb of onDisposeCallbacks) {
cb()
}
},
}
}

function createResolveIdHandler(
options: esbuild.OnResolveOptions,
callback: EsbuildOnResolveCallback,
): ResolveIdHandler {
return async function (id, importer, opts) {
const [importerWithoutNamespace, importerNamespace] =
idToPathAndNamespace(importer)
if (
options.namespace !== undefined &&
options.namespace !== importerNamespace
) {
return
}
if (options.filter !== undefined && !options.filter.test(id)) {
return
}

const result = await callback({
path: id,
importer: importerWithoutNamespace ?? '',
namespace: importerNamespace,
resolveDir: dirname(importerWithoutNamespace ?? ''),
kind:
importerWithoutNamespace === undefined
? 'entry-point'
: opts.kind === 'import'
? 'import-statement'
: opts.kind,
pluginData: {},
with: {},
})
if (!result) return
if (result.errors && result.errors.length > 0) {
throw new AggregateError(result.errors)
}
if (
(result.warnings && result.warnings.length > 0) ||
(result.watchDirs && result.watchDirs.length > 0) ||
!result.path
) {
throw new Error('not implemented')
}
for (const file of result.watchFiles ?? []) {
this.addWatchFile(file)
}

return {
id: result.namespace ? `${result.namespace}:${result.path}` : result.path,
external: result.external,
moduleSideEffects: result.sideEffects,
}
}
}

function createLoadHandler(
options: esbuild.OnLoadOptions,
callback: EsbuildOnLoadCallback,
): LoadHandler {
const textDecoder = new TextDecoder()
return async function (id) {
const [idWithoutNamespace, idNamespace] = idToPathAndNamespace(id)
if (options.namespace !== undefined && options.namespace !== idNamespace) {
return
}
if (options.filter !== undefined && !options.filter.test(id)) {
return
}

const result = await callback.call(this, {
path: idWithoutNamespace,
namespace: idNamespace,
suffix: '',
pluginData: {},
with: {},
})
if (!result) return
if (result.errors && result.errors.length > 0) {
throw new AggregateError(result.errors)
}
if (
(result.warnings && result.warnings.length > 0) ||
(result.watchDirs && result.watchDirs.length > 0) ||
!result.contents
) {
throw new Error('not implemented')
}
for (const file of result.watchFiles ?? []) {
this.addWatchFile(file)
}

return {
code:
typeof result.contents === 'string'
? result.contents
: textDecoder.decode(result.contents),
moduleType: result.loader,
}
}
}

function idToPathAndNamespace(id: string): [path: string, namespace: string]
function idToPathAndNamespace(
id: string | undefined,
): [path: string | undefined, namespace: string]
function idToPathAndNamespace(
id: string | undefined,
): [path: string | undefined, namespace: string] {
if (id === undefined) return [undefined, 'file']

const namespaceIndex = id.indexOf(':')
if (namespaceIndex >= 0) {
return [id.slice(namespaceIndex + 1), id.slice(0, namespaceIndex)]
} else {
return [id, 'file']
}
}
363 changes: 363 additions & 0 deletions packages/vite/src/node/optimizer/rolldownDepPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
import path from 'node:path'
import type { ImportKind, Plugin, RolldownPlugin } from 'rolldown'
import { JS_TYPES_RE, KNOWN_ASSET_TYPES } from '../constants'
import type { PackageCache } from '../packages'
import {
escapeRegex,
flattenId,
isBuiltin,
isExternalUrl,
moduleListContains,
normalizePath,
} from '../utils'
import { browserExternalId, optionalPeerDepId } from '../plugins/resolve'
import { isCSSRequest, isModuleCSSRequest } from '../plugins/css'
import type { Environment } from '../environment'
import { createBackCompatIdResolver } from '../idResolver'
import { isWindows } from '../../shared/utils'

const externalWithConversionNamespace =
'vite:dep-pre-bundle:external-conversion'
const convertedExternalPrefix = 'vite-dep-pre-bundle-external:'

const cjsExternalFacadeNamespace = 'vite:cjs-external-facade'
const nonFacadePrefix = 'vite-cjs-external-facade:'

const externalTypes = [
'css',
// supported pre-processor types
'less',
'sass',
'scss',
'styl',
'stylus',
'pcss',
'postcss',
// wasm
'wasm',
// known SFC types
'vue',
'svelte',
'marko',
'astro',
'imba',
// JSX/TSX may be configured to be compiled differently from how esbuild
// handles it by default, so exclude them as well
'jsx',
'tsx',
...KNOWN_ASSET_TYPES,
]

const optionalPeerDepNamespace = 'optional-peer-dep:'
const browserExternalNamespace = 'browser-external:'

export function rolldownDepPlugin(
environment: Environment,
qualified: Record<string, string>,
external: string[],
): RolldownPlugin[] {
const { isProduction } = environment.config
const { extensions } = environment.config.optimizeDeps

// remove optimizable extensions from `externalTypes` list
const allExternalTypes = extensions
? externalTypes.filter((type) => !extensions.includes('.' + type))
: externalTypes

// use separate package cache for optimizer as it caches paths around node_modules
// and it's unlikely for the core Vite process to traverse into node_modules again
const esmPackageCache: PackageCache = new Map()
const cjsPackageCache: PackageCache = new Map()

// default resolver which prefers ESM
const _resolve = createBackCompatIdResolver(environment.getTopLevelConfig(), {
asSrc: false,
scan: true,
packageCache: esmPackageCache,
})

// cjs resolver that prefers Node
const _resolveRequire = createBackCompatIdResolver(
environment.getTopLevelConfig(),
{
asSrc: false,
isRequire: true,
scan: true,
packageCache: cjsPackageCache,
},
)

const resolve = (
id: string,
importer: string | undefined,
kind: ImportKind,
resolveDir?: string,
): Promise<string | undefined> => {
let _importer: string | undefined
// explicit resolveDir - this is passed only during yarn pnp resolve for
// entries
if (resolveDir) {
_importer = normalizePath(path.join(resolveDir, '*'))
} else if (importer) {
// map importer ids to file paths for correct resolution
_importer = importer in qualified ? qualified[importer] : importer
}
const resolver = kind.startsWith('require') ? _resolveRequire : _resolve
return resolver(environment, id, _importer)
}

const resolveResult = (id: string, resolved: string) => {
if (resolved.startsWith(browserExternalId)) {
return {
id: browserExternalNamespace + id,
}
}
if (resolved.startsWith(optionalPeerDepId)) {
return {
id: optionalPeerDepNamespace + resolved,
}
}
if (isBuiltin(environment.config.resolve.builtins, resolved)) {
return
}
if (isExternalUrl(resolved)) {
return {
id: resolved,
external: true,
}
}
return {
id: path.resolve(resolved),
}
}

const allExternalTypesReg = new RegExp(
`\\.(` + allExternalTypes.join('|') + `)(\\?.*)?$`,
)

function resolveEntry(id: string) {
const flatId = flattenId(id)
if (flatId in qualified) {
return {
id: qualified[flatId],
}
}
}

return [
{
name: 'vite:dep-pre-bundle-assets',
// externalize assets and commonly known non-js file types
// See #8459 for more details about this require-import conversion
resolveId: {
filter: { id: allExternalTypesReg },
async handler(id, importer, options) {
const kind = options.kind
// if the prefix exist, it is already converted to `import`, so set `external: true`
if (id.startsWith(convertedExternalPrefix)) {
return {
id: id.slice(convertedExternalPrefix.length),
external: true,
}
}

const resolved = await resolve(id, importer, kind)
if (resolved) {
// `resolved` can be javascript even when `id` matches `allExternalTypes`
// due to cjs resolution (e.g. require("./test.pdf") for "./test.pdf.js")
// or package name (e.g. import "some-package.pdf")
if (JS_TYPES_RE.test(resolved)) {
return {
// normalize to \\ on windows for esbuild/rolldown behavior difference: https://github.com/sapphi-red-repros/rolldown-esbuild-path-normalization
id: isWindows ? resolved.replaceAll('/', '\\') : resolved,
external: false,
}
}

if (kind === 'require-call') {
// here it is not set to `external: true` to convert `require` to `import`
return {
id: externalWithConversionNamespace + resolved,
}
}
return {
id: resolved,
external: true,
}
}
},
},
},
{
name: 'vite:dep-pre-bundle',
// clear package cache when build is finished
buildEnd() {
esmPackageCache.clear()
cjsPackageCache.clear()
},
resolveId: {
filter: { id: /^[\w@][^:]/ },
async handler(id, importer, options) {
const kind = options.kind

if (moduleListContains(external, id)) {
return {
id: id,
external: true,
}
}

// ensure esbuild uses our resolved entries
let entry: { id: string } | undefined
// if this is an entry, return entry namespace resolve result
if (!importer) {
if ((entry = resolveEntry(id))) return entry
// check if this is aliased to an entry - also return entry namespace
const aliased = await _resolve(environment, id, undefined, true)
if (aliased && (entry = resolveEntry(aliased))) {
return entry
}
}

// use vite's own resolver
const resolved = await resolve(id, importer, kind)
if (resolved) {
return resolveResult(id, resolved)
}
},
},
load: {
filter: {
id: [
new RegExp(`^${externalWithConversionNamespace}`),
new RegExp(`^${browserExternalNamespace}`),
new RegExp(`^${optionalPeerDepNamespace}`),
],
},
handler(id) {
if (id.startsWith(externalWithConversionNamespace)) {
const path = id.slice(externalWithConversionNamespace.length)
// import itself with prefix (this is the actual part of require-import conversion)
const modulePath = `"${convertedExternalPrefix}${path}"`
return {
code:
isCSSRequest(path) && !isModuleCSSRequest(path)
? `import ${modulePath};`
: `export { default } from ${modulePath};` +
`export * from ${modulePath};`,
}
}

if (id.startsWith(browserExternalNamespace)) {
const path = id.slice(browserExternalNamespace.length)
if (isProduction) {
return {
code: 'module.exports = {}',
}
} else {
return {
// Return in CJS to intercept named imports. Use `Object.create` to
// create the Proxy in the prototype to workaround esbuild issue. Why?
//
// In short, esbuild cjs->esm flow:
// 1. Create empty object using `Object.create(Object.getPrototypeOf(module.exports))`.
// 2. Assign props of `module.exports` to the object.
// 3. Return object for ESM use.
//
// If we do `module.exports = new Proxy({}, {})`, step 1 returns empty object,
// step 2 does nothing as there's no props for `module.exports`. The final object
// is just an empty object.
//
// Creating the Proxy in the prototype satisfies step 1 immediately, which means
// the returned object is a Proxy that we can intercept.
//
// Note: Skip keys that are accessed by esbuild and browser devtools.
code: `\
module.exports = Object.create(new Proxy({}, {
get(_, key) {
if (
key !== '__esModule' &&
key !== '__proto__' &&
key !== 'constructor' &&
key !== 'splice'
) {
console.warn(\`Module "${path}" has been externalized for browser compatibility. Cannot access "${path}.\${key}" in client code. See http://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\`)
}
}
}))`,
}
}
}

if (id.startsWith(optionalPeerDepNamespace)) {
if (isProduction) {
return {
code: 'module.exports = {}',
}
} else {
const path = id.slice(externalWithConversionNamespace.length)
const [, peerDep, parentDep] = path.split(':')
return {
code: `throw new Error(\`Could not resolve "${peerDep}" imported by "${parentDep}". Is it installed?\`)`,
}
}
}
},
},
},
]
}

const matchesEntireLine = (text: string) => `^${escapeRegex(text)}$`

// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
export function rolldownCjsExternalPlugin(
externals: string[],
platform: 'node' | 'browser' | 'neutral',
): Plugin {
const filter = new RegExp(externals.map(matchesEntireLine).join('|'))

return {
name: 'cjs-external',
resolveId: {
filter: { id: [new RegExp(`^${nonFacadePrefix}`), filter] },
handler(id, _importer, options) {
if (id.startsWith(nonFacadePrefix)) {
return {
id: id.slice(nonFacadePrefix.length),
external: true,
}
}

if (filter.test(id)) {
const kind = options.kind
// preserve `require` for node because it's more accurate than converting it to import
if (kind === 'require-call' && platform !== 'node') {
return {
id: cjsExternalFacadeNamespace + id,
}
}

return {
id,
external: true,
}
}
},
},
load: {
filter: { id: [new RegExp(`^${cjsExternalFacadeNamespace}`)] },
handler(id) {
if (id.startsWith(cjsExternalFacadeNamespace)) {
return {
code:
`import * as m from ${JSON.stringify(
nonFacadePrefix + id.slice(cjsExternalFacadeNamespace.length),
)};` + `module.exports = m;`,
}
}
},
},
}
}
654 changes: 294 additions & 360 deletions packages/vite/src/node/optimizer/scan.ts

Large diffs are not rendered by default.

21 changes: 17 additions & 4 deletions packages/vite/src/node/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type {
CustomPluginOptions,
LoadResult,
ModuleType,
ObjectHook,
ResolveIdResult,
MinimalPluginContext as RollupMinimalPluginContext,
Plugin as RollupPlugin,
PluginContext as RollupPluginContext,
TransformPluginContext as RollupTransformPluginContext,
TransformResult,
} from 'rollup'
} from 'rolldown'
import type {
ConfigEnv,
EnvironmentOptions,
@@ -81,7 +82,7 @@ export interface TransformPluginContext
PluginContextExtension {}

// Argument Rollup types to have the PluginContextExtension
declare module 'rollup' {
declare module 'rolldown' {
export interface MinimalPluginContext extends PluginContextExtension {}
}

@@ -130,14 +131,15 @@ export interface Plugin<A = any> extends RollupPlugin<A> {
source: string,
importer: string | undefined,
options: {
attributes: Record<string, string>
// attributes: Record<string, string>
custom?: CustomPluginOptions
ssr?: boolean
/**
* @internal
*/
scan?: boolean
isEntry: boolean
kind?: 'import' | 'dynamic-import' | 'require-call'
},
) => Promise<ResolveIdResult> | ResolveIdResult
>
@@ -160,6 +162,7 @@ export interface Plugin<A = any> extends RollupPlugin<A> {
code: string,
id: string,
options?: {
moduleType: ModuleType
ssr?: boolean
},
) => Promise<TransformResult> | TransformResult
@@ -355,9 +358,19 @@ export type PluginOption = Thenable<Plugin | FalsyPlugin | PluginOption[]>

export async function resolveEnvironmentPlugins(
environment: PartialEnvironment,
): Promise<Plugin[]> {
return resolveEnvironmentPluginsRaw(
environment.getTopLevelConfig().plugins,
environment,
)
}

export async function resolveEnvironmentPluginsRaw(
plugins: readonly Plugin[],
environment: PartialEnvironment,
): Promise<Plugin[]> {
const environmentPlugins: Plugin[] = []
for (const plugin of environment.getTopLevelConfig().plugins) {
for (const plugin of plugins) {
if (plugin.applyToEnvironment) {
const applied = await plugin.applyToEnvironment(environment)
if (!applied) {
12 changes: 8 additions & 4 deletions packages/vite/src/node/plugins/asset.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import path from 'node:path'
import fsp from 'node:fs/promises'
import { Buffer } from 'node:buffer'
import * as mrmime from 'mrmime'
import type { NormalizedOutputOptions, RenderedChunk } from 'rollup'
import type { NormalizedOutputOptions, RenderedChunk } from 'rolldown'
import MagicString from 'magic-string'
import colors from 'picocolors'
import {
@@ -172,9 +172,12 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
const file = checkPublicFile(id, config) || cleanUrl(id)
this.addWatchFile(file)
// raw query, read file and return as string
return `export default ${JSON.stringify(
await fsp.readFile(file, 'utf-8'),
)}`
return {
code: `export default ${JSON.stringify(
await fsp.readFile(file, 'utf-8'),
)}`,
moduleType: 'js', // NOTE: needs to be set to avoid double `export default` in `?raw&.txt`s
}
}

if (!urlRE.test(id) && !config.assetsInclude(cleanUrl(id))) {
@@ -201,6 +204,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
? 'no-treeshake'
: false,
meta: config.command === 'build' ? { 'vite:asset': true } : undefined,
moduleType: 'js', // NOTE: needs to be set to avoid double `export default` in `.txt`s
}
},

221 changes: 116 additions & 105 deletions packages/vite/src/node/plugins/assetImportMetaUrl.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import path from 'node:path'
import MagicString from 'magic-string'
import { stripLiteral } from 'strip-literal'
import type { Plugin } from '../plugin'
import type { RolldownPlugin } from 'rolldown'
import { parseAst } from 'rolldown/parseAst'
import type { ResolvedConfig } from '../config'
import {
injectQuery,
@@ -29,7 +30,9 @@ import { hasViteIgnoreRE } from './importAnalysis'
* import.meta.glob('./dir/**.png', { eager: true, import: 'default' })[`./dir/${name}.png`]
* ```
*/
export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
export function assetImportMetaUrlPlugin(
config: ResolvedConfig,
): RolldownPlugin {
const { publicDir } = config
let assetResolver: ResolveIdFn

@@ -44,124 +47,132 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin {

return {
name: 'vite:asset-import-meta-url',
async transform(code, id) {
const { environment } = this
if (
environment.config.consumer === 'client' &&
id !== preloadHelperId &&
id !== CLIENT_ENTRY &&
code.includes('new URL') &&
code.includes(`import.meta.url`)
) {
let s: MagicString | undefined
const assetImportMetaUrlRE =
/\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg
const cleanString = stripLiteral(code)
transform: {
filter: {
code: {
include: ['new URL', 'import.meta.url'],
},
},
async handler(code, id) {
const { environment } = this
if (
environment.config.consumer === 'client' &&
id !== preloadHelperId &&
id !== CLIENT_ENTRY &&
code.includes('new URL') &&
code.includes(`import.meta.url`)
) {
let s: MagicString | undefined
const assetImportMetaUrlRE =
/\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg
const cleanString = stripLiteral(code)

let match: RegExpExecArray | null
while ((match = assetImportMetaUrlRE.exec(cleanString))) {
const [[startIndex, endIndex], [urlStart, urlEnd]] = match.indices!
if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continue
let match: RegExpExecArray | null
while ((match = assetImportMetaUrlRE.exec(cleanString))) {
const [[startIndex, endIndex], [urlStart, urlEnd]] = match.indices!
if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continue

const rawUrl = code.slice(urlStart, urlEnd)
const rawUrl = code.slice(urlStart, urlEnd)

if (!s) s = new MagicString(code)
if (!s) s = new MagicString(code)

// potential dynamic template string
if (rawUrl[0] === '`' && rawUrl.includes('${')) {
const queryDelimiterIndex = getQueryDelimiterIndex(rawUrl)
const hasQueryDelimiter = queryDelimiterIndex !== -1
const pureUrl = hasQueryDelimiter
? rawUrl.slice(0, queryDelimiterIndex) + '`'
: rawUrl
const queryString = hasQueryDelimiter
? rawUrl.slice(queryDelimiterIndex, -1)
: ''
const ast = this.parse(pureUrl)
const templateLiteral = (ast as any).body[0].expression
if (templateLiteral.expressions.length) {
const pattern = buildGlobPattern(templateLiteral)
if (pattern.startsWith('*')) {
// don't transform for patterns like this
// because users won't intend to do that in most cases
// potential dynamic template string
if (rawUrl[0] === '`' && rawUrl.includes('${')) {
const queryDelimiterIndex = getQueryDelimiterIndex(rawUrl)
const hasQueryDelimiter = queryDelimiterIndex !== -1
const pureUrl = hasQueryDelimiter
? rawUrl.slice(0, queryDelimiterIndex) + '`'
: rawUrl
const queryString = hasQueryDelimiter
? rawUrl.slice(queryDelimiterIndex, -1)
: ''
const ast = parseAst(pureUrl)
const templateLiteral = (ast as any).body[0].expression
if (templateLiteral.expressions.length) {
const pattern = buildGlobPattern(templateLiteral)
if (pattern.startsWith('*')) {
// don't transform for patterns like this
// because users won't intend to do that in most cases
continue
}

const globOptions = {
eager: true,
import: 'default',
// A hack to allow 'as' & 'query' exist at the same time
query: injectQuery(queryString, 'url'),
}
s.update(
startIndex,
endIndex,
`new URL((import.meta.glob(${JSON.stringify(
pattern,
)}, ${JSON.stringify(
globOptions,
)}))[${pureUrl}], import.meta.url)`,
)
continue
}
}

const globOptions = {
eager: true,
import: 'default',
// A hack to allow 'as' & 'query' exist at the same time
query: injectQuery(queryString, 'url'),
}
s.update(
startIndex,
endIndex,
`new URL((import.meta.glob(${JSON.stringify(
pattern,
)}, ${JSON.stringify(
globOptions,
)}))[${pureUrl}], import.meta.url)`,
)
const url = rawUrl.slice(1, -1)
if (isDataUrl(url)) {
continue
}
}

const url = rawUrl.slice(1, -1)
if (isDataUrl(url)) {
continue
}
let file: string | undefined
if (url[0] === '.') {
file = slash(path.resolve(path.dirname(id), url))
file = tryFsResolve(file, fsResolveOptions) ?? file
} else {
assetResolver ??= createBackCompatIdResolver(config, {
extensions: [],
mainFields: [],
tryIndex: false,
preferRelative: true,
})
file = await assetResolver(environment, url, id)
file ??=
url[0] === '/'
? slash(path.join(publicDir, url))
: slash(path.resolve(path.dirname(id), url))
}
let file: string | undefined
if (url[0] === '.') {
file = slash(path.resolve(path.dirname(id), url))
file = tryFsResolve(file, fsResolveOptions) ?? file
} else {
assetResolver ??= createBackCompatIdResolver(config, {
extensions: [],
mainFields: [],
tryIndex: false,
preferRelative: true,
})
file = await assetResolver(environment, url, id)
file ??=
url[0] === '/'
? slash(path.join(publicDir, url))
: slash(path.resolve(path.dirname(id), url))
}

// Get final asset URL. If the file does not exist,
// we fall back to the initial URL and let it resolve in runtime
let builtUrl: string | undefined
if (file) {
try {
if (publicDir && isParentDirectory(publicDir, file)) {
const publicPath = '/' + path.posix.relative(publicDir, file)
builtUrl = await fileToUrl(this, publicPath)
} else {
builtUrl = await fileToUrl(this, file)
// Get final asset URL. If the file does not exist,
// we fall back to the initial URL and let it resolve in runtime
let builtUrl: string | undefined
if (file) {
try {
if (publicDir && isParentDirectory(publicDir, file)) {
const publicPath = '/' + path.posix.relative(publicDir, file)
builtUrl = await fileToUrl(this, publicPath)
} else {
builtUrl = await fileToUrl(this, file)
}
} catch {
// do nothing, we'll log a warning after this
}
} catch {
// do nothing, we'll log a warning after this
}
}
if (!builtUrl) {
const rawExp = code.slice(startIndex, endIndex)
config.logger.warnOnce(
`\n${rawExp} doesn't exist at build time, it will remain unchanged to be resolved at runtime. ` +
`If this is intended, you can use the /* @vite-ignore */ comment to suppress this warning.`,
if (!builtUrl) {
const rawExp = code.slice(startIndex, endIndex)
config.logger.warnOnce(
`\n${rawExp} doesn't exist at build time, it will remain unchanged to be resolved at runtime. ` +
`If this is intended, you can use the /* @vite-ignore */ comment to suppress this warning.`,
)
builtUrl = url
}
s.update(
startIndex,
endIndex,
// NOTE: add `'' +` to opt-out rolldown's transform: https://github.com/rolldown/rolldown/issues/2745
`new URL(${JSON.stringify(builtUrl)}, '' + import.meta.url)`,
)
builtUrl = url
}
s.update(
startIndex,
endIndex,
`new URL(${JSON.stringify(builtUrl)}, import.meta.url)`,
)
}
if (s) {
return transformStableResult(s, id, config)
if (s) {
return transformStableResult(s, id, config)
}
}
}
return null
return null
},
},
}
}
22 changes: 11 additions & 11 deletions packages/vite/src/node/plugins/completeSystemWrap.ts
Original file line number Diff line number Diff line change
@@ -4,20 +4,20 @@ import type { Plugin } from '../plugin'
* make sure systemjs register wrap to had complete parameters in system format
*/
export function completeSystemWrapPlugin(): Plugin {
const SystemJSWrapRE = /System.register\(.*?(\(exports\)|\(\))/g
// const SystemJSWrapRE = /System.register\(.*?(\(exports\)|\(\))/g

return {
name: 'vite:force-systemjs-wrap-complete',

renderChunk(code, _chunk, opts) {
if (opts.format === 'system') {
return {
code: code.replace(SystemJSWrapRE, (s, s1) =>
s.replace(s1, '(exports, module)'),
),
map: null,
}
}
},
// renderChunk(code, _chunk, opts) {
// if (opts.format === 'system') {
// return {
// code: code.replace(SystemJSWrapRE, (s, s1) =>
// s.replace(s1, '(exports, module)'),
// ),
// map: null,
// }
// }
// },
}
}
556 changes: 309 additions & 247 deletions packages/vite/src/node/plugins/css.ts

Large diffs are not rendered by default.

56 changes: 21 additions & 35 deletions packages/vite/src/node/plugins/define.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { transform } from 'esbuild'
import { TraceMap, decodedMap, encodedMap } from '@jridgewell/trace-mapping'
import type { RolldownPlugin } from 'rolldown'
import { transform } from 'rolldown/experimental'
import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
import { escapeRegex } from '../utils'
import type { Environment } from '../environment'
import { isCSSRequest } from './css'
@@ -12,7 +11,7 @@ const isNonJsRequest = (request: string): boolean => nonJsRe.test(request)
const importMetaEnvMarker = '__vite_import_meta_env__'
const importMetaEnvKeyReCache = new Map<string, RegExp>()

export function definePlugin(config: ResolvedConfig): Plugin {
export function definePlugin(config: ResolvedConfig): RolldownPlugin {
const isBuild = config.command === 'build'
const isBuildLib = isBuild && config.build.lib

@@ -111,7 +110,7 @@ export function definePlugin(config: ResolvedConfig): Plugin {
return pattern
}

return {
const plugin: RolldownPlugin = {
name: 'vite:define',

async transform(code, id) {
@@ -169,56 +168,43 @@ export function definePlugin(config: ResolvedConfig): Plugin {
result.code = `const ${marker} = ${importMetaEnvVal};\n` + result.code

if (result.map) {
const map = JSON.parse(result.map)
map.mappings = ';' + map.mappings
result.map = map
result.map.mappings = ';' + result.map.mappings
}
}
}

return result
},
}
const enableNativePlugin = config.experimental.enableNativePlugin
if (enableNativePlugin) {
delete plugin.transform
plugin.options = function (option) {
const [define, _pattern, importMetaEnvVal] = getPattern(this.environment)
define['import.meta.env'] = importMetaEnvVal
option.define = define
}
}
return plugin
}

export async function replaceDefine(
environment: Environment,
code: string,
id: string,
define: Record<string, string>,
): Promise<{ code: string; map: string | null }> {
const esbuildOptions = environment.config.esbuild || {}

const result = await transform(code, {
loader: 'js',
charset: esbuildOptions.charset ?? 'utf8',
platform: 'neutral',
): Promise<{ code: string; map: ReturnType<typeof transform>['map'] | null }> {
const result = transform(id, code, {
lang: 'js',
sourceType: 'module',
define,
sourcefile: id,
sourcemap:
environment.config.command === 'build'
? !!environment.config.build.sourcemap
: true,
})

// remove esbuild's <define:...> source entries
// since they would confuse source map remapping/collapsing which expects a single source
if (result.map.includes('<define:')) {
const originalMap = new TraceMap(result.map)
if (originalMap.sources.length >= 2) {
const sourceIndex = originalMap.sources.indexOf(id)
const decoded = decodedMap(originalMap)
decoded.sources = [id]
decoded.mappings = decoded.mappings.map((segments) =>
segments.filter((segment) => {
// modify and filter
const index = segment[1]
segment[1] = 0
return index === sourceIndex
}),
)
result.map = JSON.stringify(encodedMap(new TraceMap(decoded as any)))
}
if (result.errors.length > 0) {
throw new AggregateError(result.errors, 'oxc transform error')
}

return {
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/dynamicImportVars.ts
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@ import { posix } from 'node:path'
import MagicString from 'magic-string'
import { init, parse as parseImports } from 'es-module-lexer'
import type { ImportSpecifier } from 'es-module-lexer'
import { parseAst } from 'rollup/parseAst'
import { dynamicImportToGlob } from '@rollup/plugin-dynamic-import-vars'
import { parseAst } from 'rolldown/parseAst'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import { CLIENT_ENTRY } from '../constants'
44 changes: 25 additions & 19 deletions packages/vite/src/node/plugins/esbuild.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import path from 'node:path'
import colors from 'picocolors'
import type {
Loader,
Message,
TransformOptions,
TransformResult,
} from 'esbuild'
import { transform } from 'esbuild'
import type { RawSourceMap } from '@ampproject/remapping'
import type { InternalModuleFormat, SourceMap } from 'rollup'
import type { InternalModuleFormat, SourceMap } from 'rolldown'
import type { TSConfckParseResult } from 'tsconfck'
import { TSConfckCache, TSConfckParseError, parse } from 'tsconfck'
import type { FSWatcher } from 'dep-types/chokidar'
import type {
EsbuildLoader,
EsbuildMessage,
EsbuildTransformOptions,
EsbuildTransformResult as RawEsbuildTransformResult,
} from 'types/internal/esbuildOptions'
import {
combineSourcemaps,
createDebugger,
@@ -43,7 +42,7 @@ export const defaultEsbuildSupported = {
'import-meta': true,
}

export interface ESBuildOptions extends TransformOptions {
export interface ESBuildOptions extends EsbuildTransformOptions {
include?: string | RegExp | ReadonlyArray<string | RegExp>
exclude?: string | RegExp | ReadonlyArray<string | RegExp>
jsxInject?: string
@@ -53,7 +52,7 @@ export interface ESBuildOptions extends TransformOptions {
minify?: never
}

export type ESBuildTransformResult = Omit<TransformResult, 'map'> & {
export type ESBuildTransformResult = Omit<RawEsbuildTransformResult, 'map'> & {
map: SourceMap
}

@@ -76,10 +75,16 @@ type TSConfigJSON = {
}
type TSCompilerOptions = NonNullable<TSConfigJSON['compilerOptions']>

let esbuild: Promise<typeof import('esbuild')> | undefined
const importEsbuild = () => {
esbuild ||= import('esbuild')
return esbuild
}

export async function transformWithEsbuild(
code: string,
filename: string,
options?: TransformOptions,
options?: EsbuildTransformOptions,
inMap?: object,
config?: ResolvedConfig,
watcher?: FSWatcher,
@@ -98,7 +103,7 @@ export async function transformWithEsbuild(
} else if (ext === 'cts' || ext === 'mts') {
loader = 'ts'
} else {
loader = ext as Loader
loader = ext as EsbuildLoader
}
}

@@ -179,7 +184,7 @@ export async function transformWithEsbuild(
}
}

const resolvedOptions: TransformOptions = {
const resolvedOptions: EsbuildTransformOptions = {
sourcemap: true,
// ensure source file name contains full query
sourcefile: filename,
@@ -198,6 +203,7 @@ export async function transformWithEsbuild(
delete resolvedOptions.jsxInject

try {
const { transform } = await importEsbuild()
const result = await transform(code, resolvedOptions)
let map: SourceMap
if (inMap && resolvedOptions.sourcemap) {
@@ -222,7 +228,7 @@ export async function transformWithEsbuild(
// patch error information
if (e.errors) {
e.frame = ''
e.errors.forEach((m: Message) => {
e.errors.forEach((m: EsbuildMessage) => {
if (
m.text === 'Experimental decorators are not currently enabled' ||
m.text ===
@@ -247,7 +253,7 @@ export function esbuildPlugin(config: ResolvedConfig): Plugin {

// Remove optimization options for dev as we only need to transpile them,
// and for build as the final optimization is in `buildEsbuildPlugin`
const transformOptions: TransformOptions = {
const transformOptions: EsbuildTransformOptions = {
target: 'esnext',
charset: 'utf8',
...esbuildTransformOptions,
@@ -302,7 +308,7 @@ export function esbuildPlugin(config: ResolvedConfig): Plugin {

const rollupToEsbuildFormatMap: Record<
string,
TransformOptions['format'] | undefined
EsbuildTransformOptions['format'] | undefined
> = {
es: 'esm',
cjs: 'cjs',
@@ -380,7 +386,7 @@ export const buildEsbuildPlugin = (): Plugin => {
export function resolveEsbuildTranspileOptions(
config: ResolvedConfig,
format: InternalModuleFormat,
): TransformOptions | null {
): EsbuildTransformOptions | null {
const target = config.build.target
const minify = config.build.minify === 'esbuild'

@@ -394,7 +400,7 @@ export function resolveEsbuildTranspileOptions(
const isEsLibBuild = config.build.lib && format === 'es'
const esbuildOptions = config.esbuild || {}

const options: TransformOptions = {
const options: EsbuildTransformOptions = {
charset: 'utf8',
...esbuildOptions,
loader: 'js',
@@ -466,7 +472,7 @@ export function resolveEsbuildTranspileOptions(
}
}

function prettifyMessage(m: Message, code: string): string {
function prettifyMessage(m: EsbuildMessage, code: string): string {
let res = colors.yellow(m.text)
if (m.location) {
res += `\n` + generateCodeFrame(code, m.location)
747 changes: 390 additions & 357 deletions packages/vite/src/node/plugins/html.ts

Large diffs are not rendered by default.

20 changes: 13 additions & 7 deletions packages/vite/src/node/plugins/importAnalysis.ts
Original file line number Diff line number Diff line change
@@ -9,12 +9,12 @@ import type {
ImportSpecifier,
} from 'es-module-lexer'
import { init, parse as parseImports } from 'es-module-lexer'
import { parseAst } from 'rollup/parseAst'
import type { StaticImport } from 'mlly'
import { ESM_STATIC_IMPORT_RE, parseStaticImport } from 'mlly'
import { makeLegalIdentifier } from '@rollup/pluginutils'
import type { PartialResolvedId, RollupError } from 'rollup'
import type { Identifier, Literal } from 'estree'
import type { PartialResolvedId, RollupError } from 'rolldown'
import type { Identifier, Literal, Program } from 'estree'
import { parseAst } from 'rolldown/parseAst'
import {
CLIENT_DIR,
CLIENT_PUBLIC_PATH,
@@ -58,7 +58,10 @@ import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
import type { DevEnvironment } from '../server/environment'
import { shouldExternalize } from '../external'
import { optimizedDepNeedsInterop } from '../optimizer'
import {
optimizedDepInfoFromFile,
optimizedDepNeedsInterop,
} from '../optimizer'
import {
cleanUrl,
unwrapId,
@@ -82,7 +85,6 @@ export const canSkipImportAnalysis = (id: string): boolean =>
skipRE.test(id) || isDirectCSSRequest(id)

const optimizedDepChunkRE = /\/chunk-[A-Z\d]{8}\.js/
const optimizedDepDynamicRE = /-[A-Z\d]{8}\.js/

export const hasViteIgnoreRE = /\/\*\s*@vite-ignore\s*\*\//

@@ -571,6 +573,10 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
// page reload. We could return a 404 in that case but it is safe to return the request
const file = cleanUrl(resolvedId) // Remove ?v={hash}

const depInfo = optimizedDepInfoFromFile(
depsOptimizer.metadata,
file,
)
const needsInterop = await optimizedDepNeedsInterop(
environment,
depsOptimizer.metadata,
@@ -581,7 +587,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
// Non-entry dynamic imports from dependencies will reach here as there isn't
// optimize info for them, but they don't need es interop. If the request isn't
// a dynamic import, then it is an internal Vite error
if (!optimizedDepDynamicRE.test(file)) {
if (depInfo?.isDynamicEntry) {
config.logger.error(
colors.red(
`Vite Error, ${url} optimized info should be defined`,
@@ -981,7 +987,7 @@ export function transformCjsImport(
importer: string,
config: ResolvedConfig,
): string | undefined {
const node = parseAst(importExp).body[0]
const node = (parseAst(importExp) as Program).body[0]

// `export * from '...'` may cause unexpected problem, so give it a warning
if (
110 changes: 78 additions & 32 deletions packages/vite/src/node/plugins/importAnalysisBuild.ts
Original file line number Diff line number Diff line change
@@ -5,20 +5,21 @@ import type {
ImportSpecifier,
} from 'es-module-lexer'
import { init, parse as parseImports } from 'es-module-lexer'
import type { SourceMap } from 'rollup'
import type { SourceMap } from 'rolldown'
import type { RawSourceMap } from '@ampproject/remapping'
import convertSourceMap from 'convert-source-map'
import { buildImportAnalysisPlugin as nativeBuildImportAnalysisPlugin } from 'rolldown/experimental'
import {
combineSourcemaps,
generateCodeFrame,
isInNodeModules,
numberToPos,
} from '../utils'
import type { Plugin } from '../plugin'
import { type Plugin, perEnvironmentPlugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import { toOutputFilePathInJS } from '../build'
import { genSourceMapUrl } from '../server/sourcemap'
import type { Environment } from '../environment'
import type { PartialEnvironment } from '../baseEnvironment'
import { removedPureCssFilesCache } from './css'
import { createParseErrorInfo } from './importAnalysis'

@@ -166,19 +167,52 @@ function preload(
})
}

function getPreloadCode(
environment: PartialEnvironment,
renderBuiltUrlBoolean: boolean,
isRelativeBase: boolean,
) {
const { modulePreload } = environment.config.build

const scriptRel =
modulePreload && modulePreload.polyfill
? `'modulepreload'`
: `/* @__PURE__ */ (${detectScriptRel.toString()})()`

// There are two different cases for the preload list format in __vitePreload
//
// __vitePreload(() => import(asyncChunk), [ ...deps... ])
//
// This is maintained to keep backwards compatibility as some users developed plugins
// using regex over this list to workaround the fact that module preload wasn't
// configurable.
const assetsURL =
renderBuiltUrlBoolean || isRelativeBase
? // If `experimental.renderBuiltUrl` is used, the dependencies might be relative to the current chunk.
// If relative base is used, the dependencies are relative to the current chunk.
// The importerUrl is passed as third parameter to __vitePreload in this case
`function(dep, importerUrl) { return new URL(dep, importerUrl).href }`
: // If the base isn't relative, then the deps are relative to the projects `outDir` and the base
// is appended inside __vitePreload too.
`function(dep) { return ${JSON.stringify(environment.config.base)}+dep }`
const preloadCode = `const scriptRel = ${scriptRel};const assetsURL = ${assetsURL};const seen = {};export const ${preloadMethod} = ${preload.toString()}`
return preloadCode
}

/**
* Build only. During serve this is performed as part of ./importAnalysis.
*/
export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
const getInsertPreload = (environment: Environment) =>
export function buildImportAnalysisPlugin(config: ResolvedConfig): [Plugin] {
const getInsertPreload = (environment: PartialEnvironment) =>
environment.config.consumer === 'client' &&
!config.isWorker &&
!config.build.lib

const enableNativePlugin = config.experimental.enableNativePlugin
const renderBuiltUrl = config.experimental.renderBuiltUrl
const isRelativeBase = config.base === './' || config.base === ''

return {
const jsPlugin = {
name: 'vite:build-import-analysis',
resolveId(id) {
if (id === preloadHelperId) {
@@ -188,30 +222,11 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {

load(id) {
if (id === preloadHelperId) {
const { modulePreload } = this.environment.config.build

const scriptRel =
modulePreload && modulePreload.polyfill
? `'modulepreload'`
: `/* @__PURE__ */ (${detectScriptRel.toString()})()`

// There are two different cases for the preload list format in __vitePreload
//
// __vitePreload(() => import(asyncChunk), [ ...deps... ])
//
// This is maintained to keep backwards compatibility as some users developed plugins
// using regex over this list to workaround the fact that module preload wasn't
// configurable.
const assetsURL =
renderBuiltUrl || isRelativeBase
? // If `experimental.renderBuiltUrl` is used, the dependencies might be relative to the current chunk.
// If relative base is used, the dependencies are relative to the current chunk.
// The importerUrl is passed as third parameter to __vitePreload in this case
`function(dep, importerUrl) { return new URL(dep, importerUrl).href }`
: // If the base isn't relative, then the deps are relative to the projects `outDir` and the base
// is appended inside __vitePreload too.
`function(dep) { return ${JSON.stringify(config.base)}+dep }`
const preloadCode = `const scriptRel = ${scriptRel};const assetsURL = ${assetsURL};const seen = {};export const ${preloadMethod} = ${preload.toString()}`
const preloadCode = getPreloadCode(
this.environment,
!!renderBuiltUrl,
isRelativeBase,
)
return { code: preloadCode, moduleSideEffects: false }
}
},
@@ -439,7 +454,10 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
let url = name
if (!url) {
const rawUrl = code.slice(start, end)
if (rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`)
if (
(rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`) ||
(rawUrl[0] === '`' && rawUrl[rawUrl.length - 1] === '`')
)
url = rawUrl.slice(1, -1)
}
if (!url) continue
@@ -516,7 +534,10 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
let url = name
if (!url) {
const rawUrl = code.slice(start, end)
if (rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`)
if (
(rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`) ||
(rawUrl[0] === '`' && rawUrl[rawUrl.length - 1] === '`')
)
url = rawUrl.slice(1, -1)
}
const deps = new Set<string>()
@@ -721,5 +742,30 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
}
}
},
} as Plugin
if (enableNativePlugin) {
delete jsPlugin.transform
delete jsPlugin.resolveId
delete jsPlugin.load
}
return [
jsPlugin,
enableNativePlugin
? perEnvironmentPlugin('native:import-analysis-build', (environment) => {
const preloadCode = getPreloadCode(
environment,
!!renderBuiltUrl,
isRelativeBase,
)
return nativeBuildImportAnalysisPlugin({
preloadCode,
insertPreload: getInsertPreload(environment),
// this field looks redundant, put a dummy value for now
optimizeModulePreloadRelativePaths: false,
renderBuiltUrl: !!renderBuiltUrl,
isRelativeBase,
}) as unknown as Plugin
})
: null,
].filter(Boolean) as [Plugin]
}
5 changes: 3 additions & 2 deletions packages/vite/src/node/plugins/importMetaGlob.ts
Original file line number Diff line number Diff line change
@@ -10,12 +10,13 @@ import type {
SpreadElement,
TemplateLiteral,
} from 'estree'
import type { CustomPluginOptions, RollupAstNode, RollupError } from 'rollup'
import type { RollupAstNode } from 'rollup'
import type { CustomPluginOptions, RollupError } from 'rolldown'
import MagicString from 'magic-string'
import { stringifyQuery } from 'ufo'
import type { GeneralImportGlobOptions } from 'types/importGlob'
import { parseAstAsync } from 'rollup/parseAst'
import { escapePath, glob } from 'tinyglobby'
import { parseAstAsync } from 'rolldown/parseAst'
import type { Plugin } from '../plugin'
import type { EnvironmentModuleNode } from '../server/moduleGraph'
import type { ResolvedConfig } from '../config'
123 changes: 96 additions & 27 deletions packages/vite/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import aliasPlugin, { type ResolverFunction } from '@rollup/plugin-alias'
import type { ObjectHook } from 'rollup'
import type { ObjectHook } from 'rolldown'
import {
aliasPlugin as nativeAliasPlugin,
dynamicImportVarsPlugin as nativeDynamicImportVarsPlugin,
importGlobPlugin as nativeImportGlobPlugin,
jsonPlugin as nativeJsonPlugin,
modulePreloadPolyfillPlugin as nativeModulePreloadPolyfillPlugin,
transformPlugin as nativeTransformPlugin,
wasmFallbackPlugin as nativeWasmFallbackPlugin,
wasmHelperPlugin as nativeWasmHelperPlugin,
} from 'rolldown/experimental'
import type { PluginHookUtils, ResolvedConfig } from '../config'
import { isDepOptimizationDisabled } from '../optimizer'
import type { HookHandler, Plugin, PluginWithRequiredHook } from '../plugin'
import {
type HookHandler,
type Plugin,
type PluginWithRequiredHook,
perEnvironmentPlugin,
} from '../plugin'
import { watchPackageDataPlugin } from '../packages'
import { jsonPlugin } from './json'
import { resolvePlugin } from './resolve'
import { oxcResolvePlugin, resolvePlugin } from './resolve'
import { optimizedDepsPlugin } from './optimizedDeps'
import { esbuildPlugin } from './esbuild'
import { importAnalysisPlugin } from './importAnalysis'
import { cssAnalysisPlugin, cssPlugin, cssPostPlugin } from './css'
import { assetPlugin } from './asset'
@@ -20,9 +34,9 @@ import { preAliasPlugin } from './preAlias'
import { definePlugin } from './define'
import { workerImportMetaUrlPlugin } from './workerImportMetaUrl'
import { assetImportMetaUrlPlugin } from './assetImportMetaUrl'
import { metadataPlugin } from './metadata'
import { dynamicImportVarsPlugin } from './dynamicImportVars'
import { importGlobPlugin } from './importMetaGlob'
import { oxcPlugin } from './oxc'

export async function resolvePlugins(
config: ResolvedConfig,
@@ -41,50 +55,105 @@ export async function resolvePlugins(
Object.values(config.environments).some(
(environment) => !isDepOptimizationDisabled(environment.optimizeDeps),
)
const enableNativePlugin = config.experimental.enableNativePlugin

return [
depOptimizationEnabled ? optimizedDepsPlugin() : null,
isBuild ? metadataPlugin() : null,
!isWorker ? watchPackageDataPlugin(config.packageCache) : null,
preAliasPlugin(config),
aliasPlugin({
entries: config.resolve.alias,
customResolver: viteAliasCustomResolver,
}),
!isBuild ? preAliasPlugin(config) : null,
enableNativePlugin
? nativeAliasPlugin({
entries: config.resolve.alias.map((item) => {
return {
find: item.find,
replacement: item.replacement,
}
}),
})
: aliasPlugin({
// @ts-expect-error aliasPlugin receives rollup types
entries: config.resolve.alias,
customResolver: viteAliasCustomResolver,
}),

...prePlugins,

modulePreload !== false && modulePreload.polyfill
? modulePreloadPolyfillPlugin(config)
? enableNativePlugin
? perEnvironmentPlugin(
'native:modulepreload-polyfill',
(environment) => {
if (
config.command !== 'build' ||
environment.config.consumer !== 'client'
)
return false
return nativeModulePreloadPolyfillPlugin({
skip: false,
}) as unknown as Plugin
},
)
: modulePreloadPolyfillPlugin(config)
: null,
resolvePlugin({
root: config.root,
isProduction: config.isProduction,
isBuild,
packageCache: config.packageCache,
asSrc: true,
optimizeDeps: true,
externalize: true,
}),
...(enableNativePlugin
? oxcResolvePlugin(
{
root: config.root,
isProduction: config.isProduction,
isBuild,
packageCache: config.packageCache,
asSrc: true,
optimizeDeps: true,
externalize: true,
},
isWorker ? { ...config, consumer: 'client' } : undefined,
)
: [
resolvePlugin({
root: config.root,
isProduction: config.isProduction,
isBuild,
packageCache: config.packageCache,
asSrc: true,
optimizeDeps: true,
externalize: true,
}),
]),
htmlInlineProxyPlugin(config),
cssPlugin(config),
config.esbuild !== false ? esbuildPlugin(config) : null,
jsonPlugin(config.json, isBuild),
wasmHelperPlugin(),
config.oxc !== false
? enableNativePlugin
? nativeTransformPlugin()
: oxcPlugin(config)
: null,
enableNativePlugin
? nativeJsonPlugin({
...config.json,
isBuild,
})
: jsonPlugin(config.json, isBuild),
enableNativePlugin ? nativeWasmHelperPlugin() : wasmHelperPlugin(),
webWorkerPlugin(config),
assetPlugin(config),

...normalPlugins,

wasmFallbackPlugin(),
enableNativePlugin ? nativeWasmFallbackPlugin() : wasmFallbackPlugin(),
definePlugin(config),
cssPostPlugin(config),
isBuild && buildHtmlPlugin(config),
workerImportMetaUrlPlugin(config),
assetImportMetaUrlPlugin(config),
...buildPlugins.pre,
dynamicImportVarsPlugin(config),
importGlobPlugin(config),
enableNativePlugin
? nativeDynamicImportVarsPlugin()
: dynamicImportVarsPlugin(config),
enableNativePlugin
? nativeImportGlobPlugin({
root: config.root,
restoreQueryExtension: config.experimental.importGlobRestoreExtension,
})
: importGlobPlugin(config),

...postPlugins,

3 changes: 3 additions & 0 deletions packages/vite/src/node/plugins/json.ts
Original file line number Diff line number Diff line change
@@ -71,6 +71,7 @@ export function jsonPlugin(
return {
code,
map: { mappings: '' },
moduleType: 'js',
}
}

@@ -89,6 +90,7 @@ export function jsonPlugin(
return {
code: `export default /* #__PURE__ */ JSON.parse(${JSON.stringify(json)})`,
map: { mappings: '' },
moduleType: 'js',
}
}
}
@@ -99,6 +101,7 @@ export function jsonPlugin(
namedExports: options.namedExports,
}),
map: { mappings: '' },
moduleType: 'js',
}
} catch (e) {
const position = extractJsonErrorPosition(e.message, json.length)
34 changes: 21 additions & 13 deletions packages/vite/src/node/plugins/loadFallback.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import fsp from 'node:fs/promises'
import type { RolldownPlugin } from 'rolldown'
import { cleanUrl } from '../../shared/utils'
import type { Plugin } from '../plugin'

/**
* A plugin to provide build load fallback for arbitrary request with queries.
*/
export function buildLoadFallbackPlugin(): Plugin {
export function buildLoadFallbackPlugin(): RolldownPlugin {
return {
name: 'vite:load-fallback',
async load(id) {
try {
const cleanedId = cleanUrl(id)
const content = await fsp.readFile(cleanedId, 'utf-8')
this.addWatchFile(cleanedId)
return content
} catch {
const content = await fsp.readFile(id, 'utf-8')
this.addWatchFile(id)
return content
}
load: {
filter: {
id: {
include: /\?|#/,
exclude: [/^data:/],
},
},
async handler(id) {
try {
const cleanedId = cleanUrl(id)
const content = await fsp.readFile(cleanedId, 'utf-8')
this.addWatchFile(cleanedId)
return content
} catch {
const content = await fsp.readFile(id, 'utf-8')
this.addWatchFile(id)
return content
}
},
},
}
}
3 changes: 2 additions & 1 deletion packages/vite/src/node/plugins/manifest.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import type {
OutputAsset,
OutputChunk,
RenderedChunk,
} from 'rollup'
} from 'rolldown'
import type { Plugin } from '../plugin'
import { normalizePath, sortObjectKeys } from '../utils'
import { perEnvironmentState } from '../environment'
@@ -196,6 +196,7 @@ export function getChunkOriginalFileName(
): string | undefined {
if (chunk.facadeModuleId) {
let name = normalizePath(path.relative(root, chunk.facadeModuleId))
// @ts-expect-error TODO: system format is not supported
if (format === 'system' && !chunk.name.includes('-legacy')) {
const ext = path.extname(name)
const endPos = ext.length !== 0 ? -ext.length : undefined
18 changes: 0 additions & 18 deletions packages/vite/src/node/plugins/metadata.ts

This file was deleted.

665 changes: 665 additions & 0 deletions packages/vite/src/node/plugins/oxc.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/reporter.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import path from 'node:path'
import { gzip } from 'node:zlib'
import { promisify } from 'node:util'
import colors from 'picocolors'
import type { OutputBundle } from 'rollup'
import type { OutputBundle } from 'rolldown'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import type { Environment } from '../environment'
368 changes: 347 additions & 21 deletions packages/vite/src/node/plugins/resolve.ts

Large diffs are not rendered by default.

210 changes: 105 additions & 105 deletions packages/vite/src/node/plugins/splitVendorChunk.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type {
GetManualChunk,
GetModuleInfo,
ManualChunkMeta,
OutputOptions,
} from 'rollup'
import { arraify, isInNodeModules } from '../utils'
import type { UserConfig } from '../../node'
import type {} from // GetManualChunk,
// GetModuleInfo,
// ManualChunkMeta,
// OutputOptions,
'rolldown'
// import { arraify, isInNodeModules } from '../utils'
// import type { UserConfig } from '../../node'
import type { Plugin } from '../plugin'

// This file will be built for both ESM and CJS. Avoid relying on other modules as possible.
@@ -43,112 +42,113 @@ export class SplitVendorChunkCache {
* @deprecated use build.rollupOptions.output.manualChunks or framework specific configuration
*/
export function splitVendorChunk(
options: { cache?: SplitVendorChunkCache } = {},
): GetManualChunk {
const cache = options.cache ?? new SplitVendorChunkCache()
return (id, { getModuleInfo }) => {
if (
isInNodeModules(id) &&
!isCSSRequest(id) &&
staticImportedByEntry(id, getModuleInfo, cache.cache)
) {
return 'vendor'
}
}
_options: { cache?: SplitVendorChunkCache } = {},
): () => null /* : GetManualChunk */ {
// const cache = options.cache ?? new SplitVendorChunkCache()
// return (id, { getModuleInfo }) => {
// if (
// isInNodeModules(id) &&
// !isCSSRequest(id) &&
// staticImportedByEntry(id, getModuleInfo, cache.cache)
// ) {
// return 'vendor'
// }
// }
return () => null
}

function staticImportedByEntry(
id: string,
getModuleInfo: GetModuleInfo,
cache: Map<string, boolean>,
importStack: string[] = [],
): boolean {
if (cache.has(id)) {
return cache.get(id) as boolean
}
if (importStack.includes(id)) {
// circular deps!
cache.set(id, false)
return false
}
const mod = getModuleInfo(id)
if (!mod) {
cache.set(id, false)
return false
}
// function staticImportedByEntry(
// id: string,
// getModuleInfo: GetModuleInfo,
// cache: Map<string, boolean>,
// importStack: string[] = [],
// ): boolean {
// if (cache.has(id)) {
// return cache.get(id) as boolean
// }
// if (importStack.includes(id)) {
// // circular deps!
// cache.set(id, false)
// return false
// }
// const mod = getModuleInfo(id)
// if (!mod) {
// cache.set(id, false)
// return false
// }

if (mod.isEntry) {
cache.set(id, true)
return true
}
const someImporterIs = mod.importers.some((importer) =>
staticImportedByEntry(
importer,
getModuleInfo,
cache,
importStack.concat(id),
),
)
cache.set(id, someImporterIs)
return someImporterIs
}
// if (mod.isEntry) {
// cache.set(id, true)
// return true
// }
// const someImporterIs = mod.importers.some((importer) =>
// staticImportedByEntry(
// importer,
// getModuleInfo,
// cache,
// importStack.concat(id),
// ),
// )
// cache.set(id, someImporterIs)
// return someImporterIs
// }

/**
* @deprecated use build.rollupOptions.output.manualChunks or framework specific configuration
*/
export function splitVendorChunkPlugin(): Plugin {
const caches: SplitVendorChunkCache[] = []
function createSplitVendorChunk(output: OutputOptions, config: UserConfig) {
const cache = new SplitVendorChunkCache()
caches.push(cache)
const build = config.build ?? {}
const format = output.format
if (!build.ssr && !build.lib && format !== 'umd' && format !== 'iife') {
return splitVendorChunk({ cache })
}
}
// const caches: SplitVendorChunkCache[] = []
// function createSplitVendorChunk(output: OutputOptions, config: UserConfig) {
// const cache = new SplitVendorChunkCache()
// caches.push(cache)
// const build = config.build ?? {}
// const format = output.format
// if (!build.ssr && !build.lib && format !== 'umd' && format !== 'iife') {
// return splitVendorChunk({ cache })
// }
// }
return {
name: 'vite:split-vendor-chunk',
config(config) {
let outputs = config.build?.rollupOptions?.output
if (outputs) {
outputs = arraify(outputs)
for (const output of outputs) {
const viteManualChunks = createSplitVendorChunk(output, config)
if (viteManualChunks) {
if (output.manualChunks) {
if (typeof output.manualChunks === 'function') {
const userManualChunks = output.manualChunks
output.manualChunks = (id: string, api: ManualChunkMeta) => {
return userManualChunks(id, api) ?? viteManualChunks(id, api)
}
} else {
// else, leave the object form of manualChunks untouched, as
// we can't safely replicate rollup handling.
// eslint-disable-next-line no-console
console.warn(
"(!) the `splitVendorChunk` plugin doesn't have any effect when using the object form of `build.rollupOptions.output.manualChunks`. Consider using the function form instead.",
)
}
} else {
output.manualChunks = viteManualChunks
}
}
}
} else {
return {
build: {
rollupOptions: {
output: {
manualChunks: createSplitVendorChunk({}, config),
},
},
},
}
}
},
buildStart() {
caches.forEach((cache) => cache.reset())
},
// config(config) {
// let outputs = config.build?.rollupOptions?.output
// if (outputs) {
// outputs = arraify(outputs)
// for (const output of outputs) {
// const viteManualChunks = createSplitVendorChunk(output, config)
// if (viteManualChunks) {
// if (output.manualChunks) {
// if (typeof output.manualChunks === 'function') {
// const userManualChunks = output.manualChunks
// output.manualChunks = (id: string, api: ManualChunkMeta) => {
// return userManualChunks(id, api) ?? viteManualChunks(id, api)
// }
// } else {
// // else, leave the object form of manualChunks untouched, as
// // we can't safely replicate rollup handling.
// // eslint-disable-next-line no-console
// console.warn(
// "(!) the `splitVendorChunk` plugin doesn't have any effect when using the object form of `build.rollupOptions.output.manualChunks`. Consider using the function form instead.",
// )
// }
// } else {
// output.manualChunks = viteManualChunks
// }
// }
// }
// } else {
// return {
// build: {
// rollupOptions: {
// output: {
// manualChunks: createSplitVendorChunk({}, config),
// },
// },
// },
// }
// }
// },
// buildStart() {
// caches.forEach((cache) => cache.reset())
// },
}
}
349 changes: 195 additions & 154 deletions packages/vite/src/node/plugins/worker.ts

Large diffs are not rendered by default.

166 changes: 88 additions & 78 deletions packages/vite/src/node/plugins/workerImportMetaUrl.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import path from 'node:path'
import MagicString from 'magic-string'
import type { RollupAstNode, RollupError } from 'rollup'
import { parseAstAsync } from 'rollup/parseAst'
import type { RolldownPlugin, RollupError } from 'rolldown'
import type { RollupAstNode } from 'rollup'
import { stripLiteral } from 'strip-literal'
import type { Expression, ExpressionStatement } from 'estree'
import { parseAstAsync } from 'rolldown/parseAst'
import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
import { evalValue, injectQuery, transformStableResult } from '../utils'
import { createBackCompatIdResolver } from '../idResolver'
import type { ResolveIdFn } from '../idResolver'
@@ -192,7 +192,9 @@ function isIncludeWorkerImportMetaUrl(code: string): boolean {
return false
}

export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
export function workerImportMetaUrlPlugin(
config: ResolvedConfig,
): RolldownPlugin {
const isBuild = config.command === 'build'
let workerResolver: ResolveIdFn

@@ -208,88 +210,96 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
return {
name: 'vite:worker-import-meta-url',

shouldTransformCachedModule({ code }) {
if (isBuild && config.build.watch && isIncludeWorkerImportMetaUrl(code)) {
return true
}
},

async transform(code, id) {
if (
this.environment.config.consumer === 'client' &&
isIncludeWorkerImportMetaUrl(code)
) {
let s: MagicString | undefined
const cleanString = stripLiteral(code)
const workerImportMetaUrlRE =
/\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/dg

let match: RegExpExecArray | null
while ((match = workerImportMetaUrlRE.exec(cleanString))) {
const [[, endIndex], [expStart, expEnd], [urlStart, urlEnd]] =
match.indices!

const rawUrl = code.slice(urlStart, urlEnd)

// potential dynamic template string
if (rawUrl[0] === '`' && rawUrl.includes('${')) {
this.error(
`\`new URL(url, import.meta.url)\` is not supported in dynamic template string.`,
expStart,
)
}
// shouldTransformCachedModule({ code }) {
// if (isBuild && config.build.watch && isIncludeWorkerImportMetaUrl(code)) {
// return true
// }
// },

transform: {
filter: {
code: {
include: [/(?:new Worker|new SharedWorker)/],
},
},
async handler(code, id) {
if (
this.environment.config.consumer === 'client' &&
isIncludeWorkerImportMetaUrl(code)
) {
let s: MagicString | undefined
const cleanString = stripLiteral(code)
const workerImportMetaUrlRE =
/\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/dg

let match: RegExpExecArray | null
while ((match = workerImportMetaUrlRE.exec(cleanString))) {
const [[, endIndex], [expStart, expEnd], [urlStart, urlEnd]] =
match.indices!

const rawUrl = code.slice(urlStart, urlEnd)

// potential dynamic template string
if (rawUrl[0] === '`' && rawUrl.includes('${')) {
this.error(
`\`new URL(url, import.meta.url)\` is not supported in dynamic template string.`,
expStart,
)
}

s ||= new MagicString(code)
const workerType = await getWorkerType(code, cleanString, endIndex)
const url = rawUrl.slice(1, -1)
let file: string | undefined
if (url[0] === '.') {
file = path.resolve(path.dirname(id), url)
file = slash(tryFsResolve(file, fsResolveOptions) ?? file)
} else {
workerResolver ??= createBackCompatIdResolver(config, {
extensions: [],
tryIndex: false,
preferRelative: true,
})
file = await workerResolver(this.environment, url, id)
file ??=
url[0] === '/'
? slash(path.join(config.publicDir, url))
: slash(path.resolve(path.dirname(id), url))
}
s ||= new MagicString(code)
const workerType = await getWorkerType(code, cleanString, endIndex)
const url = rawUrl.slice(1, -1)
let file: string | undefined
if (url[0] === '.') {
file = path.resolve(path.dirname(id), url)
file = slash(tryFsResolve(file, fsResolveOptions) ?? file)
} else {
workerResolver ??= createBackCompatIdResolver(config, {
extensions: [],
tryIndex: false,
preferRelative: true,
})
file = await workerResolver(this.environment, url, id)
file ??=
url[0] === '/'
? slash(path.join(config.publicDir, url))
: slash(path.resolve(path.dirname(id), url))
}

if (
isBuild &&
config.isWorker &&
config.bundleChain.at(-1) === cleanUrl(file)
) {
s.update(expStart, expEnd, 'self.location.href')
} else {
let builtUrl: string
if (isBuild) {
builtUrl = await workerFileToUrl(config, file)
if (
isBuild &&
config.isWorker &&
config.bundleChain.at(-1) === cleanUrl(file)
) {
s.update(expStart, expEnd, 'self.location.href')
} else {
builtUrl = await fileToUrl(this, cleanUrl(file))
builtUrl = injectQuery(
builtUrl,
`${WORKER_FILE_ID}&type=${workerType}`,
let builtUrl: string
if (isBuild) {
builtUrl = await workerFileToUrl(config, file)
} else {
builtUrl = await fileToUrl(this, cleanUrl(file))
builtUrl = injectQuery(
builtUrl,
`${WORKER_FILE_ID}&type=${workerType}`,
)
}
s.update(
expStart,
expEnd,
// NOTE: add `'' +` to opt-out rolldown's transform: https://github.com/rolldown/rolldown/issues/2745
`new URL(/* @vite-ignore */ ${JSON.stringify(builtUrl)}, '' + import.meta.url)`,
)
}
s.update(
expStart,
expEnd,
`new URL(/* @vite-ignore */ ${JSON.stringify(builtUrl)}, import.meta.url)`,
)
}
}

if (s) {
return transformStableResult(s, id, config)
}
if (s) {
return transformStableResult(s, id, config)
}

return null
}
return null
}
},
},
}
}
3 changes: 2 additions & 1 deletion packages/vite/src/node/publicUtils.ts
Original file line number Diff line number Diff line change
@@ -11,7 +11,8 @@ export {
DEFAULT_SERVER_MAIN_FIELDS as defaultServerMainFields,
defaultAllowedOrigins,
} from './constants'
export { version as esbuildVersion } from 'esbuild'
// NOTE: export for backward compat
export const esbuildVersion = '0.24.2'
export {
splitVendorChunkPlugin,
splitVendorChunk,
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/hmr.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import path from 'node:path'
import { EventEmitter } from 'node:events'
import colors from 'picocolors'
import type { CustomPayload, HotPayload, Update } from 'types/hmrPayload'
import type { RollupError } from 'rollup'
import type { RollupError } from 'rolldown'
import type {
InvokeMethods,
InvokeResponseData,
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ import chokidar from 'chokidar'
import type { FSWatcher, WatchOptions } from 'dep-types/chokidar'
import type { Connect } from 'dep-types/connect'
import launchEditorMiddleware from 'launch-editor-middleware'
import type { SourceMap } from 'rollup'
import type { SourceMap } from 'rolldown'
import type { ModuleRunner } from 'vite/module-runner'
import type { CommonServerOptions } from '../http'
import {
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/middlewares/error.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'node:path'
import { stripVTControlCharacters as strip } from 'node:util'
import colors from 'picocolors'
import type { RollupError } from 'rollup'
import type { RollupError } from 'rolldown'
import type { Connect } from 'dep-types/connect'
import type { ErrorPayload } from 'types/hmrPayload'
import { pad } from '../../utils'
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/middlewares/indexHtml.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import fs from 'node:fs'
import fsp from 'node:fs/promises'
import path from 'node:path'
import MagicString from 'magic-string'
import type { SourceMapInput } from 'rollup'
import type { SourceMapInput } from 'rolldown'
import type { Connect } from 'dep-types/connect'
import type { DefaultTreeAdapterMap, Token } from 'parse5'
import type { IndexHtmlTransformHook } from '../../plugins/html'
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/middlewares/transform.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import path from 'node:path'
import fsp from 'node:fs/promises'
import type { Connect } from 'dep-types/connect'
import colors from 'picocolors'
import type { ExistingRawSourceMap } from 'rollup'
import type { ExistingRawSourceMap } from 'rolldown'
import type { ViteDevServer } from '..'
import {
createDebugger,
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/mixedModuleGraph.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ModuleInfo } from 'rollup'
import type { ModuleInfo } from 'rolldown'
import type { TransformResult } from './transformRequest'
import type {
EnvironmentModuleGraph,
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/moduleGraph.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { extname } from 'node:path'
import type { ModuleInfo, PartialResolvedId } from 'rollup'
import type { ModuleInfo, PartialResolvedId } from 'rolldown'
import { isDirectCSSRequest } from '../plugins/css'
import {
normalizePath,
35 changes: 26 additions & 9 deletions packages/vite/src/node/server/pluginContainer.ts
Original file line number Diff line number Diff line change
@@ -32,7 +32,6 @@ SOFTWARE.
import fs from 'node:fs'
import { join } from 'node:path'
import { performance } from 'node:perf_hooks'
import { parseAst as rollupParseAst } from 'rollup/parseAst'
import type {
AsyncPluginHooks,
CustomPluginOptions,
@@ -57,12 +56,14 @@ import type {
SourceDescription,
SourceMap,
TransformResult,
} from 'rollup'
} from 'rolldown'
import type { RawSourceMap } from '@ampproject/remapping'
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'
import MagicString from 'magic-string'
import type { FSWatcher } from 'dep-types/chokidar'
import colors from 'picocolors'
import { parseAst as rolldownParseAst } from 'rolldown/parseAst'
import type { Program } from '@oxc-project/types'
import type { Plugin } from '../plugin'
import {
combineSourcemaps,
@@ -74,6 +75,7 @@ import {
normalizePath,
numberToPos,
prettifyUrl,
rolldownVersion,
rollupVersion,
timeFrom,
} from '../utils'
@@ -189,7 +191,7 @@ class EnvironmentPluginContainer {
) {
this._started = !autoStart
this.minimalContext = new MinimalPluginContext(
{ rollupVersion, watchMode: true },
{ rollupVersion, rolldownVersion, watchMode: true },
environment,
)
const utils = createPluginHookUtils(plugins)
@@ -349,6 +351,7 @@ class EnvironmentPluginContainer {
*/
scan?: boolean
isEntry?: boolean
kind?: 'import' | 'dynamic-import' | 'require-call'
},
): Promise<PartialResolvedId | null> {
if (!this._started) {
@@ -359,6 +362,7 @@ class EnvironmentPluginContainer {
const skipCalls = options?.skipCalls
const scan = !!options?.scan
const ssr = this.environment.config.consumer === 'server'
const kind = options?.kind
const ctx = new ResolveIdContext(this, skip, skipCalls, scan)

const mergedSkip = new Set<Plugin>(skip)
@@ -387,6 +391,7 @@ class EnvironmentPluginContainer {
isEntry: !!options?.isEntry,
ssr,
scan,
kind,
}),
)
if (!result) continue
@@ -461,7 +466,9 @@ class EnvironmentPluginContainer {
},
): Promise<{ code: string; map: SourceMap | { mappings: '' } | null }> {
const ssr = this.environment.config.consumer === 'server'
const optionsWithSSR = options ? { ...options, ssr } : { ssr }
const optionsWithSSR = options
? { ...options, ssr, moduleType: 'js' }
: { ssr, moduleType: 'js' }
const inMap = options?.inMap

const ctx = new TransformPluginContext(this, id, code, inMap as SourceMap)
@@ -549,6 +556,12 @@ class MinimalPluginContext implements RollupMinimalPluginContext {
public environment: Environment,
) {}

// FIXME: properly support this later
// eslint-disable-next-line @typescript-eslint/class-literal-property-style
get pluginName() {
return ''
}

debug(rawLog: string | RollupLog | (() => string | RollupLog)): void {
const log = this._normalizeRawLog(rawLog)
const msg = buildErrorMessage(log, [`debug: ${log.message}`], false)
@@ -586,7 +599,7 @@ class MinimalPluginContext implements RollupMinimalPluginContext {

class PluginContext
extends MinimalPluginContext
implements Omit<RollupPluginContext, 'cache'>
implements Omit<RollupPluginContext, 'cache' | 'emitChunk'>
{
ssr = false
_scan = false
@@ -595,15 +608,19 @@ class PluginContext
_resolveSkips?: Set<Plugin>
_resolveSkipCalls?: readonly SkipInformation[]

override get pluginName() {
return this._plugin.name
}

constructor(
public _plugin: Plugin,
public _container: EnvironmentPluginContainer,
) {
super(_container.minimalContext.meta, _container.environment)
}

parse(code: string, opts: any) {
return rollupParseAst(code, opts)
parse(code: string, opts: any): Program {
return rolldownParseAst(code, opts)
}

async resolve(
@@ -899,7 +916,7 @@ class LoadPluginContext extends PluginContext {

class TransformPluginContext
extends LoadPluginContext
implements Omit<RollupTransformPluginContext, 'cache'>
implements Omit<RollupTransformPluginContext, 'cache' | 'emitChunk'>
{
filename: string
originalCode: string
@@ -997,7 +1014,7 @@ class TransformPluginContext
includeContent: true,
hires: 'boundary',
source: cleanUrl(this.filename),
})
}) as SourceMap
}
return map
}
4 changes: 2 additions & 2 deletions packages/vite/src/node/server/send.ts
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import type {
import path from 'node:path'
import convertSourceMap from 'convert-source-map'
import getEtag from 'etag'
import type { SourceMap } from 'rollup'
import type { SourceMap } from 'rolldown'
import MagicString from 'magic-string'
import { createDebugger, removeTimestampQuery } from '../utils'
import { getCodeWithSourcemap } from './sourcemap'
@@ -86,7 +86,7 @@ export function send(
source: path.basename(urlWithoutTimestamp),
hires: 'boundary',
includeContent: true,
}),
}) as SourceMap,
)
}
}
49 changes: 26 additions & 23 deletions packages/vite/src/node/server/sourcemap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'node:path'
import fsp from 'node:fs/promises'
import convertSourceMap from 'convert-source-map'
import type { ExistingRawSourceMap, SourceMap } from 'rollup'
import type { ExistingRawSourceMap, SourceMap } from 'rolldown'
import type { Logger } from '../logger'
import { blankReplacer, createDebugger } from '../utils'
import { cleanUrl } from '../../shared/utils'
@@ -118,31 +118,34 @@ export function applySourcemapIgnoreList(
if (x_google_ignoreList === undefined) {
x_google_ignoreList = []
}
for (
let sourcesIndex = 0;
sourcesIndex < map.sources.length;
++sourcesIndex
) {
const sourcePath = map.sources[sourcesIndex]
if (!sourcePath) continue

const ignoreList = sourcemapIgnoreList(
path.isAbsolute(sourcePath)
? sourcePath
: path.resolve(path.dirname(sourcemapPath), sourcePath),
sourcemapPath,
)
if (logger && typeof ignoreList !== 'boolean') {
logger.warn('sourcemapIgnoreList function must return a boolean.')
}
if (map.sources) {
for (
let sourcesIndex = 0;
sourcesIndex < map.sources.length;
++sourcesIndex
) {
const sourcePath = map.sources[sourcesIndex]
if (!sourcePath) continue

const ignoreList = sourcemapIgnoreList(
path.isAbsolute(sourcePath)
? sourcePath
: path.resolve(path.dirname(sourcemapPath), sourcePath),
sourcemapPath,
)
if (logger && typeof ignoreList !== 'boolean') {
logger.warn('sourcemapIgnoreList function must return a boolean.')
}

if (ignoreList && !x_google_ignoreList.includes(sourcesIndex)) {
x_google_ignoreList.push(sourcesIndex)
if (ignoreList && !x_google_ignoreList.includes(sourcesIndex)) {
x_google_ignoreList.push(sourcesIndex)
}
}
}

if (x_google_ignoreList.length > 0) {
if (!map.x_google_ignoreList) map.x_google_ignoreList = x_google_ignoreList
if (x_google_ignoreList.length > 0) {
if (!map.x_google_ignoreList)
map.x_google_ignoreList = x_google_ignoreList
}
}
}

2 changes: 1 addition & 1 deletion packages/vite/src/node/server/transformRequest.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import { performance } from 'node:perf_hooks'
import getEtag from 'etag'
import MagicString from 'magic-string'
import { init, parse as parseImports } from 'es-module-lexer'
import type { PartialResolvedId, SourceDescription, SourceMap } from 'rollup'
import type { PartialResolvedId, SourceDescription, SourceMap } from 'rolldown'
import colors from 'picocolors'
import type { EnvironmentModuleNode } from '../server/moduleGraph'
import {
23 changes: 16 additions & 7 deletions packages/vite/src/node/server/ws.ts
Original file line number Diff line number Diff line change
@@ -10,7 +10,11 @@ import colors from 'picocolors'
import type { WebSocket as WebSocketRaw } from 'ws'
import { WebSocketServer as WebSocketServerRaw_ } from 'ws'
import type { WebSocket as WebSocketTypes } from 'dep-types/ws'
import type { ErrorPayload, HotPayload } from 'types/hmrPayload'
import type {
ErrorPayload,
FullReloadPayload,
HotPayload,
} from 'types/hmrPayload'
import type { InferCustomEventPayload } from 'types/customEvent'
import type { ResolvedConfig } from '..'
import { isObject } from '../utils'
@@ -297,9 +301,9 @@ export function createWebSocketServer(
})
})
socket.send(JSON.stringify({ type: 'connected' }))
if (bufferedError) {
socket.send(JSON.stringify(bufferedError))
bufferedError = null
if (bufferedMessage) {
socket.send(JSON.stringify(bufferedMessage))
bufferedMessage = null
}
})

@@ -345,13 +349,18 @@ export function createWebSocketServer(
// sends the error payload before the client connection is established.
// If we have no open clients, buffer the error and send it to the next
// connected client.
let bufferedError: ErrorPayload | null = null
// The same thing may happen when the optimizer runs fast enough to
// finish the bundling before the client connects.
let bufferedMessage: ErrorPayload | FullReloadPayload | null = null

const normalizedHotChannel = normalizeHotChannel(
{
send(payload) {
if (payload.type === 'error' && !wss.clients.size) {
bufferedError = payload
if (
(payload.type === 'error' || payload.type === 'full-reload') &&
!wss.clients.size
) {
bufferedMessage = payload
return
}

4 changes: 3 additions & 1 deletion packages/vite/src/node/ssr/__tests__/ssrLoadModule.spec.ts
Original file line number Diff line number Diff line change
@@ -181,7 +181,9 @@ test('can access nodejs global', async () => {
expect(mod.default).toBe(globalThis)
})

test('parse error', async () => {
// skip for now as oxc returns different error message from esbuild
// related: https://github.com/oxc-project/oxc/issues/7261
test.skip('parse error', async () => {
const server = await createDevServer()

function stripRoot(s?: string) {
2 changes: 1 addition & 1 deletion packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { assert, expect, test } from 'vitest'
import type { SourceMap } from 'rollup'
import type { SourceMap } from 'rolldown'
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'
import { transformWithEsbuild } from '../../plugins/esbuild'
import { ssrTransform } from '../ssrTransform'
2 changes: 1 addition & 1 deletion packages/vite/src/node/ssr/ssrManifestPlugin.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import type {
ParseError as EsModuleLexerParseError,
ImportSpecifier,
} from 'es-module-lexer'
import type { OutputChunk } from 'rollup'
import type { OutputChunk } from 'rolldown'
import type { Plugin } from '../plugin'
import { preloadMethod } from '../plugins/importAnalysisBuild'
import {
10 changes: 5 additions & 5 deletions packages/vite/src/node/ssr/ssrTransform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'node:path'
import MagicString from 'magic-string'
import type { RollupAstNode, SourceMap } from 'rollup'
import type { RollupAstNode } from 'rollup'
import type { SourceMap } from 'rolldown'
import type {
ExportAllDeclaration,
ExportDefaultDeclaration,
@@ -17,7 +18,7 @@ import type {
import { extract_names as extractNames } from 'periscopic'
import { walk as eswalk } from 'estree-walker'
import type { RawSourceMap } from '@ampproject/remapping'
import { parseAstAsync as rollupParseAstAsync } from 'rollup/parseAst'
import { parseAstAsync as rolldownParseAstAsync } from 'rolldown/parseAst'
import type { TransformResult } from '../server/transformRequest'
import {
combineSourcemaps,
@@ -80,10 +81,9 @@ async function ssrTransformScript(
originalCode: string,
): Promise<TransformResult | null> {
const s = new MagicString(code)

let ast: any
try {
ast = await rollupParseAstAsync(code)
ast = await rolldownParseAstAsync(code)
} catch (err) {
// enhance known rollup errors
// https://github.com/rollup/rollup/blob/42e587e0e37bc0661aa39fe7ad6f1d7fd33f825c/src/utils/bufferToAst.ts#L17-L22
@@ -423,7 +423,7 @@ async function ssrTransformScript(
if (inMap?.mappings === '') {
map = inMap
} else {
map = s.generateMap({ hires: 'boundary' })
map = s.generateMap({ hires: 'boundary' }) as SourceMap
map.sources = [path.basename(url)]
// needs to use originalCode instead of code
// because code might be already transformed even if map is null
2 changes: 1 addition & 1 deletion packages/vite/src/node/typeUtils.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import type {
ObjectHook,
MinimalPluginContext as RollupMinimalPluginContext,
Plugin as RollupPlugin,
} from 'rollup'
} from 'rolldown'

export type NonNeverKeys<T> = {
[K in keyof T]: T[K] extends never ? never : K
11 changes: 7 additions & 4 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ import type { Alias, AliasOptions } from 'dep-types/alias'
import type MagicString from 'magic-string'
import type { Equal } from '@type-challenges/utils'

import type { TransformResult } from 'rollup'
import type { TransformResult } from 'rolldown'
import { createFilter as _createFilter } from '@rollup/pluginutils'
import {
cleanUrl,
@@ -180,9 +180,12 @@ const _require = createRequire(import.meta.url)

const _dirname = path.dirname(fileURLToPath(import.meta.url))

// NOTE: we don't use VERSION variable exported from rollup to avoid importing rollup in dev
export const rollupVersion =
resolvePackageData('rollup', _dirname, true)?.data.version ?? ''
// https://github.com/rolldown/rolldown/blob/7bc51f099a916dbe31bc0582995c58cf0d0f8924/packages/rolldown/src/log/logger.ts#L67
export const rollupVersion = '4.23.0'

// NOTE: we don't use VERSION variable exported from rolldown to avoid importing rolldown in dev
export const rolldownVersion =
resolvePackageData('rolldown', _dirname, true)?.data.version ?? ''

// set in bin/vite.js
const filter = process.env.VITE_DEBUG_FILTER
2 changes: 1 addition & 1 deletion packages/vite/src/node/watch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EventEmitter } from 'node:events'
import path from 'node:path'
import type { FSWatcher, WatchOptions } from 'dep-types/chokidar'
import type { OutputOptions } from 'rollup'
import type { OutputOptions } from 'rolldown'
import colors from 'picocolors'
import { escapePath } from 'tinyglobby'
import { withTrailingSlash } from '../shared/utils'
6 changes: 3 additions & 3 deletions packages/vite/src/types/alias.d.ts
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

import type { PluginHooks } from 'rollup'
import type { FunctionPluginHooks } from 'rolldown'

export interface Alias {
find: string | RegExp
@@ -42,10 +42,10 @@ export interface Alias {

export type MapToFunction<T> = T extends Function ? T : never

export type ResolverFunction = MapToFunction<PluginHooks['resolveId']>
export type ResolverFunction = MapToFunction<FunctionPluginHooks['resolveId']>

export interface ResolverObject {
buildStart?: PluginHooks['buildStart']
buildStart?: FunctionPluginHooks['buildStart']
resolveId: ResolverFunction
}

28 changes: 28 additions & 0 deletions packages/vite/types/internal/esbuildOptions.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */

// @ts-ignore `esbuild` may not be installed
import type esbuild from 'esbuild'

/* eslint-enable @typescript-eslint/ban-ts-comment */

export type EsbuildTarget = string | string[]

export type EsbuildLoader = esbuild.Loader
export type EsbuildTransformOptions = esbuild.TransformOptions
export type EsbuildTransformResult = esbuild.TransformResult

export type EsbuildMessage = esbuild.Message

export type DepsOptimizerEsbuildOptions = Omit<
esbuild.BuildOptions,
| 'bundle'
| 'entryPoints'
| 'external'
| 'write'
| 'watch'
| 'outdir'
| 'outfile'
| 'outbase'
| 'outExtension'
| 'metafile'
>
6 changes: 5 additions & 1 deletion packages/vite/types/metadata.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
export interface ChunkMetadata {
importedAssets: Set<string>
importedCss: Set<string>
__modules: any
}

declare module 'rollup' {
declare module 'rolldown' {
export interface RenderedChunk {
viteMetadata?: ChunkMetadata
}
export interface OutputChunk {
viteMetadata?: ChunkMetadata
}
}
3 changes: 2 additions & 1 deletion playground/assets/__tests__/assets.spec.ts
Original file line number Diff line number Diff line change
@@ -626,7 +626,8 @@ test.runIf(isBuild)('manifest', async () => {
}
})

describe.runIf(isBuild)('css and assets in css in build watch', () => {
// TODO: rolldown does not support rebuild
describe.runIf(isBuild).skip('css and assets in css in build watch', () => {
test('css will not be lost and css does not contain undefined', async () => {
editFile('index.html', (code) => code.replace('Assets', 'assets'))
await notifyRebuildComplete(watcher)
3 changes: 2 additions & 1 deletion playground/assets/index.html
Original file line number Diff line number Diff line change
@@ -627,9 +627,10 @@ <h3>assets in template</h3>
import someString from './static/foo.txt?raw'
document.querySelector('.raw-query').textContent = someString

// NOTE: add `'' +` to opt-out rolldown's transform: https://github.com/rolldown/rolldown/issues/2745
const metaUrlNonExistent = new URL(
/* @vite-ignore */ 'non-existent',
import.meta.url,
'' + import.meta.url,
).pathname
text('.non-existent-import-meta-url', metaUrlNonExistent)

Original file line number Diff line number Diff line change
@@ -56,10 +56,13 @@ describe.runIf(isBuild)('build', () => {
const imgAssetEntry = manifest['../images/logo.png']
const dirFooAssetEntry = manifest['../../dir/foo.css']
const iconEntrypointEntry = manifest['icon.png']
expect(htmlEntry.css.length).toEqual(1)
expect(htmlEntry.css.length).toEqual(2)
expect(htmlEntry.assets.length).toEqual(1)
expect(mainTsEntry.assets?.length ?? 0).toBeGreaterThanOrEqual(1)
expect(mainTsEntry.assets).toContainEqual(
expect(mainTsEntry.imports.length).toBeGreaterThanOrEqual(1)
const mainTsEntryImported = manifest[mainTsEntry.imports[0]]
expect(mainTsEntryImported).toBeDefined()
expect(mainTsEntryImported.assets?.length ?? 0).toBeGreaterThanOrEqual(1)
expect(mainTsEntryImported.assets).toContainEqual(
expect.stringMatching(/assets\/url-[-\w]{8}\.css/),
)
expect(cssAssetEntry?.file).not.toBeUndefined()
Original file line number Diff line number Diff line change
@@ -8,8 +8,8 @@ beforeEach(async () => {
for (let i = 0; i < 5; i++) {
describe.runIf(isBuild)('css-codesplit build', () => {
test('should be consistent with same content', () => {
expect(findAssetFile(/style-.+\.css/)).toMatch('h2{color:#00f}')
expect(findAssetFile(/style2-.+\.css/)).toBe('')
expect(findAssetFile(/style2-.+\.css/)).toMatch('h2{color:#00f}')
expect(findAssetFile(/style-.+\.css/)).toBe('')
})
})
}
2 changes: 1 addition & 1 deletion playground/css-codesplit/__tests__/css-codesplit.spec.ts
Original file line number Diff line number Diff line change
@@ -55,7 +55,7 @@ describe.runIf(isBuild)('build', () => {
expect(sharedCSSWithJSChunk).toMatch(`/* empty css`)
// there are functions and modules in the src code that should be tree-shaken
expect(sharedCSSWithJSChunk).not.toMatch('function')
expect(sharedCSSWithJSChunk).not.toMatch(/import(?!".\/modulepreload)/)
expect(sharedCSSWithJSChunk).not.toMatch(/import(?!\s*".\/modulepreload)/)
})

test('should generate correct manifest', async () => {
26 changes: 21 additions & 5 deletions playground/css-codesplit/vite.config.js
Original file line number Diff line number Diff line change
@@ -12,12 +12,28 @@ export default defineConfig({
'shared-css-with-js': resolve(__dirname, 'shared-css-with-js.html'),
'shared-css-no-js': resolve(__dirname, 'shared-css-no-js.html'),
},
experimental: {
// set this to keep the previous chunking behavior to make tests pass easier
// as some tests relies on the chunking behavior
// (using advancedChunks enable this)
// related: https://github.com/vitejs/vite/pull/18652
strictExecutionOrder: false,
},
output: {
manualChunks(id) {
// make `chunk.css` it's own chunk for easier testing of pure css chunks
if (id.includes('chunk.css')) {
return 'chunk'
}
// manualChunks(id) {
// // make `chunk.css` it's own chunk for easier testing of pure css chunks
// if (id.includes('chunk.css')) {
// return 'chunk'
// }
// },
advancedChunks: {
groups: [
// make `chunk.css` it's own chunk for easier testing of pure css chunks
{
name: 'chunk',
test: 'chunk.css',
},
],
},
},
},
10 changes: 5 additions & 5 deletions playground/css/__tests__/css.spec.ts
Original file line number Diff line number Diff line change
@@ -506,9 +506,10 @@ test.runIf(isBuild)('Scoped CSS via cssScopeTo should be treeshaken', () => {
expect(css).not.toContain('treeshake-module-c')
})

test.runIf(isBuild)(
'Scoped CSS via cssScopeTo should be bundled separately',
() => {
// TODO: rolldown does not split sideeffect free barrel files https://github.com/rolldown/rolldown/issues/3746
test
.runIf(isBuild)
.skip('Scoped CSS via cssScopeTo should be bundled separately', () => {
const scopedIndexCss = findAssetFile(/treeshakeScoped-[-\w]{8}\.css$/)
expect(scopedIndexCss).toContain('treeshake-scoped-barrel-a')
expect(scopedIndexCss).not.toContain('treeshake-scoped-barrel-b')
@@ -517,5 +518,4 @@ test.runIf(isBuild)(
)
expect(scopedAnotherCss).toContain('treeshake-scoped-barrel-b')
expect(scopedAnotherCss).not.toContain('treeshake-scoped-barrel-a')
},
)
})
2 changes: 2 additions & 0 deletions playground/dynamic-import/__tests__/dynamic-import.spec.ts
Original file line number Diff line number Diff line change
@@ -158,6 +158,8 @@ test('should work a load path that contains parentheses.', async () => {

test.runIf(isBuild)(
'should rollup warn when static and dynamic import a module in same chunk',
// NOTE: this is a warning related to rollup's chunking behavior
{ skip: true },
async () => {
const log = serverLogs.join('\n')
expect(log).toContain(
1 change: 1 addition & 0 deletions playground/external/src/main.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import './require-polyfill'
import '@vitejs/test-dep-that-imports'
import '@vitejs/test-dep-that-requires'
7 changes: 7 additions & 0 deletions playground/external/src/require-polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as vue from 'vue'
import slash3 from 'slash3'
globalThis.require = (dep) => {
if (dep === 'vue') return vue
if (dep === 'slash3') return slash3
throw new Error(`Cannot require "${dep}"`)
}
13 changes: 7 additions & 6 deletions playground/js-sourcemap/__tests__/js-sourcemap.spec.ts
Original file line number Diff line number Diff line change
@@ -84,7 +84,7 @@ if (!isBuild) {
const map = extractSourcemap(js)
expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(`
{
"mappings": "AAAO,aAAM,MAAM;",
"mappings": "AAAA,OAAO,MAAM,MAAM",
"sources": [
"bar.ts",
],
@@ -105,7 +105,7 @@ if (!isBuild) {
const map = extractSourcemap(multi)
expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(`
{
"mappings": "AACA;AAAA,EACE;AAAA,OACK;AAEP,QAAQ,IAAI,yBAAyB,GAAG;",
"mappings": "AACA,SACE,WACK,2BAA2B;AAElC,QAAQ,IAAI,yBAAyB,IAAI",
"sources": [
"with-multiline-import.ts",
],
@@ -143,7 +143,7 @@ describe.runIf(isBuild)('build tests', () => {
{
"debugId": "00000000-0000-0000-0000-000000000000",
"ignoreList": [],
"mappings": ";+8BAAA,OAAO,2BAAuB,0BAE9B,QAAQ,IAAI,uBAAuB",
"mappings": ";sjCAEA,MAFA,OAAO,6BAAuB,wBAE9B,QAAQ,IAAI,wBAAuB",
"sources": [
"../../after-preload-dynamic.js",
],
@@ -159,7 +159,7 @@ describe.runIf(isBuild)('build tests', () => {
// verify sourcemap comment is preserved at the last line
const js = findAssetFile(/after-preload-dynamic-[-\w]{8}\.js$/)
expect(js).toMatch(
/\n\/\/# sourceMappingURL=after-preload-dynamic-[-\w]{8}\.js\.map\n$/,
/\n\/\/# sourceMappingURL=after-preload-dynamic-[-\w]{8}\.js\.map\n?$/,
)
})

@@ -176,12 +176,13 @@ describe.runIf(isBuild)('build tests', () => {
expect(js).not.toMatch(/__vite__mapDeps/)
})

test('sourcemap is correct when using object as "define" value', async () => {
// NOTE: this test is not relevant to oxc
test.skip('sourcemap is correct when using object as "define" value', async () => {
const map = findAssetFile(/with-define-object.*\.js\.map/)
expect(formatSourcemapForSnapshot(JSON.parse(map))).toMatchInlineSnapshot(`
{
"debugId": "00000000-0000-0000-0000-000000000000",
"mappings": "qBAEA,SAASA,GAAO,CACJC,EAAA,CACZ,CAEA,SAASA,GAAY,CAEX,QAAA,MAAM,qBAAsBC,CAAkB,CACxD,CAEAF,EAAK",
"mappings": "qBAEA,SAAS,GAAO,CACd,EAAA,CACD,CAED,SAAS,GAAY,CAEnB,QAAQ,MAAM,qBAAsB,CAAA,CACrC,CAED,EAAA",
"sources": [
"../../with-define-object.ts",
],
47 changes: 34 additions & 13 deletions playground/js-sourcemap/vite.config.js
Original file line number Diff line number Diff line change
@@ -10,20 +10,41 @@ export default defineConfig({
build: {
sourcemap: true,
rollupOptions: {
experimental: {
// set this to keep the previous chunking behavior to make tests pass easier
// as some tests relies on the chunking behavior
// (using advancedChunks enable this)
// related: https://github.com/vitejs/vite/pull/18652
strictExecutionOrder: false,
},
output: {
manualChunks(name) {
if (name.endsWith('after-preload-dynamic.js')) {
return 'after-preload-dynamic'
}
if (name.endsWith('after-preload-dynamic-hashbang.js')) {
return 'after-preload-dynamic-hashbang'
}
if (name.endsWith('after-preload-dynamic-no-dep.js')) {
return 'after-preload-dynamic-no-dep'
}
if (name.includes('with-define-object')) {
return 'with-define-object'
}
// manualChunks(name) {
// if (name.endsWith('after-preload-dynamic.js')) {
// return 'after-preload-dynamic'
// }
// if (name.endsWith('after-preload-dynamic-hashbang.js')) {
// return 'after-preload-dynamic-hashbang'
// }
// if (name.endsWith('after-preload-dynamic-no-dep.js')) {
// return 'after-preload-dynamic-no-dep'
// }
// if (name.includes('with-define-object')) {
// return 'with-define-object'
// }
// },
advancedChunks: {
groups: [
{ name: 'after-preload-dynamic', test: 'after-preload-dynamic.js' },
{
name: 'after-preload-dynamic-hashbang',
test: 'after-preload-dynamic-hashbang.js',
},
{
name: 'after-preload-dynamic-no-dep',
test: 'after-preload-dynamic-no-dep.js',
},
{ name: 'with-define-object', test: 'with-define-object' },
],
},
banner(chunk) {
if (chunk.name.endsWith('after-preload-dynamic-hashbang')) {
10 changes: 5 additions & 5 deletions playground/lib/__tests__/lib.spec.ts
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ describe.runIf(isBuild)('build', () => {
// esbuild helpers are injected inside of the UMD wrapper
expect(code).toMatch(/^\(function\(/)
expect(noMinifyCode).toMatch(
/^\(function\(global.+?"use strict";var.+?function\smyLib\(/s,
/^\/\*[^*]*\*\/\s*\(function\(global.+?"use strict";\s*var.+?function\smyLib\(/s,
)
expect(namedCode).toMatch(/^\(function\(/)
})
@@ -39,7 +39,7 @@ describe.runIf(isBuild)('build', () => {
// esbuild helpers are injected inside of the IIFE wrapper
expect(code).toMatch(/^var MyLib=function\(\)\{\s*"use strict";/)
expect(noMinifyCode).toMatch(
/^var MyLib\s*=\s*function\(\)\s*\{\s*"use strict";/,
/^\/\*[^*]*\*\/\s*var MyLib\s*=\s*function\(\)\s*\{\s*"use strict";/,
)
expect(namedCode).toMatch(
/^var MyLibNamed=function\([^()]+\)\{\s*"use strict";/,
@@ -51,7 +51,7 @@ describe.runIf(isBuild)('build', () => {
'dist/helpers-injection/my-lib-custom-filename.iife.js',
)
expect(code).toMatch(
`'"use strict"; return (' + expressionSyntax + ").constructor;"`,
`\\"use strict\\"; return (" + expressionSyntax + ").constructor;"`,
)
})

@@ -64,14 +64,14 @@ describe.runIf(isBuild)('build', () => {
expect(code).not.toMatch('__vitePreload')

// Test that library chunks are hashed
expect(code).toMatch(/await import\("\.\/message-[-\w]{8}.js"\)/)
expect(code).toMatch(/await import\(`\.\/message-[-\w]{8}.js`\)/)
})

test('Library mode does not have any reference to pure CSS chunks', async () => {
const code = readFile('dist/lib/dynamic-import-message.es.mjs')

// Does not import pure CSS chunks and replaced by `Promise.resolve({})` instead
expect(code).not.toMatch(/await import\("\.\/dynamic-[-\w]{8}.js"\)/)
expect(code).not.toMatch(/await import\(`\.\/dynamic-[-\w]{8}.js`\)/)
expect(code).toMatch(/await Promise.resolve\(\{.*\}\)/)
})

2 changes: 1 addition & 1 deletion playground/minify/__tests__/minify.spec.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ test.runIf(isBuild)('no minifySyntax', () => {
const cssFile = files.find((f) => f.endsWith('.css'))
const cssContent = readFile(path.resolve(assetsDir, cssFile))

expect(jsContent).toContain('{console.log("hello world")}')
// expect(jsContent).toContain('{console.log("hello world")}')
expect(jsContent).not.toContain('/*! explicit comment */')

expect(cssContent).toContain('color:#ff0000')
3 changes: 3 additions & 0 deletions playground/minify/vite.config.js
Original file line number Diff line number Diff line change
@@ -5,4 +5,7 @@ export default defineConfig({
legalComments: 'none',
minifySyntax: false,
},
build: {
cssMinify: 'esbuild',
},
})
3 changes: 2 additions & 1 deletion playground/optimize-deps/dep-incompatible/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const subUrl = new URL('./sub.js', import.meta.url)
const target = './sub.js'
const subUrl = new URL(target, import.meta.url)

export default () => import(subUrl)
46 changes: 11 additions & 35 deletions playground/optimize-deps/vite.config.js
Original file line number Diff line number Diff line change
@@ -25,18 +25,14 @@ export default defineConfig({
'@vitejs/test-dep-optimize-with-glob/**/*.js',
],
exclude: ['@vitejs/test-nested-exclude', '@vitejs/test-dep-non-optimized'],
esbuildOptions: {
rollupOptions: {
plugins: [
{
name: 'replace-a-file',
setup(build) {
build.onLoad(
{ filter: /dep-esbuild-plugin-transform(\\|\/)index\.js$/ },
() => ({
contents: `export const hello = () => 'Hello from an esbuild plugin'`,
loader: 'js',
}),
)
load(id) {
if (/dep-esbuild-plugin-transform(?:\\|\/)index\.js$/.test(id)) {
return `export const hello = () => 'Hello from an esbuild plugin'`
}
},
},
],
@@ -47,13 +43,6 @@ export default defineConfig({
build: {
// to make tests faster
minify: false,
rollupOptions: {
onwarn(msg, warn) {
// filter `"Buffer" is not exported by "__vite-browser-external"` warning
if (msg.message.includes('Buffer')) return
warn(msg)
},
},
},

plugins: [
@@ -84,19 +73,6 @@ export default defineConfig({
}
},
},
// TODO: Remove this one support for prebundling in build lands.
// It is expected that named importing in build doesn't work
// as it incurs a lot of overhead in build.
{
name: 'polyfill-named-fs-build',
apply: 'build',
enforce: 'pre',
load(id) {
if (id === '__vite-browser-external') {
return `export default {}; export function readFileSync() {}`
}
},
},
],
})

@@ -136,18 +112,18 @@ function notjs() {
return {
optimizeDeps: {
extensions: ['.notjs'],
esbuildOptions: {
rollupOptions: {
plugins: [
{
name: 'esbuild-notjs',
setup(build) {
build.onLoad({ filter: /\.notjs$/ }, ({ path }) => {
let contents = fs.readFileSync(path, 'utf-8')
load(id) {
if (id.endsWith('.notjs')) {
let contents = fs.readFileSync(id, 'utf-8')
contents = contents
.replace('<notjs>', '')
.replace('</notjs>', '')
return { contents, loader: 'js' }
})
return contents
}
},
},
],
6 changes: 4 additions & 2 deletions playground/resolve/__tests__/resolve.spec.ts
Original file line number Diff line number Diff line change
@@ -139,11 +139,13 @@ test('Resolve browser field even if module field exists', async () => {
expect(await page.textContent('.browser-module1')).toMatch('[success]')
})

test('Resolve module field if browser field is likely UMD or CJS', async () => {
// should not fallback
test.skip('Resolve module field if browser field is likely UMD or CJS', async () => {
expect(await page.textContent('.browser-module2')).toMatch('[success]')
})

test('Resolve module field if browser field is likely IIFE', async () => {
// should not fallback
test.skip('Resolve module field if browser field is likely IIFE', async () => {
expect(await page.textContent('.browser-module3')).toMatch('[success]')
})

5 changes: 2 additions & 3 deletions playground/resolve/browser-field/relative.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import rb from './no-ext.js' // no substitution
import rc from './ext'
import rd from './ext.js'
import re from './ext-index/index.js'
import rf from './ext-index'
import rg from './no-ext-index/index.js' // no substitution
import rf from './no-ext-index/index.js' // no substitution

export { ra, rb, rc, rd, re, rf, rg }
export { ra, rb, rc, rd, re, rf }
18 changes: 10 additions & 8 deletions playground/resolve/index.html
Original file line number Diff line number Diff line change
@@ -322,7 +322,8 @@ <h2>utf8-bom-package</h2>
import c from '@vitejs/test-resolve-browser-field/ext'
import d from '@vitejs/test-resolve-browser-field/ext.js'
import e from '@vitejs/test-resolve-browser-field/ext-index/index.js'
import f from '@vitejs/test-resolve-browser-field/ext-index'
// webpack does not support, so should be fine
// import f from '@vitejs/test-resolve-browser-field/ext-index'
import g from '@vitejs/test-resolve-browser-field/no-ext-index/index.js' // no substitution
import h from '@vitejs/test-resolve-browser-field/no-ext?query'
import i from '@vitejs/test-resolve-browser-field/bare-import'
@@ -334,11 +335,10 @@ <h2>utf8-bom-package</h2>
rd,
re,
rf,
rg,
} from '@vitejs/test-resolve-browser-field/relative'

const success = [main, a, c, d, e, f, h, i, ra, rc, rd, re, rf]
const noSuccess = [b, g, rb, rg]
const success = [main, a, c, d, e, h, i, ra, rc, rd, re]
const noSuccess = [b, g, rb, rf]

if (
[...success, ...noSuccess].filter((text) => text.includes('[success]'))
@@ -350,11 +350,13 @@ <h2>utf8-bom-package</h2>
import browserModule1 from '@vitejs/test-resolve-browser-module-field1'
text('.browser-module1', browserModule1)

import browserModule2 from '@vitejs/test-resolve-browser-module-field2'
text('.browser-module2', browserModule2)
// should not fallback
// import browserModule2 from '@vitejs/test-resolve-browser-module-field2'
// text('.browser-module2', browserModule2)

import browserModule3 from '@vitejs/test-resolve-browser-module-field3'
text('.browser-module3', browserModule3)
// should not fallback
// import browserModule3 from '@vitejs/test-resolve-browser-module-field3'
// text('.browser-module3', browserModule3)

import { msg as requireButWithModuleFieldMsg } from '@vitejs/test-require-pkg-with-module-field'
text('.require-pkg-with-module-field', requireButWithModuleFieldMsg)
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import {
} from '~utils'

const unexpectedTokenSyntaxErrorRE =
/(\[vite:esbuild\] )*parsing .* failed: SyntaxError: Unexpected token.*\}.*/
/(\[vite:esbuild\] )*parsing .* failed: SyntaxError: Unexpected token.*\}.*|Build failed/

describe.runIf(isBuild)('build', () => {
test('should throw an error on build', () => {
18 changes: 10 additions & 8 deletions playground/vitestSetup.ts
Original file line number Diff line number Diff line change
@@ -72,7 +72,7 @@ export let resolvedConfig: ResolvedConfig = undefined!
export let page: Page = undefined!
export let browser: Browser = undefined!
export let viteTestUrl: string = ''
export let watcher: RollupWatcher | undefined = undefined
export const watcher: RollupWatcher | undefined = undefined

export function setViteUrl(url: string): void {
viteTestUrl = url
@@ -113,6 +113,8 @@ beforeAll(async (s) => {
// suppress @vue/reactivity-transform warning
if (msg.includes('@vue/reactivity-transform')) return
if (msg.includes('Generated an empty chunk')) return
// suppress rolldown minifier warning
if (msg.includes('The built-in minifier is still under development')) return
warn.call(globalConsole, msg, ...args)
}

@@ -273,13 +275,13 @@ export async function startDefaultServe(): Promise<void> {
const builder = await createBuilder(buildConfig)
await builder.buildApp()
} else {
const rollupOutput = await build(buildConfig)
const isWatch = !!resolvedConfig!.build.watch
// in build watch,call startStaticServer after the build is complete
if (isWatch) {
watcher = rollupOutput as RollupWatcher
await notifyRebuildComplete(watcher)
}
/* const rollupOutput = */ await build(buildConfig)
// const isWatch = !!resolvedConfig!.build.watch
// // in build watch,call startStaticServer after the build is complete
// if (isWatch) {
// watcher = rollupOutput as RollupWatcher
// await notifyRebuildComplete(watcher)
// }
if (buildConfig.__test__) {
buildConfig.__test__()
}
16 changes: 8 additions & 8 deletions playground/worker/__tests__/es/worker-es.spec.ts
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ test('normal', async () => {
)
await untilUpdated(
() => page.textContent('.asset-url'),
isBuild ? '/es/assets/worker_asset-vite.svg' : '/es/vite.svg',
isBuild ? /\/es\/assets\/worker_asset-vite-[\w-]{8}\.svg/ : '/es/vite.svg',
)
await untilUpdated(() => page.textContent('.dep-cjs'), '[cjs ok]')
})
@@ -94,7 +94,7 @@ describe.runIf(isBuild)('build', () => {
test('inlined code generation', async () => {
const assetsDir = path.resolve(testDir, 'dist/es/assets')
const files = fs.readdirSync(assetsDir)
expect(files.length).toBe(35)
expect(files.length).toBe(42)
const index = files.find((f) => f.includes('main-module'))
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
const worker = files.find((f) => f.includes('my-worker'))
@@ -104,20 +104,20 @@ describe.runIf(isBuild)('build', () => {
)

// worker should have all imports resolved and no exports
expect(workerContent).not.toMatch(/import[^.]/)
expect(workerContent).not.toMatch(`export`)
expect(workerContent).not.toMatch(/import\s*["(]/)
expect(workerContent).not.toMatch(/\bexport\b/)
// chunk
expect(content).toMatch(`new Worker("/es/assets`)
expect(content).toMatch(`new SharedWorker("/es/assets`)
expect(content).toMatch('new Worker(`/es/assets')
expect(content).toMatch('new SharedWorker(`/es/assets')
// inlined worker
expect(content).toMatch(`(self.URL||self.webkitURL).createObjectURL`)
expect(content).toMatch(`self.Blob`)
expect(content).toMatch(
/try\{if\(\w+=\w+&&\(self\.URL\|\|self\.webkitURL\)\.createObjectURL\(\w+\),!\w+\)throw""/,
/try\{if\(\w+=\w+&&\(self\.URL\|\|self\.webkitURL\)\.createObjectURL\(\w+\),!\w+\)throw``/,
)
// inlined shared worker
expect(content).toMatch(
`return new SharedWorker("data:text/javascript;charset=utf-8,"+`,
'return new SharedWorker(`data:text/javascript;charset=utf-8,`+',
)
})

12 changes: 7 additions & 5 deletions playground/worker/__tests__/iife/worker-iife.spec.ts
Original file line number Diff line number Diff line change
@@ -21,7 +21,9 @@ test('normal', async () => {
)
await untilUpdated(
() => page.textContent('.asset-url'),
isBuild ? '/iife/assets/worker_asset-vite.svg' : '/iife/vite.svg',
isBuild
? /\/iife\/assets\/worker_asset-vite-[\w-]{8}\.svg/
: '/iife/vite.svg',
)
})

@@ -90,10 +92,10 @@ describe.runIf(isBuild)('build', () => {

// worker should have all imports resolved and no exports
expect(workerContent).not.toMatch(`import`)
expect(workerContent).not.toMatch(`export`)
expect(workerContent).not.toMatch(/\bexport\b/)
// chunk
expect(content).toMatch(`new Worker("/iife/assets`)
expect(content).toMatch(`new SharedWorker("/iife/assets`)
expect(content).toMatch('new Worker(`/iife/assets')
expect(content).toMatch('new SharedWorker(`/iife/assets')
// inlined
expect(content).toMatch(`(self.URL||self.webkitURL).createObjectURL`)
expect(content).toMatch(`self.Blob`)
@@ -189,7 +191,7 @@ test.runIf(isServe)('sourcemap is correct after env is injected', async () => {
const content = await (await response).text()
const { mappings } = decodeSourceMapUrl(content)
expect(mappings).toMatchInlineSnapshot(
`";;AAAA,SAAS,OAAO,kBAAkB;AAClC,OAAO,YAAY;AACnB,SAAS,MAAM,WAAW;AAC1B,SAAS,wBAAwB;AACjC,OAAO,aAAa;AACpB,MAAM,UAAU,YAAY;AAE5B,KAAK,YAAY,CAAC,MAAM;AACtB,MAAI,EAAE,SAAS,QAAQ;AACrB,SAAK,YAAY;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,EAAE,SAAS,gBAAgB;AAC7B,SAAK,YAAY;AAAA,MACf,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AACA,KAAK,YAAY;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,QAAQ,IAAI,cAAc"`,
`";;AAAA,SAAS,OAAO,kBAAkB,8BAA8B;AAChE,OAAO,YAAY,6BAA6B;AAChD,SAAS,MAAM,WAAW,2BAA2B;AACrD,SAAS,wBAAwB,uBAAuB;AACxD,OAAO,aAAa,YAAY;AAChC,MAAM,UAAU,OAAO,KAAK;AAE5B,KAAK,YAAY,CAAC,MAAM;AACtB,KAAI,EAAE,SAAS,QAAQ;AACrB,OAAK,YAAY;GACf;GACA;GACA;GACA;GACA;GACA;GACA;EACD,EAAC;CACH;AACD,KAAI,EAAE,SAAS,gBAAgB;AAC7B,OAAK,YAAY;GACf,KAAK;GACL;GACA;GACA;GACA;GACA;GACA;EACD,EAAC;CACH;AACF;AACD,KAAK,YAAY;CACf;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD,EAAC;AAGF,QAAQ,IAAI,eAAe"`,
)
})

Original file line number Diff line number Diff line change
@@ -75,11 +75,11 @@ describe.runIf(isBuild)('build', () => {
)

// worker should have all imports resolved and no exports
expect(workerContent).not.toMatch(/import(?!\.)/) // accept import.meta.url
expect(workerContent).not.toMatch(`export`)
expect(workerContent).not.toMatch(/import\s*["(]/)
expect(workerContent).not.toMatch(/\bexport\b/)
// chunk
expect(content).toMatch(`new Worker(""+new URL("../worker-entries/`)
expect(content).toMatch(`new SharedWorker(""+new URL("../worker-entries/`)
expect(content).toMatch('new Worker(``+new URL(`../worker-entries/')
expect(content).toMatch('new SharedWorker(``+new URL(`../worker-entries/')
// inlined
expect(content).toMatch(`(self.URL||self.webkitURL).createObjectURL`)
expect(content).toMatch(`self.Blob`)
Original file line number Diff line number Diff line change
@@ -84,33 +84,33 @@ describe.runIf(isBuild)('build', () => {

// worker should have all imports resolved and no exports
expect(workerContent).not.toMatch(`import`)
expect(workerContent).not.toMatch(`export`)
expect(workerContent).not.toMatch(/\bexport\b/)

// shared worker should have all imports resolved and no exports
expect(sharedWorkerContent).not.toMatch(`import`)
expect(sharedWorkerContent).not.toMatch(`export`)

// chunk
expect(content).toMatch(
`new Worker("/iife-sourcemap-hidden/assets/my-worker`,
'new Worker(`/iife-sourcemap-hidden/assets/my-worker',
)
expect(content).toMatch(`new Worker("data:text/javascript;charset=utf-8,"+`)
expect(content).toMatch('new Worker(`data:text/javascript;charset=utf-8,`+')
expect(content).toMatch(
`new Worker("/iife-sourcemap-hidden/assets/possible-ts-output-worker`,
'new Worker(`/iife-sourcemap-hidden/assets/possible-ts-output-worker',
)
expect(content).toMatch(
`new Worker("/iife-sourcemap-hidden/assets/worker-nested-worker`,
'new Worker(`/iife-sourcemap-hidden/assets/worker-nested-worker',
)
expect(content).toMatch(
`new SharedWorker("/iife-sourcemap-hidden/assets/my-shared-worker`,
'new SharedWorker(`/iife-sourcemap-hidden/assets/my-shared-worker',
)

// inlined
expect(content).toMatch(`(self.URL||self.webkitURL).createObjectURL`)
expect(content).toMatch(`self.Blob`)

expect(workerNestedWorkerContent).toMatch(
`new Worker("/iife-sourcemap-hidden/assets/sub-worker`,
'new Worker(`/iife-sourcemap-hidden/assets/sub-worker',
)
})
})
Original file line number Diff line number Diff line change
@@ -65,33 +65,33 @@ describe.runIf(isBuild)('build', () => {

// worker should have all imports resolved and no exports
expect(workerContent).not.toMatch(`import`)
expect(workerContent).not.toMatch(`export`)
expect(workerContent).not.toMatch(/\bexport\b/)

// shared worker should have all imports resolved and no exports
expect(sharedWorkerContent).not.toMatch(`import`)
expect(sharedWorkerContent).not.toMatch(`export`)

// chunk
expect(content).toMatch(
`new Worker("/iife-sourcemap-inline/assets/my-worker`,
'new Worker(`/iife-sourcemap-inline/assets/my-worker',
)
expect(content).toMatch(`new Worker("data:text/javascript;charset=utf-8,"+`)
expect(content).toMatch('new Worker(`data:text/javascript;charset=utf-8,`+')
expect(content).toMatch(
`new Worker("/iife-sourcemap-inline/assets/possible-ts-output-worker`,
'new Worker(`/iife-sourcemap-inline/assets/possible-ts-output-worker',
)
expect(content).toMatch(
`new Worker("/iife-sourcemap-inline/assets/worker-nested-worker`,
'new Worker(`/iife-sourcemap-inline/assets/worker-nested-worker',
)
expect(content).toMatch(
`new SharedWorker("/iife-sourcemap-inline/assets/my-shared-worker`,
'new SharedWorker(`/iife-sourcemap-inline/assets/my-shared-worker',
)

// inlined
expect(content).toMatch(`(self.URL||self.webkitURL).createObjectURL`)
expect(content).toMatch(`self.Blob`)

expect(workerNestedWorkerContent).toMatch(
`new Worker("/iife-sourcemap-inline/assets/sub-worker`,
'new Worker(`/iife-sourcemap-inline/assets/sub-worker',
)
})
})
14 changes: 7 additions & 7 deletions playground/worker/__tests__/sourcemap/worker-sourcemap.spec.ts
Original file line number Diff line number Diff line change
@@ -87,31 +87,31 @@ describe.runIf(isBuild)('build', () => {

// worker should have all imports resolved and no exports
expect(workerContent).not.toMatch(`import`)
expect(workerContent).not.toMatch(`export`)
expect(workerContent).not.toMatch(/\bexport\b/)

// shared worker should have all imports resolved and no exports
expect(sharedWorkerContent).not.toMatch(`import`)
expect(sharedWorkerContent).not.toMatch(`export`)

// chunk
expect(content).toMatch(`new Worker("/iife-sourcemap/assets/my-worker`)
expect(content).toMatch(`new Worker("data:text/javascript;charset=utf-8,"+`)
expect(content).toMatch('new Worker(`/iife-sourcemap/assets/my-worker')
expect(content).toMatch('new Worker(`data:text/javascript;charset=utf-8,`+')
expect(content).toMatch(
`new Worker("/iife-sourcemap/assets/possible-ts-output-worker`,
'new Worker(`/iife-sourcemap/assets/possible-ts-output-worker',
)
expect(content).toMatch(
`new Worker("/iife-sourcemap/assets/worker-nested-worker`,
'new Worker(`/iife-sourcemap/assets/worker-nested-worker',
)
expect(content).toMatch(
`new SharedWorker("/iife-sourcemap/assets/my-shared-worker`,
'new SharedWorker(`/iife-sourcemap/assets/my-shared-worker',
)

// inlined
expect(content).toMatch(`(self.URL||self.webkitURL).createObjectURL`)
expect(content).toMatch(`self.Blob`)

expect(workerNestedWorkerContent).toMatch(
`new Worker("/iife-sourcemap/assets/sub-worker`,
'new Worker(`/iife-sourcemap/assets/sub-worker',
)
})
})
8 changes: 4 additions & 4 deletions playground/worker/vite.config-es.js
Original file line number Diff line number Diff line change
@@ -13,8 +13,8 @@ export default defineConfig({
plugins: () => [workerPluginTestPlugin()],
rollupOptions: {
output: {
assetFileNames: 'assets/worker_asset-[name].[ext]',
chunkFileNames: 'assets/worker_chunk-[name].js',
assetFileNames: 'assets/worker_asset-[name]-[hash].[ext]',
chunkFileNames: 'assets/worker_chunk-[name]-[hash].js',
entryFileNames: 'assets/worker_entry-[name].js',
},
},
@@ -25,8 +25,8 @@ export default defineConfig({
filePath.endsWith('.svg') ? false : undefined,
rollupOptions: {
output: {
assetFileNames: 'assets/[name].[ext]',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name].js',
},
},
8 changes: 4 additions & 4 deletions playground/worker/vite.config-iife.js
Original file line number Diff line number Diff line change
@@ -13,8 +13,8 @@ export default defineConfig({
plugins: () => [workerPluginTestPlugin()],
rollupOptions: {
output: {
assetFileNames: 'assets/worker_asset-[name].[ext]',
chunkFileNames: 'assets/worker_chunk-[name].js',
assetFileNames: 'assets/worker_asset-[name]-[hash].[ext]',
chunkFileNames: 'assets/worker_chunk-[name]-[hash].js',
// should be overwritten to worker_entry-[name] by the config-test plugin
entryFileNames: 'assets/worker_-[name].js',
},
@@ -27,8 +27,8 @@ export default defineConfig({
manifest: true,
rollupOptions: {
output: {
assetFileNames: 'assets/[name].[ext]',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name].js',
},
},
244 changes: 234 additions & 10 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions vitest.config.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { resolve } from 'node:path'
import { defineConfig } from 'vitest/config'
import { defaultExclude, defineConfig } from 'vitest/config'

const isBuild = !!process.env.VITE_TEST_BUILD

const timeout = process.env.PWDEBUG ? Infinity : process.env.CI ? 50000 : 30000

@@ -11,6 +13,16 @@ export default defineConfig({
},
test: {
include: ['./playground/**/*.spec.[tj]s'],
exclude: [
'./playground/legacy/**/*.spec.[tj]s', // system format
...(isBuild
? [
'./playground/object-hooks/**/*.spec.[tj]s', // object hook sequential
'./playground/optimize-deps/**/*.spec.[tj]s', // https://github.com/rolldown/rolldown/issues/2031
]
: []),
...defaultExclude,
],
setupFiles: ['./playground/vitestSetup.ts'],
globalSetup: ['./playground/vitestGlobalSetup.ts'],
testTimeout: timeout,
@@ -23,7 +35,7 @@ export default defineConfig({
onConsoleLog(log) {
if (
log.match(
/experimental|jit engine|emitted file|tailwind|The CJS build of Vite/i,
/experimental|jit engine|emitted file|tailwind|The CJS build of Vite|optimizeDeps\.esbuildOptions/i,
)
)
return false