diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f60f3280712ed6..6f10675c2f33ce 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -6,7 +6,7 @@ module.exports = defineConfig({ root: true, extends: [ 'eslint:recommended', - 'plugin:node/recommended', + 'plugin:n/recommended', 'plugin:@typescript-eslint/recommended', 'plugin:regexp/recommended', ], @@ -29,14 +29,9 @@ module.exports = defineConfig({ }, ], - 'node/no-missing-import': [ - 'error', - { - allowModules: ['types', 'estree', 'less', 'sass', 'stylus'], - tryExtensions: ['.ts', '.js', '.jsx', '.tsx', '.d.ts'], - }, - ], - 'node/no-missing-require': [ + 'n/no-process-exit': 'off', + 'n/no-missing-import': 'off', + 'n/no-missing-require': [ 'error', { // for try-catching yarn pnp @@ -44,22 +39,22 @@ module.exports = defineConfig({ tryExtensions: ['.ts', '.js', '.jsx', '.tsx', '.d.ts'], }, ], - 'node/no-extraneous-import': [ + 'n/no-extraneous-import': [ 'error', { - allowModules: ['vite', 'less', 'sass', 'vitest'], + allowModules: ['vite', 'less', 'sass', 'vitest', 'unbuild'], }, ], - 'node/no-extraneous-require': [ + 'n/no-extraneous-require': [ 'error', { allowModules: ['vite'], }, ], - 'node/no-deprecated-api': 'off', - 'node/no-unpublished-import': 'off', - 'node/no-unpublished-require': 'off', - 'node/no-unsupported-features/es-syntax': 'off', + 'n/no-deprecated-api': 'off', + 'n/no-unpublished-import': 'off', + 'n/no-unpublished-require': 'off', + 'n/no-unsupported-features/es-syntax': 'off', '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/ban-types': 'off', // TODO: we should turn this on in a new PR @@ -118,7 +113,7 @@ module.exports = defineConfig({ { files: 'packages/vite/**/*.*', rules: { - 'node/no-restricted-require': [ + 'n/no-restricted-require': [ 'error', Object.keys( require('./packages/vite/package.json').devDependencies, @@ -141,32 +136,32 @@ module.exports = defineConfig({ { files: ['packages/vite/src/types/**', '*.spec.ts'], rules: { - 'node/no-extraneous-import': 'off', + 'n/no-extraneous-import': 'off', }, }, { files: ['packages/create-vite/template-*/**', '**/build.config.ts'], rules: { 'no-undef': 'off', - 'node/no-missing-import': 'off', + 'n/no-missing-import': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', }, }, { files: ['playground/**'], rules: { - 'node/no-extraneous-import': 'off', - 'node/no-extraneous-require': 'off', - 'node/no-missing-import': 'off', - 'node/no-missing-require': 'off', + 'n/no-extraneous-import': 'off', + 'n/no-extraneous-require': 'off', + 'n/no-missing-import': 'off', + 'n/no-missing-require': 'off', // engine field doesn't exist in playgrounds - 'node/no-unsupported-features/es-builtins': [ + 'n/no-unsupported-features/es-builtins': [ 'error', { version: '^14.18.0 || >=16.0.0', }, ], - 'node/no-unsupported-features/node-builtins': [ + 'n/no-unsupported-features/node-builtins': [ 'error', { version: '^14.18.0 || >=16.0.0', diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27921e06ac8f9b..49a811bd741a6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node_version: [14, 16, 18] + node_version: [14, 16, 18, 20] include: # Active LTS + other OS - os: macos-latest @@ -67,8 +67,14 @@ jobs: packages/create-vite/template** **.md + - name: Install pnpm (node 14, pnpm 7) + if: steps.changed-files.outputs.only_changed != 'true' && matrix.node_version == 14 + uses: pnpm/action-setup@v2.2.4 + with: + version: 7 + - name: Install pnpm - if: steps.changed-files.outputs.only_changed != 'true' + if: steps.changed-files.outputs.only_changed != 'true' && matrix.node_version != 14 uses: pnpm/action-setup@v2.2.4 - name: Set node version to ${{ matrix.node_version }} diff --git a/docs/_data/team.js b/docs/_data/team.js index 727e7328939eec..72641a597cee55 100644 --- a/docs/_data/team.js +++ b/docs/_data/team.js @@ -126,6 +126,18 @@ export const core = [ ], sponsor: 'https://github.com/sponsors/dominikg', }, + { + avatar: 'https://github.com/sheremet-va.png', + name: 'Vladimir', + title: 'Core team member of Vitest & Vite', + desc: 'An open source fullstack developer', + links: [ + { icon: 'github', link: 'https://github.com/sheremet-va' }, + { icon: 'mastodon', link: 'https://elk.zone/m.webtoo.ls/@sheremet_va' }, + { icon: 'twitter', link: 'https://twitter.com/sheremet_va' }, + ], + sponsor: 'https://github.com/sponsors/sheremet-va', + }, ] export const emeriti = [ diff --git a/docs/config/server-options.md b/docs/config/server-options.md index 49c766cba59d5a..ab0f1b261ddb7c 100644 --- a/docs/config/server-options.md +++ b/docs/config/server-options.md @@ -324,7 +324,7 @@ export default defineConfig({ - **Type:** `false | (sourcePath: string, sourcemapPath: string) => boolean` - **Default:** `(sourcePath) => sourcePath.includes('node_modules')` -Whether or not to ignore source files in the server sourcemap, used to populate the [`x_google_ignoreList` source map extension](https://developer.chrome.com/blog/devtools-better-angular-debugging/#the-x_google_ignorelist-source-map-extension). +Whether or not to ignore source files in the server sourcemap, used to populate the [`x_google_ignoreList` source map extension](https://developer.chrome.com/articles/x-google-ignore-list/). `server.sourcemapIgnoreList` is the equivalent of [`build.rollupOptions.output.sourcemapIgnoreList`](https://rollupjs.org/configuration-options/#output-sourcemapignorelist) for the dev server. A difference between the two config options is that the rollup function is called with a relative path for `sourcePath` while `server.sourcemapIgnoreList` is called with an absolute path. During dev, most modules have the map and the source in the same folder, so the relative path for `sourcePath` is the file name itself. In these cases, absolute paths makes it convenient to be used instead. diff --git a/docs/config/shared-options.md b/docs/config/shared-options.md index 4def2201ee891f..e67ddb72a1f176 100644 --- a/docs/config/shared-options.md +++ b/docs/config/shared-options.md @@ -111,7 +111,7 @@ When aliasing to file system paths, always use absolute paths. Relative alias va More advanced custom resolution can be achieved through [plugins](/guide/api-plugin). ::: warning Using with SSR -If you have configured aliases for [SSR externalized dependencies](/guide/ssr.md#ssr-externals), you may want to alias the actual `node_modules` packages. Both [Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) and [pnpm](https://pnpm.js.org/en/aliases) support aliasing via the `npm:` prefix. +If you have configured aliases for [SSR externalized dependencies](/guide/ssr.md#ssr-externals), you may want to alias the actual `node_modules` packages. Both [Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) and [pnpm](https://pnpm.io/aliases/) support aliasing via the `npm:` prefix. ::: ## resolve.dedupe @@ -232,7 +232,7 @@ Specify options to pass to CSS pre-processors. The file extensions are used as k - `less` - [Options](https://lesscss.org/usage/#less-options). - `styl`/`stylus` - Only [`define`](https://stylus-lang.com/docs/js.html#define-name-node) is supported, which can be passed as an object. -All preprocessor options also support the `additionalData` option, which can be used to inject extra code for each style content. +All preprocessor options also support the `additionalData` option, which can be used to inject extra code for each style content. Note that if you include actual styles and not just variables, those styles will be duplicated in the final bundle. Example: diff --git a/docs/guide/features.md b/docs/guide/features.md index 84806a352acca3..a9e3fcd859b606 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -74,7 +74,7 @@ You can read more about the transition in the [TypeScript 3.7 release notes](htt If you are using a library that heavily relies on class fields, please be careful about the library's intended usage of it. -Most libraries expect `"useDefineForClassFields": true`, such as [MobX](https://mobx.js.org/installation.html#use-spec-compliant-transpilation-for-class-properties), [Vue Class Components 8.x](https://github.com/vuejs/vue-class-component/issues/465), etc. +Most libraries expect `"useDefineForClassFields": true`, such as [MobX](https://mobx.js.org/installation.html#use-spec-compliant-transpilation-for-class-properties). But a few libraries haven't transitioned to this new default yet, including [`lit-element`](https://github.com/lit/lit-element/issues/1030). Please explicitly set `useDefineForClassFields` to `false` in these cases. diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index 27d431011a7b28..93830d1d8e5aaa 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -216,7 +216,7 @@ If a dependency needs to be transformed by Vite's pipeline, for example, because For linked dependencies, they are not externalized by default to take advantage of Vite's HMR. If this isn't desired, for example, to test dependencies as if they aren't linked, you can add it to [`ssr.external`](../config/ssr-options.md#ssr-external). :::warning Working with Aliases -If you have configured aliases that redirect one package to another, you may want to alias the actual `node_modules` packages instead to make it work for SSR externalized dependencies. Both [Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) and [pnpm](https://pnpm.js.org/en/aliases) support aliasing via the `npm:` prefix. +If you have configured aliases that redirect one package to another, you may want to alias the actual `node_modules` packages instead to make it work for SSR externalized dependencies. Both [Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) and [pnpm](https://pnpm.io/aliases/) support aliasing via the `npm:` prefix. ::: ## SSR-specific Plugin Logic diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md index f97b0447e32ff4..c9eeab879c5c5a 100644 --- a/docs/guide/troubleshooting.md +++ b/docs/guide/troubleshooting.md @@ -121,6 +121,12 @@ See [Reason: CORS request not HTTP - HTTP | MDN](https://developer.mozilla.org/e You will need to access the file with `http` protocol. The easiest way to achieve this is to run `npx vite preview`. +## Optimized Dependencies + +### Outdated pre-bundled deps when linking to a local package + +The hash key used to invalidate optimized dependencies depend on the package lock contents, the patches applied to dependencies, and the options in the Vite config file that affects the bundling of node modules. This means that Vite will detect when a dependency is overridden using a feature as [npm overrides](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides), and re-bundle your dependencies on the next server start. Vite won't invalidate the dependencies when you use a feature like [npm link](https://docs.npmjs.com/cli/v9/commands/npm-link). In case you link or unlink a dependency, you'll need to force re-optimization on the next server start by using `vite --force`. We recommend using overrides instead, which are supported now by every package manager (see also [pnpm overrides](https://pnpm.io/package_json#pnpmoverrides) and [yarn resolutions](https://yarnpkg.com/configuration/manifest/#resolutions)). + ## Others ### Module externalized for browser compatibility @@ -144,3 +150,7 @@ For example, you might see these errors. > TypeError: Cannot create property 'foo' on boolean 'false' If these code are used inside dependencies, you could use [`patch-package`](https://github.com/ds300/patch-package) (or [`yarn patch`](https://yarnpkg.com/cli/patch) or [`pnpm patch`](https://pnpm.io/cli/patch)) for an escape hatch. + +### Browser extensions + +Some browser extensions (like ad-blockers) may prevent the Vite client from sending requests to the Vite dev server. You may see a white screen without logged errors in this case. Try disabling extensions if you have this issue. diff --git a/package.json b/package.json index 0964c0a12864c9..9b915cc513878d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@vitejs/vite-monorepo", "private": true, + "type": "module", "engines": { "node": "^14.18.0 || >=16.0.0" }, @@ -50,37 +51,32 @@ "@types/json-stable-stringify": "^1.0.34", "@types/less": "^3.0.3", "@types/micromatch": "^4.0.2", - "@types/minimist": "^1.2.2", "@types/node": "^18.15.5", "@types/picomatch": "^2.3.0", - "@types/prompts": "2.4.2", "@types/resolve": "^1.20.2", "@types/sass": "~1.43.1", - "@types/semver": "^7.3.13", "@types/stylus": "^0.48.38", "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.56.0", "@typescript-eslint/parser": "^5.56.0", + "@vitejs/release-scripts": "^1.1.0", "conventional-changelog-cli": "^2.2.2", "eslint": "^8.36.0", "eslint-define-config": "^1.17.0", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-node": "^11.1.0", + "eslint-plugin-n": "^15.7.0", "eslint-plugin-regexp": "^1.13.0", "execa": "^7.1.1", "fast-glob": "^3.2.12", "fs-extra": "^11.1.1", "lint-staged": "^13.2.0", - "minimist": "^1.2.8", "npm-run-all": "^4.1.5", "picocolors": "^1.0.0", "playwright-chromium": "^1.31.2", "prettier": "2.8.5", - "prompts": "^2.4.2", "resolve": "^1.22.1", "rimraf": "^4.4.0", - "rollup": "^3.20.0", - "semver": "^7.3.8", + "rollup": "^3.20.2", "simple-git-hooks": "^2.8.1", "tslib": "^2.5.0", "tsx": "^3.12.6", @@ -108,7 +104,7 @@ "eslint --cache --fix" ] }, - "packageManager": "pnpm@7.30.0", + "packageManager": "pnpm@8.2.0", "pnpm": { "overrides": { "vite": "workspace:*" diff --git a/packages/create-vite/CHANGELOG.md b/packages/create-vite/CHANGELOG.md index d0aafa6f8e8770..99788f8cd68eae 100644 --- a/packages/create-vite/CHANGELOG.md +++ b/packages/create-vite/CHANGELOG.md @@ -1,3 +1,18 @@ +## 4.3.0-beta.0 (2023-04-06) + +* chore: use @vitejs/release-scripts (#12682) ([9c37cc1](https://github.com/vitejs/vite/commit/9c37cc1)), closes [#12682](https://github.com/vitejs/vite/issues/12682) +* chore(create-vite): revert to vite 4.2 (#12456) ([535c8c5](https://github.com/vitejs/vite/commit/535c8c5)), closes [#12456](https://github.com/vitejs/vite/issues/12456) +* chore(create-vite): rollback to vite 4.1 due to npm publish outage ([d8cb765](https://github.com/vitejs/vite/commit/d8cb765)) +* chore(create-vite): update to vite 4.3 beta ([9b0df5d](https://github.com/vitejs/vite/commit/9b0df5d)) +* feat(create-vite): lit templates will create application instead of library (#12459) ([8186b9b](https://github.com/vitejs/vite/commit/8186b9b)), closes [#12459](https://github.com/vitejs/vite/issues/12459) +* feat(create-vite): stricter TS configs in templates (#12604) ([4ffaeee](https://github.com/vitejs/vite/commit/4ffaeee)), closes [#12604](https://github.com/vitejs/vite/issues/12604) +* feat(create-vite): use typescript 5.0 in templates (#12481) ([8582e2d](https://github.com/vitejs/vite/commit/8582e2d)), closes [#12481](https://github.com/vitejs/vite/issues/12481) +* fix(create-vite): skip lib check in tsconfig templates (#12591) ([a59914c](https://github.com/vitejs/vite/commit/a59914c)), closes [#12591](https://github.com/vitejs/vite/issues/12591) +* fix(create-vite): updated js & ts templates with new react docs link (#12479) ([c327006](https://github.com/vitejs/vite/commit/c327006)), closes [#12479](https://github.com/vitejs/vite/issues/12479) +* fix(deps): update all non-major dependencies (#12389) ([3e60b77](https://github.com/vitejs/vite/commit/3e60b77)), closes [#12389](https://github.com/vitejs/vite/issues/12389) + + + ## 4.2.0 (2023-03-16) * chore(create-vite): update plugin-vue ([e06cda9](https://github.com/vitejs/vite/commit/e06cda9)) diff --git a/packages/create-vite/package.json b/packages/create-vite/package.json index 57f24d87f6ac7f..13202f53eadf96 100644 --- a/packages/create-vite/package.json +++ b/packages/create-vite/package.json @@ -1,6 +1,6 @@ { "name": "create-vite", - "version": "4.2.0", + "version": "4.3.0-beta.0", "type": "module", "license": "MIT", "author": "Evan You", @@ -13,7 +13,6 @@ "template-*", "dist" ], - "main": "index.js", "scripts": { "dev": "unbuild --stub", "build": "unbuild", @@ -33,6 +32,8 @@ }, "homepage": "https://github.com/vitejs/vite/tree/main/packages/create-vite#readme", "devDependencies": { + "@types/minimist": "^1.2.2", + "@types/prompts": "^2.4.4", "cross-spawn": "^7.0.3", "kolorist": "^1.7.0", "minimist": "^1.2.8", diff --git a/packages/create-vite/src/index.ts b/packages/create-vite/src/index.ts index 45d865530d9c3a..dfc9473e23ff55 100755 --- a/packages/create-vite/src/index.ts +++ b/packages/create-vite/src/index.ts @@ -44,16 +44,16 @@ const FRAMEWORKS: Framework[] = [ display: 'Vanilla', color: yellow, variants: [ - { - name: 'vanilla', - display: 'JavaScript', - color: yellow, - }, { name: 'vanilla-ts', display: 'TypeScript', color: blue, }, + { + name: 'vanilla', + display: 'JavaScript', + color: yellow, + }, ], }, { @@ -61,16 +61,16 @@ const FRAMEWORKS: Framework[] = [ display: 'Vue', color: green, variants: [ - { - name: 'vue', - display: 'JavaScript', - color: yellow, - }, { name: 'vue-ts', display: 'TypeScript', color: blue, }, + { + name: 'vue', + display: 'JavaScript', + color: yellow, + }, { name: 'custom-create-vue', display: 'Customize with create-vue ↗', @@ -90,26 +90,26 @@ const FRAMEWORKS: Framework[] = [ display: 'React', color: cyan, variants: [ - { - name: 'react', - display: 'JavaScript', - color: yellow, - }, { name: 'react-ts', display: 'TypeScript', color: blue, }, - { - name: 'react-swc', - display: 'JavaScript + SWC', - color: yellow, - }, { name: 'react-swc-ts', display: 'TypeScript + SWC', color: blue, }, + { + name: 'react', + display: 'JavaScript', + color: yellow, + }, + { + name: 'react-swc', + display: 'JavaScript + SWC', + color: yellow, + }, ], }, { @@ -117,16 +117,16 @@ const FRAMEWORKS: Framework[] = [ display: 'Preact', color: magenta, variants: [ - { - name: 'preact', - display: 'JavaScript', - color: yellow, - }, { name: 'preact-ts', display: 'TypeScript', color: blue, }, + { + name: 'preact', + display: 'JavaScript', + color: yellow, + }, ], }, { @@ -134,16 +134,16 @@ const FRAMEWORKS: Framework[] = [ display: 'Lit', color: lightRed, variants: [ - { - name: 'lit', - display: 'JavaScript', - color: yellow, - }, { name: 'lit-ts', display: 'TypeScript', color: blue, }, + { + name: 'lit', + display: 'JavaScript', + color: yellow, + }, ], }, { @@ -151,16 +151,16 @@ const FRAMEWORKS: Framework[] = [ display: 'Svelte', color: red, variants: [ - { - name: 'svelte', - display: 'JavaScript', - color: yellow, - }, { name: 'svelte-ts', display: 'TypeScript', color: blue, }, + { + name: 'svelte', + display: 'JavaScript', + color: yellow, + }, { name: 'custom-svelte-kit', display: 'SvelteKit ↗', diff --git a/packages/create-vite/template-lit-ts/package.json b/packages/create-vite/template-lit-ts/package.json index a564069837d479..a65419c5a0adce 100644 --- a/packages/create-vite/template-lit-ts/package.json +++ b/packages/create-vite/template-lit-ts/package.json @@ -13,6 +13,6 @@ }, "devDependencies": { "typescript": "^5.0.2", - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-lit-ts/tsconfig.json b/packages/create-vite/template-lit-ts/tsconfig.json index f7c83877a73947..75abdef2659446 100644 --- a/packages/create-vite/template-lit-ts/tsconfig.json +++ b/packages/create-vite/template-lit-ts/tsconfig.json @@ -1,18 +1,23 @@ { "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, "noEmit": true, + + /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "moduleResolution": "bundler", - "isolatedModules": true, - "allowSyntheticDefaultImports": true, - "useDefineForClassFields": false, - "skipLibCheck": true + "noFallthroughCasesInSwitch": true }, "include": ["src"] } diff --git a/packages/create-vite/template-lit/package.json b/packages/create-vite/template-lit/package.json index bbabef0a7ab0de..971f764124fc5b 100644 --- a/packages/create-vite/template-lit/package.json +++ b/packages/create-vite/template-lit/package.json @@ -12,6 +12,6 @@ "lit": "^2.6.1" }, "devDependencies": { - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-preact-ts/package.json b/packages/create-vite/template-preact-ts/package.json index 6cb020637d5b92..5fa628bf8f7c67 100644 --- a/packages/create-vite/template-preact-ts/package.json +++ b/packages/create-vite/template-preact-ts/package.json @@ -14,6 +14,6 @@ "devDependencies": { "@preact/preset-vite": "^2.5.0", "typescript": "^5.0.2", - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-preact-ts/src/main.tsx b/packages/create-vite/template-preact-ts/src/main.tsx index e0ce3e9980eecd..125b2d586a34bc 100644 --- a/packages/create-vite/template-preact-ts/src/main.tsx +++ b/packages/create-vite/template-preact-ts/src/main.tsx @@ -1,5 +1,5 @@ import { render } from 'preact' -import { App } from './app' +import { App } from './app.tsx' import './index.css' render(, document.getElementById('app') as HTMLElement) diff --git a/packages/create-vite/template-preact-ts/tsconfig.json b/packages/create-vite/template-preact-ts/tsconfig.json index cfc4dec8c13935..21abced1d38ea6 100644 --- a/packages/create-vite/template-preact-ts/tsconfig.json +++ b/packages/create-vite/template-preact-ts/tsconfig.json @@ -1,20 +1,25 @@ { "compilerOptions": { - "target": "ESNext", + "target": "ES2020", "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ "moduleResolution": "bundler", + "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "jsxImportSource": "preact" + "jsxImportSource": "preact", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/packages/create-vite/template-preact/package.json b/packages/create-vite/template-preact/package.json index d0c6f57889eab8..5c13d5e6ffcba6 100644 --- a/packages/create-vite/template-preact/package.json +++ b/packages/create-vite/template-preact/package.json @@ -13,6 +13,6 @@ }, "devDependencies": { "@preact/preset-vite": "^2.5.0", - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-preact/src/main.jsx b/packages/create-vite/template-preact/src/main.jsx index be3fbce92b202b..b088b63d9dad7c 100644 --- a/packages/create-vite/template-preact/src/main.jsx +++ b/packages/create-vite/template-preact/src/main.jsx @@ -1,5 +1,5 @@ import { render } from 'preact' -import { App } from './app' +import { App } from './app.jsx' import './index.css' render(, document.getElementById('app')) diff --git a/packages/create-vite/template-react-ts/package.json b/packages/create-vite/template-react-ts/package.json index e2abde2865cab5..700ce50d85d129 100644 --- a/packages/create-vite/template-react-ts/package.json +++ b/packages/create-vite/template-react-ts/package.json @@ -15,8 +15,8 @@ "devDependencies": { "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "@vitejs/plugin-react": "^3.1.0", + "@vitejs/plugin-react": "^4.0.0-beta.0", "typescript": "^5.0.2", - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-react-ts/src/App.tsx b/packages/create-vite/template-react-ts/src/App.tsx index 08d57ca4dd50ba..afe48ac750194a 100644 --- a/packages/create-vite/template-react-ts/src/App.tsx +++ b/packages/create-vite/template-react-ts/src/App.tsx @@ -7,7 +7,7 @@ function App() { const [count, setCount] = useState(0) return ( -
+ <>
Vite logo @@ -28,7 +28,7 @@ function App() {

Click on the Vite and React logos to learn more

-
+ ) } diff --git a/packages/create-vite/template-react-ts/src/main.tsx b/packages/create-vite/template-react-ts/src/main.tsx index 791f139e242c70..91c03f3fb20c1e 100644 --- a/packages/create-vite/template-react-ts/src/main.tsx +++ b/packages/create-vite/template-react-ts/src/main.tsx @@ -1,6 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' -import App from './App' +import App from './App.tsx' import './index.css' ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/packages/create-vite/template-react-ts/tsconfig.json b/packages/create-vite/template-react-ts/tsconfig.json index d0a38be3ee33ba..c81ef9f382291a 100644 --- a/packages/create-vite/template-react-ts/tsconfig.json +++ b/packages/create-vite/template-react-ts/tsconfig.json @@ -1,19 +1,23 @@ { "compilerOptions": { "target": "ESNext", - "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ "moduleResolution": "bundler", + "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/packages/create-vite/template-react/package.json b/packages/create-vite/template-react/package.json index 069c2fd92e1529..08c96622385dd5 100644 --- a/packages/create-vite/template-react/package.json +++ b/packages/create-vite/template-react/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "@vitejs/plugin-react": "^3.1.0", - "vite": "^4.2.1" + "@vitejs/plugin-react": "^4.0.0-beta.0", + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-react/src/App.jsx b/packages/create-vite/template-react/src/App.jsx index 4c471316eb2c04..b8b8473a3696b4 100644 --- a/packages/create-vite/template-react/src/App.jsx +++ b/packages/create-vite/template-react/src/App.jsx @@ -7,7 +7,7 @@ function App() { const [count, setCount] = useState(0) return ( -
+ <>
Vite logo @@ -28,7 +28,7 @@ function App() {

Click on the Vite and React logos to learn more

-
+ ) } diff --git a/packages/create-vite/template-react/src/main.jsx b/packages/create-vite/template-react/src/main.jsx index 5cc599199a2091..54b39dd1d900e8 100644 --- a/packages/create-vite/template-react/src/main.jsx +++ b/packages/create-vite/template-react/src/main.jsx @@ -1,6 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' -import App from './App' +import App from './App.jsx' import './index.css' ReactDOM.createRoot(document.getElementById('root')).render( diff --git a/packages/create-vite/template-svelte-ts/package.json b/packages/create-vite/template-svelte-ts/package.json index 2ff225ea8cce84..fba07484329a9a 100644 --- a/packages/create-vite/template-svelte-ts/package.json +++ b/packages/create-vite/template-svelte-ts/package.json @@ -11,11 +11,11 @@ }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.3", - "@tsconfig/svelte": "^3.0.0", + "@tsconfig/svelte": "^4.0.1", "svelte": "^3.57.0", "svelte-check": "^2.10.3", "tslib": "^2.5.0", "typescript": "^5.0.2", - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-svelte/README.md b/packages/create-vite/template-svelte/README.md index 69c2ac55e18166..382941e05ce856 100644 --- a/packages/create-vite/template-svelte/README.md +++ b/packages/create-vite/template-svelte/README.md @@ -35,7 +35,7 @@ It is likely that most cases of changing variable types in runtime are likely to **Why is HMR not preserving my local component state?** -HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). +HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/sveltejs/svelte-hmr/tree/master/packages/svelte-hmr#preservation-of-local-state). If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. diff --git a/packages/create-vite/template-svelte/package.json b/packages/create-vite/template-svelte/package.json index b60657da9326c2..c10273bff3b697 100644 --- a/packages/create-vite/template-svelte/package.json +++ b/packages/create-vite/template-svelte/package.json @@ -11,6 +11,6 @@ "devDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.3", "svelte": "^3.57.0", - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-vanilla-ts/package.json b/packages/create-vite/template-vanilla-ts/package.json index 54e3a8b4823809..78dd609fe24c15 100644 --- a/packages/create-vite/template-vanilla-ts/package.json +++ b/packages/create-vite/template-vanilla-ts/package.json @@ -10,6 +10,6 @@ }, "devDependencies": { "typescript": "^5.0.2", - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-vanilla-ts/src/main.ts b/packages/create-vite/template-vanilla-ts/src/main.ts index 2f852a7cf8532f..791547b0d382f5 100644 --- a/packages/create-vite/template-vanilla-ts/src/main.ts +++ b/packages/create-vite/template-vanilla-ts/src/main.ts @@ -1,7 +1,7 @@ import './style.css' import typescriptLogo from './typescript.svg' import viteLogo from '/vite.svg' -import { setupCounter } from './counter' +import { setupCounter } from './counter.ts' document.querySelector('#app')!.innerHTML = `
diff --git a/packages/create-vite/template-vanilla-ts/tsconfig.json b/packages/create-vite/template-vanilla-ts/tsconfig.json index 08fa7f8a928ed9..75abdef2659446 100644 --- a/packages/create-vite/template-vanilla-ts/tsconfig.json +++ b/packages/create-vite/template-vanilla-ts/tsconfig.json @@ -1,19 +1,23 @@ { "compilerOptions": { - "target": "ESNext", + "target": "ES2020", "useDefineForClassFields": true, "module": "ESNext", - "lib": ["ESNext", "DOM"], + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ "moduleResolution": "bundler", - "strict": true, + "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, - "esModuleInterop": true, "noEmit": true, + + /* Linting */ + "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noImplicitReturns": true, - "skipLibCheck": true + "noFallthroughCasesInSwitch": true }, "include": ["src"] } diff --git a/packages/create-vite/template-vanilla/package.json b/packages/create-vite/template-vanilla/package.json index 78a6bf0c5c4df7..f0b5e60c38ff33 100644 --- a/packages/create-vite/template-vanilla/package.json +++ b/packages/create-vite/template-vanilla/package.json @@ -9,6 +9,6 @@ "preview": "vite preview" }, "devDependencies": { - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/create-vite/template-vue-ts/package.json b/packages/create-vite/template-vue-ts/package.json index ca3a713e2d8e4f..583a7267fe19b9 100644 --- a/packages/create-vite/template-vue-ts/package.json +++ b/packages/create-vite/template-vue-ts/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@vitejs/plugin-vue": "^4.1.0", "typescript": "^5.0.2", - "vite": "^4.2.1", + "vite": "^4.3.0-beta.2", "vue-tsc": "^1.2.0" } } diff --git a/packages/create-vite/template-vue-ts/tsconfig.json b/packages/create-vite/template-vue-ts/tsconfig.json index ffb2e9b4969404..f82888f3d0965c 100644 --- a/packages/create-vite/template-vue-ts/tsconfig.json +++ b/packages/create-vite/template-vue-ts/tsconfig.json @@ -1,17 +1,24 @@ { "compilerOptions": { - "target": "ESNext", + "target": "ES2020", "useDefineForClassFields": true, "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ "moduleResolution": "bundler", - "strict": true, - "jsx": "preserve", + "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, - "esModuleInterop": true, - "lib": ["ESNext", "DOM"], - "skipLibCheck": true, - "noEmit": true + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/packages/create-vite/template-vue/package.json b/packages/create-vite/template-vue/package.json index 231f9af78a67a2..e54568959bec8d 100644 --- a/packages/create-vite/template-vue/package.json +++ b/packages/create-vite/template-vue/package.json @@ -13,6 +13,6 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^4.1.0", - "vite": "^4.2.1" + "vite": "^4.3.0-beta.2" } } diff --git a/packages/plugin-legacy/src/index.ts b/packages/plugin-legacy/src/index.ts index 84453271e65736..29dfb4f754a128 100644 --- a/packages/plugin-legacy/src/index.ts +++ b/packages/plugin-legacy/src/index.ts @@ -1,4 +1,4 @@ -/* eslint-disable node/no-extraneous-import */ +/* eslint-disable n/no-extraneous-import */ import path from 'node:path' import { createHash } from 'node:crypto' import { createRequire } from 'node:module' diff --git a/packages/vite/CHANGELOG.md b/packages/vite/CHANGELOG.md index a449e90d5e8647..5a938eb762f34c 100644 --- a/packages/vite/CHANGELOG.md +++ b/packages/vite/CHANGELOG.md @@ -1,3 +1,112 @@ +## 4.3.0-beta.8 (2023-04-19) + +* fix: escape msg in render restricted error html (#12889) ([3aa2127](https://github.com/vitejs/vite/commit/3aa2127)), closes [#12889](https://github.com/vitejs/vite/issues/12889) +* fix: yarn pnp considerBuiltins (#12903) ([a0e10d5](https://github.com/vitejs/vite/commit/a0e10d5)), closes [#12903](https://github.com/vitejs/vite/issues/12903) +* refactor(eslint): migrate to `eslint-plugin-n` (#12895) ([62ebe28](https://github.com/vitejs/vite/commit/62ebe28)), closes [#12895](https://github.com/vitejs/vite/issues/12895) +* feat: expose `isFileServingAllowed` as public utility (#12894) ([93e095c](https://github.com/vitejs/vite/commit/93e095c)), closes [#12894](https://github.com/vitejs/vite/issues/12894) + + + +## 4.3.0-beta.7 (2023-04-17) + +* fix: broken middleware name (#12871) ([32bef57](https://github.com/vitejs/vite/commit/32bef57)), closes [#12871](https://github.com/vitejs/vite/issues/12871) +* fix: cleanUpStaleCacheDirs once per process (#12847) ([2c58b6e](https://github.com/vitejs/vite/commit/2c58b6e)), closes [#12847](https://github.com/vitejs/vite/issues/12847) +* fix(build): do not warn when URL in CSS is externalized (#12873) ([1510996](https://github.com/vitejs/vite/commit/1510996)), closes [#12873](https://github.com/vitejs/vite/issues/12873) +* refactor: simplify crawlEndFinder (#12868) ([31f8b51](https://github.com/vitejs/vite/commit/31f8b51)), closes [#12868](https://github.com/vitejs/vite/issues/12868) +* perf: parallelize await exportsData from depsInfo (#12869) ([ab3a530](https://github.com/vitejs/vite/commit/ab3a530)), closes [#12869](https://github.com/vitejs/vite/issues/12869) + + + +## 4.3.0-beta.6 (2023-04-14) + +* fix: build time deps optimization, and ensure single crawl end call (#12851) ([fa30879](https://github.com/vitejs/vite/commit/fa30879)), closes [#12851](https://github.com/vitejs/vite/issues/12851) +* fix: correct vite config temporary name (#12833) ([cdd9c23](https://github.com/vitejs/vite/commit/cdd9c23)), closes [#12833](https://github.com/vitejs/vite/issues/12833) +* fix(importAnalysis): warning on ExportAllDeclaration (#12799) ([5136b9b](https://github.com/vitejs/vite/commit/5136b9b)), closes [#12799](https://github.com/vitejs/vite/issues/12799) +* fix(optimizer): start optimizer after buildStart (#12832) ([cfe75ee](https://github.com/vitejs/vite/commit/cfe75ee)), closes [#12832](https://github.com/vitejs/vite/issues/12832) + + + +## 4.3.0-beta.5 (2023-04-11) + +* fix: handle try-catch for fs promise when resolve https config (#12808) ([0bba402](https://github.com/vitejs/vite/commit/0bba402)), closes [#12808](https://github.com/vitejs/vite/issues/12808) +* fix(build): correctly handle warning ignore list (#12831) ([8830532](https://github.com/vitejs/vite/commit/8830532)), closes [#12831](https://github.com/vitejs/vite/issues/12831) +* fix(resolve): use different importer check for css imports (#12815) ([d037327](https://github.com/vitejs/vite/commit/d037327)), closes [#12815](https://github.com/vitejs/vite/issues/12815) +* docs: fix pnpm link (#12803) ([ad358da](https://github.com/vitejs/vite/commit/ad358da)), closes [#12803](https://github.com/vitejs/vite/issues/12803) + + + +## 4.3.0-beta.4 (2023-04-09) + +* fix: ignore sideEffects for scripts imported from html (#12786) ([f09551f](https://github.com/vitejs/vite/commit/f09551f)), closes [#12786](https://github.com/vitejs/vite/issues/12786) +* fix: warn on build when bundling code that uses nodejs built in module (#12616) ([72050f9](https://github.com/vitejs/vite/commit/72050f9)), closes [#12616](https://github.com/vitejs/vite/issues/12616) +* fix(cli): pass mode to optimize command (#12776) ([da38ad8](https://github.com/vitejs/vite/commit/da38ad8)), closes [#12776](https://github.com/vitejs/vite/issues/12776) +* fix(css): resolve at import from dependency basedir (#12796) ([46bdf7d](https://github.com/vitejs/vite/commit/46bdf7d)), closes [#12796](https://github.com/vitejs/vite/issues/12796) +* fix(worker): asset in iife worker and relative base (#12697) ([ddefc06](https://github.com/vitejs/vite/commit/ddefc06)), closes [#12697](https://github.com/vitejs/vite/issues/12697) +* fix(worker): return null for shouldTransformCachedModule (#12797) ([ea5f6fc](https://github.com/vitejs/vite/commit/ea5f6fc)), closes [#12797](https://github.com/vitejs/vite/issues/12797) +* perf: avoid side effects resolving in dev and in the optimizer/scanner (#12789) ([fb904f9](https://github.com/vitejs/vite/commit/fb904f9)), closes [#12789](https://github.com/vitejs/vite/issues/12789) + + + +## 4.3.0-beta.3 (2023-04-07) + +* fix: allow onwarn to override vite default warning handling (#12757) ([f736930](https://github.com/vitejs/vite/commit/f736930)), closes [#12757](https://github.com/vitejs/vite/issues/12757) +* fix: ensure module in graph before transforming (#12774) ([44ad321](https://github.com/vitejs/vite/commit/44ad321)), closes [#12774](https://github.com/vitejs/vite/issues/12774) +* fix: update package cache watcher (#12772) ([a78588f](https://github.com/vitejs/vite/commit/a78588f)), closes [#12772](https://github.com/vitejs/vite/issues/12772) +* perf: parallelize imports processing in import analysis plugin (#12754) ([037a6c7](https://github.com/vitejs/vite/commit/037a6c7)), closes [#12754](https://github.com/vitejs/vite/issues/12754) +* perf: unresolvedUrlToModule promise cache (#12725) ([80c526e](https://github.com/vitejs/vite/commit/80c526e)), closes [#12725](https://github.com/vitejs/vite/issues/12725) +* perf(resolve): avoid tryFsResolve for /@fs/ paths (#12450) ([3ef8aaa](https://github.com/vitejs/vite/commit/3ef8aaa)), closes [#12450](https://github.com/vitejs/vite/issues/12450) +* perf(resolve): reduce vite client path checks (#12471) ([c49af23](https://github.com/vitejs/vite/commit/c49af23)), closes [#12471](https://github.com/vitejs/vite/issues/12471) +* refactor: use simpler resolve for nested optimized deps (#12770) ([d202588](https://github.com/vitejs/vite/commit/d202588)), closes [#12770](https://github.com/vitejs/vite/issues/12770) +* chore: improve debug log filtering (#12763) ([da1cb02](https://github.com/vitejs/vite/commit/da1cb02)), closes [#12763](https://github.com/vitejs/vite/issues/12763) + + + +## 4.3.0-beta.2 (2023-04-05) + +* fix: avoid clean up while committing deps folder (#12722) ([3f4d109](https://github.com/vitejs/vite/commit/3f4d109)), closes [#12722](https://github.com/vitejs/vite/issues/12722) +* fix: ignore pnp resolve error (#12719) ([2d30ae5](https://github.com/vitejs/vite/commit/2d30ae5)), closes [#12719](https://github.com/vitejs/vite/issues/12719) +* fix: leave fully dynamic import.meta.url asset (fixes #10306) (#10549) ([56802b1](https://github.com/vitejs/vite/commit/56802b1)), closes [#10306](https://github.com/vitejs/vite/issues/10306) [#10549](https://github.com/vitejs/vite/issues/10549) +* fix: output combined sourcemap in importAnalysisBuild plugin (#12642) ([d051639](https://github.com/vitejs/vite/commit/d051639)), closes [#12642](https://github.com/vitejs/vite/issues/12642) +* fix: take in relative assets path fixes from rollup (#12695) ([81e44dd](https://github.com/vitejs/vite/commit/81e44dd)), closes [#12695](https://github.com/vitejs/vite/issues/12695) +* fix: throws error when plugin tries to resolve ID to external URL (#11731) ([49674b5](https://github.com/vitejs/vite/commit/49674b5)), closes [#11731](https://github.com/vitejs/vite/issues/11731) +* fix(css): css file emit synchronously (#12558) ([8e30025](https://github.com/vitejs/vite/commit/8e30025)), closes [#12558](https://github.com/vitejs/vite/issues/12558) +* fix(import-analysis): escape quotes correctly (#12688) ([1638ebd](https://github.com/vitejs/vite/commit/1638ebd)), closes [#12688](https://github.com/vitejs/vite/issues/12688) +* fix(optimizer): load the correct lock file (#12700) ([889eebe](https://github.com/vitejs/vite/commit/889eebe)), closes [#12700](https://github.com/vitejs/vite/issues/12700) +* fix(server): delay ws server listen when restart (#12734) ([abe9274](https://github.com/vitejs/vite/commit/abe9274)), closes [#12734](https://github.com/vitejs/vite/issues/12734) +* fix(ssr): load sourcemaps alongside modules (#11780) ([be95050](https://github.com/vitejs/vite/commit/be95050)), closes [#11780](https://github.com/vitejs/vite/issues/11780) +* fix(ssr): show ssr module loader error stack (#12651) ([050c0f9](https://github.com/vitejs/vite/commit/050c0f9)), closes [#12651](https://github.com/vitejs/vite/issues/12651) +* fix(worker): disable manifest plugins in worker build (#12661) ([20b8ef4](https://github.com/vitejs/vite/commit/20b8ef4)), closes [#12661](https://github.com/vitejs/vite/issues/12661) +* fix(worker): worker import.meta.url should not depends on document in iife mode (#12629) ([65f5ed2](https://github.com/vitejs/vite/commit/65f5ed2)), closes [#12629](https://github.com/vitejs/vite/issues/12629) +* refactor: `import.meta.url` condition from renderChunk hook of worker plugin (#12696) ([fdef8fd](https://github.com/vitejs/vite/commit/fdef8fd)), closes [#12696](https://github.com/vitejs/vite/issues/12696) +* refactor: clean up preTransformRequest (#12672) ([561227c](https://github.com/vitejs/vite/commit/561227c)), closes [#12672](https://github.com/vitejs/vite/issues/12672) +* refactor: make debugger nullable (#12687) ([89e4977](https://github.com/vitejs/vite/commit/89e4977)), closes [#12687](https://github.com/vitejs/vite/issues/12687) +* refactor: remove `ensureVolumeInPath` (#12690) ([a3150ee](https://github.com/vitejs/vite/commit/a3150ee)), closes [#12690](https://github.com/vitejs/vite/issues/12690) +* refactor: remove unused exports data props (#12740) ([4538bfe](https://github.com/vitejs/vite/commit/4538bfe)), closes [#12740](https://github.com/vitejs/vite/issues/12740) +* refactor: use `resolvePackageData` in `requireResolveFromRootWithFallback` (#12712) ([1ea38e2](https://github.com/vitejs/vite/commit/1ea38e2)), closes [#12712](https://github.com/vitejs/vite/issues/12712) +* refactor(css): simplify cached import code (#12730) ([0646754](https://github.com/vitejs/vite/commit/0646754)), closes [#12730](https://github.com/vitejs/vite/issues/12730) +* feat: reuse existing style elements in dev (#12678) ([3a41bd8](https://github.com/vitejs/vite/commit/3a41bd8)), closes [#12678](https://github.com/vitejs/vite/issues/12678) +* feat: skip pinging the server when the tab is not shown (#12698) ([bedcd8f](https://github.com/vitejs/vite/commit/bedcd8f)), closes [#12698](https://github.com/vitejs/vite/issues/12698) +* feat(create-vite): use typescript 5.0 in templates (#12481) ([8582e2d](https://github.com/vitejs/vite/commit/8582e2d)), closes [#12481](https://github.com/vitejs/vite/issues/12481) +* perf: avoid new URL() in hot path (#12654) ([f4e2fdf](https://github.com/vitejs/vite/commit/f4e2fdf)), closes [#12654](https://github.com/vitejs/vite/issues/12654) +* perf: improve isFileReadable performance (#12397) ([acf3a14](https://github.com/vitejs/vite/commit/acf3a14)), closes [#12397](https://github.com/vitejs/vite/issues/12397) +* perf: module graph url shortcuts (#12635) ([c268cfa](https://github.com/vitejs/vite/commit/c268cfa)), closes [#12635](https://github.com/vitejs/vite/issues/12635) +* perf: reduce runOptimizerIfIdleAfterMs time (#12614) ([d026a65](https://github.com/vitejs/vite/commit/d026a65)), closes [#12614](https://github.com/vitejs/vite/issues/12614) +* perf: shorcircuit resolve in ensure entry from url (#12655) ([82137d6](https://github.com/vitejs/vite/commit/82137d6)), closes [#12655](https://github.com/vitejs/vite/issues/12655) +* perf: skip es-module-lexer if have no dynamic imports (#12732) ([5d07d7c](https://github.com/vitejs/vite/commit/5d07d7c)), closes [#12732](https://github.com/vitejs/vite/issues/12732) +* perf: start preprocessing static imports before updating module graph (#12723) ([c90b46e](https://github.com/vitejs/vite/commit/c90b46e)), closes [#12723](https://github.com/vitejs/vite/issues/12723) +* perf: use package cache for one off resolve (#12744) ([77bf4ef](https://github.com/vitejs/vite/commit/77bf4ef)), closes [#12744](https://github.com/vitejs/vite/issues/12744) +* perf(css): cache lazy import (#12721) ([fedb080](https://github.com/vitejs/vite/commit/fedb080)), closes [#12721](https://github.com/vitejs/vite/issues/12721) +* perf(hmr): keep track of already traversed modules when propagating update (#12658) ([3b912fb](https://github.com/vitejs/vite/commit/3b912fb)), closes [#12658](https://github.com/vitejs/vite/issues/12658) +* perf(moduleGraph): resolve dep urls in parallel (#12619) ([4823fec](https://github.com/vitejs/vite/commit/4823fec)), closes [#12619](https://github.com/vitejs/vite/issues/12619) +* perf(resolve): skip for virtual files (#12638) ([9e13f5f](https://github.com/vitejs/vite/commit/9e13f5f)), closes [#12638](https://github.com/vitejs/vite/issues/12638) +* chore: fix resolve debug log timing (#12746) ([22f6ae6](https://github.com/vitejs/vite/commit/22f6ae6)), closes [#12746](https://github.com/vitejs/vite/issues/12746) +* chore: revert custom license resolve (#12709) ([621bb2f](https://github.com/vitejs/vite/commit/621bb2f)), closes [#12709](https://github.com/vitejs/vite/issues/12709) +* chore: set target in tsconfig.check.json (#12675) ([15177a1](https://github.com/vitejs/vite/commit/15177a1)), closes [#12675](https://github.com/vitejs/vite/issues/12675) +* chore(optimizer): remove redundant setTimeout call in scan process (#12718) ([0ce0e93](https://github.com/vitejs/vite/commit/0ce0e93)), closes [#12718](https://github.com/vitejs/vite/issues/12718) +* chore(optimizer): show full optimized deps list (#12686) ([8bef662](https://github.com/vitejs/vite/commit/8bef662)), closes [#12686](https://github.com/vitejs/vite/issues/12686) + + + ## 4.3.0-beta.1 (2023-03-29) * feat: use preview server parameter in preview server hook (#11647) ([4c142ea](https://github.com/vitejs/vite/commit/4c142ea)), closes [#11647](https://github.com/vitejs/vite/issues/11647) diff --git a/packages/vite/package.json b/packages/vite/package.json index 0e4c5d5ab799c8..b4dc688b0de802 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -1,6 +1,6 @@ { "name": "vite", - "version": "4.3.0-beta.1", + "version": "4.3.0-beta.8", "type": "module", "license": "MIT", "author": "Evan You", @@ -68,8 +68,7 @@ "dependencies": { "esbuild": "^0.17.5", "postcss": "^8.4.21", - "resolve": "^1.22.1", - "rollup": "^3.20.0" + "rollup": "^3.20.2" }, "optionalDependencies": { "fsevents": "~2.3.2" @@ -87,6 +86,7 @@ "@rollup/plugin-typescript": "^11.0.0", "@rollup/pluginutils": "^5.0.2", "@types/pnpapi": "^0.0.2", + "@types/escape-html": "^1.0.0", "acorn": "^8.8.2", "acorn-walk": "^8.2.0", "cac": "^6.7.14", @@ -101,6 +101,7 @@ "dotenv": "^16.0.3", "dotenv-expand": "^9.0.0", "es-module-lexer": "^1.2.0", + "escape-html": "^1.0.3", "estree-walker": "^3.0.3", "etag": "^1.8.1", "fast-glob": "^3.2.12", diff --git a/packages/vite/rollup.config.ts b/packages/vite/rollup.config.ts index e241a77182c4c7..7a1064778ae9f8 100644 --- a/packages/vite/rollup.config.ts +++ b/packages/vite/rollup.config.ts @@ -120,6 +120,13 @@ function createNodePlugins( pattern: /^var json = typeof JSON.+require\('jsonify'\);$/gm, replacement: 'var json = JSON', }, + // postcss-import uses the `resolve` dep if the `resolve` option is not passed. + // However, we always pass the `resolve` option. Remove this import to avoid + // bundling the `resolve` dep. + 'postcss-import/index.js': { + src: 'const resolveId = require("./lib/resolve-id")', + replacement: 'const resolveId = (id) => id', + }, }), commonjs({ diff --git a/packages/vite/rollupLicensePlugin.ts b/packages/vite/rollupLicensePlugin.ts index f1d24f4b7c8e82..d3dce56a7dfeb6 100644 --- a/packages/vite/rollupLicensePlugin.ts +++ b/packages/vite/rollupLicensePlugin.ts @@ -1,9 +1,6 @@ import fs from 'node:fs' -import path from 'node:path' import license from 'rollup-plugin-license' import colors from 'picocolors' -import fg from 'fast-glob' -import resolve from 'resolve' import type { Plugin } from 'rollup' export default function licensePlugin( @@ -66,21 +63,6 @@ export default function licensePlugin( typeof repository === 'string' ? repository : repository.url }\n` } - if (!licenseText && name) { - try { - const pkgDir = path.dirname( - resolve.sync(path.join(name, 'package.json'), { - preserveSymlinks: false, - }), - ) - const licenseFile = fg.sync(`${pkgDir}/LICENSE*`, { - caseSensitiveMatch: false, - })[0] - if (licenseFile) { - licenseText = fs.readFileSync(licenseFile, 'utf-8') - } - } catch {} - } if (licenseText) { text += '\n' + diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index ee8cd5c855d544..0c3163adfb1c06 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -2,7 +2,6 @@ import type { ErrorPayload, HMRPayload, Update } from 'types/hmrPayload' import type { ModuleNamespace, ViteHotContext } from 'types/hot' import type { InferCustomEventPayload } from 'types/customEvent' import { ErrorOverlay, overlayId } from './overlay' -// eslint-disable-next-line node/no-missing-import import '@vite/env' // injected by the hmr plugin when served @@ -315,24 +314,61 @@ async function waitForSuccessfulPing( ) { const pingHostProtocol = socketProtocol === 'wss' ? 'https' : 'http' - // eslint-disable-next-line no-constant-condition - while (true) { + const ping = async () => { + // A fetch on a websocket URL will return a successful promise with status 400, + // but will reject a networking error. + // When running on middleware mode, it returns status 426, and an cors error happens if mode is not no-cors try { - // A fetch on a websocket URL will return a successful promise with status 400, - // but will reject a networking error. - // When running on middleware mode, it returns status 426, and an cors error happens if mode is not no-cors await fetch(`${pingHostProtocol}://${hostAndPath}`, { mode: 'no-cors', }) - break - } catch (e) { - // wait ms before attempting to ping again - await new Promise((resolve) => setTimeout(resolve, ms)) + return true + } catch {} + return false + } + + if (await ping()) { + return + } + await wait(ms) + + // eslint-disable-next-line no-constant-condition + while (true) { + if (document.visibilityState === 'visible') { + if (await ping()) { + break + } + await wait(ms) + } else { + await waitForWindowShow() } } } +function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +function waitForWindowShow() { + return new Promise((resolve) => { + const onChange = async () => { + if (document.visibilityState === 'visible') { + resolve() + document.removeEventListener('visibilitychange', onChange) + } + } + document.addEventListener('visibilitychange', onChange) + }) +} + const sheetsMap = new Map() + +// collect existing style elements that may have been inserted during SSR +// to avoid FOUC or duplicate styles +document.querySelectorAll('style[data-vite-dev-id]').forEach((el) => { + sheetsMap.set(el.getAttribute('data-vite-dev-id')!, el as HTMLStyleElement) +}) + // all css imports should be inserted at the same position // because after build it will be a single css file let lastInsertedStyle: HTMLStyleElement | undefined diff --git a/packages/vite/src/node/__tests__/plugins/import.spec.ts b/packages/vite/src/node/__tests__/plugins/import.spec.ts index 792c58515f542e..679d5bdb3d0a38 100644 --- a/packages/vite/src/node/__tests__/plugins/import.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/import.spec.ts @@ -1,9 +1,19 @@ -import { describe, expect, test } from 'vitest' +import { beforeEach, describe, expect, test, vi } from 'vitest' import { transformCjsImport } from '../../plugins/importAnalysis' describe('transformCjsImport', () => { const url = './node_modules/.vite/deps/react.js' const rawUrl = 'react' + const config: any = { + command: 'serve', + logger: { + warn: vi.fn(), + }, + } + + beforeEach(() => { + config.logger.warn.mockClear() + }) test('import specifier', () => { expect( @@ -12,6 +22,8 @@ describe('transformCjsImport', () => { url, rawUrl, 0, + '', + config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + @@ -22,7 +34,14 @@ describe('transformCjsImport', () => { test('import default specifier', () => { expect( - transformCjsImport('import React from "react"', url, rawUrl, 0), + transformCjsImport( + 'import React from "react"', + url, + rawUrl, + 0, + '', + config, + ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react', @@ -34,6 +53,8 @@ describe('transformCjsImport', () => { url, rawUrl, 0, + '', + config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + @@ -43,7 +64,14 @@ describe('transformCjsImport', () => { test('import all specifier', () => { expect( - transformCjsImport('import * as react from "react"', url, rawUrl, 0), + transformCjsImport( + 'import * as react from "react"', + url, + rawUrl, + 0, + '', + config, + ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const react = __vite__cjsImport0_react', @@ -51,13 +79,33 @@ describe('transformCjsImport', () => { }) test('export all specifier', () => { - expect(transformCjsImport('export * from "react"', url, rawUrl, 0)).toBe( - undefined, + expect( + transformCjsImport( + 'export * from "react"', + url, + rawUrl, + 0, + 'modA', + config, + ), + ).toBe(undefined) + + expect(config.logger.warn).toBeCalledWith( + expect.stringContaining(`export * from "react"\` in modA`), ) expect( - transformCjsImport('export * as react from "react"', url, rawUrl, 0), + transformCjsImport( + 'export * as react from "react"', + url, + rawUrl, + 0, + '', + config, + ), ).toBe(undefined) + + expect(config.logger.warn).toBeCalledTimes(1) }) test('export name specifier', () => { @@ -67,6 +115,8 @@ describe('transformCjsImport', () => { url, rawUrl, 0, + '', + config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + @@ -81,6 +131,8 @@ describe('transformCjsImport', () => { url, rawUrl, 0, + '', + config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + @@ -92,7 +144,14 @@ describe('transformCjsImport', () => { test('export default specifier', () => { expect( - transformCjsImport('export { default } from "react"', url, rawUrl, 0), + transformCjsImport( + 'export { default } from "react"', + url, + rawUrl, + 0, + '', + config, + ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + 'const __vite__cjsExportDefault_0 = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react; ' + @@ -105,6 +164,8 @@ describe('transformCjsImport', () => { url, rawUrl, 0, + '', + config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + @@ -118,6 +179,8 @@ describe('transformCjsImport', () => { url, rawUrl, 0, + '', + config, ), ).toBe( 'import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js"; ' + diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 912129b6f70fb9..26f80c9b37a72c 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -51,13 +51,14 @@ import { initDepsOptimizer, } from './optimizer' import { loadFallbackPlugin } from './plugins/loadFallback' -import { findNearestPackageData, watchPackageDataPlugin } from './packages' +import { findNearestPackageData } from './packages' import type { PackageCache } from './packages' import { ensureWatchPlugin } from './plugins/ensureWatch' import { ESBUILD_MODULES_TARGET, VERSION } from './constants' import { resolveChokidarOptions } from './watch' import { completeSystemWrapPlugin } from './plugins/completeSystemWrap' import { mergeConfig } from './publicUtils' +import { webWorkerPostPlugin } from './plugins/worker' export interface BuildOptions { /** @@ -435,7 +436,6 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{ pre: [ completeSystemWrapPlugin(), ...(options.watch ? [ensureWatchPlugin()] : []), - watchPackageDataPlugin(config), ...(usePluginCommonjs ? [commonjsPlugin(options.commonjsOptions)] : []), dataURIPlugin(), ...(( @@ -445,6 +445,7 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{ : [rollupOptionsPlugins], ) ).filter(Boolean) as Plugin[]), + ...(config.isWorker ? [webWorkerPostPlugin()] : []), ], post: [ buildImportAnalysisPlugin(config), @@ -869,40 +870,50 @@ export function onRollupWarning( warn: WarningHandler, config: ResolvedConfig, ): void { - if (warning.code === 'UNRESOLVED_IMPORT') { - const id = warning.id - const exporter = warning.exporter - // throw unless it's commonjs external... - if (!id || !/\?commonjs-external$/.test(id)) { - throw new Error( - `[vite]: Rollup failed to resolve import "${exporter}" from "${id}".\n` + - `This is most likely unintended because it can break your application at runtime.\n` + - `If you do want to externalize this module explicitly add it to\n` + - `\`build.rollupOptions.external\``, + function viteWarn(warning: RollupWarning) { + if (warning.code === 'UNRESOLVED_IMPORT') { + const id = warning.id + const exporter = warning.exporter + // throw unless it's commonjs external... + if (!id || !/\?commonjs-external$/.test(id)) { + throw new Error( + `[vite]: Rollup failed to resolve import "${exporter}" from "${id}".\n` + + `This is most likely unintended because it can break your application at runtime.\n` + + `If you do want to externalize this module explicitly add it to\n` + + `\`build.rollupOptions.external\``, + ) + } + } + + if ( + warning.plugin === 'rollup-plugin-dynamic-import-variables' && + dynamicImportWarningIgnoreList.some((msg) => + warning.message.includes(msg), ) + ) { + return } - } - if ( - warning.plugin === 'rollup-plugin-dynamic-import-variables' && - dynamicImportWarningIgnoreList.some((msg) => warning.message.includes(msg)) - ) { - return - } + if (warningIgnoreList.includes(warning.code!)) { + return + } - if (!warningIgnoreList.includes(warning.code!)) { - const userOnWarn = config.build.rollupOptions?.onwarn - if (userOnWarn) { - userOnWarn(warning, warn) - } else if (warning.code === 'PLUGIN_WARNING') { + if (warning.code === 'PLUGIN_WARNING') { config.logger.warn( `${colors.bold( colors.yellow(`[plugin:${warning.plugin}]`), )} ${colors.yellow(warning.message)}`, ) - } else { - warn(warning) } + + warn(warning) + } + + const userOnWarn = config.build.rollupOptions?.onwarn + if (userOnWarn) { + userOnWarn(warning, viteWarn) + } else { + viteWarn(warning) } } @@ -936,12 +947,12 @@ async function cjsSsrResolveExternal( } } -function resolveUserExternal( +export function resolveUserExternal( user: ExternalOption, id: string, parentId: string | undefined, isResolved: boolean, -) { +): boolean | null | void { if (typeof user === 'function') { return user(id, parentId, isResolved) } else if (Array.isArray(user)) { @@ -1033,7 +1044,7 @@ function injectSsrFlag>( /* The following functions are copied from rollup - https://github.com/rollup/rollup/blob/c5269747cd3dd14c4b306e8cea36f248d9c1aa01/src/ast/nodes/MetaProperty.ts#L189-L232 + https://github.com/rollup/rollup/blob/0bcf0a672ac087ff2eb88fbba45ec62389a4f45f/src/ast/nodes/MetaProperty.ts#L145-L193 https://github.com/rollup/rollup The MIT License (MIT) @@ -1042,15 +1053,30 @@ function injectSsrFlag>( 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. */ +const needsEscapeRegEx = /[\n\r'\\\u2028\u2029]/ +const quoteNewlineRegEx = /([\n\r'\u2028\u2029])/g +const backSlashRegEx = /\\/g + +function escapeId(id: string): string { + if (!needsEscapeRegEx.test(id)) return id + return id.replace(backSlashRegEx, '\\\\').replace(quoteNewlineRegEx, '\\$1') +} + const getResolveUrl = (path: string, URL = 'URL') => `new ${URL}(${path}).href` const getRelativeUrlFromDocument = (relativePath: string, umd = false) => getResolveUrl( - `'${relativePath}', ${ + `'${escapeId(relativePath)}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' }document.currentScript && document.currentScript.src || document.baseURI`, ) +const getFileUrlFromFullPath = (path: string) => + `require('u' + 'rl').pathToFileURL(${path}).href` + +const getFileUrlFromRelativePath = (path: string) => + getFileUrlFromFullPath(`__dirname + '/${path}'`) + const relativeUrlMechanisms: Record< InternalModuleFormat, (relativePath: string) => string @@ -1060,22 +1086,26 @@ const relativeUrlMechanisms: Record< return getResolveUrl(`require.toUrl('${relativePath}'), document.baseURI`) }, cjs: (relativePath) => - `(typeof document === 'undefined' ? ${getResolveUrl( - `'file:' + __dirname + '/${relativePath}'`, - `(require('u' + 'rl').URL)`, + `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath( + relativePath, )} : ${getRelativeUrlFromDocument(relativePath)})`, es: (relativePath) => getResolveUrl(`'${relativePath}', import.meta.url`), iife: (relativePath) => getRelativeUrlFromDocument(relativePath), // NOTE: make sure rollup generate `module` params system: (relativePath) => getResolveUrl(`'${relativePath}', module.meta.url`), umd: (relativePath) => - `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getResolveUrl( - `'file:' + __dirname + '/${relativePath}'`, - `(require('u' + 'rl').URL)`, + `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( + relativePath, )} : ${getRelativeUrlFromDocument(relativePath, true)})`, } /* end of copy */ +const customRelativeUrlMechanisms = { + ...relativeUrlMechanisms, + 'worker-iife': (relativePath) => + getResolveUrl(`'${relativePath}', self.location.href`), +} as const satisfies Record string> + export type RenderBuiltAssetUrl = ( filename: string, type: { @@ -1125,8 +1155,10 @@ export function toOutputFilePathInJS( export function createToImportMetaURLBasedRelativeRuntime( format: InternalModuleFormat, + isWorker: boolean, ): (filename: string, importer: string) => { runtime: string } { - const toRelativePath = relativeUrlMechanisms[format] + const formatLong = isWorker && format === 'iife' ? 'worker-iife' : format + const toRelativePath = customRelativeUrlMechanisms[formatLong] return (filename, importer) => ({ runtime: toRelativePath( path.posix.relative(path.dirname(importer), filename), diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index ac9f5945c1a2f2..d18ab93fd0213f 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -276,6 +276,7 @@ cli base: options.base, configFile: options.config, logLevel: options.logLevel, + mode: options.mode, }, 'serve', ) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 19f856a040ef2b..7b4107c8aafe05 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -48,6 +48,7 @@ import { DEFAULT_EXTENSIONS, DEFAULT_MAIN_FIELDS, ENV_ENTRY, + FS_PREFIX, } from './constants' import type { InternalResolveOptions, @@ -475,8 +476,14 @@ export async function resolveConfig( ) const clientAlias = [ - { find: /^\/?@vite\/env/, replacement: ENV_ENTRY }, - { find: /^\/?@vite\/client/, replacement: CLIENT_ENTRY }, + { + find: /^\/?@vite\/env/, + replacement: path.posix.join(FS_PREFIX, normalizePath(ENV_ENTRY)), + }, + { + find: /^\/?@vite\/client/, + replacement: path.posix.join(FS_PREFIX, normalizePath(CLIENT_ENTRY)), + }, ] // resolve alias with internal client alias @@ -587,6 +594,7 @@ export async function resolveConfig( preferRelative: false, tryIndex: true, ...options, + idOnly: true, }), ], })) @@ -775,16 +783,14 @@ export async function resolveConfig( ) } - if (process.env.DEBUG) { - debug(`using resolved config: %O`, { - ...resolved, - plugins: resolved.plugins.map((p) => p.name), - worker: { - ...resolved.worker, - plugins: resolved.worker.plugins.map((p) => p.name), - }, - }) - } + debug?.(`using resolved config: %O`, { + ...resolved, + plugins: resolved.plugins.map((p) => p.name), + worker: { + ...resolved.worker, + plugins: resolved.worker.plugins.map((p) => p.name), + }, + }) if (config.build?.terserOptions && config.build.minify !== 'terser') { logger.warn( @@ -915,7 +921,7 @@ export async function loadConfigFromFile( } if (!resolvedPath) { - debug('no config file found.') + debug?.('no config file found.') return null } @@ -940,7 +946,7 @@ export async function loadConfigFromFile( bundled.code, isESM, ) - debug(`bundled config file loaded in ${getTime()}`) + debug?.(`bundled config file loaded in ${getTime()}`) const config = await (typeof userConfig === 'function' ? userConfig(configEnv) @@ -1003,6 +1009,7 @@ async function bundleConfigFile( dedupe: [], extensions: DEFAULT_EXTENSIONS, preserveSymlinks: false, + packageCache: new Map(), } // externalize bare imports @@ -1086,7 +1093,7 @@ async function loadConfigFromBundledFile( if (isESM) { const fileBase = `${fileName}.timestamp-${Date.now()}-${Math.random() .toString(16) - .slice(2)})}` + .slice(2)}` const fileNameTmp = `${fileBase}.mjs` const fileUrl = `${pathToFileURL(fileBase)}.mjs` await fsp.writeFile(fileNameTmp, bundledCode) diff --git a/packages/vite/src/node/http.ts b/packages/vite/src/node/http.ts index 88ccd4b955cdbc..afefc14fc5e1d0 100644 --- a/packages/vite/src/node/http.ts +++ b/packages/vite/src/node/http.ts @@ -137,11 +137,7 @@ export async function resolveHttpsConfig( async function readFileIfExists(value?: string | Buffer | any[]) { if (typeof value === 'string') { - try { - return fsp.readFile(path.resolve(value)) - } catch (e) { - return value - } + return fsp.readFile(path.resolve(value)).catch(() => value) } return value } diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index d2079064004c69..2ccb4fef4f89d5 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -3,7 +3,6 @@ import fsp from 'node:fs/promises' import path from 'node:path' import { promisify } from 'node:util' import { performance } from 'node:perf_hooks' -import _debug from 'debug' import colors from 'picocolors' import type { BuildContext, BuildOptions as EsbuildBuildOptions } from 'esbuild' import esbuild, { build } from 'esbuild' @@ -14,7 +13,6 @@ import type { ResolvedConfig } from '../config' import { arraify, createDebugger, - emptyDir, flattenId, getHash, isOptimizable, @@ -28,7 +26,6 @@ import { import { transformWithEsbuild } from '../plugins/esbuild' import { ESBUILD_MODULES_TARGET } from '../constants' import { resolvePackageData } from '../packages' -import type { ViteDevServer } from '../server' import { esbuildCjsExternalPlugin, esbuildDepPlugin } from './esbuildDepPlugin' import { scanImports } from './scan' export { @@ -37,22 +34,15 @@ export { getDepsOptimizer, } from './optimizer' -export const debuggerViteDeps = createDebugger('vite:deps') -const debug = debuggerViteDeps -const isDebugEnabled = _debug('vite:deps').enabled +const debug = createDebugger('vite:deps') const jsExtensionRE = /\.js$/i const jsMapExtensionRE = /\.js\.map$/i -const reExportRE = /export\s+\*\s+from/ export type ExportsData = { hasImports: boolean // exported names (for `export { a as b }`, `b` is exported name) exports: readonly string[] - facade: boolean - // es-module-lexer has a facade detection but isn't always accurate for our - // use case when the module has default export - hasReExports?: boolean // hint if the dep requires loading as jsx jsxLoader?: boolean } @@ -74,7 +64,6 @@ export interface DepsOptimizer { close: () => Promise options: DepOptimizationOptions - server?: ViteDevServer } export interface DepOptimizationConfig { @@ -165,9 +154,6 @@ export interface DepOptimizationResult { * to be able to discard the result */ commit: () => Promise - /** - * @deprecated noop - */ cancel: () => void } @@ -251,7 +237,7 @@ export async function optimizeDeps( const deps = await discoverProjectDependencies(config).result const depsString = depsLogString(Object.keys(deps)) - log(colors.green(`Optimizing dependencies:\n ${depsString}`)) + log?.(colors.green(`Optimizing dependencies:\n ${depsString}`)) await addManuallyIncludedOptimizeDeps(deps, config, ssr) @@ -341,6 +327,8 @@ export function addOptimizedDepInfo( return depInfo } +let firstLoadCachedDepOptimizationMetadata = true + /** * Creates the initial dep optimization metadata, loading it from the deps cache * if it exists and pre-bundling isn't forced @@ -353,16 +341,11 @@ export async function loadCachedDepOptimizationMetadata( ): Promise { const log = asCommand ? config.logger.info : debug - setTimeout(() => { - // Before Vite 2.9, dependencies were cached in the root of the cacheDir - // For compat, we remove the cache if we find the old structure - if (fs.existsSync(path.join(config.cacheDir, '_metadata.json'))) { - emptyDir(config.cacheDir) - } - // Fire a clean up of stale cache dirs, in case old processes didn't - // terminate correctly - cleanupDepsCacheStaleDirs(config) - }, 100) + if (firstLoadCachedDepOptimizationMetadata) { + firstLoadCachedDepOptimizationMetadata = false + // Fire up a clean up of stale processing deps dirs if older process exited early + setTimeout(() => cleanupDepsCacheStaleDirs(config), 0) + } const depsCacheDir = getDepsCacheDir(config, ssr) @@ -377,7 +360,7 @@ export async function loadCachedDepOptimizationMetadata( } catch (e) {} // hash is consistent, no need to re-bundle if (cachedMetadata && cachedMetadata.hash === getDepHash(config, ssr)) { - log('Hash is consistent. Skipping. Use --force to override.') + log?.('Hash is consistent. Skipping. Use --force to override.') // Nothing to commit or cancel as we are using the cache, we only // need to resolve the processing promise so requests can move on return cachedMetadata @@ -448,18 +431,7 @@ export function toDiscoveredDependencies( } export function depsLogString(qualifiedIds: string[]): string { - if (isDebugEnabled) { - return colors.yellow(qualifiedIds.join(`, `)) - } else { - const total = qualifiedIds.length - const maxListed = 5 - const listed = Math.min(total, maxListed) - const extra = Math.max(0, total - maxListed) - return colors.yellow( - qualifiedIds.slice(0, listed).join(`, `) + - (extra > 0 ? `, ...and ${extra} more` : ``), - ) - } + return colors.yellow(qualifiedIds.join(`, `)) } /** @@ -510,8 +482,11 @@ export function runOptimizeDeps( const qualifiedIds = Object.keys(depsInfo) let cleaned = false + let committed = false const cleanUp = () => { - if (!cleaned) { + // If commit was already called, ignore the clean up even if a cancel was requested + // This minimizes the chances of leaving the deps cache in a corrupted state + if (!cleaned && !committed) { cleaned = true // No need to wait, we can clean up in the background because temp folders // are unique per run @@ -525,9 +500,17 @@ export function runOptimizeDeps( metadata, cancel: cleanUp, commit: async () => { + if (cleaned) { + throw new Error( + 'Can not commit a Deps Optimization run as it was cancelled', + ) + } + // Ignore clean up requests after this point so the temp folder isn't deleted before + // we finish commiting the new deps cache files to the deps folder + committed = true + // Write metadata file, then commit the processing folder to the global deps cache // Rewire the file paths from the temporal processing dir to the final deps cache dir - const dataPath = path.join(processingCacheDir, '_metadata.json') fs.writeFileSync( dataPath, @@ -658,7 +641,7 @@ export function runOptimizeDeps( } } - debug( + debug?.( `Dependencies bundled in ${(performance.now() - start).toFixed(2)}ms`, ) @@ -724,23 +707,25 @@ async function prepareEsbuildOptimizerRun( const { plugins: pluginsFromConfig = [], ...esbuildOptions } = optimizeDeps?.esbuildOptions ?? {} - for (const id in depsInfo) { - const src = depsInfo[id].src! - const exportsData = await (depsInfo[id].exportsData ?? - extractExportsData(src, config, ssr)) - if (exportsData.jsxLoader) { - // Ensure that optimization won't fail by defaulting '.js' to the JSX parser. - // This is useful for packages such as Gatsby. - esbuildOptions.loader = { - '.js': 'jsx', - ...esbuildOptions.loader, + await Promise.all( + Object.keys(depsInfo).map(async (id) => { + const src = depsInfo[id].src! + const exportsData = await (depsInfo[id].exportsData ?? + extractExportsData(src, config, ssr)) + if (exportsData.jsxLoader && !esbuildOptions.loader?.['.js']) { + // Ensure that optimization won't fail by defaulting '.js' to the JSX parser. + // This is useful for packages such as Gatsby. + esbuildOptions.loader = { + '.js': 'jsx', + ...esbuildOptions.loader, + } } - } - const flatId = flattenId(id) - flatIdDeps[flatId] = src - idToExports[id] = exportsData - flatIdToExports[flatId] = exportsData - } + const flatId = flattenId(id) + flatIdDeps[flatId] = src + idToExports[id] = exportsData + flatIdToExports[flatId] = exportsData + }), + ) if (optimizerContext.cancelled) return { context: undefined, idToExports } @@ -883,6 +868,7 @@ function createOptimizeDepsIncludeResolver( scan: true, ssrOptimizeCheck: ssr, ssrConfig: config.ssr, + packageCache: new Map(), }) return async (id: string) => { const lastArrowIndex = id.lastIndexOf('>') @@ -1149,11 +1135,10 @@ export async function extractExportsData( write: false, format: 'esm', }) - const [imports, exports, facade] = parse(result.outputFiles[0].text) + const [imports, exports] = parse(result.outputFiles[0].text) return { hasImports: imports.length > 0, exports: exports.map((e) => e.n), - facade, } } @@ -1165,31 +1150,20 @@ export async function extractExportsData( parseResult = parse(entryContent) } catch { const loader = esbuildOptions.loader?.[path.extname(filePath)] || 'jsx' - debug( + debug?.( `Unable to parse: ${filePath}.\n Trying again with a ${loader} transform.`, ) const transformed = await transformWithEsbuild(entryContent, filePath, { loader, }) - // Ensure that optimization won't fail by defaulting '.js' to the JSX parser. - // This is useful for packages such as Gatsby. - esbuildOptions.loader = { - '.js': 'jsx', - ...esbuildOptions.loader, - } parseResult = parse(transformed.code) usedJsxLoader = true } - const [imports, exports, facade] = parseResult + const [imports, exports] = parseResult const exportsData: ExportsData = { hasImports: imports.length > 0, exports: exports.map((e) => e.n), - facade, - hasReExports: imports.some(({ ss, se }) => { - const exp = entryContent.slice(ss, se) - return reExportRE.test(exp) - }), jsxLoader: usedJsxLoader, } return exportsData @@ -1233,11 +1207,13 @@ function isSingleDefaultExport(exports: readonly string[]) { } const lockfileFormats = [ - { name: 'package-lock.json', checkPatches: true }, - { name: 'yarn.lock', checkPatches: true }, // Included in lockfile for v2+ - { name: 'pnpm-lock.yaml', checkPatches: false }, // Included in lockfile - { name: 'bun.lockb', checkPatches: true }, -] + { name: 'package-lock.json', checkPatches: true, manager: 'npm' }, + { name: 'yarn.lock', checkPatches: true, manager: 'yarn' }, // Included in lockfile for v2+ + { name: 'pnpm-lock.yaml', checkPatches: false, manager: 'pnpm' }, // Included in lockfile + { name: 'bun.lockb', checkPatches: true, manager: 'bun' }, +].sort((_, { manager }) => { + return process.env.npm_config_user_agent?.startsWith(manager) ? 1 : -1 +}) const lockfileNames = lockfileFormats.map((l) => l.name) export function getDepHash(config: ResolvedConfig, ssr: boolean): string { diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index e8b92b986ba3ef..f82dbe0f3bcf85 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -1,6 +1,5 @@ import colors from 'picocolors' -import _debug from 'debug' -import { getHash } from '../utils' +import { createDebugger, getHash } from '../utils' import { getDepOptimizationConfig } from '../config' import type { ResolvedConfig, ViteDevServer } from '..' import { @@ -8,7 +7,6 @@ import { addOptimizedDepInfo, createIsOptimizedDepFile, createIsOptimizedDepUrl, - debuggerViteDeps as debug, depsFromOptimizedDepInfo, depsLogString, discoverProjectDependencies, @@ -28,7 +26,7 @@ import type { OptimizedDepInfo, } from '.' -const isDebugEnabled = _debug('vite:deps').enabled +const debug = createDebugger('vite:deps') /** * The amount to wait for requests to register newly found dependencies before triggering @@ -101,7 +99,7 @@ async function createDepsOptimizer( const cachedMetadata = await loadCachedDepOptimizationMetadata(config, ssr) - let handle: NodeJS.Timeout | undefined + let debounceProcessingHandle: NodeJS.Timeout | undefined let closed = false @@ -122,7 +120,6 @@ async function createDepsOptimizer( ensureFirstRun, close, options: getDepOptimizationConfig(config, ssr), - server, } depsOptimizerMap.set(config, depsOptimizer) @@ -158,9 +155,20 @@ async function createDepsOptimizer( let enqueuedRerun: (() => void) | undefined let currentlyProcessing = false - // If there wasn't a cache or it is outdated, we need to prepare a first run let firstRunCalled = !!cachedMetadata + // During build, we wait for every module to be scanned before resolving + // optimized deps loading for rollup on each rebuild. It will be recreated + // after each buildStart. + // During dev, if this is a cold run, we wait for static imports discovered + // from the first request before resolving to minimize full page reloads. + // On warm start or after the first optimization is run, we use a simpler + // debounce strategy each time a new dep is discovered. + let crawlEndFinder: CrawlEndFinder | undefined + if (isBuild || !cachedMetadata) { + crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) + } + let optimizationResult: | { cancel: () => Promise @@ -175,14 +183,13 @@ async function createDepsOptimizer( } | undefined - let optimizingNewDeps: Promise | undefined async function close() { closed = true + crawlEndFinder?.cancel() await Promise.allSettled([ discover?.cancel(), depsOptimizer.scanProcessing, optimizationResult?.cancel(), - optimizingNewDeps, ]) } @@ -213,10 +220,10 @@ async function createDepsOptimizer( if (!isBuild) { // Important, the scanner is dev only depsOptimizer.scanProcessing = new Promise((resolve) => { - // Ensure server listen is called before the scanner - setTimeout(async () => { + // Runs in the background in case blocking high priority tasks + ;(async () => { try { - debug(colors.green(`scanning for dependencies...`)) + debug?.(colors.green(`scanning for dependencies...`)) discover = discoverProjectDependencies(config) const deps = await discover.result @@ -244,7 +251,7 @@ async function createDepsOptimizer( resolve() depsOptimizer.scanProcessing = undefined } - }, 0) + })() }) } } @@ -261,27 +268,6 @@ async function createDepsOptimizer( depOptimizationProcessing = newDepOptimizationProcessing() } - async function optimizeNewDeps() { - // a successful completion of the optimizeDeps rerun will end up - // creating new bundled version of all current and discovered deps - // in the cache dir and a new metadata info object assigned - // to _metadata. A fullReload is only issued if the previous bundled - // dependencies have changed. - - // if the rerun fails, _metadata remains untouched, current discovered - // deps are cleaned, and a fullReload is issued - - // All deps, previous known and newly discovered are rebundled, - // respect insertion order to keep the metadata file stable - - const knownDeps = prepareKnownDeps() - - startNextDiscoveredBatch() - - optimizationResult = runOptimizeDeps(config, knownDeps) - return await optimizationResult.result - } - function prepareKnownDeps() { const knownDeps: Record = {} // Clone optimized info objects, fileHash, browserHash may be changed for them @@ -297,6 +283,18 @@ async function createDepsOptimizer( } async function runOptimizer(preRunResult?: DepOptimizationResult) { + // a successful completion of the optimizeDeps rerun will end up + // creating new bundled version of all current and discovered deps + // in the cache dir and a new metadata info object assigned + // to _metadata. A fullReload is only issued if the previous bundled + // dependencies have changed. + + // if the rerun fails, _metadata remains untouched, current discovered + // deps are cleaned, and a fullReload is issued + + // All deps, previous known and newly discovered are rebundled, + // respect insertion order to keep the metadata file stable + const isRerun = firstRunCalled firstRunCalled = true @@ -304,7 +302,7 @@ async function createDepsOptimizer( enqueuedRerun = undefined // Ensure that a rerun will not be issued for current discovered deps - if (handle) clearTimeout(handle) + if (debounceProcessingHandle) clearTimeout(debounceProcessingHandle) if (closed || Object.keys(metadata.discovered).length === 0) { currentlyProcessing = false @@ -314,9 +312,17 @@ async function createDepsOptimizer( currentlyProcessing = true try { - const processingResult = - preRunResult ?? (await (optimizingNewDeps = optimizeNewDeps())) - optimizingNewDeps = undefined + let processingResult: DepOptimizationResult + if (preRunResult) { + processingResult = preRunResult + } else { + const knownDeps = prepareKnownDeps() + startNextDiscoveredBatch() + + optimizationResult = runOptimizeDeps(config, knownDeps) + processingResult = await optimizationResult.result + optimizationResult = undefined + } if (closed) { currentlyProcessing = false @@ -398,7 +404,7 @@ async function createDepsOptimizer( if (!needsReload) { await commitProcessing() - if (!isDebugEnabled) { + if (!debug) { if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) newDepsToLogHandle = setTimeout(() => { newDepsToLogHandle = undefined @@ -423,7 +429,7 @@ async function createDepsOptimizer( // once a rerun is committed processingResult.cancel() - debug( + debug?.( colors.green( `✨ delaying reload as new dependencies have been found...`, ), @@ -431,7 +437,7 @@ async function createDepsOptimizer( } else { await commitProcessing() - if (!isDebugEnabled) { + if (!debug) { if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) newDepsToLogHandle = undefined logNewlyDiscoveredDeps() @@ -476,13 +482,13 @@ async function createDepsOptimizer( } function fullReload() { - if (depsOptimizer.server) { + if (server) { // Cached transform results have stale imports (resolved to // old locations) so they need to be invalidated before the page is // reloaded. - depsOptimizer.server.moduleGraph.invalidateAll() + server.moduleGraph.invalidateAll() - depsOptimizer.server.ws.send({ + server.ws.send({ type: 'full-reload', path: '*', }) @@ -495,7 +501,7 @@ async function createDepsOptimizer( // optimizeDeps processing is finished const deps = Object.keys(metadata.discovered) const depsString = depsLogString(deps) - debug(colors.green(`new dependencies found: ${depsString}`)) + debug?.(colors.green(`new dependencies found: ${depsString}`)) runOptimizer() } @@ -535,7 +541,12 @@ async function createDepsOptimizer( // we can get a list of every missing dependency before giving to the // browser a dependency that may be outdated, thus avoiding full page reloads - if (firstRunCalled) { + if (!crawlEndFinder) { + if (isBuild) { + logger.error( + 'Vite Internal Error: Missing dependency found after crawling ended', + ) + } // Debounced rerun, let other missing dependencies be discovered before // the running next optimizeDeps debouncedProcessing() @@ -576,11 +587,11 @@ async function createDepsOptimizer( // Debounced rerun, let other missing dependencies be discovered before // the running next optimizeDeps enqueuedRerun = undefined - if (handle) clearTimeout(handle) + if (debounceProcessingHandle) clearTimeout(debounceProcessingHandle) if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) newDepsToLogHandle = undefined - handle = setTimeout(() => { - handle = undefined + debounceProcessingHandle = setTimeout(() => { + debounceProcessingHandle = undefined enqueuedRerun = rerun if (!currentlyProcessing) { enqueuedRerun() @@ -588,14 +599,22 @@ async function createDepsOptimizer( }, timeout) } + // During dev, onCrawlEnd is called once when the server starts and all static + // imports after the first request have been crawled (dynamic imports may also + // be crawled if the browser requests them right away). + // During build, onCrawlEnd will be called once after each buildStart (so in + // watch mode it will be called after each rebuild has processed every module). + // All modules are transformed first in this case (both static and dynamic). async function onCrawlEnd() { - debug(colors.green(`✨ static imports crawl ended`)) - if (firstRunCalled) { + // On build time, a missing dep appearing after onCrawlEnd is an internal error + // On dev, switch after this point to a simple debounce strategy + crawlEndFinder = undefined + + debug?.(colors.green(`✨ static imports crawl ended`)) + if (closed) { return } - currentlyProcessing = false - const crawlDeps = Object.keys(metadata.discovered) // Await for the scan+optimize step running in the background @@ -605,11 +624,12 @@ async function createDepsOptimizer( if (!isBuild && optimizationResult) { const result = await optimizationResult.result optimizationResult = undefined + currentlyProcessing = false const scanDeps = Object.keys(result.metadata.optimized) if (scanDeps.length === 0 && crawlDeps.length === 0) { - debug( + debug?.( colors.green( `✨ no dependencies found by the scanner or crawling static imports`, ), @@ -638,16 +658,16 @@ async function createDepsOptimizer( } } if (scannerMissedDeps) { - debug( + debug?.( colors.yellow( `✨ new dependencies were found while crawling that weren't detected by the scanner`, ), ) } - debug(colors.green(`✨ re-running optimizer`)) + debug?.(colors.green(`✨ re-running optimizer`)) debouncedProcessing(0) } else { - debug( + debug?.( colors.green( `✨ using post-scan optimizer result, the scanner found every used dependency`, ), @@ -656,8 +676,10 @@ async function createDepsOptimizer( runOptimizer(result) } } else { + currentlyProcessing = false + if (crawlDeps.length === 0) { - debug( + debug?.( colors.green( `✨ no dependencies found while crawling the static imports`, ), @@ -670,97 +692,113 @@ async function createDepsOptimizer( } } - const runOptimizerIfIdleAfterMs = 50 + // Called during buildStart at build time, when build --watch is used. + function resetRegisteredIds() { + crawlEndFinder?.cancel() + crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) + } - let registeredIds: { id: string; done: () => Promise }[] = [] - let seenIds = new Set() - let workersSources = new Set() - const waitingOn = new Set() - let firstRunEnsured = false + function registerWorkersSource(id: string) { + crawlEndFinder?.registerWorkersSource(id) + } + function delayDepsOptimizerUntil(id: string, done: () => Promise) { + if (crawlEndFinder && !depsOptimizer.isOptimizedDepFile(id)) { + crawlEndFinder.delayDepsOptimizerUntil(id, done) + } + } + function ensureFirstRun() { + crawlEndFinder?.ensureFirstRun() + } +} - function resetRegisteredIds() { - registeredIds = [] - seenIds = new Set() - workersSources = new Set() - waitingOn.clear() - firstRunEnsured = false +const callCrawlEndIfIdleAfterMs = 50 + +interface CrawlEndFinder { + ensureFirstRun: () => void + registerWorkersSource: (id: string) => void + delayDepsOptimizerUntil: (id: string, done: () => Promise) => void + cancel: () => void +} + +function setupOnCrawlEnd(onCrawlEnd: () => void): CrawlEndFinder { + const registeredIds = new Set() + const seenIds = new Set() + const workersSources = new Set() + let timeoutHandle: NodeJS.Timeout | undefined + + let cancelled = false + function cancel() { + cancelled = true + } + + let crawlEndCalled = false + function callOnCrawlEnd() { + if (!cancelled && !crawlEndCalled) { + crawlEndCalled = true + onCrawlEnd() + } } // If all the inputs are dependencies, we aren't going to get any // delayDepsOptimizerUntil(id) calls. We need to guard against this // by forcing a rerun if no deps have been registered + let firstRunEnsured = false function ensureFirstRun() { - if (!firstRunEnsured && !firstRunCalled && registeredIds.length === 0) { + if (!firstRunEnsured && seenIds.size === 0) { setTimeout(() => { - if (!closed && registeredIds.length === 0) { - onCrawlEnd() + if (seenIds.size === 0) { + callOnCrawlEnd() } - }, runOptimizerIfIdleAfterMs) + }, 200) } firstRunEnsured = true } function registerWorkersSource(id: string): void { workersSources.add(id) + // Avoid waiting for this id, as it may be blocked by the rollup // bundling process of the worker that also depends on the optimizer - registeredIds = registeredIds.filter((registered) => registered.id !== id) - if (waitingOn.has(id)) { - waitingOn.delete(id) - runOptimizerWhenIdle() - } + registeredIds.delete(id) + + checkIfCrawlEndAfterTimeout() } function delayDepsOptimizerUntil(id: string, done: () => Promise): void { - if (!depsOptimizer.isOptimizedDepFile(id) && !seenIds.has(id)) { + if (!seenIds.has(id)) { seenIds.add(id) - registeredIds.push({ id, done }) - runOptimizerWhenIdle() + if (!workersSources.has(id)) { + registeredIds.add(id) + done() + .catch(() => {}) + .finally(() => markIdAsDone(id)) + } } } + function markIdAsDone(id: string): void { + registeredIds.delete(id) + checkIfCrawlEndAfterTimeout() + } - async function runOptimizerWhenIdle() { - if (waitingOn.size > 0) return - - const processingRegisteredIds = registeredIds - registeredIds = [] - - const donePromises = processingRegisteredIds.map(async (registeredId) => { - waitingOn.add(registeredId.id) - try { - await registeredId.done() - } finally { - waitingOn.delete(registeredId.id) - } - }) - - const afterLoad = () => { - if (closed) return - if ( - registeredIds.length > 0 && - registeredIds.every((registeredId) => - workersSources.has(registeredId.id), - ) - ) { - return - } + function checkIfCrawlEndAfterTimeout() { + if (cancelled || registeredIds.size > 0) return - if (registeredIds.length > 0) { - runOptimizerWhenIdle() - } else { - onCrawlEnd() - } - } + if (timeoutHandle) clearTimeout(timeoutHandle) + timeoutHandle = setTimeout( + callOnCrawlEndWhenIdle, + callCrawlEndIfIdleAfterMs, + ) + } + async function callOnCrawlEndWhenIdle() { + if (cancelled || registeredIds.size > 0) return + callOnCrawlEnd() + } - const results = await Promise.allSettled(donePromises) - if ( - registeredIds.length > 0 || - results.some((result) => result.status === 'rejected') - ) { - afterLoad() - } else { - setTimeout(afterLoad, runOptimizerIfIdleAfterMs) - } + return { + ensureFirstRun, + registerWorkersSource, + delayDepsOptimizerUntil, + cancel, } } @@ -811,7 +849,7 @@ function findInteropMismatches( // This only happens when a discovered dependency has mixed ESM and CJS syntax // and it hasn't been manually added to optimizeDeps.needsInterop needsInteropMismatch.push(dep) - debug(colors.cyan(`✨ needsInterop mismatch detected for ${dep}`)) + debug?.(colors.cyan(`✨ needsInterop mismatch detected for ${dep}`)) } } } diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 7aa5902d23e04b..5571d031245f6d 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -34,7 +34,6 @@ import { transformGlobImport } from '../plugins/importMetaGlob' type ResolveIdOptions = Parameters[2] -const isDebug = process.env.DEBUG const debug = createDebugger('vite:deps') const htmlTypesRE = /\.(html|vue|svelte|astro|imba)$/ @@ -85,7 +84,7 @@ export function scanImports(config: ResolvedConfig): { } if (scanContext.cancelled) return - debug( + debug?.( `Crawling dependencies using entries: ${entries .map((entry) => `\n ${colors.dim(entry)}`) .join('')}`, @@ -141,7 +140,7 @@ export function scanImports(config: ResolvedConfig): { throw e }) .finally(() => { - if (isDebug) { + if (debug) { const duration = (performance.now() - start).toFixed(2) const depsStr = Object.keys(orderedDependencies(deps)) diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index b4a0d7948240e6..c82367a48cca64 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -1,8 +1,7 @@ import fs from 'node:fs' import path from 'node:path' import { createRequire } from 'node:module' -import { createFilter, safeRealpathSync } from './utils' -import type { ResolvedConfig } from './config' +import { createFilter, isInNodeModules, safeRealpathSync } from './utils' import type { Plugin } from './plugin' // eslint-disable-next-line @typescript-eslint/consistent-type-imports @@ -37,11 +36,10 @@ export interface PackageData { } } -export function invalidatePackageData( +function invalidatePackageData( packageCache: PackageCache, pkgPath: string, ): void { - packageCache.delete(pkgPath) const pkgDir = path.dirname(pkgPath) packageCache.forEach((pkg, cacheKey) => { if (pkg.dir === pkgDir) { @@ -60,7 +58,14 @@ export function resolvePackageData( const cacheKey = getRpdCacheKey(pkgName, basedir, preserveSymlinks) if (packageCache?.has(cacheKey)) return packageCache.get(cacheKey)! - const pkg = pnp.resolveToUnqualified(pkgName, basedir) + let pkg: string | null + try { + pkg = pnp.resolveToUnqualified(pkgName, basedir, { + considerBuiltins: false, + }) + } catch { + return null + } if (!pkg) return null const pkgData = loadPackageData(path.join(pkg, 'package.json')) @@ -212,17 +217,21 @@ export function loadPackageData(pkgPath: string): PackageData { return pkg } -export function watchPackageDataPlugin(config: ResolvedConfig): Plugin { +export function watchPackageDataPlugin(packageCache: PackageCache): Plugin { + // a list of files to watch before the plugin is ready const watchQueue = new Set() - let watchFile = (id: string) => { + const watchedDirs = new Set() + + const watchFileStub = (id: string) => { watchQueue.add(id) } + let watchFile = watchFileStub - const { packageCache } = config const setPackageData = packageCache.set.bind(packageCache) packageCache.set = (id, pkg) => { - if (id.endsWith('.json')) { - watchFile(id) + if (!isInNodeModules(pkg.dir) && !watchedDirs.has(pkg.dir)) { + watchedDirs.add(pkg.dir) + watchFile(path.join(pkg.dir, 'package.json')) } return setPackageData(id, pkg) } @@ -230,16 +239,21 @@ export function watchPackageDataPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:watch-package-data', buildStart() { - watchFile = this.addWatchFile + watchFile = this.addWatchFile.bind(this) watchQueue.forEach(watchFile) watchQueue.clear() }, buildEnd() { - watchFile = (id) => watchQueue.add(id) + watchFile = watchFileStub }, watchChange(id) { if (id.endsWith('/package.json')) { - invalidatePackageData(packageCache, id) + invalidatePackageData(packageCache, path.normalize(id)) + } + }, + handleHotUpdate({ file }) { + if (file.endsWith('/package.json')) { + invalidatePackageData(packageCache, path.normalize(file)) } }, } diff --git a/packages/vite/src/node/plugin.ts b/packages/vite/src/node/plugin.ts index f2d73cbf1232e0..e522295f30c896 100644 --- a/packages/vite/src/node/plugin.ts +++ b/packages/vite/src/node/plugin.ts @@ -9,13 +9,12 @@ import type { TransformResult, } from 'rollup' export type { PluginContext } from 'rollup' -import type { UserConfig } from './config' +import type { ConfigEnv, ResolvedConfig, UserConfig } from './config' import type { ServerHook } from './server' import type { IndexHtmlTransform } from './plugins/html' import type { ModuleNode } from './server/moduleGraph' import type { HmrContext } from './server/hmr' import type { PreviewServerHook } from './preview' -import type { ConfigEnv, ResolvedConfig } from './' /** * Vite plugins extends the Rollup plugin interface with a few extra diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 6dcf8be3a9c1a0..174b3e5826646c 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -69,6 +69,7 @@ export function renderAssetUrlInJS( ): MagicString | undefined { const toRelativeRuntime = createToImportMetaURLBasedRelativeRuntime( opts.format, + config.isWorker, ) let match: RegExpExecArray | null diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index 3f61d2ddbd0b46..6872de5249e014 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -10,6 +10,7 @@ import { slash, transformStableResult, } from '../utils' +import { CLIENT_ENTRY } from '../constants' import { fileToUrl } from './asset' import { preloadHelperId } from './importAnalysisBuild' @@ -33,6 +34,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { if ( !options?.ssr && id !== preloadHelperId && + id !== CLIENT_ENTRY && code.includes('new URL') && code.includes(`import.meta.url`) ) { @@ -56,7 +58,13 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { const ast = this.parse(rawUrl) const templateLiteral = (ast as any).body[0].expression if (templateLiteral.expressions.length) { - const pattern = JSON.stringify(buildGlobPattern(templateLiteral)) + 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 + } + // Note: native import.meta.url is not supported in the baseline // target so we use the global location here. It can be // window.location or self.location in case it is used in a Web Worker. @@ -64,7 +72,9 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { s.update( index, index + exp.length, - `new URL((import.meta.glob(${pattern}, { eager: true, import: 'default', as: 'url' }))[${rawUrl}], self.location)`, + `new URL((import.meta.glob(${JSON.stringify( + pattern, + )}, { eager: true, import: 'default', as: 'url' }))[${rawUrl}], self.location)`, ) continue } diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 1ea8cba9c510d9..7366c1b093799d 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -26,7 +26,7 @@ import type { RawSourceMap } from '@ampproject/remapping' import { getCodeWithSourcemap, injectSourcesContent } from '../server/sourcemap' import type { ModuleNode } from '../server/moduleGraph' import type { ResolveFn, ViteDevServer } from '../' -import { toOutputFilePathInCss } from '../build' +import { resolveUserExternal, toOutputFilePathInCss } from '../build' import { CLIENT_PUBLIC_PATH, CSS_LANGS_RE, @@ -230,10 +230,21 @@ export function cssPlugin(config: ResolvedConfig): Plugin { return fileToUrl(resolved, config, this) } if (config.command === 'build') { - // #9800 If we cannot resolve the css url, leave a warning. - config.logger.warnOnce( - `\n${url} referenced in ${id} didn't resolve at build time, it will remain unchanged to be resolved at runtime`, - ) + const isExternal = config.build.rollupOptions.external + ? resolveUserExternal( + config.build.rollupOptions.external, + url, // use URL as id since id could not be resolved + id, + false, + ) + : false + + if (!isExternal) { + // #9800 If we cannot resolve the css url, leave a warning. + config.logger.warnOnce( + `\n${url} referenced in ${id} didn't resolve at build time, it will remain unchanged to be resolved at runtime`, + ) + } } return url } @@ -316,6 +327,8 @@ export function cssPlugin(config: ResolvedConfig): Plugin { export function cssPostPlugin(config: ResolvedConfig): Plugin { // styles initialization in buildStart causes a styling loss in watch const styles: Map = new Map() + // list of css emit tasks to guarantee the files are emitted in a deterministic order + let emitTasks: Promise[] = [] let pureCssChunks: Set // when there are multiple rollup outputs and extracting CSS, only emit once, @@ -353,6 +366,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { pureCssChunks = new Set() outputToExtractedCSSMap = new Map() hasEmitted = false + emitTasks = [] }, async transform(css, id, options) { @@ -563,7 +577,22 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { const cssFileName = ensureFileExt(cssAssetName, '.css') chunkCSS = resolveAssetUrlsInCss(chunkCSS, cssAssetName) - chunkCSS = await finalizeCss(chunkCSS, true, config) + + const previousTask = emitTasks[emitTasks.length - 1] + // finalizeCss is async which makes `emitFile` non-deterministic, so + // we use a `.then` to wait for previous tasks before finishing this + const thisTask = finalizeCss(chunkCSS, true, config).then((css) => { + chunkCSS = css + // make sure the previous task is also finished, this works recursively + return previousTask + }) + + // push this task so the next task can wait for this one + emitTasks.push(thisTask) + const emitTasksLength = emitTasks.length + + // wait for this and previous tasks to finish + await thisTask // emit corresponding css file const referenceId = this.emitFile({ @@ -577,6 +606,11 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { .get(config)! .set(referenceId, { originalName, isEntry }) chunk.viteMetadata!.importedCss.add(this.getFileName(referenceId)) + + if (emitTasksLength === emitTasks.length) { + // this is the last task, clear `emitTasks` to free up memory + emitTasks = [] + } } else if (!config.build.ssr) { // legacy build and inline css @@ -891,7 +925,7 @@ async function compileCSS( if (needInlineImport) { postcssPlugins.unshift( - (await import('postcss-import')).default({ + (await importPostcssImport()).default({ async resolve(id, basedir) { const publicFile = checkPublicFile(id, config) if (publicFile) { @@ -906,6 +940,18 @@ async function compileCSS( if (resolved) { return path.resolve(resolved) } + + // postcss-import falls back to `resolve` dep if this is unresolved, + // but we've shimmed to remove the `resolve` dep to cut on bundle size. + // warn here to provide a better error message. + if (!path.isAbsolute(id)) { + config.logger.error( + colors.red( + `Unable to resolve \`@import "${id}"\` from ${basedir}`, + ), + ) + } + return id }, nameLayer(index) { @@ -926,7 +972,7 @@ async function compileCSS( if (isModule) { postcssPlugins.unshift( - (await import('postcss-modules')).default({ + (await importPostcssModules()).default({ ...modulesOptions, localsConvention: modulesOptions?.localsConvention, getJSON( @@ -963,31 +1009,30 @@ async function compileCSS( let postcssResult: PostCSS.Result try { const source = removeDirectQuery(id) + const postcss = await importPostcss() // postcss is an unbundled dep and should be lazy imported - postcssResult = await (await import('postcss')) - .default(postcssPlugins) - .process(code, { - ...postcssOptions, - parser: - lang === 'sss' - ? loadPreprocessor(PostCssDialectLang.sss, config.root) - : postcssOptions.parser, - to: source, - from: source, - ...(devSourcemap - ? { - map: { - inline: false, - annotation: false, - // postcss may return virtual files - // we cannot obtain content of them, so this needs to be enabled - sourcesContent: true, - // when "prev: preprocessorMap", the result map may include duplicate filename in `postcssResult.map.sources` - // prev: preprocessorMap, - }, - } - : {}), - }) + postcssResult = await postcss.default(postcssPlugins).process(code, { + ...postcssOptions, + parser: + lang === 'sss' + ? loadPreprocessor(PostCssDialectLang.sss, config.root) + : postcssOptions.parser, + to: source, + from: source, + ...(devSourcemap + ? { + map: { + inline: false, + annotation: false, + // postcss may return virtual files + // we cannot obtain content of them, so this needs to be enabled + sourcesContent: true, + // when "prev: preprocessorMap", the result map may include duplicate filename in `postcssResult.map.sources` + // prev: preprocessorMap, + }, + } + : {}), + }) // record CSS dependencies from @imports for (const message of postcssResult.messages) { @@ -1055,6 +1100,22 @@ async function compileCSS( } } +function createCachedImport(imp: () => Promise): () => T | Promise { + let cached: T | Promise + return () => { + if (!cached) { + cached = imp().then((module) => { + cached = module + return module + }) + } + return cached + } +} +const importPostcssImport = createCachedImport(() => import('postcss-import')) +const importPostcssModules = createCachedImport(() => import('postcss-modules')) +const importPostcss = createCachedImport(() => import('postcss')) + export interface PreprocessCSSResult { code: string map?: SourceMapInput diff --git a/packages/vite/src/node/plugins/dynamicImportVars.ts b/packages/vite/src/node/plugins/dynamicImportVars.ts index 7933618d49da08..45a27c6f466431 100644 --- a/packages/vite/src/node/plugins/dynamicImportVars.ts +++ b/packages/vite/src/node/plugins/dynamicImportVars.ts @@ -7,6 +7,7 @@ import { dynamicImportToGlob } from '@rollup/plugin-dynamic-import-vars' import type { KnownAsTypeMap } from 'types/importGlob' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '../config' +import { CLIENT_ENTRY } from '../constants' import { createFilter, normalizePath, @@ -20,6 +21,11 @@ import { toAbsoluteGlob } from './importMetaGlob' export const dynamicImportHelperId = '\0vite/dynamic-import-helper' const relativePathRE = /^\.{1,2}\// +// fast path to check if source contains a dynamic import. we check for a +// trailing slash too as a dynamic import statement can have comments between +// the `import` and the `(`. +const hasDynamicImportRE = /\bimport\s*[(/]/ + interface DynamicImportRequest { as?: keyof KnownAsTypeMap } @@ -162,7 +168,11 @@ export function dynamicImportVarsPlugin(config: ResolvedConfig): Plugin { }, async transform(source, importer) { - if (!filter(importer)) { + if ( + !filter(importer) || + importer === CLIENT_ENTRY || + !hasDynamicImportRE.test(source) + ) { return } diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index fac8c44b140f67..c92e7fe80c1e84 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -20,11 +20,11 @@ import { generateCodeFrame, timeFrom, } from '../utils' -import type { ResolvedConfig, ViteDevServer } from '..' +import type { ViteDevServer } from '../server' +import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' -import { searchForWorkspaceRoot } from '..' +import { searchForWorkspaceRoot } from '../server/searchRoot' -const isDebug = process.env.DEBUG const debug = createDebugger('vite:esbuild') const INJECT_HELPERS_IIFE_RE = @@ -193,7 +193,7 @@ export async function transformWithEsbuild( map, } } catch (e: any) { - debug(`esbuild error with options used: `, resolvedOptions) + debug?.(`esbuild error with options used: `, resolvedOptions) // patch error information if (e.errors) { e.frame = '' @@ -455,7 +455,7 @@ function initTSConfck(root: string, force = false) { } async function initTSConfckParseOptions(workspaceRoot: string) { - const start = isDebug ? performance.now() : 0 + const start = debug ? performance.now() : 0 const options: TSConfckParseOptions = { cache: new Map(), @@ -468,7 +468,7 @@ async function initTSConfckParseOptions(workspaceRoot: string) { resolveWithEmptyIfConfigNotFound: true, } - isDebug && debug(timeFrom(start), 'tsconfck init', colors.dim(workspaceRoot)) + debug?.(timeFrom(start), 'tsconfck init', colors.dim(workspaceRoot)) return options } diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index c1a7b6403502cb..119f0192cabb43 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -53,7 +53,6 @@ import { cjsShouldExternalizeForSSR, shouldExternalizeForSSR, } from '../ssr/ssrExternal' -import { transformRequest } from '../server/transformRequest' import { getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer' import { checkPublicFile } from './asset' import { @@ -63,7 +62,6 @@ import { import { isCSSRequest, isDirectCSSRequest, isModuleCSSRequest } from './css' import { browserExternalId } from './resolve' -const isDebug = !!process.env.DEBUG const debug = createDebugger('vite:import-analysis') const clientDir = normalizePath(CLIENT_DIR) @@ -82,6 +80,12 @@ const hasViteIgnoreRE = /\/\*\s*@vite-ignore\s*\*\// const cleanUpRawUrlRE = /\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm const urlIsStringRE = /^(?:'.*'|".*"|`.*`)$/ +interface UrlPosition { + url: string + start: number + end: number +} + export function isExplicitImportRequired(url: string): boolean { return !isJSRequest(cleanUrl(url)) && !isCSSRequest(url) } @@ -208,7 +212,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { const prettyImporter = prettifyUrl(importer, root) if (canSkipImportAnalysis(importer)) { - isDebug && debug(colors.dim(`[skipped] ${prettyImporter}`)) + debug?.(colors.dim(`[skipped] ${prettyImporter}`)) return null } @@ -260,12 +264,9 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { if (!imports.length && !(this as any)._addedImports) { importerModule.isSelfAccepting = false - isDebug && - debug( - `${timeFrom(start)} ${colors.dim( - `[no imports] ${prettyImporter}`, - )}`, - ) + debug?.( + `${timeFrom(start)} ${colors.dim(`[no imports] ${prettyImporter}`)}`, + ) return source } @@ -276,14 +277,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { let s: MagicString | undefined const str = () => s || (s = new MagicString(source)) const importedUrls = new Set() - const staticImportedUrls = new Set<{ url: string; id: string }>() - const acceptedUrls = new Set<{ - url: string - start: number - end: number - }>() let isPartiallySelfAccepting = false - const acceptedExports = new Set() const importedBindings = enablePartialAccept ? new Map>() : null @@ -391,10 +385,12 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // up-to-date version of this module. try { // delay setting `isSelfAccepting` until the file is actually used (#7870) - const depModule = await moduleGraph.ensureEntryFromUrl( + // We use an internal function to avoid resolving the url again + const depModule = await moduleGraph._ensureEntryFromUrl( unwrapId(url), ssr, canSkipImportAnalysis(url) || forceSkipImportAnalysis, + resolved, ) if (depModule.lastHMRTimestamp > 0) { url = injectQuery(url, `t=${depModule.lastHMRTimestamp}`) @@ -413,250 +409,302 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { return [url, resolved.id] } - for (let index = 0; index < imports.length; index++) { - const { - s: start, - e: end, - ss: expStart, - se: expEnd, - d: dynamicIndex, - // #2083 User may use escape path, - // so use imports[index].n to get the unescaped string - n: specifier, - a: assertIndex, - } = imports[index] - - const rawUrl = source.slice(start, end) - - // check import.meta usage - if (rawUrl === 'import.meta') { - const prop = source.slice(end, end + 4) - if (prop === '.hot') { - hasHMR = true - const endHot = end + 4 + (source[end + 4] === '?' ? 1 : 0) - if (source.slice(endHot, endHot + 7) === '.accept') { - // further analyze accepted modules - if (source.slice(endHot, endHot + 14) === '.acceptExports') { - lexAcceptedHmrExports( - source, - source.indexOf('(', endHot + 14) + 1, - acceptedExports, - ) - isPartiallySelfAccepting = true - } else if ( - lexAcceptedHmrDeps( - source, - source.indexOf('(', endHot + 7) + 1, - acceptedUrls, - ) - ) { - isSelfAccepting = true + const orderedAcceptedUrls = new Array | undefined>( + imports.length, + ) + const orderedAcceptedExports = new Array | undefined>( + imports.length, + ) + + await Promise.all( + imports.map(async (importSpecifier, index) => { + const { + s: start, + e: end, + ss: expStart, + se: expEnd, + d: dynamicIndex, + // #2083 User may use escape path, + // so use imports[index].n to get the unescaped string + n: specifier, + a: assertIndex, + } = importSpecifier + + const rawUrl = source.slice(start, end) + + // check import.meta usage + if (rawUrl === 'import.meta') { + const prop = source.slice(end, end + 4) + if (prop === '.hot') { + hasHMR = true + const endHot = end + 4 + (source[end + 4] === '?' ? 1 : 0) + if (source.slice(endHot, endHot + 7) === '.accept') { + // further analyze accepted modules + if (source.slice(endHot, endHot + 14) === '.acceptExports') { + const importAcceptedExports = (orderedAcceptedExports[index] = + new Set()) + lexAcceptedHmrExports( + source, + source.indexOf('(', endHot + 14) + 1, + importAcceptedExports, + ) + isPartiallySelfAccepting = true + } else { + const importAcceptedUrls = (orderedAcceptedUrls[index] = + new Set()) + if ( + lexAcceptedHmrDeps( + source, + source.indexOf('(', endHot + 7) + 1, + importAcceptedUrls, + ) + ) { + isSelfAccepting = true + } + } } + } else if (prop === '.env') { + hasEnv = true } - } else if (prop === '.env') { - hasEnv = true + return } - continue - } - - const isDynamicImport = dynamicIndex > -1 - // strip import assertions as we can process them ourselves - if (!isDynamicImport && assertIndex > -1) { - str().remove(end + 1, expEnd) - } + const isDynamicImport = dynamicIndex > -1 - // static import or valid string in dynamic import - // If resolvable, let's resolve it - if (specifier) { - // skip external / data uri - if (isExternalUrl(specifier) || isDataUrl(specifier)) { - continue + // strip import assertions as we can process them ourselves + if (!isDynamicImport && assertIndex > -1) { + str().remove(end + 1, expEnd) } - // skip ssr external - if (ssr) { - if (config.legacy?.buildSsrCjsExternalHeuristics) { - if (cjsShouldExternalizeForSSR(specifier, server._ssrExternals)) { - continue + + // static import or valid string in dynamic import + // If resolvable, let's resolve it + if (specifier) { + // skip external / data uri + if (isExternalUrl(specifier) || isDataUrl(specifier)) { + return + } + // skip ssr external + if (ssr) { + if (config.legacy?.buildSsrCjsExternalHeuristics) { + if ( + cjsShouldExternalizeForSSR(specifier, server._ssrExternals) + ) { + return + } + } else if (shouldExternalizeForSSR(specifier, config)) { + return + } + if (isBuiltin(specifier)) { + return } - } else if (shouldExternalizeForSSR(specifier, config)) { - continue } - if (isBuiltin(specifier)) { - continue + // skip client + if (specifier === clientPublicPath) { + return } - } - // skip client - if (specifier === clientPublicPath) { - continue - } - - // warn imports to non-asset /public files - if ( - specifier[0] === '/' && - !config.assetsInclude(cleanUrl(specifier)) && - !specifier.endsWith('.json') && - checkPublicFile(specifier, config) - ) { - throw new Error( - `Cannot import non-asset file ${specifier} which is inside /public.` + - `JS/CSS files inside /public are copied as-is on build and ` + - `can only be referenced via diff --git a/playground/html/side-effects/package.json b/playground/html/side-effects/package.json new file mode 100644 index 00000000000000..72a1449164f79b --- /dev/null +++ b/playground/html/side-effects/package.json @@ -0,0 +1,6 @@ +{ + "name": "@vitejs/test-html-side-effects", + "private": true, + "version": "0.0.0", + "sideEffects": false +} diff --git a/playground/html/side-effects/sideEffects.js b/playground/html/side-effects/sideEffects.js new file mode 100644 index 00000000000000..6bfb77c68206c0 --- /dev/null +++ b/playground/html/side-effects/sideEffects.js @@ -0,0 +1 @@ +console.log('message from sideEffects script') diff --git a/playground/html/vite.config.js b/playground/html/vite.config.js index e6301ec5ebf4dd..c03421c16045b0 100644 --- a/playground/html/vite.config.js +++ b/playground/html/vite.config.js @@ -28,6 +28,7 @@ export default defineConfig({ valid: resolve(__dirname, 'valid.html'), importmapOrder: resolve(__dirname, 'importmapOrder.html'), env: resolve(__dirname, 'env.html'), + sideEffects: resolve(__dirname, 'side-effects/index.html'), }, }, }, diff --git a/playground/nested-deps/__tests__/nested-deps.spec.ts b/playground/nested-deps/__tests__/nested-deps.spec.ts index 19c280b228b551..04618ece544ad9 100644 --- a/playground/nested-deps/__tests__/nested-deps.spec.ts +++ b/playground/nested-deps/__tests__/nested-deps.spec.ts @@ -10,7 +10,8 @@ test('handle nested package', async () => { expect(await page.textContent('.side-c')).toBe(c) expect(await page.textContent('.d')).toBe('D@1.0.0') expect(await page.textContent('.nested-d')).toBe('D-nested@1.0.0') - // TODO: Review if the test is correct // expect(await page.textContent('.nested-e')).toBe('1') + + expect(await page.textContent('.absolute-f')).toBe('F@2.0.0') }) diff --git a/playground/nested-deps/index.html b/playground/nested-deps/index.html index 2efb4f71044208..86dbc149fff32e 100644 --- a/playground/nested-deps/index.html +++ b/playground/nested-deps/index.html @@ -22,6 +22,8 @@

nested dependency nested-D (dep of D)

exclude dependency of pre-bundled dependency

nested module instance count:
+

absolute dependency path:

+