diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfccbea368c449..cde7dfb03825ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,8 @@ on: - main - release/* - feat/* + - fix/* + - perf/* pull_request: workflow_dispatch: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fdae54c968c964..b105499159bebb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ If you want to use break point and explore code execution you can use the ["Run 2. Click on the "Run and Debug" icon in the activity bar of the editor. -3. Click on the "Javascript Debug Terminal" button. +3. Click on the "JavaScript Debug Terminal" button. 4. It will open a terminal, then go to `packages/playground/xxx` and run `pnpm run dev`. @@ -69,7 +69,9 @@ And re-run `pnpm install` to link the package. Each package under `packages/playground/` contains a `__tests__` directory. The tests are run using [Jest](https://jestjs.io/) + [Playwright](https://playwright.dev/) with custom integrations to make writing tests simple. The detailed setup is inside `jest.config.js` and `scripts/jest*` files. -Each test can be run under either dev server mode or build mode. Make sure that [Vite has been built](#repo-setup). +Before running the tests, make sure that [Vite has been built](#repo-setup). On Windows, you may want to [activate Developer Mode](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development) to solve [issues with symlink creation for non-admins](https://github.com/vitejs/vite/issues/7390). + +Each test can be run under either dev server mode or build mode. - `pnpm test` by default runs every test in both serve and build mode. @@ -227,7 +229,7 @@ The english docs are embedded in the main Vite repo, to allow contributors to wo 1. In order to get all doc files, you first need to clone this repo in your personal account. 2. Keep all the files in `docs/` and remove everything else. - - You should setup your translation site based on all the files in `docs/` folder as a Vitepress project. + - You should setup your translation site based on all the files in `docs/` folder as a VitePress project. (that said, `package.json` is need). - Refresh git history by removing `.git` and then `git init` @@ -236,7 +238,7 @@ The english docs are embedded in the main Vite repo, to allow contributors to wo - During this stage, you may be translating documents and synchronizing updates at the same time, but don't worry about that, it's very common in translation contribution. -4. Push your commits to your Github repo. you can setup a netlify preview as well. -5. Use [Ryu-cho](https://github.com/vuejs-translations/ryu-cho) tool to setup a Github Action, automatically track English docs update later. +4. Push your commits to your GitHub repo. you can setup a netlify preview as well. +5. Use [Ryu-cho](https://github.com/vuejs-translations/ryu-cho) tool to setup a GitHub Action, automatically track English docs update later. We recommend talking with others in Vite Land so you find more contributors for your language to share the maintenance work. Once the translation is done, communicate it to the Vite team so the repo can be moved to the official vitejs org in GitHub. diff --git a/docs/blog/announcing-vite2.md b/docs/blog/announcing-vite2.md index 0bf520dfdfcb9a..0007205debba4a 100644 --- a/docs/blog/announcing-vite2.md +++ b/docs/blog/announcing-vite2.md @@ -34,7 +34,7 @@ The [programmatic API](https://vitejs.dev/guide/api-javascript.html) has also be ### esbuild Powered Dep Pre-Bundling -Since Vite is a native ESM dev server, it pre-bundles dependencies to reduce the number browser requests and handle CommonJS to ESM conversion. Previously Vite did this using Rollup, and in 2.0 it now uses `esbuild` which results in 10-100x faster dependency pre-bundling. As a reference, cold-booting a test app with heavy dependencies like React Material UI previously took 28 seconds on an M1-powered Macbook Pro and now takes ~1.5 seconds. Expect similar improvements if you are switching from a traditional bundler based setup. +Since Vite is a native ESM dev server, it pre-bundles dependencies to reduce the number browser requests and handle CommonJS to ESM conversion. Previously Vite did this using Rollup, and in 2.0 it now uses `esbuild` which results in 10-100x faster dependency pre-bundling. As a reference, cold-booting a test app with heavy dependencies like React Material UI previously took 28 seconds on an M1-powered MacBook Pro and now takes ~1.5 seconds. Expect similar improvements if you are switching from a traditional bundler based setup. ### First-class CSS Support diff --git a/docs/config/index.md b/docs/config/index.md index 905ffae7a89cea..d7560e58736a43 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -156,6 +156,8 @@ export default defineConfig(({ command, mode }) => { - Starting from `2.0.0-beta.70`, string values will be used as raw expressions, so if defining a string constant, it needs to be explicitly quoted (e.g. with `JSON.stringify`). + - To be consistent with [esbuild behavior](https://esbuild.github.io/api/#define), expressions must either be a JSON object (null, boolean, number, string, array, or object) or a single identifier. + - Replacements are performed only when the match is surrounded by word boundaries (`\b`). ::: warning @@ -327,6 +329,14 @@ export default defineConfig(({ command, mode }) => { }) ``` +### css.devSourcemap + +- **Experimental** +- **Type:** `boolean` +- **Default:** `false` + + Whether to enable sourcemaps during dev. + ### json.namedExports - **Type:** `boolean` @@ -347,7 +357,7 @@ export default defineConfig(({ command, mode }) => { - **Type:** `ESBuildOptions | false` - `ESBuildOptions` extends [ESbuild's own transform options](https://esbuild.github.io/api/#transform-api). The most common use case is customizing JSX: + `ESBuildOptions` extends [esbuild's own transform options](https://esbuild.github.io/api/#transform-api). The most common use case is customizing JSX: ```js export default defineConfig({ @@ -358,9 +368,9 @@ export default defineConfig(({ command, mode }) => { }) ``` - By default, ESBuild is applied to `ts`, `jsx` and `tsx` files. You can customize this with `esbuild.include` and `esbuild.exclude`, which can be a regex, a [picomatch](https://github.com/micromatch/picomatch#globbing-features) pattern, or an array of either. + By default, esbuild is applied to `ts`, `jsx` and `tsx` files. You can customize this with `esbuild.include` and `esbuild.exclude`, which can be a regex, a [picomatch](https://github.com/micromatch/picomatch#globbing-features) pattern, or an array of either. - In addition, you can also use `esbuild.jsxInject` to automatically inject JSX helper imports for every file transformed by ESBuild: + In addition, you can also use `esbuild.jsxInject` to automatically inject JSX helper imports for every file transformed by esbuild: ```js export default defineConfig({ @@ -370,7 +380,7 @@ export default defineConfig(({ command, mode }) => { }) ``` - Set to `false` to disable ESbuild transforms. + Set to `false` to disable esbuild transforms. ### assetsInclude @@ -542,14 +552,12 @@ export default defineConfig(({ command, mode }) => { ### server.hmr -- **Type:** `boolean | { protocol?: string, host?: string, port?: number | false, path?: string, timeout?: number, overlay?: boolean, clientPort?: number, server?: Server }` +- **Type:** `boolean | { protocol?: string, host?: string, port?: number, path?: string, timeout?: number, overlay?: boolean, clientPort?: number, server?: Server }` Disable or configure HMR connection (in cases where the HMR websocket must use a different address from the http server). Set `server.hmr.overlay` to `false` to disable the server error overlay. - Set `server.hmr.port` to `false` when connecting to a domain without a port. - `clientPort` is an advanced option that overrides the port only on the client side, allowing you to serve the websocket on a different port than the client code looks for it on. Useful if you're using an SSL proxy in front of your dev server. If specifying `server.hmr.server`, Vite will process HMR connection requests through the provided server. If not in middleware mode, Vite will attempt to process HMR connection requests through the existing server. This can be helpful when using self-signed certificates or when you want to expose Vite over a network on a single port. @@ -841,7 +849,7 @@ export default defineConfig({ - **Type:** `boolean | 'terser' | 'esbuild'` - **Default:** `'esbuild'` - Set to `false` to disable minification, or specify the minifier to use. The default is [Esbuild](https://github.com/evanw/esbuild) which is 20 ~ 40x faster than terser and only 1 ~ 2% worse compression. [Benchmarks](https://github.com/privatenumber/minification-benchmarks) + Set to `false` to disable minification, or specify the minifier to use. The default is [esbuild](https://github.com/evanw/esbuild) which is 20 ~ 40x faster than terser and only 1 ~ 2% worse compression. [Benchmarks](https://github.com/privatenumber/minification-benchmarks) Note the `build.minify` option is not available when using the `'es'` format in lib mode. @@ -965,9 +973,9 @@ export default defineConfig({ - **Type:** `string | string[]` - By default, Vite will crawl your `index.html` to detect dependencies that need to be pre-bundled. If `build.rollupOptions.input` is specified, Vite will crawl those entry points instead. + By default, Vite will crawl all your `.html` files to detect dependencies that need to be pre-bundled (ignoring `node_modules`, `build.outDir`, `__tests__` and `coverage`). If `build.rollupOptions.input` is specified, Vite will crawl those entry points instead. - If neither of these fit your needs, you can specify custom entries using this option - the value should be a [fast-glob pattern](https://github.com/mrmlnc/fast-glob#basic-syntax) or array of patterns that are relative from Vite project root. This will overwrite default entries inference. + If neither of these fit your needs, you can specify custom entries using this option - the value should be a [fast-glob pattern](https://github.com/mrmlnc/fast-glob#basic-syntax) or array of patterns that are relative from Vite project root. This will overwrite default entries inference. Only `node_modules` and `build.outDir` folders will be ignored by default when `optimizeDeps.entries` is explicitily defined. If other folders needs to be ignored, you can use an ignore pattern as part of the entries list, marked with an initial `!`. ### optimizeDeps.exclude diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index f4ddf59d8abcd1..46eabab04e8868 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -123,3 +123,11 @@ The following HMR events are dispatched by Vite automatically: - `'vite:error'` when an error occurs (e.g. syntax error) Custom HMR events can also be sent from plugins. See [handleHotUpdate](./api-plugin#handlehotupdate) for more details. + +## `hot.send(event, data)` + +Send custom events back to Vite's dev server. + +If called before connected, the data will be buffered and sent once the connection is established. + +See [Client-server Communication](/guide/api-plugin.html#client-server-communication) for more details. diff --git a/docs/guide/api-javascript.md b/docs/guide/api-javascript.md index ddaa04279737f5..0128be26bd92ba 100644 --- a/docs/guide/api-javascript.md +++ b/docs/guide/api-javascript.md @@ -1,6 +1,6 @@ # JavaScript API -Vite's JavaScript APIs are fully typed, and it's recommended to use TypeScript or enable JS type checking in VSCode to leverage the intellisense and validation. +Vite's JavaScript APIs are fully typed, and it's recommended to use TypeScript or enable JS type checking in VS Code to leverage the intellisense and validation. ## `createServer` diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index b3888b0fd7009b..50353765261e34 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -113,7 +113,7 @@ Virtual modules are a useful scheme that allows you to pass build time informati ```js export default function myPlugin() { - const virtualModuleId = '@my-virtual-module' + const virtualModuleId = 'virtual:my-module' const resolvedVirtualModuleId = '\0' + virtualModuleId return { @@ -135,7 +135,7 @@ export default function myPlugin() { Which allows importing the module in JavaScript: ```js -import { msg } from '@my-virtual-module' +import { msg } from 'virtual:my-module' console.log(msg) ``` @@ -186,8 +186,10 @@ Vite plugins can also provide hooks that serve Vite-specific purposes. These hoo const partialConfigPlugin = () => ({ name: 'return-partial', config: () => ({ - alias: { - foo: 'bar' + resolve: { + alias: { + foo: 'bar' + } } }) }) @@ -480,7 +482,7 @@ export default defineConfig({ Check out [Vite Rollup Plugins](https://vite-rollup-plugins.patak.dev) for a list of compatible official Rollup plugins with usage instructions. -## Path normalization +## Path Normalization Vite normalizes paths while resolving ids to use POSIX separators ( / ) while preserving the volume in Windows. On the other hand, Rollup keeps resolved paths untouched by default, so resolved ids have win32 separators ( \\ ) in Windows. However, Rollup plugins use a [`normalizePath` utility function](https://github.com/rollup/plugins/tree/master/packages/pluginutils#normalizepath) from `@rollup/pluginutils` internally, which converts separators to POSIX before performing comparisons. This means that when these plugins are used in Vite, the `include` and `exclude` config pattern and other similar paths against resolved ids comparisons work correctly. @@ -492,3 +494,87 @@ import { normalizePath } from 'vite' normalizePath('foo\\bar') // 'foo/bar' normalizePath('foo/bar') // 'foo/bar' ``` + +## Client-server Communication + +Since Vite 2.9, we provide some utilities for plugins to help handle the communication with clients. + +### Server to Client + +On the plugin side, we could use `server.ws.send` to broadcast events to all the clients: + +```js +// vite.config.js +export default defineConfig({ + plugins: [ + { + // ... + configureServer(server) { + server.ws.send('my:greetings', { msg: 'hello' }) + } + } + ] +}) +``` + +::: tip NOTE +We recommend **alway prefixing** your event names to avoid collisions with other plugins. +::: + +On the client side, use [`hot.on`](/guide/api-hmr.html#hot-on-event-cb) to listen to the events: + +```ts +// client side +if (import.meta.hot) { + import.meta.hot.on('my:greetings', (data) => { + console.log(data.msg) // hello + }) +} +``` + +### Client to Server + +To send events from the client to the server, we can use [`hot.send`](/guide/api-hmr.html#hot-send-event-payload): + +```ts +// client side +if (import.meta.hot) { + import.meta.hot.send('my:from-client', { msg: 'Hey!' }) +} +``` + +Then use `server.ws.on` and listen to the events on the server side: + +```js +// vite.config.js +export default defineConfig({ + plugins: [ + { + // ... + configureServer(server) { + server.ws.on('my:from-client', (data, client) => { + console.log('Message from client:', data.msg) // Hey! + // reply only to the client (if needed) + client.send('my:ack', { msg: 'Hi! I got your message!' }) + }) + } + } + ] +}) +``` + +### TypeScript for Custom Events + +It is possible to type custom events by extending the `CustomEventMap` interface: + +```ts +// events.d.ts +import 'vite/types/customEvent' + +declare module 'vite/types/customEvent' { + interface CustomEventMap { + 'custom:foo': { msg: string } + // 'event-key': payload + } +} +``` diff --git a/docs/guide/assets.md b/docs/guide/assets.md index fd5aa46f49467f..9776dcdd032f35 100644 --- a/docs/guide/assets.md +++ b/docs/guide/assets.md @@ -103,8 +103,17 @@ function getImageUrl(name) { } ``` -During the production build, Vite will perform necessary transforms so that the URLs still point to the correct location even after bundling and asset hashing. +During the production build, Vite will perform necessary transforms so that the URLs still point to the correct location even after bundling and asset hashing. However, the URL string must be static so it can be analyzed, otherwise the code will be left as is, which can cause runtime errors if `build.target` does not support `import.meta.url` -::: warning Note: Does not work with SSR +```js +// Vite will not transform this +const imgUrl = new URL(imagePath, import.meta.url).href +``` + +::: warning Does not work with SSR This pattern does not work if you are using Vite for Server-Side Rendering, because `import.meta.url` have different semantics in browsers vs. Node.js. The server bundle also cannot determine the client host URL ahead of time. ::: + +::: warning Esbuild target config is necessary +This pattern needs esbuild target to be set to `es2020` or higher. `vite@2.x` use `es2019` as default target. Set [build-target](https://vitejs.dev/config/#build-target) and [optimizedeps.esbuildoptions.target](https://vitejs.dev/config/#optimizedeps-esbuildoptions) to `es2020` or higher if you intend to use this partten. +::: diff --git a/docs/guide/env-and-mode.md b/docs/guide/env-and-mode.md index d3a6a575bce64c..1649feda8c7501 100644 --- a/docs/guide/env-and-mode.md +++ b/docs/guide/env-and-mode.md @@ -37,7 +37,7 @@ Vite uses [dotenv](https://github.com/motdotla/dotenv) to load additional enviro An env file for a specific mode (e.g. `.env.production`) will take higher priority than a generic one (e.g. `.env`). -In addition, environment variables that already exist when Vite is executed have the highest priority and will not be overwritten by `.env` files. +In addition, environment variables that already exist when Vite is executed have the highest priority and will not be overwritten by `.env` files. For example, when running `VITE_SOME_KEY=123 vite build`. `.env` files are loaded at the start of Vite. Restart the server after making changes. ::: @@ -57,7 +57,7 @@ If you want to customize env variables prefix, see [envPrefix](/config/index#env :::warning SECURITY NOTES -- `.env.*.local` files are local-only and can contain sensitive variables. You should add `.local` to your `.gitignore` to avoid them being checked into git. +- `.env.*.local` files are local-only and can contain sensitive variables. You should add `*.local` to your `.gitignore` to avoid them being checked into git. - Since any variables exposed to your Vite source code will end up in your client bundle, `VITE_*` variables should _not_ contain any sensitive information. ::: diff --git a/docs/guide/features.md b/docs/guide/features.md index 7bf870646da909..ebed85cd2d529d 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -352,6 +352,24 @@ In the production build, `.wasm` files smaller than `assetInlineLimit` will be i ## Web Workers +### Import with Constructors + +A web worker script can be imported using [`new Worker()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) and [`new SharedWorker()`](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker/SharedWorker). Compared to the worker suffixes, this syntax leans closer to the standards and is the **recommended** way to create workers. + +```ts +const worker = new Worker(new URL('./worker.js', import.meta.url)) +``` + +The worker constructor also accepts options, which can be used to create "module" workers: + +```ts +const worker = new Worker(new URL('./worker.js', import.meta.url), { + type: 'module' +}) +``` + +### Import with Query Suffixes + A web worker script can be directly imported by appending `?worker` or `?sharedworker` to the import request. The default export will be a custom worker constructor: ```js @@ -368,6 +386,8 @@ By default, the worker script will be emitted as a separate chunk in the product import MyWorker from './worker?worker&inline' ``` +See [Worker Options](/config/#worker-options) for details on configuring the bundling of all workers. + ## Build Optimizations > Features listed below are automatically applied as part of the build process and there is no need for explicit configuration unless you want to disable them. diff --git a/docs/guide/static-deploy.md b/docs/guide/static-deploy.md index 43b047dd7f53eb..8c6c219f568a9e 100644 --- a/docs/guide/static-deploy.md +++ b/docs/guide/static-deploy.md @@ -299,7 +299,7 @@ Vercel CLI ### Vercel for Git -1. Push your code to your git repository (GitHub, GitLab, BitBucket). +1. Push your code to your git repository (GitHub, GitLab, Bitbucket). 2. [Import your Vite project](https://vercel.com/new) into Vercel. 3. Vercel will detect that you are using Vite and will enable the correct settings for your deployment. 4. Your application is deployed! (e.g. [vite-vue-template.vercel.app](https://vite-vue-template.vercel.app/)) diff --git a/docs/guide/why.md b/docs/guide/why.md index 89ca7698394246..9a24cf64777611 100644 --- a/docs/guide/why.md +++ b/docs/guide/why.md @@ -18,7 +18,7 @@ Vite improves the dev server start time by first dividing the modules in an appl - **Dependencies** are mostly plain JavaScript that do not change often during development. Some large dependencies (e.g. component libraries with hundreds of modules) are also quite expensive to process. Dependencies may also be shipped in various module formats (e.g. ESM or CommonJS). - Vite [pre-bundles dependencies](./dep-pre-bundling) using [esbuild](https://esbuild.github.io/). Esbuild is written in Go and pre-bundles dependencies 10-100x faster than JavaScript-based bundlers. + Vite [pre-bundles dependencies](./dep-pre-bundling) using [esbuild](https://esbuild.github.io/). esbuild is written in Go and pre-bundles dependencies 10-100x faster than JavaScript-based bundlers. - **Source code** often contains non-plain JavaScript that needs transforming (e.g. JSX, CSS or Vue/Svelte components), and will be edited very often. Also, not all source code needs to be loaded at the same time (e.g. with route-based code-splitting). diff --git a/jest.config.ts b/jest.config.ts index 7d4831524d01c3..11663af4e08107 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -6,7 +6,7 @@ const config: Config.InitialOptions = { testMatch: process.env.VITE_TEST_BUILD ? ['**/playground/**/*.spec.[jt]s?(x)'] : ['**/*.spec.[jt]s?(x)'], - testTimeout: process.env.CI ? 30000 : 10000, + testTimeout: process.env.CI ? 50000 : 20000, globalSetup: './scripts/jestGlobalSetup.cjs', globalTeardown: './scripts/jestGlobalTeardown.cjs', testEnvironment: './scripts/jestEnv.cjs', diff --git a/package.json b/package.json index 858e7502d53324..27445e0d25cb29 100644 --- a/package.json +++ b/package.json @@ -34,37 +34,37 @@ "ci-docs": "run-s build-vite build-plugin-vue build-docs" }, "devDependencies": { - "@microsoft/api-extractor": "^7.19.5", + "@microsoft/api-extractor": "^7.20.0", "@types/fs-extra": "^9.0.13", "@types/jest": "^27.4.1", "@types/node": "^16.11.26", "@types/prompts": "^2.0.14", "@types/semver": "^7.3.9", - "@typescript-eslint/eslint-plugin": "^5.16.0", - "@typescript-eslint/parser": "^5.16.0", + "@typescript-eslint/eslint-plugin": "^5.17.0", + "@typescript-eslint/parser": "^5.17.0", "conventional-changelog-cli": "^2.2.2", "cross-env": "^7.0.3", "esbuild": "^0.14.27", - "eslint": "^8.11.0", + "eslint": "^8.12.0", "eslint-define-config": "^1.3.0", "eslint-plugin-node": "^11.1.0", "execa": "^5.1.1", "fs-extra": "^10.0.1", "jest": "^27.5.1", "lint-staged": "^12.3.7", - "minimist": "^1.2.5", + "minimist": "^1.2.6", "node-fetch": "^2.6.6", "npm-run-all": "^4.1.5", "picocolors": "^1.0.0", - "playwright-chromium": "^1.20.0", - "prettier": "2.6.0", + "playwright-chromium": "^1.20.2", + "prettier": "2.6.2", "prompts": "^2.4.2", "rimraf": "^3.0.2", "rollup": "^2.59.0", "semver": "^7.3.5", "simple-git-hooks": "^2.7.0", "sirv": "^2.0.2", - "ts-jest": "^27.1.3", + "ts-jest": "^27.1.4", "ts-node": "^10.4.0", "typescript": "~4.5.4", "vite": "workspace:*", @@ -85,11 +85,18 @@ "eslint --ext .ts" ] }, - "packageManager": "pnpm@6.32.3", + "packageManager": "pnpm@6.32.4", "pnpm": { "overrides": { "vite": "workspace:*", "@vitejs/plugin-vue": "workspace:*" + }, + "packageExtensions": { + "postcss-load-config": { + "peerDependencies": { + "postcss": "*" + } + } } } } diff --git a/packages/create-vite/CHANGELOG.md b/packages/create-vite/CHANGELOG.md index b81d7ec6fac49a..bf69fae7e535ad 100644 --- a/packages/create-vite/CHANGELOG.md +++ b/packages/create-vite/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.9.0 (2022-03-30) + +* chore: add isolatedModules to create-vite > template-vue-ts > tsconfig (#7304) ([21990ea](https://github.com/vitejs/vite/commit/21990ea)), closes [#7304](https://github.com/vitejs/vite/issues/7304) +* chore(deps): update all non-major dependencies (#7490) ([42c15f6](https://github.com/vitejs/vite/commit/42c15f6)), closes [#7490](https://github.com/vitejs/vite/issues/7490) +* docs(vue-ts): update note on vue type support in ts (#6165) ([cfc7648](https://github.com/vitejs/vite/commit/cfc7648)), closes [#6165](https://github.com/vitejs/vite/issues/6165) +* workflow: separate version bumping and publishing on release (#6879) ([fe8ef39](https://github.com/vitejs/vite/commit/fe8ef39)), closes [#6879](https://github.com/vitejs/vite/issues/6879) + + + # [2.8.0](https://github.com/vitejs/vite/compare/create-vite@2.7.2...create-vite@2.8.0) (2022-02-09) diff --git a/packages/create-vite/package.json b/packages/create-vite/package.json index 5d873b83fe515b..72955697360477 100644 --- a/packages/create-vite/package.json +++ b/packages/create-vite/package.json @@ -1,6 +1,6 @@ { "name": "create-vite", - "version": "2.8.0", + "version": "2.9.0", "license": "MIT", "author": "Evan You", "bin": { @@ -26,7 +26,7 @@ "homepage": "https://github.com/vitejs/vite/tree/main/packages/create-vite#readme", "dependencies": { "kolorist": "^1.5.1", - "minimist": "^1.2.5", + "minimist": "^1.2.6", "prompts": "^2.4.2" } } diff --git a/packages/create-vite/template-lit-ts/package.json b/packages/create-vite/template-lit-ts/package.json index 5c0a7c7948ee58..061def321a22e6 100644 --- a/packages/create-vite/template-lit-ts/package.json +++ b/packages/create-vite/template-lit-ts/package.json @@ -19,7 +19,7 @@ "lit": "^2.0.2" }, "devDependencies": { - "vite": "^2.8.0", + "vite": "^2.9.0", "typescript": "^4.5.4" } } diff --git a/packages/create-vite/template-lit/package.json b/packages/create-vite/template-lit/package.json index ca3c71428f22c9..89f4fee41576c6 100644 --- a/packages/create-vite/template-lit/package.json +++ b/packages/create-vite/template-lit/package.json @@ -17,6 +17,6 @@ "lit": "^2.0.2" }, "devDependencies": { - "vite": "^2.8.0" + "vite": "^2.9.0" } } diff --git a/packages/create-vite/template-preact-ts/package.json b/packages/create-vite/template-preact-ts/package.json index df1726ba6c4f7b..dfa6659650c5ae 100644 --- a/packages/create-vite/template-preact-ts/package.json +++ b/packages/create-vite/template-preact-ts/package.json @@ -13,6 +13,6 @@ "devDependencies": { "@preact/preset-vite": "^2.1.5", "typescript": "^4.5.4", - "vite": "^2.8.0" + "vite": "^2.9.0" } } diff --git a/packages/create-vite/template-preact/package.json b/packages/create-vite/template-preact/package.json index 2865df70374acf..02528128f6442e 100644 --- a/packages/create-vite/template-preact/package.json +++ b/packages/create-vite/template-preact/package.json @@ -12,6 +12,6 @@ }, "devDependencies": { "@preact/preset-vite": "^2.1.5", - "vite": "^2.8.0" + "vite": "^2.9.0" } } diff --git a/packages/create-vite/template-react-ts/package.json b/packages/create-vite/template-react-ts/package.json index 3b1e21b7dbca8c..69274929dd903d 100644 --- a/packages/create-vite/template-react-ts/package.json +++ b/packages/create-vite/template-react-ts/package.json @@ -8,14 +8,14 @@ "preview": "vite preview" }, "dependencies": { - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "devDependencies": { - "@types/react": "^17.0.33", - "@types/react-dom": "^17.0.10", - "@vitejs/plugin-react": "^1.0.7", - "typescript": "^4.5.4", - "vite": "^2.8.0" + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@vitejs/plugin-react": "^1.3.0", + "typescript": "^4.6.3", + "vite": "^2.9.1" } } diff --git a/packages/create-vite/template-react-ts/src/main.tsx b/packages/create-vite/template-react-ts/src/main.tsx index 606a3cf44ec02b..4a1b15096e15b1 100644 --- a/packages/create-vite/template-react-ts/src/main.tsx +++ b/packages/create-vite/template-react-ts/src/main.tsx @@ -1,11 +1,10 @@ import React from 'react' -import ReactDOM from 'react-dom' -import './index.css' +import ReactDOM from 'react-dom/client' import App from './App' +import './index.css' -ReactDOM.render( +ReactDOM.createRoot(document.getElementById('root')!).render( - , - document.getElementById('root') + ) diff --git a/packages/create-vite/template-react/package.json b/packages/create-vite/template-react/package.json index 7b0f87c6bc95f5..4f443aefcb3cdb 100644 --- a/packages/create-vite/template-react/package.json +++ b/packages/create-vite/template-react/package.json @@ -8,11 +8,13 @@ "preview": "vite preview" }, "dependencies": { - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "devDependencies": { - "@vitejs/plugin-react": "^1.0.7", - "vite": "^2.8.0" + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@vitejs/plugin-react": "^1.3.0", + "vite": "^2.9.1" } } diff --git a/packages/create-vite/template-react/src/main.jsx b/packages/create-vite/template-react/src/main.jsx index 606a3cf44ec02b..9af0bb638e42c0 100644 --- a/packages/create-vite/template-react/src/main.jsx +++ b/packages/create-vite/template-react/src/main.jsx @@ -1,11 +1,10 @@ import React from 'react' -import ReactDOM from 'react-dom' -import './index.css' +import ReactDOM from 'react-dom/client' import App from './App' +import './index.css' -ReactDOM.render( +ReactDOM.createRoot(document.getElementById('root')).render( - , - document.getElementById('root') + ) diff --git a/packages/create-vite/template-svelte-ts/README.md b/packages/create-vite/template-svelte-ts/README.md index a9d516a32c682e..4ef762ffec4df3 100644 --- a/packages/create-vite/template-svelte-ts/README.md +++ b/packages/create-vite/template-svelte-ts/README.md @@ -4,7 +4,7 @@ This template should help get you started developing with Svelte and TypeScript ## Recommended IDE Setup -[VSCode](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). ## Need an official Svelte framework? diff --git a/packages/create-vite/template-svelte-ts/package.json b/packages/create-vite/template-svelte-ts/package.json index 3f2c63759285e5..32e080146e0b6e 100644 --- a/packages/create-vite/template-svelte-ts/package.json +++ b/packages/create-vite/template-svelte-ts/package.json @@ -17,6 +17,6 @@ "svelte-preprocess": "^4.9.8", "tslib": "^2.3.1", "typescript": "^4.5.4", - "vite": "^2.8.0" + "vite": "^2.9.0" } } diff --git a/packages/create-vite/template-svelte/README.md b/packages/create-vite/template-svelte/README.md index 8e35d33d2bc7c8..50ea7ed3b9132d 100644 --- a/packages/create-vite/template-svelte/README.md +++ b/packages/create-vite/template-svelte/README.md @@ -4,7 +4,7 @@ This template should help get you started developing with Svelte in Vite. ## Recommended IDE Setup -[VSCode](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). ## Need an official Svelte framework? diff --git a/packages/create-vite/template-svelte/package.json b/packages/create-vite/template-svelte/package.json index e81ffa8288b6ef..da049c170731b9 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": "^1.0.0-next.30", "svelte": "^3.44.0", - "vite": "^2.8.0" + "vite": "^2.9.0" } } diff --git a/packages/create-vite/template-vanilla-ts/package.json b/packages/create-vite/template-vanilla-ts/package.json index 9b6cc582bfdb11..8dd7a0bb800909 100644 --- a/packages/create-vite/template-vanilla-ts/package.json +++ b/packages/create-vite/template-vanilla-ts/package.json @@ -9,6 +9,6 @@ }, "devDependencies": { "typescript": "^4.5.4", - "vite": "^2.8.0" + "vite": "^2.9.0" } } diff --git a/packages/create-vite/template-vanilla/package.json b/packages/create-vite/template-vanilla/package.json index f409412bbdc272..ff318e00f14fd1 100644 --- a/packages/create-vite/template-vanilla/package.json +++ b/packages/create-vite/template-vanilla/package.json @@ -8,6 +8,6 @@ "preview": "vite preview" }, "devDependencies": { - "vite": "^2.8.0" + "vite": "^2.9.0" } } diff --git a/packages/create-vite/template-vue-ts/README.md b/packages/create-vite/template-vue-ts/README.md index b53dcfb1a715e6..e432516724c1a7 100644 --- a/packages/create-vite/template-vue-ts/README.md +++ b/packages/create-vite/template-vue-ts/README.md @@ -1,16 +1,16 @@ -# Vue 3 + Typescript + Vite +# Vue 3 + TypeScript + Vite -This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 ` diff --git a/packages/create-vite/template-vue-ts/src/components/HelloWorld.vue b/packages/create-vite/template-vue-ts/src/components/HelloWorld.vue index 2d61249ac32f9c..38dae70739a15a 100644 --- a/packages/create-vite/template-vue-ts/src/components/HelloWorld.vue +++ b/packages/create-vite/template-vue-ts/src/components/HelloWorld.vue @@ -11,7 +11,7 @@ const count = ref(0)

Recommended IDE setup: - VSCode + VS Code + Volar

diff --git a/packages/create-vite/template-vue/README.md b/packages/create-vite/template-vue/README.md index c0793a82398e08..eea15cef41ea60 100644 --- a/packages/create-vite/template-vue/README.md +++ b/packages/create-vite/template-vue/README.md @@ -4,4 +4,4 @@ This template should help get you started developing with Vue 3 in Vite. The tem ## Recommended IDE Setup -- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) +- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) diff --git a/packages/create-vite/template-vue/package.json b/packages/create-vite/template-vue/package.json index 3136fcda2a0bc7..707b664d3dfff7 100644 --- a/packages/create-vite/template-vue/package.json +++ b/packages/create-vite/template-vue/package.json @@ -11,7 +11,7 @@ "vue": "^3.2.25" }, "devDependencies": { - "@vitejs/plugin-vue": "^2.2.0", - "vite": "^2.8.0" + "@vitejs/plugin-vue": "^2.3.0", + "vite": "^2.9.0" } } diff --git a/packages/create-vite/template-vue/src/App.vue b/packages/create-vite/template-vue/src/App.vue index 742233037df99e..09bbb6a561285f 100644 --- a/packages/create-vite/template-vue/src/App.vue +++ b/packages/create-vite/template-vue/src/App.vue @@ -1,6 +1,6 @@ diff --git a/packages/create-vite/template-vue/src/components/HelloWorld.vue b/packages/create-vite/template-vue/src/components/HelloWorld.vue index 48a5ca9095156b..aa607e31e0ad7c 100644 --- a/packages/create-vite/template-vue/src/components/HelloWorld.vue +++ b/packages/create-vite/template-vue/src/components/HelloWorld.vue @@ -13,7 +13,7 @@ const count = ref(0)

Recommended IDE setup: - VSCode + VS Code + Volar

diff --git a/packages/playground/assets/__tests__/assets.spec.ts b/packages/playground/assets/__tests__/assets.spec.ts index f7a8ae0ba4a349..e08de24265e24a 100644 --- a/packages/playground/assets/__tests__/assets.spec.ts +++ b/packages/playground/assets/__tests__/assets.spec.ts @@ -194,6 +194,16 @@ test('?url import', async () => { ) }) +test('?url import on css', async () => { + const src = readFile('css/icons.css') + const txt = await page.textContent('.url-css') + expect(txt).toEqual( + isBuild + ? `data:text/css;base64,${Buffer.from(src).toString('base64')}` + : '/foo/css/icons.css' + ) +}) + describe('unicode url', () => { test('from js import', async () => { const src = readFile('テスト-測試-white space.js') diff --git a/packages/playground/assets/index.html b/packages/playground/assets/index.html index 3cf8ea050faac0..b0ec76f5483b6f 100644 --- a/packages/playground/assets/index.html +++ b/packages/playground/assets/index.html @@ -134,6 +134,9 @@

?raw import

?url import

+

?url import with css

+ +

new URL('...', import.meta.url)

@@ -246,6 +249,9 @@

import unicodeUrl from './テスト-測試-white space.js?url' text('.unicode-url', unicodeUrl) + import cssUrl from './css/icons.css?url' + text('.url-css', cssUrl) + // const url = new URL('non_existent_file.png', import.meta.url) const metaUrl = new URL('./nested/asset.png', import.meta.url) text('.import-meta-url', metaUrl) @@ -266,7 +272,6 @@

document.querySelector('.import-meta-url-img-comma-nl').src = metaUrlWithCommaNL - import classNames from './css/foo.module.css' document.querySelector('#foo').className = classNames['foo-module'] @@ -276,7 +281,6 @@

const metaUrlNonExistent = new URL('non-existent', import.meta.url).pathname text('.non-existent-import-meta-url', metaUrlNonExistent) - /** * don't process the code in the comment * const url = new URL('non_existent_file.png', import.meta.url) diff --git a/packages/playground/cli-module/__tests__/serve.js b/packages/playground/cli-module/__tests__/serve.js index 1cda05f0adc21a..cf873fd481830b 100644 --- a/packages/playground/cli-module/__tests__/serve.js +++ b/packages/playground/cli-module/__tests__/serve.js @@ -5,10 +5,10 @@ const path = require('path') // eslint-disable-next-line node/no-restricted-require const execa = require('execa') -const { workspaceRoot } = require('../../testUtils') +const { workspaceRoot, ports } = require('../../testUtils') const isWindows = process.platform === 'win32' -const port = (exports.port = 9511) // make sure this port is unique across tests with custom servers +const port = (exports.port = ports['cli-module']) const viteBin = path.join(workspaceRoot, 'packages', 'vite', 'bin', 'vite.js') /** @@ -78,7 +78,7 @@ exports.serve = async function serve(root, isProd) { const timeoutError = `server process still alive after 3s` try { killProcess(serverProcess) - await resolvedOrTimeout(serverProcess, 3000, timeoutError) + await resolvedOrTimeout(serverProcess, 10000, timeoutError) } catch (e) { if (e === timeoutError || (!serverProcess.killed && !isWindows)) { collectErrorStreams('server', e) diff --git a/packages/playground/cli/__tests__/serve.js b/packages/playground/cli/__tests__/serve.js index 5dd058f4e1a83c..3ad375d9d1f543 100644 --- a/packages/playground/cli/__tests__/serve.js +++ b/packages/playground/cli/__tests__/serve.js @@ -5,10 +5,10 @@ const path = require('path') // eslint-disable-next-line node/no-restricted-require const execa = require('execa') -const { workspaceRoot } = require('../../testUtils') +const { workspaceRoot, ports } = require('../../testUtils') const isWindows = process.platform === 'win32' -const port = (exports.port = 9510) // make sure this port is unique across tests with custom servers +const port = (exports.port = ports.cli) const viteBin = path.join(workspaceRoot, 'packages', 'vite', 'bin', 'vite.js') /** diff --git a/packages/playground/css-sourcemap/__tests__/serve.spec.ts b/packages/playground/css-sourcemap/__tests__/serve.spec.ts index 50c256298143ab..11e33a78af8424 100644 --- a/packages/playground/css-sourcemap/__tests__/serve.spec.ts +++ b/packages/playground/css-sourcemap/__tests__/serve.spec.ts @@ -1,11 +1,11 @@ -import { fromComment } from 'convert-source-map' import { URL } from 'url' -import { normalizePath } from 'vite' -import { isBuild, testDir } from 'testUtils' +import { + extractSourcemap, + formatSourcemapForSnapshot, + isBuild +} from 'testUtils' if (!isBuild) { - const root = normalizePath(testDir) - const getStyleTagContentIncluding = async (content: string) => { const styles = await page.$$('style') for (const style of styles) { @@ -17,18 +17,67 @@ if (!isBuild) { throw new Error('Not found') } - const extractSourcemap = (content: string) => { - const lines = content.trim().split('\n') - return fromComment(lines[lines.length - 1]).toObject() - } + test('inline css', async () => { + const css = await getStyleTagContentIncluding('.inline ') + const map = extractSourcemap(css) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + Object { + "mappings": "AAGO;AACP,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACX,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC,CAAC;", + "sources": Array [ + "/root/index.html", + ], + "sourcesContent": Array [ + " + - const formatSourcemapForSnapshot = (map: any) => { - const m = { ...map } - delete m.file - delete m.names - m.sources = m.sources.map((source) => source.replace(root, '/root')) - return m - } + + +
+

CSS Sourcemap

+ +

<inline>

+ +

<linked>: no import

+

<linked>: with import

+ +

<imported>: no import

+

<imported>: with import

+ +

<imported sass>

+

<imported sass> with module

+ +

<imported less> with string additionalData

+ +

<imported stylus>

+
+ + + + + ", + ], + "version": 3, + } + `) + }) test('linked css', async () => { const res = await page.request.get( @@ -207,6 +256,12 @@ if (!isBuild) { } `) }) + + test('should not output missing source file warning', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/) + }) + }) } else { test('this file only includes test for serve', () => { expect(true).toBe(true) diff --git a/packages/playground/css-sourcemap/index.html b/packages/playground/css-sourcemap/index.html index 2fedceb8f2be44..a943c1d113a9b4 100644 --- a/packages/playground/css-sourcemap/index.html +++ b/packages/playground/css-sourcemap/index.html @@ -1,9 +1,17 @@ + +

CSS Sourcemap

+

<inline>

+

<linked>: no import

<linked>: with import

@@ -33,3 +41,5 @@

CSS Sourcemap

import './imported.styl' + + diff --git a/packages/playground/css-sourcemap/package.json b/packages/playground/css-sourcemap/package.json index c29f18d4dee0d7..c7e9e61372cefa 100644 --- a/packages/playground/css-sourcemap/package.json +++ b/packages/playground/css-sourcemap/package.json @@ -9,7 +9,6 @@ "preview": "vite preview" }, "devDependencies": { - "convert-source-map": "^1.8.0", "less": "^4.1.2", "magic-string": "^0.25.7", "sass": "^1.43.4", diff --git a/packages/playground/css-sourcemap/vite.config.js b/packages/playground/css-sourcemap/vite.config.js index 2e70a4a0894406..4fbeaa9a13aeb0 100644 --- a/packages/playground/css-sourcemap/vite.config.js +++ b/packages/playground/css-sourcemap/vite.config.js @@ -10,6 +10,7 @@ module.exports = { } }, css: { + devSourcemap: true, preprocessorOptions: { less: { additionalData: '@color: red;' @@ -36,5 +37,25 @@ module.exports = { }, build: { sourcemap: true - } + }, + plugins: [ + { + name: 'virtual-html', + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + if (req.url === '/virtual.html') { + const t = await server.transformIndexHtml( + '/virtual.html', + '

virtual html

' + ) + res.setHeader('Content-Type', 'text/html') + res.statusCode = 200 + res.end(t) + return + } + next() + }) + } + } + ] } diff --git a/packages/playground/css/__tests__/css.spec.ts b/packages/playground/css/__tests__/css.spec.ts index 360e46dbbba150..d3577176b4bb16 100644 --- a/packages/playground/css/__tests__/css.spec.ts +++ b/packages/playground/css/__tests__/css.spec.ts @@ -54,6 +54,12 @@ test('css import from js', async () => { await untilUpdated(() => getColor(atImport), 'blue') }) +test('css import asset with space', async () => { + const importedWithSpace = await page.$('.import-with-space') + + expect(await getBg(importedWithSpace)).toMatch(/.*ok\..*png/) +}) + test('postcss config', async () => { const imported = await page.$('.postcss .nesting') expect(await getColor(imported)).toBe('pink') @@ -236,6 +242,11 @@ test('css modules w/ sass', async () => { await untilUpdated(() => getColor(imported), 'blue') }) +test('inline css modules', async () => { + const css = await page.textContent('.modules-inline') + expect(css).toMatch(/\.inline-module__apply-color-inline___[\w-]{5}/) +}) + test('@import dependency w/ style entry', async () => { expect(await getColor('.css-dep')).toBe('purple') }) @@ -318,7 +329,7 @@ test('PostCSS dir-dependency', async () => { } }) -test('Url separation', async () => { +test('URL separation', async () => { const urlSeparated = await page.$('.url-separated') const baseUrl = 'url(images/dog.webp)' const cases = new Array(5) @@ -365,7 +376,23 @@ test('minify css', async () => { expect(cssFile).not.toMatch('#ffff00b3') }) +test('?raw', async () => { + const rawImportCss = await page.$('.raw-imported-css') + + expect(await rawImportCss.textContent()).toBe( + require('fs').readFileSync(require.resolve('../raw-imported.css'), 'utf-8') + ) +}) + test('import css in less', async () => { expect(await getColor('.css-in-less')).toBe('yellow') expect(await getColor('.css-in-less-2')).toBe('blue') }) + +test("relative path rewritten in Less's data-uri", async () => { + // relative path passed to Less's data-uri is rewritten to absolute, + // the Less inlines it + expect(await getBg('.form-box-data-uri')).toMatch( + /^url\("data:image\/svg\+xml,%3Csvg/ + ) +}) diff --git a/packages/playground/css/__tests__/postcss-plugins-different-dir.spec.ts b/packages/playground/css/__tests__/postcss-plugins-different-dir.spec.ts index 19e9a43ae4ff6e..8bedc26ee354c8 100644 --- a/packages/playground/css/__tests__/postcss-plugins-different-dir.spec.ts +++ b/packages/playground/css/__tests__/postcss-plugins-different-dir.spec.ts @@ -1,10 +1,10 @@ -import { getColor, getBgColor } from '../../testUtils' +import { getColor, getBgColor, ports } from '../../testUtils' import { createServer } from 'vite' import path from 'path' // Regression test for https://github.com/vitejs/vite/issues/4000 test('postcss plugins in different dir', async () => { - const port = 5006 + const port = ports['css/postcss-plugins-different-dir'] const server = await createServer({ root: path.join(__dirname, '..', '..', 'tailwind'), logLevel: 'silent', diff --git a/packages/playground/css/folder with space/ok.png b/packages/playground/css/folder with space/ok.png new file mode 100644 index 00000000000000..a8d1e52510c41c Binary files /dev/null and b/packages/playground/css/folder with space/ok.png differ diff --git a/packages/playground/css/folder with space/space.css b/packages/playground/css/folder with space/space.css new file mode 100644 index 00000000000000..55a8532da32a94 --- /dev/null +++ b/packages/playground/css/folder with space/space.css @@ -0,0 +1,5 @@ +.import-with-space { + color: green; + background: url(spacefolder/ok.png); + background-position: center; +} diff --git a/packages/playground/css/imported.css b/packages/playground/css/imported.css index 65743d08b932a7..7d582995fab9fd 100644 --- a/packages/playground/css/imported.css +++ b/packages/playground/css/imported.css @@ -1,4 +1,5 @@ @import './imported-at-import.css'; +@import 'spacefolder/space.css'; .imported { color: green; diff --git a/packages/playground/css/index.html b/packages/playground/css/index.html index acbbe44a7f8a60..dd496a8ffdce11 100644 --- a/packages/playground/css/index.html +++ b/packages/playground/css/index.html @@ -10,6 +10,10 @@

CSS

@import in import from js: This should be purple

+

+ @import from file with space: This should be green and have a background + image +

Imported css string:


   

@@ -45,6 +49,10 @@ 

CSS

Imported Less string:


 
+  
+ tests Less's `data-uri()` function with relative image paths +
+

Stylus: This should be blue

Stylus additionalData: This should be orange @@ -85,6 +93,9 @@

CSS


 
+  

Inline CSS module:

+

+
   

@import dependency w/ style enrtrypoints: this should be purple

@@ -101,7 +112,7 @@

CSS

- Url separation preservation: should have valid background-image + URL separation preservation: should have valid background-image

Inlined import - this should NOT be red.

@@ -118,6 +129,9 @@

CSS


+
+  

Raw Support

+

 
 
 
diff --git a/packages/playground/css/inline.module.css b/packages/playground/css/inline.module.css
new file mode 100644
index 00000000000000..9566e21e2cd1af
--- /dev/null
+++ b/packages/playground/css/inline.module.css
@@ -0,0 +1,3 @@
+.apply-color-inline {
+  color: turquoise;
+}
diff --git a/packages/playground/css/less.less b/packages/playground/css/less.less
index 69ffa830862014..49cbd3c3bb336e 100644
--- a/packages/playground/css/less.less
+++ b/packages/playground/css/less.less
@@ -1,6 +1,9 @@
 @import '@/nested/nested';
 @import './nested/css-in-less.less';
 
+// Test data-uri calls with relative images.
+@import './less/components/form.less';
+
 @color: blue;
 
 .less {
diff --git a/packages/playground/css/less/components/form.less b/packages/playground/css/less/components/form.less
new file mode 100644
index 00000000000000..feaaea94ce1bba
--- /dev/null
+++ b/packages/playground/css/less/components/form.less
@@ -0,0 +1,4 @@
+.form-box-data-uri {
+  // data-uri() calls with relative paths should be replaced just like urls.
+  background-image: data-uri('../images/backgrounds/form-select.svg');
+}
diff --git a/packages/playground/css/less/images/backgrounds/form-select.svg b/packages/playground/css/less/images/backgrounds/form-select.svg
new file mode 100644
index 00000000000000..8aaf69c09e03f4
--- /dev/null
+++ b/packages/playground/css/less/images/backgrounds/form-select.svg
@@ -0,0 +1,4 @@
+
+  
+  
+
diff --git a/packages/playground/css/main.js b/packages/playground/css/main.js
index 3599ed0d60562c..564b3a56677dbd 100644
--- a/packages/playground/css/main.js
+++ b/packages/playground/css/main.js
@@ -12,6 +12,9 @@ text('.imported-less', less)
 import stylus from './stylus.styl'
 text('.imported-stylus', stylus)
 
+import rawCss from './raw-imported.css?raw'
+text('.raw-imported-css', rawCss)
+
 import mod from './mod.module.css'
 document.querySelector('.modules').classList.add(mod['apply-color'])
 text('.modules-code', JSON.stringify(mod, null, 2))
@@ -35,6 +38,9 @@ text(
   JSON.stringify(composesPathResolvingMod, null, 2)
 )
 
+import inlineMod from './inline.module.css?inline'
+text('.modules-inline', inlineMod)
+
 import './dep.css'
 import './glob-dep.css'
 
diff --git a/packages/playground/css/postcss-caching/css.spec.ts b/packages/playground/css/postcss-caching/css.spec.ts
index 6c85d127003680..e8ba73154b6bc8 100644
--- a/packages/playground/css/postcss-caching/css.spec.ts
+++ b/packages/playground/css/postcss-caching/css.spec.ts
@@ -1,9 +1,9 @@
-import { getColor } from '../../testUtils'
+import { getColor, ports } from '../../testUtils'
 import { createServer } from 'vite'
 import path from 'path'
 
 test('postcss config', async () => {
-  const port = 5005
+  const port = ports['css/postcss-caching']
   const startServer = async (root) => {
     const server = await createServer({
       root,
diff --git a/packages/playground/css/raw-imported.css b/packages/playground/css/raw-imported.css
new file mode 100644
index 00000000000000..ac0aee96390c33
--- /dev/null
+++ b/packages/playground/css/raw-imported.css
@@ -0,0 +1,3 @@
+.raw-imported {
+  color: yellow;
+}
diff --git a/packages/playground/css/vite.config.js b/packages/playground/css/vite.config.js
index 53d001d8387989..639a1302debb88 100644
--- a/packages/playground/css/vite.config.js
+++ b/packages/playground/css/vite.config.js
@@ -9,7 +9,8 @@ module.exports = {
   },
   resolve: {
     alias: {
-      '@': __dirname
+      '@': __dirname,
+      spacefolder: __dirname + '/folder with space'
     }
   },
   css: {
diff --git a/packages/playground/dynamic-import/__tests__/dynamic-import.spec.ts b/packages/playground/dynamic-import/__tests__/dynamic-import.spec.ts
index 9f8276d5e4d855..c7157ef4652ec6 100644
--- a/packages/playground/dynamic-import/__tests__/dynamic-import.spec.ts
+++ b/packages/playground/dynamic-import/__tests__/dynamic-import.spec.ts
@@ -8,6 +8,10 @@ test('should load literal dynamic import', async () => {
 test('should load full dynamic import from public', async () => {
   await page.click('.qux')
   await untilUpdated(() => page.textContent('.view'), 'Qux view', true)
+  // No warning should be logged as we are using @vite-ignore
+  expect(
+    serverLogs.some((log) => log.includes('cannot be analyzed by vite'))
+  ).toBe(false)
 })
 
 test('should load data URL of `blob:`', async () => {
diff --git a/packages/playground/dynamic-import/nested/index.js b/packages/playground/dynamic-import/nested/index.js
index 1a118ecc79051b..5518c56a35a2cc 100644
--- a/packages/playground/dynamic-import/nested/index.js
+++ b/packages/playground/dynamic-import/nested/index.js
@@ -35,7 +35,7 @@ document.querySelector('.mxd2').addEventListener('click', async () => {
   const test = { jss: '../mxd.js' }
   const ttest = test
   const view = 'mxd'
-  const { default: mxdDynamic } = await import(test.jss)
+  const { default: mxdDynamic } = await import(/*@vite-ignore*/ test.jss)
   text('.view', mxdStatic === mxdDynamic)
 })
 
diff --git a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts
index c618186b9bcd64..eba1e441881710 100644
--- a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts
+++ b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts
@@ -23,6 +23,15 @@ describe('main', () => {
       expect(await page.textContent('.safe-fetch-status')).toBe('200')
     })
 
+    test('safe fetch with special characters', async () => {
+      expect(
+        await page.textContent('.safe-fetch-subdir-special-characters')
+      ).toMatch('KEY=safe')
+      expect(
+        await page.textContent('.safe-fetch-subdir-special-characters-status')
+      ).toBe('200')
+    })
+
     test('unsafe fetch', async () => {
       expect(await page.textContent('.unsafe-fetch')).toMatch('403 Restricted')
       expect(await page.textContent('.unsafe-fetch-status')).toBe('403')
@@ -33,6 +42,13 @@ describe('main', () => {
       expect(await page.textContent('.safe-fs-fetch-status')).toBe('200')
     })
 
+    test('safe fs fetch with special characters', async () => {
+      expect(await page.textContent('.safe-fs-fetch-special-characters')).toBe(
+        stringified
+      )
+      expect(await page.textContent('.safe-fs-fetch-status')).toBe('200')
+    })
+
     test('unsafe fs fetch', async () => {
       expect(await page.textContent('.unsafe-fs-fetch')).toBe('')
       expect(await page.textContent('.unsafe-fs-fetch-status')).toBe('403')
diff --git a/packages/playground/fs-serve/root/src/index.html b/packages/playground/fs-serve/root/src/index.html
index 9e4f728a593a91..951e14ad2cce91 100644
--- a/packages/playground/fs-serve/root/src/index.html
+++ b/packages/playground/fs-serve/root/src/index.html
@@ -11,6 +11,8 @@ 

Safe Fetch

Safe Fetch Subdirectory


 

+

+

 
 

Unsafe Fetch


@@ -19,6 +21,8 @@ 

Unsafe Fetch

Safe /@fs/ Fetch


 

+

+

 
 

Unsafe /@fs/ Fetch


@@ -56,6 +60,16 @@ 

Denied

text('.safe-fetch-subdir', JSON.stringify(data)) }) + // inside allowed dir, with special characters, safe fetch + fetch('/src/special%20characters%20%C3%A5%C3%A4%C3%B6/safe.txt') + .then((r) => { + text('.safe-fetch-subdir-special-characters-status', r.status) + return r.text() + }) + .then((data) => { + text('.safe-fetch-subdir-special-characters', JSON.stringify(data)) + }) + // outside of allowed dir, treated as unsafe fetch('/unsafe.txt') .then((r) => { @@ -92,6 +106,20 @@

Denied

console.error(e) }) + // not imported before, inside root with special characters, treated as safe + fetch( + '/@fs/' + + ROOT + + '/root/src/special%20characters%20%C3%A5%C3%A4%C3%B6/safe.json' + ) + .then((r) => { + text('.safe-fs-fetch-special-characters-status', r.status) + return r.json() + }) + .then((data) => { + text('.safe-fs-fetch-special-characters', JSON.stringify(data)) + }) + // .env, denied by default fetch('/@fs/' + ROOT + '/root/.env') .then((r) => { diff --git "a/packages/playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.json" "b/packages/playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.json" new file mode 100644 index 00000000000000..84f96593c10bad --- /dev/null +++ "b/packages/playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.json" @@ -0,0 +1,3 @@ +{ + "msg": "safe" +} diff --git "a/packages/playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.txt" "b/packages/playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.txt" new file mode 100644 index 00000000000000..3f3d0607101642 --- /dev/null +++ "b/packages/playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.txt" @@ -0,0 +1 @@ +KEY=safe diff --git a/packages/playground/glob-import/__tests__/glob-import.spec.ts b/packages/playground/glob-import/__tests__/glob-import.spec.ts index fff8d9fe202ebc..ebdf6c0ab29193 100644 --- a/packages/playground/glob-import/__tests__/glob-import.spec.ts +++ b/packages/playground/glob-import/__tests__/glob-import.spec.ts @@ -66,6 +66,12 @@ const rawResult = { } } +const relativeRawResult = { + '../glob-import/dir/baz.json': { + msg: 'baz' + } +} + test('should work', async () => { expect(await page.textContent('.result')).toBe( JSON.stringify(allResult, null, 2) @@ -81,6 +87,12 @@ test('import glob raw', async () => { ) }) +test('import relative glob raw', async () => { + expect(await page.textContent('.relative-glob-raw')).toBe( + JSON.stringify(relativeRawResult, null, 2) + ) +}) + if (!isBuild) { test('hmr for adding/removing files', async () => { addFile('dir/a.js', '') diff --git a/packages/playground/glob-import/index.html b/packages/playground/glob-import/index.html index 52d41b817a169c..64f456aeb4d6a2 100644 --- a/packages/playground/glob-import/index.html +++ b/packages/playground/glob-import/index.html @@ -1,6 +1,7 @@

 

 

+

 
 
 
+
+
diff --git a/packages/playground/hmr/__tests__/hmr.spec.ts b/packages/playground/hmr/__tests__/hmr.spec.ts
index 1f28763a90df94..7325c9fe47943a 100644
--- a/packages/playground/hmr/__tests__/hmr.spec.ts
+++ b/packages/playground/hmr/__tests__/hmr.spec.ts
@@ -16,7 +16,7 @@ if (!isBuild) {
   test('self accept', async () => {
     const el = await page.$('.app')
 
-    editFile('hmr.js', (code) => code.replace('const foo = 1', 'const foo = 2'))
+    editFile('hmr.ts', (code) => code.replace('const foo = 1', 'const foo = 2'))
     await untilUpdated(() => el.textContent(), '2')
 
     expect(browserLogs).toMatchObject([
@@ -24,11 +24,11 @@ if (!isBuild) {
       'foo was: 1',
       '(self-accepting 1) foo is now: 2',
       '(self-accepting 2) foo is now: 2',
-      '[vite] hot updated: /hmr.js'
+      '[vite] hot updated: /hmr.ts'
     ])
     browserLogs.length = 0
 
-    editFile('hmr.js', (code) => code.replace('const foo = 2', 'const foo = 3'))
+    editFile('hmr.ts', (code) => code.replace('const foo = 2', 'const foo = 3'))
     await untilUpdated(() => el.textContent(), '3')
 
     expect(browserLogs).toMatchObject([
@@ -36,7 +36,7 @@ if (!isBuild) {
       'foo was: 2',
       '(self-accepting 1) foo is now: 3',
       '(self-accepting 2) foo is now: 3',
-      '[vite] hot updated: /hmr.js'
+      '[vite] hot updated: /hmr.ts'
     ])
     browserLogs.length = 0
   })
@@ -57,7 +57,7 @@ if (!isBuild) {
       '(single dep) nested foo is now: 1',
       '(multi deps) foo is now: 2',
       '(multi deps) nested foo is now: 1',
-      '[vite] hot updated: /hmrDep.js via /hmr.js'
+      '[vite] hot updated: /hmrDep.js via /hmr.ts'
     ])
     browserLogs.length = 0
 
@@ -74,7 +74,7 @@ if (!isBuild) {
       '(single dep) nested foo is now: 1',
       '(multi deps) foo is now: 3',
       '(multi deps) nested foo is now: 1',
-      '[vite] hot updated: /hmrDep.js via /hmr.js'
+      '[vite] hot updated: /hmrDep.js via /hmr.ts'
     ])
     browserLogs.length = 0
   })
@@ -95,7 +95,7 @@ if (!isBuild) {
       '(single dep) nested foo is now: 2',
       '(multi deps) foo is now: 3',
       '(multi deps) nested foo is now: 2',
-      '[vite] hot updated: /hmrDep.js via /hmr.js'
+      '[vite] hot updated: /hmrDep.js via /hmr.ts'
     ])
     browserLogs.length = 0
 
@@ -112,7 +112,7 @@ if (!isBuild) {
       '(single dep) nested foo is now: 3',
       '(multi deps) foo is now: 3',
       '(multi deps) nested foo is now: 3',
-      '[vite] hot updated: /hmrDep.js via /hmr.js'
+      '[vite] hot updated: /hmrDep.js via /hmr.ts'
     ])
     browserLogs.length = 0
   })
@@ -123,11 +123,16 @@ if (!isBuild) {
     await untilUpdated(() => el.textContent(), 'edited')
   })
 
+  test('plugin client-server communication', async () => {
+    const el = await page.$('.custom-communication')
+    await untilUpdated(() => el.textContent(), '3')
+  })
+
   test('full-reload encodeURI path', async () => {
     await page.goto(
       viteTestUrl + '/unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html'
     )
-    let el = await page.$('#app')
+    const el = await page.$('#app')
     expect(await el.textContent()).toBe('title')
     await editFile(
       'unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html',
@@ -155,4 +160,38 @@ if (!isBuild) {
     expect(textprev).not.toMatch('direct')
     expect(textpost).not.toMatch('direct')
   })
+
+  test('not loaded dynamic import', async () => {
+    await page.goto(viteTestUrl + '/dynamic-import/index.html')
+
+    let btn = await page.$('button')
+    expect(await btn.textContent()).toBe('Counter 0')
+    await btn.click()
+    expect(await btn.textContent()).toBe('Counter 1')
+
+    // Modifying `index.ts` triggers a page reload, as expected
+    editFile('dynamic-import/index.ts', (code) => code)
+    await page.waitForNavigation()
+    btn = await page.$('button')
+    expect(await btn.textContent()).toBe('Counter 0')
+
+    await btn.click()
+    expect(await btn.textContent()).toBe('Counter 1')
+
+    // #7561
+    // `dep.ts` defines `import.module.hot.accept` and has not been loaded.
+    // Therefore, modifying it has no effect (doesn't trigger a page reload).
+    // (Note that, a dynamic import that is never loaded and that does not
+    // define `accept.module.hot.accept` may wrongfully trigger a full page
+    // reload, see discussion at #7561.)
+    editFile('dynamic-import/dep.ts', (code) => code)
+    try {
+      await page.waitForNavigation({ timeout: 1000 })
+    } catch (err) {
+      const errMsg = 'page.waitForNavigation: Timeout 1000ms exceeded.'
+      expect(err.message.slice(0, errMsg.length)).toBe(errMsg)
+    }
+    btn = await page.$('button')
+    expect(await btn.textContent()).toBe('Counter 1')
+  })
 }
diff --git a/packages/playground/hmr/dynamic-import/dep.ts b/packages/playground/hmr/dynamic-import/dep.ts
new file mode 100644
index 00000000000000..aca649f226f770
--- /dev/null
+++ b/packages/playground/hmr/dynamic-import/dep.ts
@@ -0,0 +1,2 @@
+// This file is never loaded
+import.meta.hot.accept(() => {})
diff --git a/packages/playground/hmr/dynamic-import/index.html b/packages/playground/hmr/dynamic-import/index.html
new file mode 100644
index 00000000000000..f5290ad4f1e507
--- /dev/null
+++ b/packages/playground/hmr/dynamic-import/index.html
@@ -0,0 +1,2 @@
+
+
diff --git a/packages/playground/hmr/dynamic-import/index.ts b/packages/playground/hmr/dynamic-import/index.ts
new file mode 100644
index 00000000000000..0230140278989f
--- /dev/null
+++ b/packages/playground/hmr/dynamic-import/index.ts
@@ -0,0 +1,12 @@
+const btn = document.querySelector('button')
+let count = 0
+const update = () => {
+  btn.textContent = `Counter ${count}`
+}
+btn.onclick = () => {
+  count++
+  update()
+}
+function neverCalled() {
+  import('./dep')
+}
diff --git a/packages/playground/hmr/event.d.ts b/packages/playground/hmr/event.d.ts
new file mode 100644
index 00000000000000..151a9cc3b861cd
--- /dev/null
+++ b/packages/playground/hmr/event.d.ts
@@ -0,0 +1,9 @@
+import 'vite/types/customEvent'
+
+declare module 'vite/types/customEvent' {
+  interface CustomEventMap {
+    'custom:foo': { msg: string }
+    'custom:remote-add': { a: number; b: number }
+    'custom:remote-add-result': { result: string }
+  }
+}
diff --git a/packages/playground/hmr/hmr.js b/packages/playground/hmr/hmr.ts
similarity index 81%
rename from packages/playground/hmr/hmr.js
rename to packages/playground/hmr/hmr.ts
index 01dca20f5dd71c..113b87bc5865d4 100644
--- a/packages/playground/hmr/hmr.js
+++ b/packages/playground/hmr/hmr.ts
@@ -41,7 +41,7 @@ if (import.meta.hot) {
         update.type === 'css-update' && update.path.match('global.css')
     )
     if (cssUpdate) {
-      const el = document.querySelector('#global-css')
+      const el = document.querySelector('#global-css') as HTMLLinkElement
       text('.css-prev', el.href)
       // We don't have a vite:afterUpdate event, but updates are currently sync
       setTimeout(() => {
@@ -54,9 +54,15 @@ if (import.meta.hot) {
     console.log(`>>> vite:error -- ${event.type}`)
   })
 
-  import.meta.hot.on('foo', ({ msg }) => {
+  import.meta.hot.on('custom:foo', ({ msg }) => {
     text('.custom', msg)
   })
+
+  // send custom event to server to calculate 1 + 2
+  import.meta.hot.send('custom:remote-add', { a: 1, b: 2 })
+  import.meta.hot.on('custom:remote-add-result', ({ result }) => {
+    text('.custom-communication', result)
+  })
 }
 
 function text(el, text) {
diff --git a/packages/playground/hmr/index.html b/packages/playground/hmr/index.html
index 766338598e51ad..0add7c26011a01 100644
--- a/packages/playground/hmr/index.html
+++ b/packages/playground/hmr/index.html
@@ -1,9 +1,10 @@
 
-
+
 
 
+
diff --git a/packages/playground/hmr/tsconfig.json b/packages/playground/hmr/tsconfig.json new file mode 100644 index 00000000000000..41b16fdc65ec8c --- /dev/null +++ b/packages/playground/hmr/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["."], + "exclude": ["**/dist/**"], + "compilerOptions": { + "target": "es2019", + "module": "esnext", + "outDir": "dist", + "allowJs": true, + "esModuleInterop": true, + "moduleResolution": "node", + "baseUrl": ".", + "jsx": "preserve", + "types": ["vite/client", "jest", "node"] + } +} diff --git a/packages/playground/hmr/vite.config.js b/packages/playground/hmr/vite.config.js deleted file mode 100644 index c34637844e2170..00000000000000 --- a/packages/playground/hmr/vite.config.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @type {import('vite').UserConfig} - */ -module.exports = { - plugins: [ - { - name: 'mock-custom', - async handleHotUpdate({ file, read, server }) { - if (file.endsWith('customFile.js')) { - const content = await read() - const msg = content.match(/export const msg = '(\w+)'/)[1] - server.ws.send({ - type: 'custom', - event: 'foo', - data: { - msg - } - }) - } - } - } - ] -} diff --git a/packages/playground/hmr/vite.config.ts b/packages/playground/hmr/vite.config.ts new file mode 100644 index 00000000000000..ef5d3cf36a2fcb --- /dev/null +++ b/packages/playground/hmr/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [ + { + name: 'mock-custom', + async handleHotUpdate({ file, read, server }) { + if (file.endsWith('customFile.js')) { + const content = await read() + const msg = content.match(/export const msg = '(\w+)'/)[1] + server.ws.send('custom:foo', { msg }) + } + }, + configureServer(server) { + server.ws.on('custom:remote-add', ({ a, b }, client) => { + client.send('custom:remote-add-result', { result: a + b }) + }) + } + } + ] +}) diff --git a/packages/playground/html/__tests__/html.spec.ts b/packages/playground/html/__tests__/html.spec.ts index 66f537e5026361..834db1f6126cad 100644 --- a/packages/playground/html/__tests__/html.spec.ts +++ b/packages/playground/html/__tests__/html.spec.ts @@ -231,7 +231,7 @@ if (!isBuild) { await editFile('invalid.html', (content) => { return content.replace('
Good') }) - const content = await page.waitForSelector('text=Good Html') + const content = await page.waitForSelector('text=Good HTML') expect(content).toBeTruthy() }) }) diff --git a/packages/playground/html/invalid.html b/packages/playground/html/invalid.html index 5b5cf429687466..8acea73f16bdad 100644 --- a/packages/playground/html/invalid.html +++ b/packages/playground/html/invalid.html @@ -1 +1 @@ -
+
diff --git a/packages/playground/js-sourcemap/__tests__/build.spec.ts b/packages/playground/js-sourcemap/__tests__/build.spec.ts new file mode 100644 index 00000000000000..e36c1f52d2c1f8 --- /dev/null +++ b/packages/playground/js-sourcemap/__tests__/build.spec.ts @@ -0,0 +1,13 @@ +import { isBuild } from 'testUtils' + +if (isBuild) { + test('should not output sourcemap warning (#4939)', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch('Sourcemap is likely to be incorrect') + }) + }) +} else { + test('this file only includes test for build', () => { + expect(true).toBe(true) + }) +} diff --git a/packages/playground/js-sourcemap/__tests__/serve.spec.ts b/packages/playground/js-sourcemap/__tests__/serve.spec.ts new file mode 100644 index 00000000000000..a1ffdddc37ecd5 --- /dev/null +++ b/packages/playground/js-sourcemap/__tests__/serve.spec.ts @@ -0,0 +1,44 @@ +import { URL } from 'url' +import { + extractSourcemap, + formatSourcemapForSnapshot, + isBuild +} from 'testUtils' + +if (!isBuild) { + test('js', async () => { + const res = await page.request.get(new URL('./foo.js', page.url()).href) + const js = await res.text() + const lines = js.split('\n') + expect(lines[lines.length - 1].includes('//')).toBe(false) // expect no sourcemap + }) + + test('ts', async () => { + const res = await page.request.get(new URL('./bar.ts', page.url()).href) + const js = await res.text() + const map = extractSourcemap(js) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + Object { + "mappings": "AAAO,aAAM,MAAM;", + "sources": Array [ + "/root/bar.ts", + ], + "sourcesContent": Array [ + "export const bar = 'bar' + ", + ], + "version": 3, + } + `) + }) + + test('should not output missing source file warning', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/) + }) + }) +} else { + test('this file only includes test for serve', () => { + expect(true).toBe(true) + }) +} diff --git a/packages/playground/js-sourcemap/bar.ts b/packages/playground/js-sourcemap/bar.ts new file mode 100644 index 00000000000000..1fc11814f22e80 --- /dev/null +++ b/packages/playground/js-sourcemap/bar.ts @@ -0,0 +1 @@ +export const bar = 'bar' diff --git a/packages/playground/js-sourcemap/foo.js b/packages/playground/js-sourcemap/foo.js new file mode 100644 index 00000000000000..cb356468240d50 --- /dev/null +++ b/packages/playground/js-sourcemap/foo.js @@ -0,0 +1 @@ +export const foo = 'foo' diff --git a/packages/playground/js-sourcemap/index.html b/packages/playground/js-sourcemap/index.html new file mode 100644 index 00000000000000..025b161011a3fa --- /dev/null +++ b/packages/playground/js-sourcemap/index.html @@ -0,0 +1,6 @@ +
+

JS Sourcemap

+
+ + + diff --git a/packages/playground/js-sourcemap/package.json b/packages/playground/js-sourcemap/package.json new file mode 100644 index 00000000000000..e5a97aea80830f --- /dev/null +++ b/packages/playground/js-sourcemap/package.json @@ -0,0 +1,11 @@ +{ + "name": "test-js-sourcemap", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "debug": "node --inspect-brk ../../vite/bin/vite", + "preview": "vite preview" + } +} diff --git a/packages/playground/js-sourcemap/vite.config.js b/packages/playground/js-sourcemap/vite.config.js new file mode 100644 index 00000000000000..bc9d1748cab964 --- /dev/null +++ b/packages/playground/js-sourcemap/vite.config.js @@ -0,0 +1,8 @@ +/** + * @type {import('vite').UserConfig} + */ +module.exports = { + build: { + sourcemap: true + } +} diff --git a/packages/playground/legacy/__tests__/legacy.spec.ts b/packages/playground/legacy/__tests__/legacy.spec.ts index b8025694437502..65bd39ff32b1d1 100644 --- a/packages/playground/legacy/__tests__/legacy.spec.ts +++ b/packages/playground/legacy/__tests__/legacy.spec.ts @@ -83,4 +83,8 @@ if (isBuild) { test('should emit css file', async () => { expect(listAssets().some((filename) => filename.endsWith('.css'))) }) + + test('includes structuredClone polyfill which is supported after core-js v3', () => { + expect(findAssetFile(/polyfills-legacy/)).toMatch('"structuredClone"') + }) } diff --git a/packages/playground/legacy/__tests__/ssr/serve.js b/packages/playground/legacy/__tests__/ssr/serve.js index df43f180afb188..c7ef2ed3520e53 100644 --- a/packages/playground/legacy/__tests__/ssr/serve.js +++ b/packages/playground/legacy/__tests__/ssr/serve.js @@ -2,8 +2,9 @@ // this is automtically detected by scripts/jestPerTestSetup.ts and will replace // the default e2e test serve behavior const path = require('path') +const { ports } = require('../../../testUtils') -const port = (exports.port = 9527) +const port = (exports.port = ports['legacy/ssr']) /** * @param {string} root diff --git a/packages/playground/legacy/index.html b/packages/playground/legacy/index.html index bdc2feac6b4fbe..d481766463cd4f 100644 --- a/packages/playground/legacy/index.html +++ b/packages/playground/legacy/index.html @@ -1,6 +1,7 @@

+
diff --git a/packages/playground/legacy/main.js b/packages/playground/legacy/main.js index b05acf439bdff8..31579b4717810d 100644 --- a/packages/playground/legacy/main.js +++ b/packages/playground/legacy/main.js @@ -21,6 +21,12 @@ text('#env', `is legacy: ${isLegacy}`) // Iterators text('#iterators', [...new Set(['hello'])].join('')) +// structuredClone is supported core.js v3.20.0+ +text( + '#features-after-corejs-3', + JSON.stringify(structuredClone({ foo: 'foo' })) +) + // babel-helpers // Using `String.raw` to inject `@babel/plugin-transform-template-literals` // helpers. diff --git a/packages/playground/lib/__tests__/serve.js b/packages/playground/lib/__tests__/serve.js index 15c64de40276d1..eac6980286af52 100644 --- a/packages/playground/lib/__tests__/serve.js +++ b/packages/playground/lib/__tests__/serve.js @@ -5,8 +5,9 @@ const path = require('path') const http = require('http') const sirv = require('sirv') +const { ports } = require('../../testUtils') -const port = (exports.port = 9527) +const port = (exports.port = ports.lib) /** * @param {string} root diff --git a/packages/playground/optimize-missing-deps/__test__/serve.js b/packages/playground/optimize-missing-deps/__test__/serve.js index 9f293024f83913..89a6ce3593cd0e 100644 --- a/packages/playground/optimize-missing-deps/__test__/serve.js +++ b/packages/playground/optimize-missing-deps/__test__/serve.js @@ -3,8 +3,9 @@ // the default e2e test serve behavior const path = require('path') +const { ports } = require('../../testUtils') -const port = (exports.port = 9529) +const port = (exports.port = ports['optimize-missing-deps']) /** * @param {string} root diff --git a/packages/playground/package.json b/packages/playground/package.json index 58ef368099e82f..75b1d15d299319 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -3,6 +3,7 @@ "private": true, "version": "1.0.0", "devDependencies": { + "convert-source-map": "^1.8.0", "css-color-names": "^1.0.1" } } diff --git a/packages/playground/resolve/__tests__/resolve.spec.ts b/packages/playground/resolve/__tests__/resolve.spec.ts index b64da138033fc0..46f8f6138b39a4 100644 --- a/packages/playground/resolve/__tests__/resolve.spec.ts +++ b/packages/playground/resolve/__tests__/resolve.spec.ts @@ -17,7 +17,10 @@ test('deep import with exports field', async () => { }) test('deep import with query with exports field', async () => { - expect(await page.textContent('.exports-deep-query')).not.toMatch('fail') + // since it is imported with `?url` it should return a url + expect(await page.textContent('.exports-deep-query')).toMatch( + isBuild ? /base64/ : '/exports-path/deep.json' + ) }) test('deep import with exports field + exposed dir', async () => { @@ -58,6 +61,18 @@ test('dont add extension to directory name (./dir-with-ext.js/index.js)', async expect(await page.textContent('.dir-with-ext')).toMatch('[success]') }) +test('resolve to the `browser` field instead of `module` when the importer is a `require` call', async () => { + expect( + await page.textContent('.require-pkg-with-browser-and-module-field') + ).toMatch('[success]') +}) + +test('resolve to the `main` field instead of `module` when the importer is a `require` call', async () => { + expect(await page.textContent('.require-pkg-with-esm-entries')).toMatch( + '[success]' + ) +}) + test('a ts module can import another ts module using its corresponding js file name', async () => { expect(await page.textContent('.ts-extension')).toMatch('[success]') }) diff --git a/packages/playground/resolve/index.html b/packages/playground/resolve/index.html index c0569345d86837..2478c89b495f49 100644 --- a/packages/playground/resolve/index.html +++ b/packages/playground/resolve/index.html @@ -58,6 +58,18 @@

Resolve file name containing dot

Browser Field

fail

+

+ Resolve to the `browser` field instead of `module` when the importer is a + `require` call +

+

fail

+ +

+ Resolve to the `main` field instead of `module` when the importer is a + `require` call +

+

fail

+

CSS Entry

@@ -168,10 +180,11 @@

resolve package that contains # in path

import e from 'resolve-browser-field/ext-index/index.js' import f from 'resolve-browser-field/ext-index' import g from 'resolve-browser-field/no-ext-index/index.js' // no substitution + import h from 'resolve-browser-field/no-ext?query' import { ra, rb, rc, rd, re, rf, rg } from 'resolve-browser-field/relative' - const success = [main, a, c, d, e, f, ra, rc, rd, re, rf] + const success = [main, a, c, d, e, f, h, ra, rc, rd, re, rf] const noSuccess = [b, g, rb, rg] if ( @@ -181,6 +194,12 @@

resolve package that contains # in path

text('.browser', main) } + import { msg as requireBrowserMsg } from 'require-pkg-with-browser-and-module-field' + text('.require-pkg-with-browser-and-module-field', requireBrowserMsg) + + import { msg as requireMainMsg } from 'require-pkg-with-esm-entries' + text('.require-pkg-with-esm-entries', requireMainMsg) + import { msg as customExtMsg } from './custom-ext' text('.custom-ext', customExtMsg) diff --git a/packages/playground/resolve/package.json b/packages/playground/resolve/package.json index 5e0f53b4c8468a..4b8d497b3dbb27 100644 --- a/packages/playground/resolve/package.json +++ b/packages/playground/resolve/package.json @@ -12,6 +12,8 @@ "@babel/runtime": "^7.16.0", "es5-ext": "0.10.53", "normalize.css": "^8.0.1", + "require-pkg-with-browser-and-module-field": "link:./require-pkg-with-browser-and-module-field", + "require-pkg-with-esm-entries": "link:./require-pkg-with-esm-entries", "resolve-browser-field": "link:./browser-field", "resolve-custom-condition": "link:./custom-condition", "resolve-custom-main-field": "link:./custom-main-field", diff --git a/packages/playground/resolve/require-pkg-with-browser-and-module-field/dep.cjs b/packages/playground/resolve/require-pkg-with-browser-and-module-field/dep.cjs new file mode 100644 index 00000000000000..3fb20b76d48b79 --- /dev/null +++ b/packages/playground/resolve/require-pkg-with-browser-and-module-field/dep.cjs @@ -0,0 +1,5 @@ +const BigNumber = require('bignumber.js') + +const x = new BigNumber('1111222233334444555566') + +module.exports = x.toString() diff --git a/packages/playground/resolve/require-pkg-with-browser-and-module-field/index.cjs b/packages/playground/resolve/require-pkg-with-browser-and-module-field/index.cjs new file mode 100644 index 00000000000000..86d3360ab38dcb --- /dev/null +++ b/packages/playground/resolve/require-pkg-with-browser-and-module-field/index.cjs @@ -0,0 +1,8 @@ +const dep = require('./dep.cjs') + +const msg = + dep === '1.111222233334444555566e+21' + ? '[success] require-pkg-with-browser-and-module-field' + : '[failed] require-pkg-with-browser-and-module-field' + +exports.msg = msg diff --git a/packages/playground/resolve/require-pkg-with-browser-and-module-field/package.json b/packages/playground/resolve/require-pkg-with-browser-and-module-field/package.json new file mode 100644 index 00000000000000..2a0419b331c407 --- /dev/null +++ b/packages/playground/resolve/require-pkg-with-browser-and-module-field/package.json @@ -0,0 +1,9 @@ +{ + "name": "require-pkg-with-browser-and-module-field", + "private": true, + "version": "1.0.0", + "main": "./index.cjs", + "dependencies": { + "bignumber.js": "9.0.2" + } +} diff --git a/packages/playground/resolve/require-pkg-with-esm-entries/index.cjs b/packages/playground/resolve/require-pkg-with-esm-entries/index.cjs new file mode 100644 index 00000000000000..55958fbdba26ee --- /dev/null +++ b/packages/playground/resolve/require-pkg-with-esm-entries/index.cjs @@ -0,0 +1,9 @@ +const fromEvent = require('callbag-from-event') + +const msg = + // should be the exported function instead of the ES Module record (`{ default: ... }`) + typeof fromEvent === 'function' + ? '[success] require-pkg-with-esm-entries' + : '[failed] require-pkg-with-esm-entries' + +exports.msg = msg diff --git a/packages/playground/resolve/require-pkg-with-esm-entries/package.json b/packages/playground/resolve/require-pkg-with-esm-entries/package.json new file mode 100644 index 00000000000000..b845364bb6f19a --- /dev/null +++ b/packages/playground/resolve/require-pkg-with-esm-entries/package.json @@ -0,0 +1,9 @@ +{ + "name": "require-pkg-with-esm-entries", + "private": true, + "version": "1.0.0", + "main": "./index.cjs", + "dependencies": { + "callbag-from-event": "1.3.0" + } +} diff --git a/packages/playground/resolve/vite.config.js b/packages/playground/resolve/vite.config.js index be1b75e431383a..c1282f4ffc789d 100644 --- a/packages/playground/resolve/vite.config.js +++ b/packages/playground/resolve/vite.config.js @@ -40,5 +40,11 @@ module.exports = { } } } - ] + ], + optimizeDeps: { + include: [ + 'require-pkg-with-browser-and-module-field', + 'require-pkg-with-esm-entries' + ] + } } diff --git a/packages/playground/ssr-deps/__tests__/serve.js b/packages/playground/ssr-deps/__tests__/serve.js index 5ba5724f2b7a94..6c2584601c9331 100644 --- a/packages/playground/ssr-deps/__tests__/serve.js +++ b/packages/playground/ssr-deps/__tests__/serve.js @@ -3,8 +3,9 @@ // the default e2e test serve behavior const path = require('path') +const { ports } = require('../../testUtils') -const port = (exports.port = 9530) +const port = (exports.port = ports['ssr-deps']) /** * @param {string} root diff --git a/packages/playground/ssr-html/__tests__/serve.js b/packages/playground/ssr-html/__tests__/serve.js index 5ba5724f2b7a94..d119397700cf75 100644 --- a/packages/playground/ssr-html/__tests__/serve.js +++ b/packages/playground/ssr-html/__tests__/serve.js @@ -3,8 +3,9 @@ // the default e2e test serve behavior const path = require('path') +const { ports } = require('../../testUtils') -const port = (exports.port = 9530) +const port = (exports.port = ports['ssr-html']) /** * @param {string} root diff --git a/packages/playground/ssr-pug/__tests__/serve.js b/packages/playground/ssr-pug/__tests__/serve.js index 5ba5724f2b7a94..392aa831ebb82d 100644 --- a/packages/playground/ssr-pug/__tests__/serve.js +++ b/packages/playground/ssr-pug/__tests__/serve.js @@ -3,8 +3,9 @@ // the default e2e test serve behavior const path = require('path') +const { ports } = require('../../testUtils') -const port = (exports.port = 9530) +const port = (exports.port = ports['ssr-pug']) /** * @param {string} root diff --git a/packages/playground/ssr-react/__tests__/serve.js b/packages/playground/ssr-react/__tests__/serve.js index 1bc028c03dc27c..07476e0726e268 100644 --- a/packages/playground/ssr-react/__tests__/serve.js +++ b/packages/playground/ssr-react/__tests__/serve.js @@ -3,8 +3,9 @@ // the default e2e test serve behavior const path = require('path') +const { ports } = require('../../testUtils') -const port = (exports.port = 9528) +const port = (exports.port = ports['ssr-react']) /** * @param {string} root diff --git a/packages/playground/ssr-vue/__tests__/serve.js b/packages/playground/ssr-vue/__tests__/serve.js index 1e220fed9630e4..5bcd5a4639877a 100644 --- a/packages/playground/ssr-vue/__tests__/serve.js +++ b/packages/playground/ssr-vue/__tests__/serve.js @@ -3,8 +3,9 @@ // the default e2e test serve behavior const path = require('path') +const { ports } = require('../../testUtils') -const port = (exports.port = 9527) +const port = (exports.port = ports['ssr-vue']) /** * @param {string} root diff --git a/packages/playground/ssr-webworker/__tests__/serve.js b/packages/playground/ssr-webworker/__tests__/serve.js index f4f207b85026c6..38a957b0a333ea 100644 --- a/packages/playground/ssr-webworker/__tests__/serve.js +++ b/packages/playground/ssr-webworker/__tests__/serve.js @@ -3,8 +3,9 @@ // the default e2e test serve behavior const path = require('path') +const { ports } = require('../../testUtils') -const port = (exports.port = 9528) +const port = (exports.port = ports['ssr-webworker']) /** * @param {string} root diff --git a/packages/playground/tailwind-sourcemap/__tests__/build.spec.ts b/packages/playground/tailwind-sourcemap/__tests__/build.spec.ts new file mode 100644 index 00000000000000..e36c1f52d2c1f8 --- /dev/null +++ b/packages/playground/tailwind-sourcemap/__tests__/build.spec.ts @@ -0,0 +1,13 @@ +import { isBuild } from 'testUtils' + +if (isBuild) { + test('should not output sourcemap warning (#4939)', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch('Sourcemap is likely to be incorrect') + }) + }) +} else { + test('this file only includes test for build', () => { + expect(true).toBe(true) + }) +} diff --git a/packages/playground/tailwind-sourcemap/__tests__/serve.spec.ts b/packages/playground/tailwind-sourcemap/__tests__/serve.spec.ts new file mode 100644 index 00000000000000..d961f75e4536e5 --- /dev/null +++ b/packages/playground/tailwind-sourcemap/__tests__/serve.spec.ts @@ -0,0 +1,13 @@ +import { isBuild } from 'testUtils' + +if (!isBuild) { + test('should not output missing source file warning', () => { + serverLogs.forEach((log) => { + expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/) + }) + }) +} else { + test('this file only includes test for serve', () => { + expect(true).toBe(true) + }) +} diff --git a/packages/playground/tailwind-sourcemap/index.html b/packages/playground/tailwind-sourcemap/index.html new file mode 100644 index 00000000000000..95c8c5da7716d1 --- /dev/null +++ b/packages/playground/tailwind-sourcemap/index.html @@ -0,0 +1,9 @@ +
+

Tailwind Sourcemap

+ +

foo

+
+ + diff --git a/packages/playground/tailwind-sourcemap/package.json b/packages/playground/tailwind-sourcemap/package.json new file mode 100644 index 00000000000000..5c374f3bf47f1b --- /dev/null +++ b/packages/playground/tailwind-sourcemap/package.json @@ -0,0 +1,14 @@ +{ + "name": "test-tailwind-sourcemap", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "debug": "node --inspect-brk ../../vite/bin/vite", + "preview": "vite preview" + }, + "dependencies": { + "tailwindcss": "^3.0.23" + } +} diff --git a/packages/playground/tailwind-sourcemap/postcss.config.js b/packages/playground/tailwind-sourcemap/postcss.config.js new file mode 100644 index 00000000000000..eab3760cbc7b42 --- /dev/null +++ b/packages/playground/tailwind-sourcemap/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + tailwindcss: { config: __dirname + '/tailwind.config.js' } + } +} diff --git a/packages/playground/tailwind-sourcemap/tailwind.config.js b/packages/playground/tailwind-sourcemap/tailwind.config.js new file mode 100644 index 00000000000000..f89a536ccd742f --- /dev/null +++ b/packages/playground/tailwind-sourcemap/tailwind.config.js @@ -0,0 +1,7 @@ +module.exports = { + content: ['./index.html'], + theme: { + extend: {} + }, + plugins: [] +} diff --git a/packages/playground/tailwind-sourcemap/tailwind.css b/packages/playground/tailwind-sourcemap/tailwind.css new file mode 100644 index 00000000000000..b5c61c956711f9 --- /dev/null +++ b/packages/playground/tailwind-sourcemap/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/playground/tailwind-sourcemap/vite.config.js b/packages/playground/tailwind-sourcemap/vite.config.js new file mode 100644 index 00000000000000..70fea77247bcd4 --- /dev/null +++ b/packages/playground/tailwind-sourcemap/vite.config.js @@ -0,0 +1,11 @@ +/** + * @type {import('vite').UserConfig} + */ +module.exports = { + css: { + devSourcemap: true + }, + build: { + sourcemap: true + } +} diff --git a/packages/playground/testUtils.ts b/packages/playground/testUtils.ts index 0c8186d4ed121d..427fea6d947a4f 100644 --- a/packages/playground/testUtils.ts +++ b/packages/playground/testUtils.ts @@ -7,6 +7,25 @@ import path from 'path' import colors from 'css-color-names' import type { ElementHandle } from 'playwright-chromium' import type { Manifest } from 'vite' +import { normalizePath } from 'vite' +import { fromComment } from 'convert-source-map' + +// make sure these ports are unique +export const ports = { + cli: 9510, + 'cli-module': 9511, + 'legacy/ssr': 9520, + lib: 9521, + 'optimize-missing-deps': 9522, + 'ssr-deps': 9600, + 'ssr-html': 9601, + 'ssr-pug': 9602, + 'ssr-react': 9603, + 'ssr-vue': 9604, + 'ssr-webworker': 9605, + 'css/postcss-caching': 5005, + 'css/postcss-plugins-different-dir': 5006 +} export function slash(p: string): string { return p.replace(/\\/g, '/') @@ -138,3 +157,17 @@ export async function untilUpdated( * Send the rebuild complete message in build watch */ export { notifyRebuildComplete } from '../../scripts/jestPerTestSetup' + +export const extractSourcemap = (content: string) => { + const lines = content.trim().split('\n') + return fromComment(lines[lines.length - 1]).toObject() +} + +export const formatSourcemapForSnapshot = (map: any) => { + const root = normalizePath(testDir) + const m = { ...map } + delete m.file + delete m.names + m.sources = m.sources.map((source) => source.replace(root, '/root')) + return m +} diff --git a/packages/playground/vue-sourcemap/Js.vue b/packages/playground/vue-sourcemap/Js.vue new file mode 100644 index 00000000000000..3a5577099f67d3 --- /dev/null +++ b/packages/playground/vue-sourcemap/Js.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/packages/playground/vue-sourcemap/Main.vue b/packages/playground/vue-sourcemap/Main.vue index 04ddf50071ccb3..b9b03596f5aea5 100644 --- a/packages/playground/vue-sourcemap/Main.vue +++ b/packages/playground/vue-sourcemap/Main.vue @@ -1,5 +1,7 @@ + + diff --git a/packages/playground/vue-sourcemap/__tests__/serve.spec.ts b/packages/playground/vue-sourcemap/__tests__/serve.spec.ts index 193b0afb9ba73f..08b4c04face111 100644 --- a/packages/playground/vue-sourcemap/__tests__/serve.spec.ts +++ b/packages/playground/vue-sourcemap/__tests__/serve.spec.ts @@ -1,10 +1,11 @@ -import { fromComment } from 'convert-source-map' -import { normalizePath } from 'vite' -import { isBuild, testDir } from 'testUtils' +import { + extractSourcemap, + formatSourcemapForSnapshot, + isBuild +} from 'testUtils' +import { URL } from 'url' if (!isBuild) { - const root = normalizePath(testDir) - const getStyleTagContentIncluding = async (content: string) => { const styles = await page.$$('style') for (const style of styles) { @@ -16,18 +17,63 @@ if (!isBuild) { throw new Error('Not found') } - const extractSourcemap = (content: string) => { - const lines = content.trim().split('\n') - return fromComment(lines[lines.length - 1]).toObject() - } + test('js', async () => { + const res = await page.request.get(new URL('./Js.vue', page.url()).href) + const js = await res.text() + const map = extractSourcemap(js) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + Object { + "mappings": "AAKA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;;;;AAGP;AACd,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;wBARlB,oBAAiB,WAAd,MAAU", + "sources": Array [ + "/root/Js.vue", + ], + "sourcesContent": Array [ + " - const formatSourcemapForSnapshot = (map: any) => { - const m = { ...map } - delete m.file - delete m.names - m.sources = m.sources.map((source) => source.replace(root, '/root')) - return m - } + + + + ", + ], + "version": 3, + } + `) + }) + + test('ts', async () => { + const res = await page.request.get(new URL('./Ts.vue', page.url()).href) + const js = await res.text() + const map = extractSourcemap(js) + expect(formatSourcemapForSnapshot(map)).toMatchInlineSnapshot(` + Object { + "mappings": ";AAKA,QAAQ,IAAI,WAAW;;;;AAIvB,YAAQ,IAAI,UAAU;;;;;;;;uBARpB,oBAAiB,WAAd,MAAU", + "sources": Array [ + "/root/Ts.vue", + ], + "sourcesContent": Array [ + " + + + + + ", + ], + "version": 3, + } + `) + }) test('css', async () => { const css = await getStyleTagContentIncluding('.css ') diff --git a/packages/playground/vue-sourcemap/package.json b/packages/playground/vue-sourcemap/package.json index 5672b5e3d9d57d..286940b01efa58 100644 --- a/packages/playground/vue-sourcemap/package.json +++ b/packages/playground/vue-sourcemap/package.json @@ -10,7 +10,6 @@ }, "devDependencies": { "@vitejs/plugin-vue": "workspace:*", - "convert-source-map": "^1.8.0", "less": "^4.1.2", "sass": "^1.43.4" }, diff --git a/packages/playground/vue-sourcemap/vite.config.js b/packages/playground/vue-sourcemap/vite.config.js index 045410259fe590..2a48cad3cb00d4 100644 --- a/packages/playground/vue-sourcemap/vite.config.js +++ b/packages/playground/vue-sourcemap/vite.config.js @@ -5,6 +5,7 @@ const vuePlugin = require('@vitejs/plugin-vue') */ module.exports = { css: { + devSourcemap: true, preprocessorOptions: { less: { additionalData: '@color: red;' diff --git a/packages/playground/vue/Main.vue b/packages/playground/vue/Main.vue index d10ae401f7aa8e..87319acdf6f736 100644 --- a/packages/playground/vue/Main.vue +++ b/packages/playground/vue/Main.vue @@ -20,6 +20,7 @@ + diff --git a/packages/playground/vue/workerTest.js b/packages/playground/vue/workerTest.js new file mode 100644 index 00000000000000..fcde5e19b30677 --- /dev/null +++ b/packages/playground/vue/workerTest.js @@ -0,0 +1 @@ +self.postMessage('worker load!') diff --git a/packages/playground/worker/__tests__/es/es-worker.spec.ts b/packages/playground/worker/__tests__/es/es-worker.spec.ts new file mode 100644 index 00000000000000..c7fd0d6c19e4bc --- /dev/null +++ b/packages/playground/worker/__tests__/es/es-worker.spec.ts @@ -0,0 +1,102 @@ +import fs from 'fs' +import path from 'path' +import { untilUpdated, isBuild, testDir } from '../../../testUtils' +import type { Page } from 'playwright-chromium' + +test('normal', async () => { + await page.click('.ping') + await untilUpdated(() => page.textContent('.pong'), 'pong') + await untilUpdated( + () => page.textContent('.mode'), + isBuild ? 'production' : 'development' + ) + await untilUpdated( + () => page.textContent('.bundle-with-plugin'), + 'worker bundle with plugin success!' + ) +}) + +test('TS output', async () => { + await page.click('.ping-ts-output') + await untilUpdated(() => page.textContent('.pong-ts-output'), 'pong') +}) + +test('inlined', async () => { + await page.click('.ping-inline') + await untilUpdated(() => page.textContent('.pong-inline'), 'pong') +}) + +const waitSharedWorkerTick = ( + (resolvedSharedWorkerCount: number) => async (page: Page) => { + await untilUpdated(async () => { + const count = await page.textContent('.tick-count') + // ignore the initial 0 + return count === '1' ? 'page loaded' : '' + }, 'page loaded') + // test.concurrent sequential is not guaranteed + // force page to wait to ensure two pages overlap in time + resolvedSharedWorkerCount++ + if (resolvedSharedWorkerCount < 2) return + + await untilUpdated(() => { + return resolvedSharedWorkerCount === 2 ? 'all pages loaded' : '' + }, 'all pages loaded') + } +)(0) + +test.concurrent.each([[true], [false]])('shared worker', async (doTick) => { + if (doTick) { + await page.click('.tick-shared') + } + await waitSharedWorkerTick(page) +}) + +test('worker emitted', async () => { + await untilUpdated(() => page.textContent('.nested-worker'), 'pong') +}) + +if (isBuild) { + const assetsDir = path.resolve(testDir, 'dist/es/assets') + // assert correct files + test('inlined code generation', async () => { + const files = fs.readdirSync(assetsDir) + expect(files.length).toBe(22) + const index = files.find((f) => f.includes('main-module')) + const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') + const worker = files.find((f) => f.includes('my-worker')) + const workerContent = fs.readFileSync( + path.resolve(assetsDir, worker), + 'utf-8' + ) + + // worker should have all imports resolved and no exports + expect(workerContent).not.toMatch(`import`) + expect(workerContent).not.toMatch(`export`) + // chunk + expect(content).toMatch(`new Worker("/es/assets`) + expect(content).toMatch(`new SharedWorker("/es/assets`) + // inlined + expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`) + expect(content).toMatch(`window.Blob`) + }) +} + +test('module worker', async () => { + expect(await page.textContent('.shared-worker-import-meta-url')).toMatch( + 'A string' + ) +}) + +test('classic worker', async () => { + expect(await page.textContent('.classic-worker')).toMatch('A classic') + expect(await page.textContent('.classic-shared-worker')).toMatch('A classic') +}) + +test('emit chunk', async () => { + expect(await page.textContent('.emti-chunk-worker')).toMatch( + '["A string",{"type":"emit-chunk-sub-worker","data":"A string"},{"type":"module-and-worker:worker","data":"A string"},{"type":"module-and-worker:module","data":"module and worker"},{"type":"emit-chunk-sub-worker","data":{"module":"module and worker","msg1":"module1","msg2":"module2","msg3":"module3"}}]' + ) + expect(await page.textContent('.emti-chunk-dynamic-import-worker')).toMatch( + '"A string/es/"' + ) +}) diff --git a/packages/playground/worker/__tests__/es/vite.config.js b/packages/playground/worker/__tests__/es/vite.config.js new file mode 100644 index 00000000000000..931d457792c4f9 --- /dev/null +++ b/packages/playground/worker/__tests__/es/vite.config.js @@ -0,0 +1 @@ +module.exports = require('../../vite.config-es') diff --git a/packages/playground/worker/__tests__/iife/vite.config.js b/packages/playground/worker/__tests__/iife/vite.config.js new file mode 100644 index 00000000000000..4204f532b7ac1c --- /dev/null +++ b/packages/playground/worker/__tests__/iife/vite.config.js @@ -0,0 +1 @@ +module.exports = require('../../vite.config') diff --git a/packages/playground/worker/__tests__/worker.spec.ts b/packages/playground/worker/__tests__/iife/worker.spec.ts similarity index 77% rename from packages/playground/worker/__tests__/worker.spec.ts rename to packages/playground/worker/__tests__/iife/worker.spec.ts index 6d93e810c0c510..fa9f72fe76131c 100644 --- a/packages/playground/worker/__tests__/worker.spec.ts +++ b/packages/playground/worker/__tests__/iife/worker.spec.ts @@ -1,6 +1,6 @@ import fs from 'fs' import path from 'path' -import { untilUpdated, isBuild, testDir } from '../../testUtils' +import { untilUpdated, isBuild, testDir } from '../../../testUtils' import type { Page } from 'playwright-chromium' test('normal', async () => { @@ -51,21 +51,20 @@ test.concurrent.each([[true], [false]])('shared worker', async (doTick) => { await waitSharedWorkerTick(page) }) -test('worker emitted', async () => { - await untilUpdated(() => page.textContent('.nested-worker'), 'pong') +test('worker emitted and import.meta.url in nested worker', async () => { await untilUpdated( - () => page.textContent('.nested-worker-dynamic-import'), - '"msg":"pong"' + () => page.textContent('.nested-worker'), + 'pong http://localhost:3000/iife/sub-worker.js?worker_file' ) }) if (isBuild) { - const assetsDir = path.resolve(testDir, 'dist/assets') + const assetsDir = path.resolve(testDir, 'dist/iife/assets') // assert correct files test('inlined code generation', async () => { const files = fs.readdirSync(assetsDir) - expect(files.length).toBe(11) - const index = files.find((f) => f.includes('index')) + expect(files.length).toBe(13) + const index = files.find((f) => f.includes('main-module')) const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') const worker = files.find((f) => f.includes('my-worker')) const workerContent = fs.readFileSync( @@ -77,15 +76,21 @@ if (isBuild) { expect(workerContent).not.toMatch(`import`) expect(workerContent).not.toMatch(`export`) // chunk - expect(content).toMatch(`new Worker("/assets`) - expect(content).toMatch(`new SharedWorker("/assets`) + expect(content).toMatch(`new Worker("/iife/assets`) + expect(content).toMatch(`new SharedWorker("/iife/assets`) // inlined expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`) expect(content).toMatch(`window.Blob`) }) } -test('classic worker is run', async () => { +test('module worker', async () => { + expect(await page.textContent('.shared-worker-import-meta-url')).toMatch( + 'A string' + ) +}) + +test('classic worker', async () => { expect(await page.textContent('.classic-worker')).toMatch('A classic') expect(await page.textContent('.classic-shared-worker')).toMatch('A classic') }) diff --git a/packages/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts b/packages/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts new file mode 100644 index 00000000000000..d846a5de2311d0 --- /dev/null +++ b/packages/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts @@ -0,0 +1,129 @@ +import fs from 'fs' +import path from 'path' +import { untilUpdated, isBuild, testDir } from '../../../testUtils' +import { Page } from 'playwright-chromium' + +if (isBuild) { + const assetsDir = path.resolve(testDir, 'dist/iife-sourcemap-hidden/assets') + // assert correct files + test('sourcemap generation for web workers', async () => { + const files = fs.readdirSync(assetsDir) + // should have 2 worker chunk + expect(files.length).toBe(25) + const index = files.find((f) => f.includes('main-module')) + const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') + const indexSourcemap = getSourceMapUrl(content) + const worker = files.find((f) => /^my-worker\.\w+\.js$/.test(f)) + const workerContent = fs.readFileSync( + path.resolve(assetsDir, worker), + 'utf-8' + ) + const workerSourcemap = getSourceMapUrl(workerContent) + const sharedWorker = files.find((f) => + /^my-shared-worker\.\w+\.js$/.test(f) + ) + const sharedWorkerContent = fs.readFileSync( + path.resolve(assetsDir, sharedWorker), + 'utf-8' + ) + const sharedWorkerSourcemap = getSourceMapUrl(sharedWorkerContent) + const possibleTsOutputWorker = files.find((f) => + /^possible-ts-output-worker\.\w+\.js$/.test(f) + ) + const possibleTsOutputWorkerContent = fs.readFileSync( + path.resolve(assetsDir, possibleTsOutputWorker), + 'utf-8' + ) + const possibleTsOutputWorkerSourcemap = getSourceMapUrl( + possibleTsOutputWorkerContent + ) + const workerNestedWorker = files.find((f) => + /^worker-nested-worker\.\w+\.js$/.test(f) + ) + const workerNestedWorkerContent = fs.readFileSync( + path.resolve(assetsDir, workerNestedWorker), + 'utf-8' + ) + const workerNestedWorkerSourcemap = getSourceMapUrl( + workerNestedWorkerContent + ) + const subWorker = files.find((f) => /^sub-worker\.\w+\.js$/.test(f)) + const subWorkerContent = fs.readFileSync( + path.resolve(assetsDir, subWorker), + 'utf-8' + ) + const subWorkerSourcemap = getSourceMapUrl(subWorkerContent) + + expect(files).toContainEqual(expect.stringMatching(/^index\.\w+\.js\.map$/)) + expect(files).toContainEqual( + expect.stringMatching(/^my-worker\.\w+\.js\.map$/) + ) + expect(files).toContainEqual( + expect.stringMatching(/^my-shared-worker\.\w+\.js\.map$/) + ) + expect(files).toContainEqual( + expect.stringMatching(/^possible-ts-output-worker\.\w+\.js\.map$/) + ) + expect(files).toContainEqual( + expect.stringMatching(/^worker-nested-worker\.\w+\.js\.map$/) + ) + expect(files).toContainEqual( + expect.stringMatching(/^sub-worker\.\w+\.js\.map$/) + ) + + // sourcemap should exist and have a data URL + expect(indexSourcemap).toBe(null) + expect(workerSourcemap).toBe(null) + expect(sharedWorkerSourcemap).toBe(null) + expect(possibleTsOutputWorkerSourcemap).toBe(null) + expect(workerNestedWorkerSourcemap).toBe(null) + expect(subWorkerSourcemap).toBe(null) + + // worker should have all imports resolved and no exports + expect(workerContent).not.toMatch(`import`) + expect(workerContent).not.toMatch(`export`) + + // shared worker should have all imports resolved and no exports + expect(sharedWorkerContent).not.toMatch(`import`) + expect(sharedWorkerContent).not.toMatch(`export`) + + // chunk + expect(content).toMatch( + `new Worker("/iife-sourcemap-hidden/assets/my-worker` + ) + expect(content).toMatch(`new Worker("data:application/javascript;base64`) + expect(content).toMatch( + `new Worker("/iife-sourcemap-hidden/assets/possible-ts-output-worker` + ) + expect(content).toMatch( + `new Worker("/iife-sourcemap-hidden/assets/worker-nested-worker` + ) + expect(content).toMatch( + `new SharedWorker("/iife-sourcemap-hidden/assets/my-shared-worker` + ) + + // inlined + expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`) + expect(content).toMatch(`window.Blob`) + + expect(workerNestedWorkerContent).toMatch( + `new Worker("/iife-sourcemap-hidden/assets/sub-worker` + ) + }) +} else { + // Workaround so that testing serve does not emit + // "Your test suite must contain at least one test" + test('true', () => { + expect(true).toBe(true) + }) +} + +function getSourceMapUrl(code: string): string { + const regex = /\/\/[#@]\s(?:source(?:Mapping)?URL)=\s*(\S+)/g + const results = regex.exec(code) + + if (results && results.length >= 2) { + return results[1] + } + return null +} diff --git a/packages/playground/worker/__tests__/sourcemap-hidden/vite.config.js b/packages/playground/worker/__tests__/sourcemap-hidden/vite.config.js new file mode 100644 index 00000000000000..d51907577e9deb --- /dev/null +++ b/packages/playground/worker/__tests__/sourcemap-hidden/vite.config.js @@ -0,0 +1 @@ +module.exports = require('../../vite.config-sourcemap')('hidden') diff --git a/packages/playground/worker/__tests__/sourcemap-inline/sourcemap-inline-worker.spec.ts b/packages/playground/worker/__tests__/sourcemap-inline/sourcemap-inline-worker.spec.ts new file mode 100644 index 00000000000000..ceda7dae1fec7c --- /dev/null +++ b/packages/playground/worker/__tests__/sourcemap-inline/sourcemap-inline-worker.spec.ts @@ -0,0 +1,112 @@ +import fs from 'fs' +import path from 'path' +import { untilUpdated, isBuild, testDir } from '../../../testUtils' +import { Page } from 'playwright-chromium' + +if (isBuild) { + const assetsDir = path.resolve(testDir, 'dist/iife-sourcemap-inline/assets') + // assert correct files + test('sourcemap generation for web workers', async () => { + const files = fs.readdirSync(assetsDir) + // should have 2 worker chunk + expect(files.length).toBe(13) + const index = files.find((f) => f.includes('main-module')) + const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') + const indexSourcemap = getSourceMapUrl(content) + const worker = files.find((f) => /^my-worker\.\w+\.js$/.test(f)) + const workerContent = fs.readFileSync( + path.resolve(assetsDir, worker), + 'utf-8' + ) + const workerSourcemap = getSourceMapUrl(workerContent) + const sharedWorker = files.find((f) => + /^my-shared-worker\.\w+\.js$/.test(f) + ) + const sharedWorkerContent = fs.readFileSync( + path.resolve(assetsDir, sharedWorker), + 'utf-8' + ) + const sharedWorkerSourcemap = getSourceMapUrl(sharedWorkerContent) + const possibleTsOutputWorker = files.find((f) => + /^possible-ts-output-worker\.\w+\.js$/.test(f) + ) + const possibleTsOutputWorkerContent = fs.readFileSync( + path.resolve(assetsDir, possibleTsOutputWorker), + 'utf-8' + ) + const possibleTsOutputWorkerSourcemap = getSourceMapUrl( + possibleTsOutputWorkerContent + ) + const workerNestedWorker = files.find((f) => + /^worker-nested-worker\.\w+\.js$/.test(f) + ) + const workerNestedWorkerContent = fs.readFileSync( + path.resolve(assetsDir, workerNestedWorker), + 'utf-8' + ) + const workerNestedWorkerSourcemap = getSourceMapUrl( + workerNestedWorkerContent + ) + const subWorker = files.find((f) => /^sub-worker\.\w+\.js$/.test(f)) + const subWorkerContent = fs.readFileSync( + path.resolve(assetsDir, subWorker), + 'utf-8' + ) + const subWorkerSourcemap = getSourceMapUrl(subWorkerContent) + + // sourcemap should exist and have a data URL + expect(indexSourcemap).toMatch(/^data:/) + expect(workerSourcemap).toMatch(/^data:/) + expect(sharedWorkerSourcemap).toMatch(/^data:/) + expect(possibleTsOutputWorkerSourcemap).toMatch(/^data:/) + expect(workerNestedWorkerSourcemap).toMatch(/^data:/) + expect(subWorkerSourcemap).toMatch(/^data:/) + + // worker should have all imports resolved and no exports + expect(workerContent).not.toMatch(`import`) + expect(workerContent).not.toMatch(`export`) + + // shared worker should have all imports resolved and no exports + expect(sharedWorkerContent).not.toMatch(`import`) + expect(sharedWorkerContent).not.toMatch(`export`) + + // chunk + expect(content).toMatch( + `new Worker("/iife-sourcemap-inline/assets/my-worker` + ) + expect(content).toMatch(`new Worker("data:application/javascript;base64`) + expect(content).toMatch( + `new Worker("/iife-sourcemap-inline/assets/possible-ts-output-worker` + ) + expect(content).toMatch( + `new Worker("/iife-sourcemap-inline/assets/worker-nested-worker` + ) + expect(content).toMatch( + `new SharedWorker("/iife-sourcemap-inline/assets/my-shared-worker` + ) + + // inlined + expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`) + expect(content).toMatch(`window.Blob`) + + expect(workerNestedWorkerContent).toMatch( + `new Worker("/iife-sourcemap-inline/assets/sub-worker` + ) + }) +} else { + // Workaround so that testing serve does not emit + // "Your test suite must contain at least one test" + test('true', () => { + expect(true).toBe(true) + }) +} + +function getSourceMapUrl(code: string): string { + const regex = /\/\/[#@]\s(?:source(?:Mapping)?URL)=\s*(\S+)/g + const results = regex.exec(code) + + if (results && results.length >= 2) { + return results[1] + } + return null +} diff --git a/packages/playground/worker/__tests__/sourcemap-inline/vite.config.js b/packages/playground/worker/__tests__/sourcemap-inline/vite.config.js new file mode 100644 index 00000000000000..abe37cd56accd6 --- /dev/null +++ b/packages/playground/worker/__tests__/sourcemap-inline/vite.config.js @@ -0,0 +1 @@ +module.exports = require('../../vite.config-sourcemap')('inline') diff --git a/packages/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts b/packages/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts new file mode 100644 index 00000000000000..54e4f1cb9f2d58 --- /dev/null +++ b/packages/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts @@ -0,0 +1,131 @@ +import fs from 'fs' +import path from 'path' +import { untilUpdated, isBuild, testDir } from '../../../testUtils' +import { Page } from 'playwright-chromium' + +if (isBuild) { + const assetsDir = path.resolve(testDir, 'dist/iife-sourcemap/assets') + // assert correct files + test('sourcemap generation for web workers', async () => { + const files = fs.readdirSync(assetsDir) + // should have 2 worker chunk + expect(files.length).toBe(25) + const index = files.find((f) => f.includes('main-module')) + const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') + const indexSourcemap = getSourceMapUrl(content) + const worker = files.find((f) => /^my-worker\.\w+\.js$/.test(f)) + const workerContent = fs.readFileSync( + path.resolve(assetsDir, worker), + 'utf-8' + ) + const workerSourcemap = getSourceMapUrl(workerContent) + const sharedWorker = files.find((f) => + /^my-shared-worker\.\w+\.js$/.test(f) + ) + const sharedWorkerContent = fs.readFileSync( + path.resolve(assetsDir, sharedWorker), + 'utf-8' + ) + const sharedWorkerSourcemap = getSourceMapUrl(sharedWorkerContent) + const possibleTsOutputWorker = files.find((f) => + /^possible-ts-output-worker\.\w+\.js$/.test(f) + ) + const possibleTsOutputWorkerContent = fs.readFileSync( + path.resolve(assetsDir, possibleTsOutputWorker), + 'utf-8' + ) + const possibleTsOutputWorkerSourcemap = getSourceMapUrl( + possibleTsOutputWorkerContent + ) + const workerNestedWorker = files.find((f) => + /^worker-nested-worker\.\w+\.js$/.test(f) + ) + const workerNestedWorkerContent = fs.readFileSync( + path.resolve(assetsDir, workerNestedWorker), + 'utf-8' + ) + const workerNestedWorkerSourcemap = getSourceMapUrl( + workerNestedWorkerContent + ) + const subWorker = files.find((f) => /^sub-worker\.\w+\.js$/.test(f)) + const subWorkerContent = fs.readFileSync( + path.resolve(assetsDir, subWorker), + 'utf-8' + ) + const subWorkerSourcemap = getSourceMapUrl(subWorkerContent) + + expect(files).toContainEqual(expect.stringMatching(/^index\.\w+\.js\.map$/)) + expect(files).toContainEqual( + expect.stringMatching(/^my-worker\.\w+\.js\.map$/) + ) + expect(files).toContainEqual( + expect.stringMatching(/^my-shared-worker\.\w+\.js\.map$/) + ) + expect(files).toContainEqual( + expect.stringMatching(/^possible-ts-output-worker\.\w+\.js\.map$/) + ) + expect(files).toContainEqual( + expect.stringMatching(/^worker-nested-worker\.\w+\.js\.map$/) + ) + expect(files).toContainEqual( + expect.stringMatching(/^sub-worker\.\w+\.js\.map$/) + ) + + // sourcemap should exist and have a data URL + expect(indexSourcemap).toMatch(/^main-module\.\w+\.js\.map$/) + expect(workerSourcemap).toMatch(/^my-worker\.\w+\.js\.map$/) + expect(sharedWorkerSourcemap).toMatch(/^my-shared-worker\.\w+\.js\.map$/) + expect(possibleTsOutputWorkerSourcemap).toMatch( + /^possible-ts-output-worker\.\w+\.js\.map$/ + ) + expect(workerNestedWorkerSourcemap).toMatch( + /^worker-nested-worker\.\w+\.js\.map$/ + ) + expect(subWorkerSourcemap).toMatch(/^sub-worker\.\w+\.js\.map$/) + + // worker should have all imports resolved and no exports + expect(workerContent).not.toMatch(`import`) + expect(workerContent).not.toMatch(`export`) + + // shared worker should have all imports resolved and no exports + expect(sharedWorkerContent).not.toMatch(`import`) + expect(sharedWorkerContent).not.toMatch(`export`) + + // chunk + expect(content).toMatch(`new Worker("/iife-sourcemap/assets/my-worker`) + expect(content).toMatch(`new Worker("data:application/javascript;base64`) + expect(content).toMatch( + `new Worker("/iife-sourcemap/assets/possible-ts-output-worker` + ) + expect(content).toMatch( + `new Worker("/iife-sourcemap/assets/worker-nested-worker` + ) + expect(content).toMatch( + `new SharedWorker("/iife-sourcemap/assets/my-shared-worker` + ) + + // inlined + expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`) + expect(content).toMatch(`window.Blob`) + + expect(workerNestedWorkerContent).toMatch( + `new Worker("/iife-sourcemap/assets/sub-worker` + ) + }) +} else { + // Workaround so that testing serve does not emit + // "Your test suite must contain at least one test" + test('true', () => { + expect(true).toBe(true) + }) +} + +function getSourceMapUrl(code: string): string { + const regex = /\/\/[#@]\s(?:source(?:Mapping)?URL)=\s*(\S+)/g + const results = regex.exec(code) + + if (results && results.length >= 2) { + return results[1] + } + return null +} diff --git a/packages/playground/worker/__tests__/sourcemap/vite.config.js b/packages/playground/worker/__tests__/sourcemap/vite.config.js new file mode 100644 index 00000000000000..7d3aeeeb774e18 --- /dev/null +++ b/packages/playground/worker/__tests__/sourcemap/vite.config.js @@ -0,0 +1 @@ +module.exports = require('../../vite.config-sourcemap')(true) diff --git a/packages/playground/worker/newUrl/classic-shared-worker.js b/packages/playground/worker/classic-shared-worker.js similarity index 58% rename from packages/playground/worker/newUrl/classic-shared-worker.js rename to packages/playground/worker/classic-shared-worker.js index 462e49dfa8847f..8bd39e194f0618 100644 --- a/packages/playground/worker/newUrl/classic-shared-worker.js +++ b/packages/playground/worker/classic-shared-worker.js @@ -1,4 +1,4 @@ -importScripts('/classic.js') +importScripts(`/${self.location.pathname.split('/')[1]}/classic.js`) self.onconnect = (event) => { const port = event.ports[0] diff --git a/packages/playground/worker/classic-worker.js b/packages/playground/worker/classic-worker.js index bb6f9c3f49fc84..0700428ee0c80b 100644 --- a/packages/playground/worker/classic-worker.js +++ b/packages/playground/worker/classic-worker.js @@ -1,29 +1,5 @@ -// prettier-ignore -function text(el, text) { - document.querySelector(el).textContent = text -} +importScripts(`/${self.location.pathname.split("/")[1]}/classic.js`) -const classicWorker = new Worker( - new URL('./newUrl/classic-worker.js', import.meta.url) /* , */ , - // test comment - -) - -classicWorker.addEventListener('message', ({ data }) => { - text('.classic-worker', data) -}) -classicWorker.postMessage('ping') - -const classicSharedWorker = new SharedWorker( - new URL('./newUrl/classic-shared-worker.js', import.meta.url), - { - type: 'classic' - } -) -classicSharedWorker.port.addEventListener('message', (ev) => { - text( - '.classic-shared-worker', - ev.data - ) +self.addEventListener('message', () => { + self.postMessage(self.constant) }) -classicSharedWorker.port.start() diff --git a/packages/playground/worker/emit-chunk-dynamic-import-worker.js b/packages/playground/worker/emit-chunk-dynamic-import-worker.js new file mode 100644 index 00000000000000..f96e0b15d26497 --- /dev/null +++ b/packages/playground/worker/emit-chunk-dynamic-import-worker.js @@ -0,0 +1,3 @@ +import('./modules/module').then((module) => { + self.postMessage(module.default + import.meta.env.BASE_URL) +}) diff --git a/packages/playground/worker/emit-chunk-nested-worker.js b/packages/playground/worker/emit-chunk-nested-worker.js new file mode 100644 index 00000000000000..6cb72b9488cfaf --- /dev/null +++ b/packages/playground/worker/emit-chunk-nested-worker.js @@ -0,0 +1,28 @@ +import SubWorker from './emit-chunk-sub-worker?worker' +const subWorker = new SubWorker() + +subWorker.onmessage = (event) => { + self.postMessage({ + type: 'emit-chunk-sub-worker', + data: event.data + }) +} + +const moduleWorker = new Worker( + new URL('./module-and-worker.js', import.meta.url), + { type: 'module' } +) + +moduleWorker.onmessage = (event) => { + self.postMessage({ + type: 'module-and-worker:worker', + data: event.data + }) +} + +import('./module-and-worker').then((res) => { + self.postMessage({ + type: 'module-and-worker:module', + data: res.module + }) +}) diff --git a/packages/playground/worker/emit-chunk-sub-worker.js b/packages/playground/worker/emit-chunk-sub-worker.js new file mode 100644 index 00000000000000..5d20becc781dd7 --- /dev/null +++ b/packages/playground/worker/emit-chunk-sub-worker.js @@ -0,0 +1,8 @@ +Promise.all([ + import('./module-and-worker'), + import('./modules/module2'), + import('./modules/module3') +]).then((data) => { + const _data = { ...data[0], ...data[1], ...data[2] } + self.postMessage(_data) +}) diff --git a/packages/playground/worker/index.html b/packages/playground/worker/index.html index b3525da299ff5a..602aa3d06bfcac 100644 --- a/packages/playground/worker/index.html +++ b/packages/playground/worker/index.html @@ -1,3 +1,9 @@ +

worker template error match:

+ + const worker = new Worker(new URL('./worker.js', import.meta.url)) + + +

format iife:

Expected values:
@@ -20,113 +26,67 @@ 0
-

new Worker(new Url('path', import.meta.url), { type: 'module' })

-
- -

new SharedWorker(new Url('path', import.meta.url), { type: 'module' })

-
- -

nested worker

-
-
-

new Worker(new Url('path', import.meta.url))

-
- -

new Worker(new Url('path', import.meta.url), { type: 'classic' })

-
- - + .classname { + color: green; + } + + diff --git a/packages/playground/worker/module-and-worker.js b/packages/playground/worker/module-and-worker.js new file mode 100644 index 00000000000000..659e556f08e4a6 --- /dev/null +++ b/packages/playground/worker/module-and-worker.js @@ -0,0 +1,5 @@ +import constant from './modules/module' + +self.postMessage(constant) + +export const module = 'module and worker' diff --git a/packages/playground/worker/newUrl/module.js b/packages/playground/worker/modules/module.js similarity index 100% rename from packages/playground/worker/newUrl/module.js rename to packages/playground/worker/modules/module.js diff --git a/packages/playground/worker/modules/module1.js b/packages/playground/worker/modules/module1.js new file mode 100644 index 00000000000000..191db09d29c44f --- /dev/null +++ b/packages/playground/worker/modules/module1.js @@ -0,0 +1 @@ +export const msg1 = 'module1' diff --git a/packages/playground/worker/modules/module2.js b/packages/playground/worker/modules/module2.js new file mode 100644 index 00000000000000..60447933b8b16e --- /dev/null +++ b/packages/playground/worker/modules/module2.js @@ -0,0 +1,3 @@ +export * from './module' +export * from './module1' +export const msg2 = 'module2' diff --git a/packages/playground/worker/modules/module3.js b/packages/playground/worker/modules/module3.js new file mode 100644 index 00000000000000..33355423bc030e --- /dev/null +++ b/packages/playground/worker/modules/module3.js @@ -0,0 +1,2 @@ +export * from './module' +export const msg3 = 'module3' diff --git a/packages/playground/worker/test-plugin.tsx b/packages/playground/worker/modules/test-plugin.tsx similarity index 100% rename from packages/playground/worker/test-plugin.tsx rename to packages/playground/worker/modules/test-plugin.tsx diff --git a/packages/playground/worker/workerImport.js b/packages/playground/worker/modules/workerImport.js similarity index 100% rename from packages/playground/worker/workerImport.js rename to packages/playground/worker/modules/workerImport.js diff --git a/packages/playground/worker/my-worker.ts b/packages/playground/worker/my-worker.ts index 550382be72c331..dd6061885128c7 100644 --- a/packages/playground/worker/my-worker.ts +++ b/packages/playground/worker/my-worker.ts @@ -1,5 +1,5 @@ -import { msg, mode } from './workerImport' -import { bundleWithPlugin } from './test-plugin' +import { msg, mode } from './modules/workerImport' +import { bundleWithPlugin } from './modules/test-plugin' self.onmessage = (e) => { if (e.data === 'ping') { diff --git a/packages/playground/worker/newUrl/classic-worker.js b/packages/playground/worker/newUrl/classic-worker.js deleted file mode 100644 index 865810c76fbf85..00000000000000 --- a/packages/playground/worker/newUrl/classic-worker.js +++ /dev/null @@ -1,5 +0,0 @@ -importScripts('/classic.js') - -self.addEventListener('message', () => { - self.postMessage(self.constant) -}) diff --git a/packages/playground/worker/newUrl/url-worker.js b/packages/playground/worker/newUrl/url-worker.js deleted file mode 100644 index afd91bfe613dc2..00000000000000 --- a/packages/playground/worker/newUrl/url-worker.js +++ /dev/null @@ -1,3 +0,0 @@ -import constant from './module' - -self.postMessage(constant) diff --git a/packages/playground/worker/package.json b/packages/playground/worker/package.json index 131df8c4cbf336..ba4e7fe87e4a98 100644 --- a/packages/playground/worker/package.json +++ b/packages/playground/worker/package.json @@ -5,8 +5,20 @@ "scripts": { "dev": "vite", "build": "vite build", - "debug": "node --inspect-brk ../../vite/bin/vite", - "preview": "vite preview" + "preview": "vite preview", + "dev:es": "vite --config ./vite.config-es.js dev", + "build:es": "vite --config ./vite.config-es.js build", + "preview:es": "vite --config ./vite.config-es.js preview", + "dev:sourcemap": "cross-env WORKER_MODE=sourcemap vite --config ./vite.config-sourcemap.js dev", + "build:sourcemap": "cross-env WORKER_MODE=sourcemap vite --config ./vite.config-sourcemap.js build", + "preview:sourcemap": "cross-env WORKER_MODE=sourcemap vite --config ./vite.config-sourcemap.js preview", + "dev:sourcemap-hidden": "cross-env WORKER_MODE=hidden vite --config ./vite.config-sourcemap.js dev", + "build:sourcemap-hidden": "cross-env WORKER_MODE=hidden vite --config ./vite.config-sourcemap.js build", + "preview:sourcemap-hidden": "cross-env WORKER_MODE=hidden vite --config ./vite.config-sourcemap.js preview", + "dev:sourcemap-inline": "cross-env WORKER_MODE=inline vite --config ./vite.config-sourcemap.js dev", + "build:sourcemap-inline": "cross-env WORKER_MODE=inline vite --config ./vite.config-sourcemap.js build", + "preview:sourcemap-inline": "cross-env WORKER_MODE=inline vite --config ./vite.config-sourcemap.js preview", + "debug": "node --inspect-brk ../../vite/bin/vite" }, "devDependencies": { "@vitejs/plugin-vue-jsx": "workspace:*" diff --git a/packages/playground/worker/possible-ts-output-worker.mjs b/packages/playground/worker/possible-ts-output-worker.mjs index 2bcce3faa8a50e..25f1a447617cd9 100644 --- a/packages/playground/worker/possible-ts-output-worker.mjs +++ b/packages/playground/worker/possible-ts-output-worker.mjs @@ -1,4 +1,4 @@ -import { msg, mode } from './workerImport' +import { msg, mode } from './modules/workerImport' self.onmessage = (e) => { if (e.data === 'ping') { diff --git a/packages/playground/worker/sub-worker.js b/packages/playground/worker/sub-worker.js index ab64b3667099bb..eff49dfbb46ba6 100644 --- a/packages/playground/worker/sub-worker.js +++ b/packages/playground/worker/sub-worker.js @@ -1,13 +1,5 @@ self.onmessage = (event) => { if (event.data === 'ping') { - self.postMessage('pong') + self.postMessage(`pong ${import.meta.url}`) } } -const data = import('./workerImport') -data.then((data) => { - const { mode, msg } = data - self.postMessage({ - mode, - msg - }) -}) diff --git a/packages/playground/worker/newUrl/url-shared-worker.js b/packages/playground/worker/url-shared-worker.js similarity index 69% rename from packages/playground/worker/newUrl/url-shared-worker.js rename to packages/playground/worker/url-shared-worker.js index f52de169243056..3535d5c277ec84 100644 --- a/packages/playground/worker/newUrl/url-shared-worker.js +++ b/packages/playground/worker/url-shared-worker.js @@ -1,4 +1,4 @@ -import constant from './module' +import constant from './modules/module' self.onconnect = (event) => { const port = event.ports[0] diff --git a/packages/playground/worker/url-worker.js b/packages/playground/worker/url-worker.js new file mode 100644 index 00000000000000..c25cbefdff89ec --- /dev/null +++ b/packages/playground/worker/url-worker.js @@ -0,0 +1 @@ +self.postMessage('A string' + import.meta.env.BASE_URL + import.meta.url) diff --git a/packages/playground/worker/vite.config-es.js b/packages/playground/worker/vite.config-es.js new file mode 100644 index 00000000000000..a65dece2d0db21 --- /dev/null +++ b/packages/playground/worker/vite.config-es.js @@ -0,0 +1,29 @@ +const vueJsx = require('@vitejs/plugin-vue-jsx') +const vite = require('vite') +const path = require('path') + +module.exports = vite.defineConfig({ + base: '/es/', + enforce: 'pre', + worker: { + format: 'es', + plugins: [vueJsx()] + }, + build: { + outDir: 'dist/es' + }, + plugins: [ + { + name: 'resolve-format-es', + + transform(code, id) { + if (id.includes('main.js')) { + return code.replace( + `/* flag: will replace in vite config import("./format-es.js") */`, + `import("./main-format-es")` + ) + } + } + } + ] +}) diff --git a/packages/playground/worker/vite.config-sourcemap.js b/packages/playground/worker/vite.config-sourcemap.js new file mode 100644 index 00000000000000..ea1c66a33a44d7 --- /dev/null +++ b/packages/playground/worker/vite.config-sourcemap.js @@ -0,0 +1,24 @@ +const vueJsx = require('@vitejs/plugin-vue-jsx') +const vite = require('vite') + +module.exports = vite.defineConfig((sourcemap) => { + sourcemap = process.env.WORKER_MODE || sourcemap + if (sourcemap === 'sourcemap') { + sourcemap = true + } + return { + base: `/iife-${ + typeof sourcemap === 'boolean' ? 'sourcemap' : 'sourcemap-' + sourcemap + }/`, + worker: { + format: 'iife', + plugins: [vueJsx()] + }, + build: { + outDir: `dist/iife-${ + typeof sourcemap === 'boolean' ? 'sourcemap' : 'sourcemap-' + sourcemap + }/`, + sourcemap: sourcemap + } + } +}) diff --git a/packages/playground/worker/vite.config.js b/packages/playground/worker/vite.config.js new file mode 100644 index 00000000000000..b7760bc4d7a240 --- /dev/null +++ b/packages/playground/worker/vite.config.js @@ -0,0 +1,13 @@ +const vueJsx = require('@vitejs/plugin-vue-jsx') +const vite = require('vite') + +module.exports = vite.defineConfig({ + base: '/iife/', + worker: { + format: 'iife', + plugins: [vueJsx()] + }, + build: { + outDir: 'dist/iife' + } +}) diff --git a/packages/playground/worker/vite.config.ts b/packages/playground/worker/vite.config.ts deleted file mode 100644 index 6cef7d9cea0bed..00000000000000 --- a/packages/playground/worker/vite.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import vueJsx from '@vitejs/plugin-vue-jsx' -import { defineConfig } from 'vite' - -export default defineConfig({ - worker: { - format: 'es', - plugins: [vueJsx()] - } -}) diff --git a/packages/playground/worker/worker/main-classic.js b/packages/playground/worker/worker/main-classic.js new file mode 100644 index 00000000000000..4ef7776a56876e --- /dev/null +++ b/packages/playground/worker/worker/main-classic.js @@ -0,0 +1,28 @@ +// prettier-ignore +function text(el, text) { + document.querySelector(el).textContent = text +} + +let classicWorker = new Worker( + new URL('../classic-worker.js', import.meta.url) /* , */ + // test comment +) + +// just test for case: ') ... ,' mean no worker options parmas +classicWorker = new Worker(new URL('../classic-worker.js', import.meta.url)) + +classicWorker.addEventListener('message', ({ data }) => { + text('.classic-worker', JSON.stringify(data)) +}) +classicWorker.postMessage('ping') + +const classicSharedWorker = new SharedWorker( + new URL('../classic-shared-worker.js', import.meta.url), + { + type: 'classic' + } +) +classicSharedWorker.port.addEventListener('message', (ev) => { + text('.classic-shared-worker', JSON.stringify(ev.data)) +}) +classicSharedWorker.port.start() diff --git a/packages/playground/worker/worker/main-format-es.js b/packages/playground/worker/worker/main-format-es.js new file mode 100644 index 00000000000000..801c13469151a3 --- /dev/null +++ b/packages/playground/worker/worker/main-format-es.js @@ -0,0 +1,41 @@ +// run when format es +import NestedWorker from '../emit-chunk-nested-worker?worker' + +function text(el, text) { + document.querySelector(el).textContent = text +} + +text('.format-es', 'format es:') + +const nestedWorker = new NestedWorker() +const dataList = [] +nestedWorker.addEventListener('message', (ev) => { + dataList.push(ev.data) + text( + '.emti-chunk-worker', + JSON.stringify( + dataList.sort( + (a, b) => JSON.stringify(a).length - JSON.stringify(b).length + ) + ) + ) +}) + +const dynamicImportWorker = new Worker( + new URL('../emit-chunk-dynamic-import-worker.js', import.meta.url), + { + type: 'module' + } +) +dynamicImportWorker.addEventListener('message', (ev) => { + text('.emti-chunk-dynamic-import-worker', JSON.stringify(ev.data)) +}) + +const moduleWorker = new Worker( + new URL('../module-and-worker.js', import.meta.url), + { type: 'module' } +) + +moduleWorker.addEventListener('message', (ev) => { + text('.module-and-worker-worker', JSON.stringify(ev.data)) +}) diff --git a/packages/playground/worker/worker/main-module.js b/packages/playground/worker/worker/main-module.js new file mode 100644 index 00000000000000..417cf1728c4b09 --- /dev/null +++ b/packages/playground/worker/worker/main-module.js @@ -0,0 +1,85 @@ +import myWorker from '../my-worker?worker' +import InlineWorker from '../my-worker?worker&inline' +import mySharedWorker from '../my-shared-worker?sharedworker&name=shared' +import TSOutputWorker from '../possible-ts-output-worker?worker' +import NestedWorker from '../worker-nested-worker?worker' +import { mode } from '../modules/workerImport' + +function text(el, text) { + document.querySelector(el).textContent = text +} + +document.querySelector('.mode-true').textContent = mode + +const worker = new myWorker() +worker.addEventListener('message', (e) => { + text('.pong', e.data.msg) + text('.mode', e.data.mode) + text('.bundle-with-plugin', e.data.bundleWithPlugin) +}) + +document.querySelector('.ping').addEventListener('click', () => { + worker.postMessage('ping') +}) + +const inlineWorker = new InlineWorker() +inlineWorker.addEventListener('message', (e) => { + text('.pong-inline', e.data.msg) +}) + +document.querySelector('.ping-inline').addEventListener('click', () => { + console.log('111') + inlineWorker.postMessage('ping') +}) + +const sharedWorker = new mySharedWorker() +document.querySelector('.tick-shared').addEventListener('click', () => { + sharedWorker.port.postMessage('tick') +}) + +sharedWorker.port.addEventListener('message', (event) => { + text('.tick-count', event.data) +}) + +sharedWorker.port.start() + +const tsOutputWorker = new TSOutputWorker() +tsOutputWorker.addEventListener('message', (e) => { + text('.pong-ts-output', e.data.msg) +}) + +document.querySelector('.ping-ts-output').addEventListener('click', () => { + tsOutputWorker.postMessage('ping') +}) + +const nestedWorker = new NestedWorker() +nestedWorker.addEventListener('message', (ev) => { + if (typeof ev.data === 'string') { + text('.nested-worker', JSON.stringify(ev.data)) + } +}) +nestedWorker.postMessage('ping') + +const workerOptions = { type: 'module' } +// url import worker +const w = new Worker( + new URL('../url-worker.js', import.meta.url), + /* @vite-ignore */ workerOptions +) +w.addEventListener('message', (ev) => + text('.worker-import-meta-url', JSON.stringify(ev.data)) +) + +const genWorkerName = () => 'module' +const w2 = new SharedWorker( + new URL('../url-shared-worker.js', import.meta.url), + { + /* @vite-ignore */ + name: genWorkerName(), + type: 'module' + } +) +w2.port.addEventListener('message', (ev) => { + text('.shared-worker-import-meta-url', JSON.stringify(ev.data)) +}) +w2.port.start() diff --git a/packages/playground/worker/worker/main.js b/packages/playground/worker/worker/main.js new file mode 100644 index 00000000000000..953b5ef1bf3b53 --- /dev/null +++ b/packages/playground/worker/worker/main.js @@ -0,0 +1,3 @@ +/* flag: will replace in vite config import("./format-es.js") */ +import('./main-module') +import('./main-classic') diff --git a/packages/plugin-legacy/CHANGELOG.md b/packages/plugin-legacy/CHANGELOG.md index e92ca1e12357fe..ced87d2efd665c 100644 --- a/packages/plugin-legacy/CHANGELOG.md +++ b/packages/plugin-legacy/CHANGELOG.md @@ -1,3 +1,17 @@ +## 1.8.0 (2022-03-30) + +* fix(deps): update all non-major dependencies (#6782) ([e38be3e](https://github.com/vitejs/vite/commit/e38be3e)), closes [#6782](https://github.com/vitejs/vite/issues/6782) +* fix(deps): update all non-major dependencies (#7392) ([b63fc3b](https://github.com/vitejs/vite/commit/b63fc3b)), closes [#7392](https://github.com/vitejs/vite/issues/7392) +* fix(plugin-legacy): always fallback legacy build when CSP (#6535) ([a118a1d](https://github.com/vitejs/vite/commit/a118a1d)), closes [#6535](https://github.com/vitejs/vite/issues/6535) +* fix(plugin-legacy): polyfill latest features (#7514) ([cb388e2](https://github.com/vitejs/vite/commit/cb388e2)), closes [#7514](https://github.com/vitejs/vite/issues/7514) +* fix(plugin-legacy): require Vite 2.8.0 (#6272) (#6869) ([997b8f1](https://github.com/vitejs/vite/commit/997b8f1)), closes [#6272](https://github.com/vitejs/vite/issues/6272) [#6869](https://github.com/vitejs/vite/issues/6869) +* chore(deps): update all non-major dependencies (#6905) ([839665c](https://github.com/vitejs/vite/commit/839665c)), closes [#6905](https://github.com/vitejs/vite/issues/6905) +* docs(vite-legacy): Note about using `regenerator-runtime` in Content Security Policy environment (#7 ([0fd6422](https://github.com/vitejs/vite/commit/0fd6422)), closes [#7234](https://github.com/vitejs/vite/issues/7234) +* workflow: separate version bumping and publishing on release (#6879) ([fe8ef39](https://github.com/vitejs/vite/commit/fe8ef39)), closes [#6879](https://github.com/vitejs/vite/issues/6879) +* release: plugin-legacy@1.7.1 ([19a58dd](https://github.com/vitejs/vite/commit/19a58dd)) + + + ## [1.7.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.7.0...plugin-legacy@1.7.1) (2022-02-11) ### Bug Fixes diff --git a/packages/plugin-legacy/README.md b/packages/plugin-legacy/README.md index ec7e630c87755a..36da971c6a17c2 100644 --- a/packages/plugin-legacy/README.md +++ b/packages/plugin-legacy/README.md @@ -1,8 +1,6 @@ # @vitejs/plugin-legacy [![npm](https://img.shields.io/npm/v/@vitejs/plugin-legacy.svg)](https://npmjs.com/package/@vitejs/plugin-legacy) -**Note: this plugin requires `vite@^2.0.0`**. - -Vite's default browser support baseline is [Native ESM](https://caniuse.com/es6-module). This plugin provides support for legacy browsers that do not support native ESM. +Vite's default browser support baseline is [Native ESM](https://caniuse.com/es6-module). This plugin provides support for legacy browsers that do not support native ESM when building for production. By default, this plugin will: diff --git a/packages/plugin-legacy/index.js b/packages/plugin-legacy/index.js index 626b48f85ea955..41f7157ebfc533 100644 --- a/packages/plugin-legacy/index.js +++ b/packages/plugin-legacy/index.js @@ -330,7 +330,10 @@ function viteLegacyPlugin(options = {}) { loose: false, useBuiltIns: needPolyfills ? 'usage' : false, corejs: needPolyfills - ? { version: 3, proposals: false } + ? { + version: require('core-js/package.json').version, + proposals: false + } : undefined, shippedProposals: true, ignoreBrowserslistConfig: options.ignoreBrowserslistConfig diff --git a/packages/plugin-legacy/package.json b/packages/plugin-legacy/package.json index 6f3e44cc3e9fdd..aa8c478e4dae18 100644 --- a/packages/plugin-legacy/package.json +++ b/packages/plugin-legacy/package.json @@ -1,6 +1,6 @@ { "name": "@vitejs/plugin-legacy", - "version": "1.7.1", + "version": "1.8.0", "license": "MIT", "author": "Evan You", "files": [ diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index b1d1b8bddba63e..c05c0989d6beb4 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.3.0 (2022-03-30) + +* feat(plugin-react): adding jsxPure option (#7088) ([d451435](https://github.com/vitejs/vite/commit/d451435)), closes [#7088](https://github.com/vitejs/vite/issues/7088) +* fix(deps): update all non-major dependencies (#6782) ([e38be3e](https://github.com/vitejs/vite/commit/e38be3e)), closes [#6782](https://github.com/vitejs/vite/issues/6782) +* fix(deps): update all non-major dependencies (#7392) ([b63fc3b](https://github.com/vitejs/vite/commit/b63fc3b)), closes [#7392](https://github.com/vitejs/vite/issues/7392) +* chore: fix publish, build vite before plugin-react and plugin-vue (#6988) ([620a9bd](https://github.com/vitejs/vite/commit/620a9bd)), closes [#6988](https://github.com/vitejs/vite/issues/6988) +* chore(deps): update all non-major dependencies (#6905) ([839665c](https://github.com/vitejs/vite/commit/839665c)), closes [#6905](https://github.com/vitejs/vite/issues/6905) +* workflow: separate version bumping and publishing on release (#6879) ([fe8ef39](https://github.com/vitejs/vite/commit/fe8ef39)), closes [#6879](https://github.com/vitejs/vite/issues/6879) + + + # [1.2.0](https://github.com/vitejs/vite/compare/plugin-react@1.1.4...plugin-react@1.2.0) (2022-02-09) diff --git a/packages/plugin-react/README.md b/packages/plugin-react/README.md index 588f947ec8b08c..e8d27d0e57242b 100644 --- a/packages/plugin-react/README.md +++ b/packages/plugin-react/README.md @@ -75,7 +75,7 @@ react({ }) ``` -This option does not enable _code transformation_. That is handled by ESBuild. +This option does not enable _code transformation_. That is handled by esbuild. **Note:** TypeScript syntax is handled automatically. diff --git a/packages/plugin-react/package.json b/packages/plugin-react/package.json index f182a7f5a2fb10..e0230a4a61cd30 100644 --- a/packages/plugin-react/package.json +++ b/packages/plugin-react/package.json @@ -1,6 +1,6 @@ { "name": "@vitejs/plugin-react", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "author": "Evan You", "contributors": [ @@ -39,7 +39,7 @@ "@babel/plugin-transform-react-jsx-self": "^7.16.7", "@babel/plugin-transform-react-jsx-source": "^7.16.7", "@rollup/pluginutils": "^4.2.0", - "react-refresh": "^0.11.0", + "react-refresh": "^0.12.0", "resolve": "^1.22.0" } } diff --git a/packages/plugin-react/src/fast-refresh.ts b/packages/plugin-react/src/fast-refresh.ts index 70562bbbdfc5b7..4672e26f6264e3 100644 --- a/packages/plugin-react/src/fast-refresh.ts +++ b/packages/plugin-react/src/fast-refresh.ts @@ -1,10 +1,15 @@ import type { types as t } from '@babel/core' import fs from 'fs' +import path from 'path' export const runtimePublicPath = '/@react-refresh' -const runtimeFilePath = require.resolve( - 'react-refresh/cjs/react-refresh-runtime.development.js' +const reactRefreshDir = path.dirname( + require.resolve('react-refresh/package.json') +) +const runtimeFilePath = path.join( + reactRefreshDir, + 'cjs/react-refresh-runtime.development.js' ) export const runtimeCode = ` diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index afa1e2c5422da2..cefab59d94fe53 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -32,6 +32,12 @@ export interface Options { * @default "react" */ jsxImportSource?: string + /** + * Set this to `true` to annotate the JSX factory with `\/* @__PURE__ *\/`. + * This option is ignored when `jsxRuntime` is not `"automatic"`. + * @default true + */ + jsxPure?: boolean /** * Babel configuration applied in both dev and prod. @@ -170,7 +176,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] { if (isReactModule && filter(id)) { useFastRefresh = true plugins.push([ - await loadPlugin('react-refresh/babel.js'), + await loadPlugin('react-refresh/babel'), { skipEnvCheck: true } ]) } @@ -195,7 +201,8 @@ export default function viteReact(opts: Options = {}): PluginOption[] { ), { runtime: 'automatic', - importSource: opts.jsxImportSource + importSource: opts.jsxImportSource, + pure: opts.jsxPure !== false } ]) diff --git a/packages/plugin-vue-jsx/CHANGELOG.md b/packages/plugin-vue-jsx/CHANGELOG.md index 43632e9a88ade8..3a1b9e681b45ea 100644 --- a/packages/plugin-vue-jsx/CHANGELOG.md +++ b/packages/plugin-vue-jsx/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.3.9 (2022-03-30) + +* fix(deps): update all non-major dependencies (#7392) ([b63fc3b](https://github.com/vitejs/vite/commit/b63fc3b)), closes [#7392](https://github.com/vitejs/vite/issues/7392) +* chore(deps): update all non-major dependencies (#6905) ([839665c](https://github.com/vitejs/vite/commit/839665c)), closes [#6905](https://github.com/vitejs/vite/issues/6905) + + + ## [1.3.8](https://github.com/vitejs/vite/compare/plugin-vue@2.2.4...plugin-vue@1.3.8) (2022-02-28) diff --git a/packages/plugin-vue-jsx/package.json b/packages/plugin-vue-jsx/package.json index ed807955ed0c87..c6a352eccc99a0 100644 --- a/packages/plugin-vue-jsx/package.json +++ b/packages/plugin-vue-jsx/package.json @@ -1,6 +1,6 @@ { "name": "@vitejs/plugin-vue-jsx", - "version": "1.3.8", + "version": "1.3.9", "license": "MIT", "author": "Evan You", "files": [ diff --git a/packages/plugin-vue/CHANGELOG.md b/packages/plugin-vue/CHANGELOG.md index 6fb47f160b1689..135daffbfaaa67 100644 --- a/packages/plugin-vue/CHANGELOG.md +++ b/packages/plugin-vue/CHANGELOG.md @@ -1,3 +1,26 @@ +## 2.3.1 (2022-03-30) + +* chore(plugin-vue): revert #7527, lower vite peer dep ([447bbeb](https://github.com/vitejs/vite/commit/447bbeb)), closes [#7527](https://github.com/vitejs/vite/issues/7527) + + + +## 2.3.0 (2022-03-30) + +* chore(plugin-vue): bump vite peer dep to 2.9.0 (#7472) ([12fd1d9](https://github.com/vitejs/vite/commit/12fd1d9)), closes [#7472](https://github.com/vitejs/vite/issues/7472) +* feat(css): css.devSourcemap option (#7471) ([57f14cb](https://github.com/vitejs/vite/commit/57f14cb)), closes [#7471](https://github.com/vitejs/vite/issues/7471) +* fix(plugin-vue): respect __VUE_PROD_DEVTOOLS__ setting (#4984) ([90e812a](https://github.com/vitejs/vite/commit/90e812a)), closes [#4984](https://github.com/vitejs/vite/issues/4984) + + + +## 2.3.0-beta.0 (2022-03-22) + +* fix(deps): update all non-major dependencies (#7392) ([b63fc3b](https://github.com/vitejs/vite/commit/b63fc3b)), closes [#7392](https://github.com/vitejs/vite/issues/7392) +* feat: css sourcemap support during dev (#7173) ([38a655f](https://github.com/vitejs/vite/commit/38a655f)), closes [#7173](https://github.com/vitejs/vite/issues/7173) +* chore(deps): update all non-major dependencies (#6905) ([839665c](https://github.com/vitejs/vite/commit/839665c)), closes [#6905](https://github.com/vitejs/vite/issues/6905) +* docs(vue): add transformAssetUrls example (#7232) ([08e928c](https://github.com/vitejs/vite/commit/08e928c)), closes [#7232](https://github.com/vitejs/vite/issues/7232) + + + ## [2.2.4](https://github.com/vitejs/vite/compare/plugin-vue@2.2.3...plugin-vue@2.2.4) (2022-02-28) diff --git a/packages/plugin-vue/package.json b/packages/plugin-vue/package.json index 6eccae93df840d..39b3bc87a8b1df 100644 --- a/packages/plugin-vue/package.json +++ b/packages/plugin-vue/package.json @@ -1,6 +1,6 @@ { "name": "@vitejs/plugin-vue", - "version": "2.2.4", + "version": "2.3.1", "license": "MIT", "author": "Evan You", "files": [ diff --git a/packages/plugin-vue/src/index.ts b/packages/plugin-vue/src/index.ts index 28a15996363285..7bdf63b700fa15 100644 --- a/packages/plugin-vue/src/index.ts +++ b/packages/plugin-vue/src/index.ts @@ -63,7 +63,9 @@ export interface ResolvedOptions extends Options { compiler: typeof _compiler root: string sourceMap: boolean + cssDevSourcemap: boolean devServer?: ViteDevServer + devToolsEnabled?: boolean } export default function vuePlugin(rawOptions: Options = {}): Plugin { @@ -97,7 +99,9 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { customElement, reactivityTransform, root: process.cwd(), - sourceMap: true + sourceMap: true, + cssDevSourcemap: false, + devToolsEnabled: process.env.NODE_ENV !== 'production' } // Temporal handling for 2.7 breaking change @@ -135,7 +139,10 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { ...options, root: config.root, sourceMap: config.command === 'build' ? !!config.build.sourcemap : true, - isProduction: config.isProduction + cssDevSourcemap: config.css?.devSourcemap ?? false, + isProduction: config.isProduction, + devToolsEnabled: + !!config.define!.__VUE_PROD_DEVTOOLS__ || !config.isProduction } }, diff --git a/packages/plugin-vue/src/main.ts b/packages/plugin-vue/src/main.ts index 7fb26fd39c6c03..44b1de74721efd 100644 --- a/packages/plugin-vue/src/main.ts +++ b/packages/plugin-vue/src/main.ts @@ -27,7 +27,7 @@ export async function transformMain( ssr: boolean, asCustomElement: boolean ) { - const { devServer, isProduction } = options + const { devServer, isProduction, devToolsEnabled } = options // prev descriptor is only set and used for hmr const prevDescriptor = getPrevDescriptor(filename) @@ -102,9 +102,12 @@ export async function transformMain( if (hasScoped) { attachedProps.push([`__scopeId`, JSON.stringify(`data-v-${descriptor.id}`)]) } - if (devServer && !isProduction) { + if (devToolsEnabled || (devServer && !isProduction)) { // expose filename during serve for devtools to pickup - attachedProps.push([`__file`, JSON.stringify(filename)]) + attachedProps.push([ + `__file`, + JSON.stringify(isProduction ? path.basename(filename) : filename) + ]) } // HMR diff --git a/packages/plugin-vue/src/style.ts b/packages/plugin-vue/src/style.ts index c124f3ec744e4f..cab75791bd54ec 100644 --- a/packages/plugin-vue/src/style.ts +++ b/packages/plugin-vue/src/style.ts @@ -23,13 +23,17 @@ export async function transformStyle( isProd: options.isProduction, source: code, scoped: block.scoped, - postcssOptions: { - map: { - from: filename, - inline: false, - annotation: false - } - } + ...(options.cssDevSourcemap + ? { + postcssOptions: { + map: { + from: filename, + inline: false, + annotation: false + } + } + } + : {}) }) if (result.errors.length) { diff --git a/packages/vite/CHANGELOG.md b/packages/vite/CHANGELOG.md index 1809b04a4e8c92..0f91a004602caa 100644 --- a/packages/vite/CHANGELOG.md +++ b/packages/vite/CHANGELOG.md @@ -1,48 +1,112 @@ -## 2.9.0-beta.6 (2022-03-22) +## 2.9.1 (2022-03-31) +* fix: allow port 0 to be provided to server (#7530) ([173e4c9](https://github.com/vitejs/vite/commit/173e4c9)), closes [#7530](https://github.com/vitejs/vite/issues/7530) +* fix: brotli let for reassignment (#7544) ([d0253d7](https://github.com/vitejs/vite/commit/d0253d7)), closes [#7544](https://github.com/vitejs/vite/issues/7544) +* fix: dynamic import warning with @vite-ignore (#7533) ([29c1ec0](https://github.com/vitejs/vite/commit/29c1ec0)), closes [#7533](https://github.com/vitejs/vite/issues/7533) +* fix: remove unneeded skipping optimization log (#7531) ([41fa2f5](https://github.com/vitejs/vite/commit/41fa2f5)), closes [#7531](https://github.com/vitejs/vite/issues/7531) +* docs(changelog): fix raw glob imports syntax (#7540) ([87fbe13](https://github.com/vitejs/vite/commit/87fbe13)), closes [#7540](https://github.com/vitejs/vite/issues/7540) +* chore: 2.9 release notes (#7525) ([4324f48](https://github.com/vitejs/vite/commit/4324f48)), closes [#7525](https://github.com/vitejs/vite/issues/7525) -## 2.9.0-beta.5 (2022-03-22) +# [2.9.0](https://github.com/vitejs/vite/compare/v2.8.6...v2.9.0) (2022-03-30) -* fix: avoid mangling code from incorrect magic-string usage (#7397) ([68d76c9](https://github.com/vitejs/vite/commit/68d76c9)), closes [#7397](https://github.com/vitejs/vite/issues/7397) -* fix(config): server restart on config dependencies changed on windows (#7366) ([c43467a](https://github.com/vitejs/vite/commit/c43467a)), closes [#7366](https://github.com/vitejs/vite/issues/7366) -* fix(deps): update all non-major dependencies (#7392) ([b63fc3b](https://github.com/vitejs/vite/commit/b63fc3b)), closes [#7392](https://github.com/vitejs/vite/issues/7392) -* feat: css sourcemap support during dev (#7173) ([38a655f](https://github.com/vitejs/vite/commit/38a655f)), closes [#7173](https://github.com/vitejs/vite/issues/7173) +### Faster Cold Start +Before 2.9, the first time dev was run on a project Vite needed to perform a scan phase to discover dependencies and then pre-bundle them before starting the server. In 2.9 both scanning [#7379](https://github.com/vitejs/vite/issues/7379) and pre-bundling [#6758](https://github.com/vitejs/vite/issues/6758) of dependencies are now non-blocking, so the server starts right away during cold start. We also now allow requests to flow through the pipeline improving initial cold start load speed and increasing the chances of discovering new missing dependencies when re-processing and letting Vite populate the module graph and the browser to process files. In many cases, there is also no need to full-reload the page when new dependencies are discovered. +### CSS Sourcemap support during dev (experimental) -## 2.9.0-beta.4 (2022-03-19) +Vite now supports CSS sourcemaps [#7173](https://github.com/vitejs/vite/issues/7173). This feature is still experimental, and it is disabled by default to avoid incurring a performance penalty for users that don't need it. To enable it, set [css.devSourcemap](https://vitejs.dev/config/#css-devsourcemap) to `true`. -* fix: add version to optimized chunks, fix #7323 (#7350) ([1be1db6](https://github.com/vitejs/vite/commit/1be1db6)), closes [#7323](https://github.com/vitejs/vite/issues/7323) [#7350](https://github.com/vitejs/vite/issues/7350) -* fix: browser cache of newly discovered deps (#7378) ([392a0de](https://github.com/vitejs/vite/commit/392a0de)), closes [#7378](https://github.com/vitejs/vite/issues/7378) -* fix: do not warn (about not being able to bundle non module scripts) when src is an external url (#7 ([0646fe8](https://github.com/vitejs/vite/commit/0646fe8)), closes [#7380](https://github.com/vitejs/vite/issues/7380) -* fix: overwrite deps info browserHash only on commit (#7359) ([1e9615d](https://github.com/vitejs/vite/commit/1e9615d)), closes [#7359](https://github.com/vitejs/vite/issues/7359) -* chore: fix typo in comment (#7370) ([e682863](https://github.com/vitejs/vite/commit/e682863)), closes [#7370](https://github.com/vitejs/vite/issues/7370) -* chore: update es-module-lexer (#7357) ([fde0f3c](https://github.com/vitejs/vite/commit/fde0f3c)), closes [#7357](https://github.com/vitejs/vite/issues/7357) -* chore(deps): update all non-major dependencies (#6905) ([839665c](https://github.com/vitejs/vite/commit/839665c)), closes [#6905](https://github.com/vitejs/vite/issues/6905) +### Avoid splitting vendor chunks by default +Vite's default chunking strategy was a good fit for most SPAs, but it wasn't ideal in some other use cases. Vite doesn't have enough context to make the best decision here, so in Vite 2.9 the previous chunking strategy is now [opt-in](https://vitejs.dev/guide/build.html#chunking-strategy) [#6534](https://github.com/vitejs/vite/issues/6534) and Vite will no longer split vendor libs in a separate chunk. +### Web Workers enhancements -## 2.9.0-beta.3 (2022-03-16) +Web Workers now supports source map generation (see [#5417](https://github.com/vitejs/vite/issues/5417)). The implementation is also now more robust, fixing several issues encountered in previous versions ([#6599](https://github.com/vitejs/vite/issues/6599)). -* fix: delayed full page reload (#7347) ([fa0820a](https://github.com/vitejs/vite/commit/fa0820a)), closes [#7347](https://github.com/vitejs/vite/issues/7347) -* fix: early discovery of missing deps, fix #7333 (#7346) ([7d2f37c](https://github.com/vitejs/vite/commit/7d2f37c)), closes [#7333](https://github.com/vitejs/vite/issues/7333) [#7346](https://github.com/vitejs/vite/issues/7346) -* fix: unhandled exception on eager transformRequest (#7345) ([c3260a4](https://github.com/vitejs/vite/commit/c3260a4)), closes [#7345](https://github.com/vitejs/vite/issues/7345) -* fix: update to esbuild 0.14.27, fix #6994 (#7320) ([65aaeee](https://github.com/vitejs/vite/commit/65aaeee)), closes [#6994](https://github.com/vitejs/vite/issues/6994) [#7320](https://github.com/vitejs/vite/issues/7320) -* chore: comment typo (#7344) ([61df324](https://github.com/vitejs/vite/commit/61df324)), closes [#7344](https://github.com/vitejs/vite/issues/7344) +### Raw Glob Imports + +Glob imports support for the `raw` modifier syntax has changed to using `{ as: 'raw' }`, which works in the same way as the `?raw` suffix in regular imports: +```js +const examples = import.meta.globEager('./examples/*.html', { as: 'raw' }) +``` +The `{ assert: { type: 'raw' }}` syntax introduced in v2.8 has been deprecated. See [#7017](https://github.com/vitejs/vite/issues/7017) for more information. -## 2.9.0-beta.2 (2022-03-14) +### `envDir` changes -* fix: `ssrExternal` should not skip nested dependencies (#7154) ([f8f934a](https://github.com/vitejs/vite/commit/f8f934a)), closes [#7154](https://github.com/vitejs/vite/issues/7154) -* fix: dep with dynamic import wrong error log (#7313) ([769f535](https://github.com/vitejs/vite/commit/769f535)), closes [#7313](https://github.com/vitejs/vite/issues/7313) +The `envDir` now correctly loads `.env` files in the specified directory only (defaults to `root`). Previously, it would load files above the directory, which imposed security issues. If you had relied on the previous behaviour, make sure you move your `.env` files to the correct directory, or configure the `envDir` option. + +### New tools for Plugin and Framework Authors + +#### Client Server Communication API + +Vite now provides utilities for plugins to help handle the communication with clients connected to Vite's server [#7437](https://github.com/vitejs/vite/issues/7437). Reusing the open WebSocket connection between the server and clients several use cases can be simplified ([vite-plugin-inspect](https://github.com/antfu/vite-plugin-inspect), [SliDev](https://sli.dev), and many others). Check out the [Client Server Communication docs](https://vitejs.dev/guide/api-plugin.html#client-server-communication) for more information. + +```js +// Send a message from the client to the server +if (import.meta.hot) { + import.meta.hot.send('my:from-client', { msg: 'Hey!' }) +} +``` + +```js +// And listen to client messages in a plugin + configureServer(server) { + server.ws.on('my:from-client', (data, client) => { + console.log('Message from client:', data.msg) // Hey! + // ... + }) + } +``` + +#### `importedCss` and `importedAssets` to RenderedChunk type +Replace the internal `chunkToEmittedCssFileMap` and `chunkToEmittedAssetsMap` variables with public properties added by Vite to `RenderedChunk` objects in the `renderChunk` phase. These is useful for Vite-based frameworks that generate their own HTML. See [#6629](https://github.com/vitejs/vite/issues/6629). +#### Optimize Custom Extensions (experimental) -## 2.9.0-beta.1 (2022-03-14) +A new `optimizeDeps.extensions: string[]` option is available to enable pre-bundling of custom extensions. A respective esbuild plugin is required to handle that extension. e.g. `['.svelte', '.svelte.md']`. See [#6801](https://github.com/vitejs/vite/issues/6801) for more information. + +### Bug Fixes + +* fix: build path error on Windows (#7383) ([e3c7c7a](https://github.com/vitejs/vite/commit/e3c7c7a)), closes [#7383](https://github.com/vitejs/vite/issues/7383) +* fix: import url worker two times (#7468) ([f05a813](https://github.com/vitejs/vite/commit/f05a813)), closes [#7468](https://github.com/vitejs/vite/issues/7468) +* fix: import with query with exports/browser field (#7098) ([9ce6732](https://github.com/vitejs/vite/commit/9ce6732)), closes [#7098](https://github.com/vitejs/vite/issues/7098) +* fix: make @fs URLs work with special characters (#7510) ([2b7dad1](https://github.com/vitejs/vite/commit/2b7dad1)), closes [#7510](https://github.com/vitejs/vite/issues/7510) +* fix: tailwind css sourcemap warning (#7480) ([90df0bb](https://github.com/vitejs/vite/commit/90df0bb)), closes [#7480](https://github.com/vitejs/vite/issues/7480) +* fix: worker match only run in js (#7500) ([9481c7d](https://github.com/vitejs/vite/commit/9481c7d)), closes [#7500](https://github.com/vitejs/vite/issues/7500) +* fix: Correctly process urls when they are rewritten to contain space (#7452) ([9ee2cf6](https://github.com/vitejs/vite/commit/9ee2cf6)), closes [#7452](https://github.com/vitejs/vite/issues/7452) +* fix: custom event payload type (#7498) ([28b0660](https://github.com/vitejs/vite/commit/28b0660)), closes [#7498](https://github.com/vitejs/vite/issues/7498) +* fix: handle relative path glob raw import, fix #7307 (#7371) ([7f8dc58](https://github.com/vitejs/vite/commit/7f8dc58)), closes [#7307](https://github.com/vitejs/vite/issues/7307) [#7371](https://github.com/vitejs/vite/issues/7371) +* fix: import.meta.url in worker (#7464) ([8ac4b12](https://github.com/vitejs/vite/commit/8ac4b12)), closes [#7464](https://github.com/vitejs/vite/issues/7464) +* fix: optimizeDeps.entries default ignore paths (#7469) ([4c95e99](https://github.com/vitejs/vite/commit/4c95e99)), closes [#7469](https://github.com/vitejs/vite/issues/7469) +* fix: errors in worker handling (#7236) ([77dc1a1](https://github.com/vitejs/vite/commit/77dc1a1)), closes [#7236](https://github.com/vitejs/vite/issues/7236) +* fix: consider undefined port when checking port (#7318) ([c7fc7c3](https://github.com/vitejs/vite/commit/c7fc7c3)), closes [#7318](https://github.com/vitejs/vite/issues/7318) +* fix: inline style css sourcemap (#7434) ([47668b5](https://github.com/vitejs/vite/commit/47668b5)), closes [#7434](https://github.com/vitejs/vite/issues/7434) +* fix: sourcemap missing source files warning with cached vue (#7442) ([a2ce20d](https://github.com/vitejs/vite/commit/a2ce20d)), closes [#7442](https://github.com/vitejs/vite/issues/7442) +* fix: update tsconfck to 1.2.1 (#7424) ([a90b03b](https://github.com/vitejs/vite/commit/a90b03b)), closes [#7424](https://github.com/vitejs/vite/issues/7424) +* fix: virtual html sourcemap warning (#7440) ([476786b](https://github.com/vitejs/vite/commit/476786b)), closes [#7440](https://github.com/vitejs/vite/issues/7440) +* fix(less): empty less file error (#7412) ([0535c70](https://github.com/vitejs/vite/commit/0535c70)), closes [#7412](https://github.com/vitejs/vite/issues/7412) +* fix(resolve): skip `module` field when the importer is a `require` call (#7438) ([fe4c1ed](https://github.com/vitejs/vite/commit/fe4c1ed)), closes [#7438](https://github.com/vitejs/vite/issues/7438) +* fix: avoid mangling code from incorrect magic-string usage (#7397) ([68d76c9](https://github.com/vitejs/vite/commit/68d76c9)), closes [#7397](https://github.com/vitejs/vite/issues/7397) +* fix(config): server restart on config dependencies changed on windows (#7366) ([c43467a](https://github.com/vitejs/vite/commit/c43467a)), closes [#7366](https://github.com/vitejs/vite/issues/7366) +* fix(deps): update all non-major dependencies (#7392) ([b63fc3b](https://github.com/vitejs/vite/commit/b63fc3b)), closes [#7392](https://github.com/vitejs/vite/issues/7392) +* fix: add version to optimized chunks, fix #7323 (#7350) ([1be1db6](https://github.com/vitejs/vite/commit/1be1db6)), closes [#7323](https://github.com/vitejs/vite/issues/7323) [#7350](https://github.com/vitejs/vite/issues/7350) +* fix: browser cache of newly discovered deps (#7378) ([392a0de](https://github.com/vitejs/vite/commit/392a0de)), closes [#7378](https://github.com/vitejs/vite/issues/7378) +* fix: do not warn (about not being able to bundle non module scripts) when src is an external url (#7 ([0646fe8](https://github.com/vitejs/vite/commit/0646fe8)), closes [#7380](https://github.com/vitejs/vite/issues/7380) +* fix: overwrite deps info browserHash only on commit (#7359) ([1e9615d](https://github.com/vitejs/vite/commit/1e9615d)), closes [#7359](https://github.com/vitejs/vite/issues/7359) +* fix: delayed full page reload (#7347) ([fa0820a](https://github.com/vitejs/vite/commit/fa0820a)), closes [#7347](https://github.com/vitejs/vite/issues/7347) +* fix: early discovery of missing deps, fix #7333 (#7346) ([7d2f37c](https://github.com/vitejs/vite/commit/7d2f37c)), closes [#7333](https://github.com/vitejs/vite/issues/7333) [#7346](https://github.com/vitejs/vite/issues/7346) +* fix: unhandled exception on eager transformRequest (#7345) ([c3260a4](https://github.com/vitejs/vite/commit/c3260a4)), closes [#7345](https://github.com/vitejs/vite/issues/7345) +* fix: update to esbuild 0.14.27, fix #6994 (#7320) ([65aaeee](https://github.com/vitejs/vite/commit/65aaeee)), closes [#6994](https://github.com/vitejs/vite/issues/6994) [#7320](https://github.com/vitejs/vite/issues/7320) +* fix: `ssrExternal` should not skip nested dependencies (#7154) ([f8f934a](https://github.com/vitejs/vite/commit/f8f934a)), closes [#7154](https://github.com/vitejs/vite/issues/7154) +* fix: dep with dynamic import wrong error log (#7313) ([769f535](https://github.com/vitejs/vite/commit/769f535)), closes [#7313](https://github.com/vitejs/vite/issues/7313) * fix: avoid caching transform result of invalidated module (#7254) ([2d7ba72](https://github.com/vitejs/vite/commit/2d7ba72)), closes [#7254](https://github.com/vitejs/vite/issues/7254) * fix: dont replace define in json (#7294) ([fc5c937](https://github.com/vitejs/vite/commit/fc5c937)), closes [#7294](https://github.com/vitejs/vite/issues/7294) * fix: main browserHash after stable optimization rerun (#7284) ([98eefa8](https://github.com/vitejs/vite/commit/98eefa8)), closes [#7284](https://github.com/vitejs/vite/issues/7284) @@ -53,15 +117,6 @@ * fix: use relative paths in _metadata.json (#7299) ([8b945f5](https://github.com/vitejs/vite/commit/8b945f5)), closes [#7299](https://github.com/vitejs/vite/issues/7299) * fix(asset): allow non-existent url (#7306) ([6bc45a2](https://github.com/vitejs/vite/commit/6bc45a2)), closes [#7306](https://github.com/vitejs/vite/issues/7306) * fix(hmr): hmr style tag no support in html (#7262) ([fae120a](https://github.com/vitejs/vite/commit/fae120a)), closes [#7262](https://github.com/vitejs/vite/issues/7262) -* chore: clarify writableEnded guard comment (#7256) ([dddda1e](https://github.com/vitejs/vite/commit/dddda1e)), closes [#7256](https://github.com/vitejs/vite/issues/7256) -* chore: new line for non-existent url (#7308) ([522faf8](https://github.com/vitejs/vite/commit/522faf8)), closes [#7308](https://github.com/vitejs/vite/issues/7308) -* chore: remove unused code (#7303) ([467512b](https://github.com/vitejs/vite/commit/467512b)), closes [#7303](https://github.com/vitejs/vite/issues/7303) -* feat: expose ssrRewriteStacktrace (#7091) ([d4ae45d](https://github.com/vitejs/vite/commit/d4ae45d)), closes [#7091](https://github.com/vitejs/vite/issues/7091) - - - -## 2.9.0-beta.0 (2022-03-09) - * fix: `import.meta.url` should not throw (#7219) ([5de3a98](https://github.com/vitejs/vite/commit/5de3a98)), closes [#7219](https://github.com/vitejs/vite/issues/7219) * fix: allow `localhost` as a valid hostname (#7092) ([4194cce](https://github.com/vitejs/vite/commit/4194cce)), closes [#7092](https://github.com/vitejs/vite/issues/7092) * fix: build optimize deps metada location (#7214) ([dc46adf](https://github.com/vitejs/vite/commit/dc46adf)), closes [#7214](https://github.com/vitejs/vite/issues/7214) @@ -87,18 +142,93 @@ * fix(server): base middleware redirect with search and hash (#6574) ([a516e85](https://github.com/vitejs/vite/commit/a516e85)), closes [#6574](https://github.com/vitejs/vite/issues/6574) * fix(server): ensure consistency for url to file mapping in importAnalysis and static middleware (#65 ([b214115](https://github.com/vitejs/vite/commit/b214115)), closes [#6518](https://github.com/vitejs/vite/issues/6518) * fix(ssr): bypass missing resolve error in SSR (#7164) ([a4927c5](https://github.com/vitejs/vite/commit/a4927c5)), closes [#7164](https://github.com/vitejs/vite/issues/7164) -* chore: replace SourceMapConsumer with trace-mapping (#6746) ([ed4d191](https://github.com/vitejs/vite/commit/ed4d191)), closes [#6746](https://github.com/vitejs/vite/issues/6746) + + +### Features + +* feat(worker): Add sourcemap support for worker bundles (#5417) ([465b6b9](https://github.com/vitejs/vite/commit/465b6b9)), closes [#5417](https://github.com/vitejs/vite/issues/5417) +* feat(type): support typing for custom events (#7476) ([50a8765](https://github.com/vitejs/vite/commit/50a8765)), closes [#7476](https://github.com/vitejs/vite/issues/7476) +* refactor(types): share hot context type (#7475) ([64ddff0](https://github.com/vitejs/vite/commit/64ddff0)), closes [#7475](https://github.com/vitejs/vite/issues/7475) +* feat: support importing css with ?raw (#5796) ([fedb106](https://github.com/vitejs/vite/commit/fedb106)), closes [#5796](https://github.com/vitejs/vite/issues/5796) +* feat(css): css.devSourcemap option (#7471) ([57f14cb](https://github.com/vitejs/vite/commit/57f14cb)), closes [#7471](https://github.com/vitejs/vite/issues/7471) +* feat(dev): expose APIs for client-server communication (#7437) ([e29ea8e](https://github.com/vitejs/vite/commit/e29ea8e)), closes [#7437](https://github.com/vitejs/vite/issues/7437) +* feat: hide optimized deps found during scan phase logs (#7419) ([f4934e8](https://github.com/vitejs/vite/commit/f4934e8)), closes [#7419](https://github.com/vitejs/vite/issues/7419) +* feat: non-blocking scanning of dependencies (#7379) ([676f545](https://github.com/vitejs/vite/commit/676f545)), closes [#7379](https://github.com/vitejs/vite/issues/7379) +* feat: css sourcemap support during dev (#7173) ([38a655f](https://github.com/vitejs/vite/commit/38a655f)), closes [#7173](https://github.com/vitejs/vite/issues/7173) +* feat: expose ssrRewriteStacktrace (#7091) ([d4ae45d](https://github.com/vitejs/vite/commit/d4ae45d)), closes [#7091](https://github.com/vitejs/vite/issues/7091) * feat: add `importedCss` and `importedAssets` to RenderedChunk type (#6629) ([8d0fc90](https://github.com/vitejs/vite/commit/8d0fc90)), closes [#6629](https://github.com/vitejs/vite/issues/6629) * feat: non-blocking pre bundling of dependencies (#6758) ([24bb3e4](https://github.com/vitejs/vite/commit/24bb3e4)), closes [#6758](https://github.com/vitejs/vite/issues/6758) * feat: optimize custom extensions (#6801) ([c11af23](https://github.com/vitejs/vite/commit/c11af23)), closes [#6801](https://github.com/vitejs/vite/issues/6801) * feat: show all prebundle deps when debug (#6726) ([e626055](https://github.com/vitejs/vite/commit/e626055)), closes [#6726](https://github.com/vitejs/vite/issues/6726) -* feat(config): hmr add disable port config (#6624) ([ce07a0a](https://github.com/vitejs/vite/commit/ce07a0a)), closes [#6624](https://github.com/vitejs/vite/issues/6624) * feat(glob): import.meta.glob support alias path (#6526) ([86882ad](https://github.com/vitejs/vite/commit/86882ad)), closes [#6526](https://github.com/vitejs/vite/issues/6526) * feat(perf): tsconfck perf improvement (#7055) ([993ea39](https://github.com/vitejs/vite/commit/993ea39)), closes [#7055](https://github.com/vitejs/vite/issues/7055) * feat(worker): bundle worker emit asset file (#6599) ([0ade335](https://github.com/vitejs/vite/commit/0ade335)), closes [#6599](https://github.com/vitejs/vite/issues/6599) * refactor: avoid splitting vendor chunk by default (#6534) ([849e845](https://github.com/vitejs/vite/commit/849e845)), closes [#6534](https://github.com/vitejs/vite/issues/6534) +### Beta Changelogs + + +#### [2.9.0-beta.11](https://github.com/vitejs/vite/compare/v2.9.0-beta.10...v2.9.0-beta.11) (2022-03-29) + +See [2.9.0-beta.11 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.11/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.10](https://github.com/vitejs/vite/compare/v2.9.0-beta.9...v2.9.0-beta.10) (2022-03-28) + +See [2.9.0-beta.10 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.10/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.9](https://github.com/vitejs/vite/compare/v2.9.0-beta.8...v2.9.0-beta.9) (2022-03-26) + +See [2.9.0-beta.9 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.9/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.8](https://github.com/vitejs/vite/compare/v2.9.0-beta.7...v2.9.0-beta.8) (2022-03-24) + +See [2.9.0-beta.8 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.8/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.7](https://github.com/vitejs/vite/compare/v2.9.0-beta.6...v2.9.0-beta.7) (2022-03-23) + +See [2.9.0-beta.7 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.7/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.6](https://github.com/vitejs/vite/compare/v2.9.0-beta.5...v2.9.0-beta.6) (2022-03-22) + +See [2.9.0-beta.6 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.6/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.5](https://github.com/vitejs/vite/compare/v2.9.0-beta.4...v2.9.0-beta.5) (2022-03-22) + +See [2.9.0-beta.5 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.5/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.4](https://github.com/vitejs/vite/compare/v2.9.0-beta.3...v2.9.0-beta.4) (2022-03-19) + +See [2.9.0-beta.4 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.4/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.3](https://github.com/vitejs/vite/compare/v2.9.0-beta.2...v2.9.0-beta.3) (2022-03-16) + +See [2.9.0-beta.3 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.3/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.2](https://github.com/vitejs/vite/compare/v2.9.0-beta.1...v2.9.0-beta.2) (2022-03-14) + +See [2.9.0-beta.2 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.2/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.1](https://github.com/vitejs/vite/compare/v2.9.0-beta.0...v2.9.0-beta.1) (2022-03-14) + +See [2.9.0-beta.1 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.1/packages/vite/CHANGELOG.md) + + +#### [2.9.0-beta.0](https://github.com/vitejs/vite/compare/v2.8.6...v2.9.0-beta.0) (2022-03-09) + +See [2.9.0-beta.0 changelog](https://github.com/vitejs/vite/blob/v2.9.0-beta.0/packages/vite/CHANGELOG.md) + + ## [2.8.6](https://github.com/vitejs/vite/compare/v2.8.5...v2.8.6) (2022-03-01) @@ -198,7 +328,7 @@ ### Default preview port -New default port for `vite preview` is 4173 (avoid conflicts in MacOS that took over the 5000 port) +New default port for `vite preview` is 4173 (avoid conflicts in macOS that took over the 5000 port) ### Workers using standard syntax diff --git a/packages/vite/package.json b/packages/vite/package.json index c1aa489f340fc0..d2a351c6aa0869 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -1,6 +1,6 @@ { "name": "vite", - "version": "2.9.0-beta.6", + "version": "2.9.1", "license": "MIT", "author": "Evan You", "description": "Native-ESM powered web dev build tool", @@ -57,7 +57,7 @@ "@babel/types": "^7.17.0", "@jridgewell/trace-mapping": "^0.3.4", "@rollup/plugin-alias": "^3.1.9", - "@rollup/plugin-commonjs": "^21.0.2", + "@rollup/plugin-commonjs": "^21.0.3", "@rollup/plugin-dynamic-import-vars": "^1.4.2", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "13.1.3", @@ -74,7 +74,7 @@ "@types/node": "^16.11.26", "@types/resolve": "^1.20.1", "@types/sass": "~1.43.1", - "@types/stylus": "^0.48.36", + "@types/stylus": "^0.48.37", "@types/ws": "^8.5.3", "@vue/compiler-dom": "^3.2.31", "acorn": "^8.7.0", @@ -88,7 +88,7 @@ "debug": "^4.3.4", "dotenv": "^14.3.2", "dotenv-expand": "^5.1.0", - "es-module-lexer": "^0.10.4", + "es-module-lexer": "^0.10.5", "estree-walker": "^2.0.2", "etag": "^1.8.1", "fast-glob": "^3.2.11", @@ -96,15 +96,15 @@ "json5": "^2.2.1", "launch-editor-middleware": "^2.3.0", "magic-string": "^0.26.1", - "micromatch": "^4.0.4", + "micromatch": "^4.0.5", "mrmime": "^1.0.0", - "node-forge": "^1.3.0", + "node-forge": "^1.3.1", "okie": "^1.0.1", "open": "^8.4.0", "periscopic": "^2.0.3", "picocolors": "^1.0.0", - "postcss-import": "^14.0.2", - "postcss-load-config": "^3.1.3", + "postcss-import": "^14.1.0", + "postcss-load-config": "^3.1.4", "postcss-modules": "^4.3.1", "resolve.exports": "^1.1.0", "rollup-plugin-license": "^2.6.1", @@ -113,7 +113,7 @@ "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", "terser": "^5.12.1", - "tsconfck": "^1.2.0", + "tsconfck": "^1.2.2", "tslib": "^2.3.1", "types": "link:./types", "ws": "^8.5.0" diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index a9e8fb639de958..420d084ef9a981 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -1,12 +1,6 @@ -import type { - ErrorPayload, - FullReloadPayload, - HMRPayload, - PrunePayload, - Update, - UpdatePayload -} from 'types/hmrPayload' -import type { CustomEventName } from 'types/customEvent' +import type { ErrorPayload, HMRPayload, Update } from 'types/hmrPayload' +import type { 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' @@ -15,7 +9,7 @@ import '@vite/env' declare const __BASE__: string declare const __HMR_PROTOCOL__: string declare const __HMR_HOSTNAME__: string -declare const __HMR_PORT__: string | false +declare const __HMR_PORT__: string declare const __HMR_TIMEOUT__: number declare const __HMR_ENABLE_OVERLAY__: boolean @@ -24,12 +18,10 @@ console.log('[vite] connecting...') // use server configuration, then fallback to inference const socketProtocol = __HMR_PROTOCOL__ || (location.protocol === 'https:' ? 'wss' : 'ws') -const socketHost = __HMR_PORT__ - ? `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}` - : `${__HMR_HOSTNAME__ || location.hostname}` - +const socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}` const socket = new WebSocket(`${socketProtocol}://${socketHost}`, 'vite-hmr') const base = __BASE__ || '/' +const messageBuffer: string[] = [] function warnFailedFetch(err: Error, path: string | string[]) { if (!err.message.match('fetch')) { @@ -59,9 +51,10 @@ async function handleMessage(payload: HMRPayload) { switch (payload.type) { case 'connected': console.log(`[vite] connected.`) + sendMessageBuffer() // proxy(nginx, docker) hmr ws maybe caused timeout, // so send ping package let ws keep alive. - setInterval(() => socket.send('ping'), __HMR_TIMEOUT__) + setInterval(() => socket.send('{"type":"ping"}'), __HMR_TIMEOUT__) break case 'update': notifyListeners('vite:beforeUpdate', payload) @@ -101,7 +94,7 @@ async function handleMessage(payload: HMRPayload) { }) break case 'custom': { - notifyListeners(payload.event as CustomEventName, payload.data) + notifyListeners(payload.event, payload.data) break } case 'full-reload': @@ -154,19 +147,9 @@ async function handleMessage(payload: HMRPayload) { } } -function notifyListeners( - event: 'vite:beforeUpdate', - payload: UpdatePayload -): void -function notifyListeners(event: 'vite:beforePrune', payload: PrunePayload): void -function notifyListeners( - event: 'vite:beforeFullReload', - payload: FullReloadPayload -): void -function notifyListeners(event: 'vite:error', payload: ErrorPayload): void function notifyListeners( - event: CustomEventName, - data: any + event: T, + data: InferCustomEventPayload ): void function notifyListeners(event: string, data: any): void { const cbs = customListenersMap.get(event) @@ -361,6 +344,13 @@ async function fetchUpdate({ path, acceptedPath, timestamp }: Update) { } } +function sendMessageBuffer() { + if (socket.readyState === 1) { + messageBuffer.forEach((msg) => socket.send(msg)) + messageBuffer.length = 0 + } +} + interface HotModule { id: string callbacks: HotCallback[] @@ -382,9 +372,7 @@ const ctxToListenersMap = new Map< Map void)[]> >() -// Just infer the return type for now -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const createHotContext = (ownerPath: string) => { +export function createHotContext(ownerPath: string): ViteHotContext { if (!dataMap.has(ownerPath)) { dataMap.set(ownerPath, {}) } @@ -425,12 +413,12 @@ export const createHotContext = (ownerPath: string) => { hotModulesMap.set(ownerPath, mod) } - const hot = { + const hot: ViteHotContext = { get data() { return dataMap.get(ownerPath) }, - accept(deps: any, callback?: any) { + accept(deps?: any, callback?: any) { if (typeof deps === 'function' || !deps) { // self-accept: hot.accept(() => {}) acceptDeps([ownerPath], ([mod]) => deps && deps(mod)) @@ -451,10 +439,11 @@ export const createHotContext = (ownerPath: string) => { ) }, - dispose(cb: (data: any) => void) { + dispose(cb) { disposeMap.set(ownerPath, cb) }, + // @ts-expect-error untyped prune(cb: (data: any) => void) { pruneMap.set(ownerPath, cb) }, @@ -470,7 +459,7 @@ export const createHotContext = (ownerPath: string) => { }, // custom events - on: (event: string, cb: (data: any) => void) => { + on(event, cb) { const addToMap = (map: Map) => { const existing = map.get(event) || [] existing.push(cb) @@ -478,6 +467,11 @@ export const createHotContext = (ownerPath: string) => { } addToMap(customListenersMap) addToMap(newListeners) + }, + + send(event, data) { + messageBuffer.push(JSON.stringify({ type: 'custom', event, data })) + sendMessageBuffer() } } diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 021fd6d6f4baef..ff03352a20d7a7 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -19,7 +19,6 @@ import type { } from 'rollup' import type Rollup from 'rollup' import { buildReporterPlugin } from './plugins/reporter' -import { buildHtmlPlugin } from './plugins/html' import { buildEsbuildPlugin } from './plugins/esbuild' import { terserPlugin } from './plugins/terser' import type { Terser } from 'types/terser' @@ -307,11 +306,11 @@ export function resolveBuildPlugins(config: ResolvedConfig): { post: Plugin[] } { const options = config.build + return { pre: [ ...(options.watch ? [ensureWatchPlugin()] : []), watchPackageDataPlugin(config), - buildHtmlPlugin(config), commonjsPlugin(options.commonjsOptions), dataURIPlugin(), dynamicImportVars(options.dynamicImportVarsOptions), @@ -322,7 +321,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): { ], post: [ buildImportAnalysisPlugin(config), - buildEsbuildPlugin(config), + ...(config.esbuild !== false ? [buildEsbuildPlugin(config)] : []), ...(options.minify ? [terserPlugin(config)] : []), ...(options.manifest ? [manifestPlugin(config)] : []), ...(options.ssrManifest ? [ssrManifestPlugin(config)] : []), diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 68fe8abe85fc6d..9910cbb3a8b004 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -248,6 +248,7 @@ export type ResolvedConfig = Readonly< cacheDir: string command: 'build' | 'serve' mode: string + isWorker: boolean isProduction: boolean env: Record resolve: ResolveOptions & { @@ -476,6 +477,7 @@ export async function resolveConfig( cacheDir, command, mode, + isWorker: false, isProduction, plugins: userPlugins, server, @@ -508,7 +510,7 @@ export async function resolveConfig( // flat config.worker.plugin const [workerPrePlugins, workerNormalPlugins, workerPostPlugins] = sortUserPlugins(config.worker?.plugins as Plugin[]) - const workerResolved = { ...resolved } + const workerResolved: ResolvedConfig = { ...resolved, isWorker: true } resolved.worker.plugins = await resolvePlugins( workerResolved, workerPrePlugins, diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index 9612cd8c96460d..1741bf2dd7a94b 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -6,6 +6,21 @@ export const DEFAULT_MAIN_FIELDS = [ 'jsnext' ] +/** + * A non-exhaustive list of known-to-be-ES-module entry names. + * From + */ +export const KNOWN_ESM_MAIN_FIELDS = [ + 'module', + 'jsnext:main', + 'jsnext', + 'esnext', + 'es2015', + 'es2020', + 'fesm2015', + 'fesm2020' +] + export const DEFAULT_EXTENSIONS = [ '.mjs', '.js', diff --git a/packages/vite/src/node/importGlob.ts b/packages/vite/src/node/importGlob.ts index 8ed3ba66d09744..a759bee2b5fa59 100644 --- a/packages/vite/src/node/importGlob.ts +++ b/packages/vite/src/node/importGlob.ts @@ -147,7 +147,7 @@ export async function transformImportGlob( ) } entries += ` ${JSON.stringify(file)}: ${JSON.stringify( - await fsp.readFile(path.join(base, file), 'utf-8') + await fsp.readFile(path.join(base, files[i]), 'utf-8') )},` } else { const importeeUrl = isCSSRequest(importee) ? `${importee}?used` : importee diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index f0f217c9be03d3..2e849d846527ca 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -40,7 +40,8 @@ export type { DepOptimizationOptions, DepOptimizationResult, DepOptimizationProcessing, - OptimizedDepInfo + OptimizedDepInfo, + OptimizedDeps } from './optimizer' export type { Plugin } from './plugin' export type { PackageCache, PackageData } from './packages' @@ -72,7 +73,11 @@ export type { TransformOptions as EsbuildTransformOptions } from 'esbuild' export type { ESBuildOptions, ESBuildTransformResult } from './plugins/esbuild' export type { Manifest, ManifestChunk } from './plugins/manifest' export type { ResolveOptions, InternalResolveOptions } from './plugins/resolve' -export type { WebSocketServer } from './server/ws' +export type { + WebSocketServer, + WebSocketClient, + WebSocketCustomListener +} from './server/ws' export type { PluginContainer } from './server/pluginContainer' export type { ModuleGraph, ModuleNode, ResolvedUrl } from './server/moduleGraph' export type { SendOptions } from './server/send' @@ -103,6 +108,7 @@ export type { export type { Terser } from 'types/terser' export type { RollupCommonJSOptions } from 'types/commonjs' export type { RollupDynamicImportVarsOptions } from 'types/dynamicImportVars' +export type { CustomEventMap, InferCustomEventPayload } from 'types/customEvent' export type { Matcher, AnymatchPattern, AnymatchFn } from 'types/anymatch' export type { SplitVendorChunkCache } from './plugins/splitVendorChunk' diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts index 3ff86c213a54a2..4303be0ec876e7 100644 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts @@ -37,8 +37,7 @@ const externalTypes = [ export function esbuildDepPlugin( qualified: Record, exportsData: Record, - config: ResolvedConfig, - ssr?: boolean + config: ResolvedConfig ): Plugin { // remove optimizable extensions from `externalTypes` list const allExternalTypes = config.optimizeDeps.extensions @@ -48,12 +47,13 @@ export function esbuildDepPlugin( : externalTypes // default resolver which prefers ESM - const _resolve = config.createResolver({ asSrc: false }) + const _resolve = config.createResolver({ asSrc: false, scan: true }) // cjs resolver that prefers Node const _resolveRequire = config.createResolver({ asSrc: false, - isRequire: true + isRequire: true, + scan: true }) const resolve = ( @@ -72,7 +72,7 @@ export function esbuildDepPlugin( _importer = importer in qualified ? qualified[importer] : importer } const resolver = kind.startsWith('require') ? _resolveRequire : _resolve - return resolver(id, _importer, undefined, ssr) + return resolver(id, _importer, undefined) } return { diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 2bdd54dc3a899a..88c41801938b98 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -21,7 +21,8 @@ import { scanImports } from './scan' import { transformWithEsbuild } from '../plugins/esbuild' import { performance } from 'perf_hooks' -const debug = createDebugger('vite:deps') +export const debuggerViteDeps = createDebugger('vite:deps') +const debug = debuggerViteDeps const isDebugEnabled = _debug('vite:deps').enabled const jsExtensionRE = /\.js$/i @@ -33,6 +34,12 @@ export type ExportsData = ReturnType & { hasReExports?: true } +export interface OptimizedDeps { + metadata: DepOptimizationMetadata + scanProcessing?: Promise + registerMissingImport: (id: string, resolved: string) => OptimizedDepInfo +} + export interface DepOptimizationOptions { /** * By default, Vite will crawl your `index.html` to detect dependencies that @@ -97,17 +104,11 @@ export interface DepOptimizationOptions { } export interface DepOptimizationResult { - /** - * After a re-optimization, the internal bundled chunks may change - * and a full page reload is required if that is the case - * If the files are stable, we can avoid the reload that is expensive - * for large applications - */ - alteredFiles: boolean + metadata: DepOptimizationMetadata /** * When doing a re-run, if there are newly discovered dependendencies - * the page reload will be delayed until the next rerun so the - * result will be discarded + * the page reload will be delayed until the next rerun so we need + * to be able to discard the result */ commit: () => void cancel: () => void @@ -119,8 +120,9 @@ export interface DepOptimizationProcessing { } export interface OptimizedDepInfo { + id: string file: string - src: string + src?: string needsInterop?: boolean browserHash?: string fileHash?: string @@ -155,6 +157,10 @@ export interface DepOptimizationMetadata { * Metadata for each newly discovered dependency after processing */ discovered: Record + /** + * OptimizedDepInfo list + */ + depInfoList: OptimizedDepInfo[] } /** @@ -163,45 +169,65 @@ export interface DepOptimizationMetadata { export async function optimizeDeps( config: ResolvedConfig, force = config.server.force, - asCommand = false, - newDeps?: Record, // missing imports encountered after server has started - ssr?: boolean + asCommand = false ): Promise { - const { metadata, run } = await createOptimizeDepsRun( + const log = asCommand ? config.logger.info : debug + + const cachedMetadata = loadCachedDepOptimizationMetadata( config, force, - asCommand, - null, - newDeps, - ssr + asCommand ) - const result = await run() + if (cachedMetadata) { + return cachedMetadata + } + const depsInfo = await discoverProjectDependencies(config) + + const depsString = depsLogString(Object.keys(depsInfo)) + log(colors.green(`Optimizing dependencies:\n ${depsString}`)) + + const result = await runOptimizeDeps(config, depsInfo) + result.commit() - return metadata + + return result.metadata +} + +export function createOptimizedDepsMetadata( + config: ResolvedConfig, + timestamp?: string +): DepOptimizationMetadata { + const hash = getDepHash(config) + return { + hash, + browserHash: getOptimizedBrowserHash(hash, {}, timestamp), + optimized: {}, + chunks: {}, + discovered: {}, + depInfoList: [] + } +} + +export function addOptimizedDepInfo( + metadata: DepOptimizationMetadata, + type: 'optimized' | 'discovered' | 'chunks', + depInfo: OptimizedDepInfo +): OptimizedDepInfo { + metadata[type][depInfo.id] = depInfo + metadata.depInfoList.push(depInfo) + return depInfo } /** - * Internally, Vite uses this function to prepare a optimizeDeps run. When Vite starts, we can get - * the metadata and start the server without waiting for the optimizeDeps processing to be completed + * Creates the initial dep optimization metadata, loading it from the deps cache + * if it exists and pre-bundling isn't forced */ -export async function createOptimizeDepsRun( +export function loadCachedDepOptimizationMetadata( config: ResolvedConfig, force = config.server.force, - asCommand = false, - currentData: DepOptimizationMetadata | null = null, - newDeps?: Record, // missing imports encountered after server has started - ssr?: boolean -): Promise<{ - metadata: DepOptimizationMetadata - run: () => Promise -}> { - config = { - ...config, - command: 'build' - } - - const { root, logger } = config - const log = asCommand ? logger.info : debug + asCommand = false +): DepOptimizationMetadata | undefined { + const log = asCommand ? config.logger.info : debug // 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 @@ -210,49 +236,105 @@ export async function createOptimizeDepsRun( } const depsCacheDir = getDepsCacheDir(config) - const processingCacheDir = getProcessingDepsCacheDir(config) - - const mainHash = getDepHash(root, config) - - const processing = newDepOptimizationProcessing() - - const metadata: DepOptimizationMetadata = { - hash: mainHash, - browserHash: mainHash, - optimized: {}, - chunks: {}, - discovered: {} - } if (!force) { - let prevData: DepOptimizationMetadata | undefined + let cachedMetadata: DepOptimizationMetadata | undefined try { - const prevDataPath = path.join(depsCacheDir, '_metadata.json') - prevData = parseOptimizedDepsMetadata( - fs.readFileSync(prevDataPath, 'utf-8'), + const cachedMetadataPath = path.join(depsCacheDir, '_metadata.json') + cachedMetadata = parseOptimizedDepsMetadata( + fs.readFileSync(cachedMetadataPath, 'utf-8'), depsCacheDir ) } catch (e) {} // hash is consistent, no need to re-bundle - if (prevData && prevData.hash === metadata.hash) { + if (cachedMetadata && cachedMetadata.hash === getDepHash(config)) { 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 - const resolve = () => { - processing.resolve() - } - return { - metadata: prevData, - run: async () => { - return { - alteredFiles: false, - commit: resolve, - cancel: resolve - } - } - } + return cachedMetadata + } + } else { + config.logger.info('Forced re-optimization of dependencies') + } + + // Start with a fresh cache + removeDirSync(depsCacheDir) +} + +/** + * Initial optimizeDeps at server start. Perform a fast scan using esbuild to + * find deps to pre-bundle and include user hard-coded dependencies + */ +export async function discoverProjectDependencies( + config: ResolvedConfig, + timestamp?: string +): Promise> { + const { deps, missing } = await scanImports(config) + + const missingIds = Object.keys(missing) + if (missingIds.length) { + throw new Error( + `The following dependencies are imported but could not be resolved:\n\n ${missingIds + .map( + (id) => + `${colors.cyan(id)} ${colors.white( + colors.dim(`(imported by ${missing[id]})`) + )}` + ) + .join(`\n `)}\n\nAre they installed?` + ) + } + + await addManuallyIncludedOptimizeDeps(deps, config) + + const browserHash = getOptimizedBrowserHash( + getDepHash(config), + deps, + timestamp + ) + const discovered: Record = {} + for (const id in deps) { + const entry = deps[id] + discovered[id] = { + id, + file: getOptimizedDepPath(id, config), + src: entry, + browserHash: browserHash } } + return discovered +} + +export function depsLogString(qualifiedIds: string[]): string { + if (isDebugEnabled) { + return colors.yellow(qualifiedIds.join(`\n `)) + } 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` : ``) + ) + } +} + +/** + * Internally, Vite uses this function to prepare a optimizeDeps run. When Vite starts, we can get + * the metadata and start the server without waiting for the optimizeDeps processing to be completed + */ +export async function runOptimizeDeps( + config: ResolvedConfig, + depsInfo: Record +): Promise { + config = { + ...config, + command: 'build' + } + + const depsCacheDir = getDepsCacheDir(config) + const processingCacheDir = getProcessingDepsCacheDir(config) // Create a temporal directory so we don't need to delete optimized deps // until they have been processed. This also avoids leaving the deps cache @@ -270,319 +352,190 @@ export async function createOptimizeDepsRun( JSON.stringify({ type: 'module' }) ) - let newBrowserHash: string - - let deps: Record - if (!newDeps) { - // Initial optimizeDeps at server start. Perform a fast scan using esbuild to - // find deps to pre-bundle and include user hard-coded dependencies - - let missing: Record - ;({ deps, missing } = await scanImports(config)) - - const missingIds = Object.keys(missing) - if (missingIds.length) { - processing.resolve() - throw new Error( - `The following dependencies are imported but could not be resolved:\n\n ${missingIds - .map( - (id) => - `${colors.cyan(id)} ${colors.white( - colors.dim(`(imported by ${missing[id]})`) - )}` - ) - .join(`\n `)}\n\nAre they installed?` - ) - } - - try { - await addManuallyIncludedOptimizeDeps(deps, config) - } catch (e) { - processing.resolve() - throw e - } + const metadata = createOptimizedDepsMetadata(config) - // update browser hash - newBrowserHash = metadata.browserHash = getOptimizedBrowserHash( - metadata.hash, - deps - ) - - // We generate the mapping of dependency ids to their cache file location - // before processing the dependencies with esbuild. This allow us to continue - // processing files in the importAnalysis and resolve plugins - for (const id in deps) { - const entry = deps[id] - metadata.optimized[id] = { - file: getOptimizedDepPath(id, config), - src: entry, - browserHash: newBrowserHash, - processing: processing.promise - } - } - } else { - // Missing dependencies were found at run-time, optimizeDeps called while the - // server is running - deps = depsFromOptimizedDepInfo(newDeps) + metadata.browserHash = getOptimizedBrowserHash( + metadata.hash, + depsFromOptimizedDepInfo(depsInfo) + ) - metadata.optimized = newDeps + // We prebundle dependencies with esbuild and cache them, but there is no need + // to wait here. Code that needs to access the cached deps needs to await + // the optimizedDepInfo.processing promise for each dep - // For reruns keep current global browser hash and newDeps individual hashes until we know - // if files are stable so we can avoid a full page reload - metadata.browserHash = currentData!.browserHash - newBrowserHash = getOptimizedBrowserHash(metadata.hash, deps) - } + const qualifiedIds = Object.keys(depsInfo) - return { metadata, run: prebundleDeps } - - async function prebundleDeps(): Promise { - // We prebundle dependencies with esbuild and cache them, but there is no need - // to wait here. Code that needs to access the cached deps needs to await - // the optimizeDepInfo.processing promise for each dep - - const qualifiedIds = Object.keys(deps) - - if (!qualifiedIds.length) { - return { - alteredFiles: false, - commit() { - // Write metadata file, delete `deps` folder and rename the `processing` folder to `deps` - commitProcessingDepsCacheSync() - log(`No dependencies to bundle. Skipping.\n\n\n`) - processing.resolve() - }, - cancel - } + if (!qualifiedIds.length) { + return { + metadata, + commit() { + // Write metadata file, delete `deps` folder and rename the `processing` folder to `deps` + commitProcessingDepsCacheSync() + }, + cancel } + } - let depsString: string - if (isDebugEnabled) { - depsString = colors.yellow(qualifiedIds.join(`\n `)) + // esbuild generates nested directory output with lowest common ancestor base + // this is unpredictable and makes it difficult to analyze entry / output + // mapping. So what we do here is: + // 1. flatten all ids to eliminate slash + // 2. in the plugin, read the entry ourselves as virtual files to retain the + // path. + const flatIdDeps: Record = {} + const idToExports: Record = {} + const flatIdToExports: Record = {} + + const { plugins = [], ...esbuildOptions } = + config.optimizeDeps?.esbuildOptions ?? {} + + await init + for (const id in depsInfo) { + const flatId = flattenId(id) + const filePath = (flatIdDeps[flatId] = depsInfo[id].src!) + let exportsData: ExportsData + if (config.optimizeDeps.extensions?.some((ext) => filePath.endsWith(ext))) { + // For custom supported extensions, build the entry file to transform it into JS, + // and then parse with es-module-lexer. Note that the `bundle` option is not `true`, + // so only the entry file is being transformed. + const result = await build({ + ...esbuildOptions, + plugins, + entryPoints: [filePath], + write: false, + format: 'esm' + }) + exportsData = parse(result.outputFiles[0].text) as ExportsData } else { - const total = qualifiedIds.length - const maxListed = 5 - const listed = Math.min(total, maxListed) - const extra = Math.max(0, total - maxListed) - depsString = colors.yellow( - qualifiedIds.slice(0, listed).join(`\n `) + - (extra > 0 ? `\n (...and ${extra} more)` : ``) - ) - } - - if (!asCommand) { - if (!newDeps) { - // This is auto run on server start - let the user know that we are - // pre-optimizing deps - logger.info(colors.green(`Pre-bundling dependencies:\n ${depsString}`)) - logger.info( - `(this will be run only when your dependencies or config have changed)` + const entryContent = fs.readFileSync(filePath, 'utf-8') + try { + exportsData = parse(entryContent) as ExportsData + } catch { + debug( + `Unable to parse dependency: ${id}. Trying again with a JSX transform.` ) - } - } else { - logger.info(colors.green(`Optimizing dependencies:\n ${depsString}`)) - } - - // esbuild generates nested directory output with lowest common ancestor base - // this is unpredictable and makes it difficult to analyze entry / output - // mapping. So what we do here is: - // 1. flatten all ids to eliminate slash - // 2. in the plugin, read the entry ourselves as virtual files to retain the - // path. - const flatIdDeps: Record = {} - const idToExports: Record = {} - const flatIdToExports: Record = {} - - const { plugins = [], ...esbuildOptions } = - config.optimizeDeps?.esbuildOptions ?? {} - - await init - for (const id in deps) { - const flatId = flattenId(id) - const filePath = (flatIdDeps[flatId] = deps[id]) - let exportsData: ExportsData - if ( - config.optimizeDeps.extensions?.some((ext) => filePath.endsWith(ext)) - ) { - // For custom supported extensions, build the entry file to transform it into JS, - // and then parse with es-module-lexer. Note that the `bundle` option is not `true`, - // so only the entry file is being transformed. - const result = await build({ - ...esbuildOptions, - plugins, - entryPoints: [filePath], - write: false, - format: 'esm' + const transformed = await transformWithEsbuild(entryContent, filePath, { + loader: 'jsx' }) - exportsData = parse(result.outputFiles[0].text) as ExportsData - } else { - const entryContent = fs.readFileSync(filePath, 'utf-8') - try { - exportsData = parse(entryContent) as ExportsData - } catch { - debug( - `Unable to parse dependency: ${id}. Trying again with a JSX transform.` - ) - const transformed = await transformWithEsbuild( - entryContent, - filePath, - { - loader: 'jsx' - } - ) - // 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 - } - exportsData = parse(transformed.code) as ExportsData + // 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 } - for (const { ss, se } of exportsData[0]) { - const exp = entryContent.slice(ss, se) - if (/export\s+\*\s+from/.test(exp)) { - exportsData.hasReExports = true - } + exportsData = parse(transformed.code) as ExportsData + } + for (const { ss, se } of exportsData[0]) { + const exp = entryContent.slice(ss, se) + if (/export\s+\*\s+from/.test(exp)) { + exportsData.hasReExports = true } } - - idToExports[id] = exportsData - flatIdToExports[flatId] = exportsData } - const define: Record = { - 'process.env.NODE_ENV': JSON.stringify(config.mode) - } - for (const key in config.define) { - const value = config.define[key] - define[key] = typeof value === 'string' ? value : JSON.stringify(value) - } + idToExports[id] = exportsData + flatIdToExports[flatId] = exportsData + } - const start = performance.now() - - const result = await build({ - absWorkingDir: process.cwd(), - entryPoints: Object.keys(flatIdDeps), - bundle: true, - format: 'esm', - target: config.build.target || undefined, - external: config.optimizeDeps?.exclude, - logLevel: 'error', - splitting: true, - sourcemap: true, - outdir: processingCacheDir, - ignoreAnnotations: true, - metafile: true, - define, - plugins: [ - ...plugins, - esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) - ], - ...esbuildOptions - }) + const define: Record = { + 'process.env.NODE_ENV': JSON.stringify(config.mode) + } + for (const key in config.define) { + const value = config.define[key] + define[key] = typeof value === 'string' ? value : JSON.stringify(value) + } - const meta = result.metafile! + const start = performance.now() + + const result = await build({ + absWorkingDir: process.cwd(), + entryPoints: Object.keys(flatIdDeps), + bundle: true, + format: 'esm', + target: config.build.target || undefined, + external: config.optimizeDeps?.exclude, + logLevel: 'error', + splitting: true, + sourcemap: true, + outdir: processingCacheDir, + ignoreAnnotations: true, + metafile: true, + define, + plugins: [ + ...plugins, + esbuildDepPlugin(flatIdDeps, flatIdToExports, config) + ], + ...esbuildOptions + }) - // the paths in `meta.outputs` are relative to `process.cwd()` - const processingCacheDirOutputPath = path.relative( - process.cwd(), - processingCacheDir - ) + const meta = result.metafile! - for (const id in deps) { - const optimizedInfo = metadata.optimized[id] - optimizedInfo.needsInterop = needsInterop( - id, - idToExports[id], - meta.outputs, - processingCacheDirOutputPath - ) - const output = - meta.outputs[ - path.relative(process.cwd(), getProcessingDepPath(id, config)) - ] - if (output) { - // We only need to hash the output.imports in to check for stability, but adding the hash - // and file path gives us a unique hash that may be useful for other things in the future - optimizedInfo.fileHash = getHash( - metadata.hash + optimizedInfo.file + JSON.stringify(output.imports) - ) - } - } + // the paths in `meta.outputs` are relative to `process.cwd()` + const processingCacheDirOutputPath = path.relative( + process.cwd(), + processingCacheDir + ) - // This only runs when missing deps are processed. Previous optimized deps are stable if - // the newly discovered deps don't have common chunks with them. Comparing their fileHash we - // can find out if it is safe to keep the current browser state. If one of the file hashes - // changed, a full page reload is needed - let alteredFiles = false - if (currentData) { - alteredFiles = Object.keys(currentData.optimized).some((dep) => { - const currentInfo = currentData.optimized[dep] - const info = metadata.optimized[dep] - return ( - !info?.fileHash || - !currentInfo?.fileHash || - info?.fileHash !== currentInfo?.fileHash - ) - }) - debug(`optimized deps have altered files: ${alteredFiles}`) - } + for (const id in depsInfo) { + const output = esbuildOutputFromId(meta.outputs, id, processingCacheDir) + + addOptimizedDepInfo(metadata, 'optimized', { + ...depsInfo[id], + needsInterop: needsInterop(id, idToExports[id], output), + // We only need to hash the output.imports in to check for stability, but adding the hash + // and file path gives us a unique hash that may be useful for other things in the future + fileHash: getHash( + metadata.hash + depsInfo[id].file + JSON.stringify(output.imports) + ), + browserHash: metadata.browserHash + }) + } - for (const o of Object.keys(meta.outputs)) { - if (!o.match(jsMapExtensionRE)) { - const id = path - .relative(processingCacheDirOutputPath, o) - .replace(jsExtensionRE, '') - const file = getOptimizedDepPath(id, config) - if (!findFileInfo(metadata.optimized, file)) { - metadata.chunks[id] = { - file, - src: '', - needsInterop: false, - browserHash: - (!alteredFiles && currentData?.chunks[id]?.browserHash) || - newBrowserHash - } - } + for (const o of Object.keys(meta.outputs)) { + if (!o.match(jsMapExtensionRE)) { + const id = path + .relative(processingCacheDirOutputPath, o) + .replace(jsExtensionRE, '') + const file = getOptimizedDepPath(id, config) + if ( + !findOptimizedDepInfoInRecord( + metadata.optimized, + (depInfo) => depInfo.file === file + ) + ) { + addOptimizedDepInfo(metadata, 'chunks', { + id, + file, + needsInterop: false, + browserHash: metadata.browserHash + }) } } + } - if (alteredFiles) { - metadata.browserHash = newBrowserHash - } + const dataPath = path.join(processingCacheDir, '_metadata.json') + writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata, depsCacheDir)) - debug(`deps bundled in ${(performance.now() - start).toFixed(2)}ms`) + debug(`deps bundled in ${(performance.now() - start).toFixed(2)}ms`) - return { - alteredFiles, - commit() { - if (alteredFiles) { - // Overwrite individual hashes with the new global browserHash, a full page reload is required - // New deps that ended up with a different hash replaced while doing analysis import are going to - // return a not found so the browser doesn't cache them. And will properly get loaded after the reload - for (const id in deps) { - metadata.optimized[id].browserHash = newBrowserHash - } - } - // Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync - commitProcessingDepsCacheSync() - processing.resolve() - }, - cancel - } + return { + metadata, + commit() { + // Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync + commitProcessingDepsCacheSync() + }, + cancel } function commitProcessingDepsCacheSync() { - // Rewire the file paths from the temporal processing dir to the final deps cache dir - const dataPath = path.join(processingCacheDir, '_metadata.json') - writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata, depsCacheDir)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir + // Rewire the file paths from the temporal processing dir to the final deps cache dir removeDirSync(depsCacheDir) fs.renameSync(processingCacheDir, depsCacheDir) } function cancel() { removeDirSync(processingCacheDir) - processing.resolve() } } @@ -639,38 +592,20 @@ export function depsFromOptimizedDepInfo( depsInfo: Record ) { return Object.fromEntries( - Object.entries(depsInfo).map((d) => [d[0], d[1].src]) + Object.entries(depsInfo).map((d) => [d[0], d[1].src!]) ) } -export function getHash(text: string) { - return createHash('sha256').update(text).digest('hex').substring(0, 8) -} - -function getOptimizedBrowserHash(hash: string, deps: Record) { - return getHash(hash + JSON.stringify(deps)) -} - -function getCachedDepFilePath(id: string, depsCacheDir: string) { - return normalizePath(path.resolve(depsCacheDir, flattenId(id) + '.js')) -} - export function getOptimizedDepPath(id: string, config: ResolvedConfig) { - return getCachedDepFilePath(id, getDepsCacheDir(config)) + return normalizePath( + path.resolve(getDepsCacheDir(config), flattenId(id) + '.js') + ) } export function getDepsCacheDir(config: ResolvedConfig) { return normalizePath(path.resolve(config.cacheDir, 'deps')) } -function getProcessingDepFilePath(id: string, processingCacheDir: string) { - return normalizePath(path.resolve(processingCacheDir, flattenId(id) + '.js')) -} - -function getProcessingDepPath(id: string, config: ResolvedConfig) { - return getProcessingDepFilePath(id, getProcessingDepsCacheDir(config)) -} - function getProcessingDepsCacheDir(config: ResolvedConfig) { return normalizePath(path.resolve(config.cacheDir, 'processing')) } @@ -701,27 +636,48 @@ export function createIsOptimizedDepUrl(config: ResolvedConfig) { function parseOptimizedDepsMetadata( jsonMetadata: string, depsCacheDir: string -) { - const metadata = JSON.parse(jsonMetadata, (key: string, value: string) => { - // Paths can be absolute or relative to the deps cache dir where - // the _metadata.json is located - if (key === 'file' || key === 'src') { - return normalizePath(path.resolve(depsCacheDir, value)) +): DepOptimizationMetadata | undefined { + const { hash, browserHash, optimized, chunks } = JSON.parse( + jsonMetadata, + (key: string, value: string) => { + // Paths can be absolute or relative to the deps cache dir where + // the _metadata.json is located + if (key === 'file' || key === 'src') { + return normalizePath(path.resolve(depsCacheDir, value)) + } + return value } - return value - }) - const { browserHash } = metadata - for (const o of Object.keys(metadata.optimized)) { - const depInfo = metadata.optimized[o] - depInfo.browserHash = browserHash + ) + if ( + !chunks || + Object.values(optimized).some((depInfo: any) => !depInfo.fileHash) + ) { + // outdated _metadata.json version, ignore + return + } + const metadata = { + hash, + browserHash, + optimized: {}, + discovered: {}, + chunks: {}, + depInfoList: [] } - metadata.chunks ||= {} // Support missing chunks for back compat - for (const o of Object.keys(metadata.chunks)) { - const depInfo = metadata.chunks[o] - depInfo.src = '' - depInfo.browserHash = browserHash + for (const id of Object.keys(optimized)) { + addOptimizedDepInfo(metadata, 'optimized', { + ...optimized[id], + id, + browserHash + }) + } + for (const id of Object.keys(chunks)) { + addOptimizedDepInfo(metadata, 'chunks', { + ...chunks[id], + id, + browserHash, + needsInterop: false + }) } - metadata.discovered = {} return metadata } @@ -735,50 +691,53 @@ function stringifyOptimizedDepsMetadata( metadata: DepOptimizationMetadata, depsCacheDir: string ) { + const { hash, browserHash, optimized, chunks } = metadata return JSON.stringify( - metadata, - (key: string, value: any) => { - if (key === 'discovered' || key === 'processing') { - return - } + { + hash, + browserHash, + optimized: Object.fromEntries( + Object.values(optimized).map( + ({ id, src, file, fileHash, needsInterop }) => [ + id, + { + src, + file, + fileHash, + needsInterop + } + ] + ) + ), + chunks: Object.fromEntries( + Object.values(chunks).map(({ id, file }) => [id, { file }]) + ) + }, + (key: string, value: string) => { + // Paths can be absolute or relative to the deps cache dir where + // the _metadata.json is located if (key === 'file' || key === 'src') { return normalizePath(path.relative(depsCacheDir, value)) } - if (key === 'optimized') { - // Only remove browserHash for individual dep info - const cleaned: Record = {} - for (const dep of Object.keys(value)) { - const { browserHash, ...c } = value[dep] - cleaned[dep] = c - } - return cleaned - } - if (key === 'optimized') { - return Object.keys(value).reduce( - (cleaned: Record, dep: string) => { - const { browserHash, ...c } = value[dep] - cleaned[dep] = c - return cleaned - }, - {} - ) - } - if (key === 'chunks') { - return Object.keys(value).reduce( - (cleaned: Record, dep: string) => { - const { browserHash, needsInterop, src, ...c } = value[dep] - cleaned[dep] = c - return cleaned - }, - {} - ) - } return value }, 2 ) } +function esbuildOutputFromId( + outputs: Record, + id: string, + cacheDirOutputPath: string +): any { + const flatId = flattenId(id) + '.js' + return outputs[ + normalizePath( + path.relative(process.cwd(), path.join(cacheDirOutputPath, flatId)) + ) + ] +} + // https://github.com/vitejs/vite/issues/1724#issuecomment-767619642 // a list of modules that pretends to be ESM but still uses `require`. // this causes esbuild to wrap them as CJS even when its entry appears to be ESM. @@ -787,8 +746,7 @@ const KNOWN_INTEROP_IDS = new Set(['moment']) function needsInterop( id: string, exportsData: ExportsData, - outputs: Record, - cacheDirOutputPath: string + output: { exports: string[] } ): boolean { if (KNOWN_INTEROP_IDS.has(id)) { return true @@ -802,17 +760,7 @@ function needsInterop( // if a peer dependency used require() on a ESM dependency, esbuild turns the // ESM dependency's entry chunk into a single default export... detect // such cases by checking exports mismatch, and force interop. - const flatId = flattenId(id) + '.js' - let generatedExports: string[] | undefined - for (const output in outputs) { - if ( - normalizePath(output) === - normalizePath(path.join(cacheDirOutputPath, flatId)) - ) { - generatedExports = outputs[output].exports - break - } - } + const generatedExports: string[] = output.exports if ( !generatedExports || @@ -829,8 +777,8 @@ function isSingleDefaultExport(exports: readonly string[]) { const lockfileFormats = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'] -function getDepHash(root: string, config: ResolvedConfig): string { - let content = lookupFile(root, lockfileFormats) || '' +export function getDepHash(config: ResolvedConfig): string { + let content = lookupFile(config.root, lockfileFormats) || '' // also take config into account // only a subset of config options that can affect dep optimization content += JSON.stringify( @@ -860,27 +808,44 @@ function getDepHash(root: string, config: ResolvedConfig): string { return value } ) - return createHash('sha256').update(content).digest('hex').substring(0, 8) + return getHash(content) } -export function optimizeDepInfoFromFile( +function getOptimizedBrowserHash( + hash: string, + deps: Record, + timestamp = '' +) { + return getHash(hash + JSON.stringify(deps) + timestamp) +} + +export function getHash(text: string): string { + return createHash('sha256').update(text).digest('hex').substring(0, 8) +} + +export function optimizedDepInfoFromId( metadata: DepOptimizationMetadata, - file: string + id: string ): OptimizedDepInfo | undefined { return ( - findFileInfo(metadata.optimized, file) || - findFileInfo(metadata.discovered, file) || - findFileInfo(metadata.chunks, file) + metadata.optimized[id] || metadata.discovered[id] || metadata.chunks[id] ) } -function findFileInfo( - dependenciesInfo: Record, +export function optimizedDepInfoFromFile( + metadata: DepOptimizationMetadata, file: string +): OptimizedDepInfo | undefined { + return metadata.depInfoList.find((depInfo) => depInfo.file === file) +} + +function findOptimizedDepInfoInRecord( + dependenciesInfo: Record, + callbackFn: (depInfo: OptimizedDepInfo, id: string) => any ): OptimizedDepInfo | undefined { for (const o of Object.keys(dependenciesInfo)) { const info = dependenciesInfo[o] - if (info.file === file) { + if (callbackFn(info, o)) { return info } } @@ -890,7 +855,7 @@ export async function optimizedDepNeedsInterop( metadata: DepOptimizationMetadata, file: string ): Promise { - const depInfo = optimizeDepInfoFromFile(metadata, file) + const depInfo = optimizedDepInfoFromFile(metadata, file) if (!depInfo) return undefined diff --git a/packages/vite/src/node/optimizer/registerMissing.ts b/packages/vite/src/node/optimizer/registerMissing.ts index 256098f351e2e8..ee4824389c202b 100644 --- a/packages/vite/src/node/optimizer/registerMissing.ts +++ b/packages/vite/src/node/optimizer/registerMissing.ts @@ -1,18 +1,26 @@ import colors from 'picocolors' +import _debug from 'debug' import { - createOptimizeDepsRun, + runOptimizeDeps, getOptimizedDepPath, getHash, depsFromOptimizedDepInfo, - newDepOptimizationProcessing + newDepOptimizationProcessing, + loadCachedDepOptimizationMetadata, + createOptimizedDepsMetadata, + addOptimizedDepInfo, + discoverProjectDependencies, + depsLogString, + debuggerViteDeps as debug } from '.' import type { - DepOptimizationMetadata, DepOptimizationProcessing, - OptimizedDepInfo + OptimizedDepInfo, + OptimizedDeps } from '.' import type { ViteDevServer } from '..' -import { resolveSSRExternal } from '../ssr/ssrExternal' + +const isDebugEnabled = _debug('vite:deps').enabled /** * The amount to wait for requests to register newly found dependencies before triggering @@ -20,16 +28,39 @@ import { resolveSSRExternal } from '../ssr/ssrExternal' */ const debounceMs = 100 -export function createMissingImporterRegisterFn( - server: ViteDevServer, - initialProcessingPromise: Promise -): (id: string, resolved: string, ssr?: boolean) => OptimizedDepInfo { - const { logger } = server.config - let metadata = server._optimizeDepsMetadata! +export function createOptimizedDeps(server: ViteDevServer): OptimizedDeps { + const { config } = server + const { logger } = config + + const sessionTimestamp = Date.now().toString() + + const cachedMetadata = loadCachedDepOptimizationMetadata(config) + + const optimizedDeps: OptimizedDeps = { + metadata: + cachedMetadata || createOptimizedDepsMetadata(config, sessionTimestamp), + registerMissingImport + } let handle: NodeJS.Timeout | undefined let newDepsDiscovered = false + let newDepsToLog: string[] = [] + let newDepsToLogHandle: NodeJS.Timeout | undefined + const logNewlyDiscoveredDeps = () => { + if (newDepsToLog.length) { + config.logger.info( + colors.green( + `✨ new dependencies optimized: ${depsLogString(newDepsToLog)}` + ), + { + timestamp: true + } + ) + newDepsToLog = [] + } + } + let depOptimizationProcessing = newDepOptimizationProcessing() let depOptimizationProcessingQueue: DepOptimizationProcessing[] = [] const resolveEnqueuedProcessingPromises = () => { @@ -41,40 +72,80 @@ export function createMissingImporterRegisterFn( } let enqueuedRerun: (() => void) | undefined - let currentlyProcessing = true - initialProcessingPromise.then(() => { - currentlyProcessing = false - enqueuedRerun?.() - }) + let currentlyProcessing = false - async function rerun(ssr: boolean | undefined) { - // debounce time to wait for new missing deps finished, issue a new - // optimization of deps (both old and newly found) once the previous - // optimizeDeps processing is finished + // If there wasn't a cache or it is outdated, perform a fast scan with esbuild + // to quickly find project dependencies and do a first optimize run + if (!cachedMetadata) { + currentlyProcessing = true + + const scanPhaseProcessing = newDepOptimizationProcessing() + optimizedDeps.scanProcessing = scanPhaseProcessing.promise + + const warmUp = async () => { + try { + debug(colors.green(`scanning for dependencies...`), { + timestamp: true + }) + + const { metadata } = optimizedDeps + + const discovered = await discoverProjectDependencies( + config, + sessionTimestamp + ) + + // Respect the scan phase discover order to improve reproducibility + for (const depInfo of Object.values(discovered)) { + addOptimizedDepInfo(metadata, 'discovered', { + ...depInfo, + processing: depOptimizationProcessing.promise + }) + } + + debug( + colors.green( + `dependencies found: ${depsLogString(Object.keys(discovered))}` + ), + { + timestamp: true + } + ) + + scanPhaseProcessing.resolve() + optimizedDeps.scanProcessing = undefined + + runOptimizer() + } catch (e) { + logger.error(e.message) + if (optimizedDeps.scanProcessing) { + scanPhaseProcessing.resolve() + optimizedDeps.scanProcessing = undefined + } + } + } + + setTimeout(warmUp, 0) + } + + async function runOptimizer(isRerun = false) { + // Ensure that rerun is called sequentially + enqueuedRerun = undefined + currentlyProcessing = true + + // Ensure that a rerun will not be issued for current discovered deps + if (handle) clearTimeout(handle) // a succesful 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 server._optimizeDepsMetadata. A fullReload is only issued if + // to optimizeDeps.metadata. A fullReload is only issued if // the previous bundled dependencies have changed. - // if the rerun fails, server._optimizeDepsMetadata remains untouched, + // if the rerun fails, optimizeDeps.metadata remains untouched, // current discovered deps are cleaned, and a fullReload is issued - // Ensure that rerun is called sequentially - enqueuedRerun = undefined - currentlyProcessing = true - - logger.info( - colors.yellow( - `new dependencies found: ${Object.keys(metadata.discovered).join( - ', ' - )}, updating...` - ), - { - timestamp: true - } - ) + let { metadata } = optimizedDeps // All deps, previous known and newly discovered are rebundled, // respect insertion order to keep the metadata file stable @@ -85,9 +156,10 @@ export function createMissingImporterRegisterFn( for (const dep of Object.keys(metadata.optimized)) { newDeps[dep] = { ...metadata.optimized[dep] } } - // Don't clone discovered info objects, they are read after awaited for (const dep of Object.keys(metadata.discovered)) { - newDeps[dep] = metadata.discovered[dep] + // Clone the discovered info discarding its processing promise + const { processing, ...info } = metadata.discovered[dep] + newDeps[dep] = info } newDepsDiscovered = false @@ -100,51 +172,87 @@ export function createMissingImporterRegisterFn( // dependencies will be asigned this promise from this point depOptimizationProcessing = newDepOptimizationProcessing() - let newData: DepOptimizationMetadata | null = null - try { - const optimizeDeps = await createOptimizeDepsRun( - server.config, - true, - false, - metadata, - newDeps, - ssr - ) - - const processingResult = await optimizeDeps.run() + const processingResult = await runOptimizeDeps(config, newDeps) + + const newData = processingResult.metadata + + // After a re-optimization, if the internal bundled chunks change a full page reload + // is required. If the files are stable, we can avoid the reload that is expensive + // for large applications. Comparing their fileHash we can find out if it is safe to + // keep the current browser state. + const needsReload = + metadata.hash !== newData.hash || + Object.keys(metadata.optimized).some((dep) => { + return ( + metadata.optimized[dep].fileHash !== newData.optimized[dep].fileHash + ) + }) const commitProcessing = () => { processingResult.commit() - newData = optimizeDeps.metadata + // While optimizeDeps is running, new missing deps may be discovered, + // in which case they will keep being added to metadata.discovered + for (const id in metadata.discovered) { + if (!newData.optimized[id]) { + addOptimizedDepInfo(newData, 'discovered', metadata.discovered[id]) + } + } - // update ssr externals - if (ssr) { - server._ssrExternals = resolveSSRExternal( - server.config, - Object.keys(newData.optimized) - ) + // If we don't reload the page, we need to keep browserHash stable + if (!needsReload) { + newData.browserHash = metadata.browserHash + for (const dep in newData.chunks) { + newData.chunks[dep].browserHash = metadata.browserHash + } + for (const dep in newData.optimized) { + newData.optimized[dep].browserHash = ( + metadata.optimized[dep] || metadata.discovered[dep] + ).browserHash + } } - // While optimizeDeps is running, new missing deps may be discovered, - // in which case they will keep being added to metadata.discovered - for (const o of Object.keys(metadata.discovered)) { - if (!newData.optimized[o]) { - newData.discovered[o] = metadata.discovered[o] + // Commit hash and needsInterop changes to the discovered deps info + // object. Allow for code to await for the discovered processing promise + // and use the information in the same object + for (const o in newData.optimized) { + const discovered = metadata.discovered[o] + if (discovered) { + const optimized = newData.optimized[o] + discovered.browserHash = optimized.browserHash + discovered.fileHash = optimized.fileHash + discovered.needsInterop = optimized.needsInterop + discovered.processing = undefined } } - metadata = server._optimizeDepsMetadata = newData + if (isRerun) { + newDepsToLog.push( + ...Object.keys(newData.optimized).filter( + (dep) => !metadata.optimized[dep] + ) + ) + } + + metadata = optimizedDeps.metadata = newData resolveEnqueuedProcessingPromises() } - if (!processingResult.alteredFiles) { + if (!needsReload) { commitProcessing() - logger.info(colors.green(`✨ new dependencies pre-bundled...`), { - timestamp: true - }) + if (!isDebugEnabled) { + if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) + newDepsToLogHandle = setTimeout(() => { + newDepsToLogHandle = undefined + logNewlyDiscoveredDeps() + }, 2 * debounceMs) + } else { + debug(colors.green(`✨ optimized dependencies unchanged`), { + timestamp: true + }) + } } else { if (newDepsDiscovered) { // There are newly discovered deps, and another rerun is about to be @@ -153,7 +261,7 @@ export function createMissingImporterRegisterFn( // once a rerun is committed processingResult.cancel() - logger.info( + debug( colors.green( `✨ delaying reload as new dependencies have been found...` ), @@ -164,8 +272,14 @@ export function createMissingImporterRegisterFn( } else { commitProcessing() + if (!isDebugEnabled) { + if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) + newDepsToLogHandle = undefined + logNewlyDiscoveredDeps() + } + logger.info( - colors.green(`✨ dependencies updated, reloading page...`), + colors.green(`✨ optimized dependencies changed. reloading`), { timestamp: true } @@ -202,7 +316,17 @@ export function createMissingImporterRegisterFn( }) } - const discoveredTimestamp = Date.now() + async function rerun() { + // debounce time to wait for new missing deps finished, issue a new + // optimization of deps (both old and newly found) once the previous + // optimizeDeps processing is finished + const deps = Object.keys(optimizedDeps.metadata.discovered) + const depsString = depsLogString(deps) + debug(colors.green(`new dependencies found: ${depsString}`), { + timestamp: true + }) + runOptimizer(true) + } function getDiscoveredBrowserHash( hash: string, @@ -210,18 +334,21 @@ export function createMissingImporterRegisterFn( missing: Record ) { return getHash( - hash + - JSON.stringify(deps) + - JSON.stringify(missing) + - discoveredTimestamp + hash + JSON.stringify(deps) + JSON.stringify(missing) + sessionTimestamp ) } - return function registerMissingImport( + function registerMissingImport( id: string, resolved: string, ssr?: boolean ): OptimizedDepInfo { + if (optimizedDeps.scanProcessing) { + config.logger.error( + 'Vite internal error: registering missing import before initial scanning is over' + ) + } + const { metadata } = optimizedDeps const optimized = metadata.optimized[id] if (optimized) { return optimized @@ -237,7 +364,8 @@ export function createMissingImporterRegisterFn( return missing } newDepsDiscovered = true - missing = metadata.discovered[id] = { + missing = addOptimizedDepInfo(metadata, 'discovered', { + id, file: getOptimizedDepPath(id, server.config), src: resolved, // Assing a browserHash to this missing dependency that is unique to @@ -252,15 +380,17 @@ export function createMissingImporterRegisterFn( // loading of this pre-bundled dep needs to await for its processing // promise to be resolved processing: depOptimizationProcessing.promise - } + }) // Debounced rerun, let other missing dependencies be discovered before // the running next optimizeDeps enqueuedRerun = undefined if (handle) clearTimeout(handle) + if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) + newDepsToLogHandle = undefined handle = setTimeout(() => { handle = undefined - enqueuedRerun = () => rerun(ssr) + enqueuedRerun = rerun if (!currentlyProcessing) { enqueuedRerun() } @@ -270,4 +400,6 @@ export function createMissingImporterRegisterFn( // esbuild is run to generate the pre-bundle return missing } + + return optimizedDeps } diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index d1ac4eb249a8f3..e940617386eb35 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -123,18 +123,29 @@ export async function scanImports(config: ResolvedConfig): Promise<{ debug(`Scan completed in ${(performance.now() - start).toFixed(2)}ms:`, deps) return { - deps, + // Ensure a fixed order so hashes are stable and improve logs + deps: orderedDependencies(deps), missing } } +function orderedDependencies(deps: Record) { + const depsList = Object.entries(deps) + // Ensure the same browserHash for the same set of dependencies + depsList.sort((a, b) => a[0].localeCompare(b[0])) + return Object.fromEntries(depsList) +} + function globEntries(pattern: string | string[], config: ResolvedConfig) { return glob(pattern, { cwd: config.root, ignore: [ '**/node_modules/**', `**/${config.build.outDir}/**`, - `**/__tests__/**` + // if there aren't explicit entries, also ignore other common folders + ...(config.optimizeDeps.entries + ? [] + : [`**/__tests__/**`, `**/coverage/**`]) ], absolute: true }) @@ -165,7 +176,10 @@ function esbuildScanPlugin( } const resolved = await container.resolveId( id, - importer && normalizePath(importer) + importer && normalizePath(importer), + { + scan: true + } ) const res = resolved?.id seen.set(key, res) diff --git a/packages/vite/src/node/plugin.ts b/packages/vite/src/node/plugin.ts index 36674e242bd33e..354b246dd9f182 100644 --- a/packages/vite/src/node/plugin.ts +++ b/packages/vite/src/node/plugin.ts @@ -121,7 +121,14 @@ export interface Plugin extends RollupPlugin { this: PluginContext, source: string, importer: string | undefined, - options: { custom?: CustomPluginOptions; ssr?: boolean } + options: { + custom?: CustomPluginOptions + ssr?: boolean + /** + * @internal + */ + scan?: boolean + } ): Promise | ResolveIdResult load?( this: PluginContext, diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index b0c59bed808604..a3f8e441b0f933 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -3,7 +3,12 @@ import MagicString from 'magic-string' import path from 'path' import { fileToUrl } from './asset' import type { ResolvedConfig } from '../config' -import { multilineCommentsRE, singlelineCommentsRE } from '../utils' +import { + multilineCommentsRE, + singlelineCommentsRE, + stringsRE, + blankReplacer +} from '../utils' /** * Convert `new URL('./foo.png', import.meta.url)` to its resolved built URL @@ -27,12 +32,18 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { const importMetaUrlRE = /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*,?\s*\)/g const noCommentsCode = code - .replace(multilineCommentsRE, (m) => ' '.repeat(m.length)) - .replace(singlelineCommentsRE, (m) => ' '.repeat(m.length)) + .replace(multilineCommentsRE, blankReplacer) + .replace(singlelineCommentsRE, blankReplacer) + .replace(stringsRE, (m) => `'${'\0'.repeat(m.length - 2)}'`) + let s: MagicString | null = null let match: RegExpExecArray | null while ((match = importMetaUrlRE.exec(noCommentsCode))) { - const { 0: exp, 1: rawUrl, index } = match + const { 0: exp, 1: emptyUrl, index } = match + + const urlStart = exp.indexOf(emptyUrl) + index + const urlEnd = urlStart + emptyUrl.length + const rawUrl = code.slice(urlStart, urlEnd) if (!s) s = new MagicString(code) diff --git a/packages/vite/src/node/plugins/clientInjections.ts b/packages/vite/src/node/plugins/clientInjections.ts index e86bec0826d72f..1c9a0d393327c7 100644 --- a/packages/vite/src/node/plugins/clientInjections.ts +++ b/packages/vite/src/node/plugins/clientInjections.ts @@ -23,7 +23,7 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { const protocol = options.protocol || null const timeout = options.timeout || 30000 const overlay = options.overlay !== false - let port: number | string | false | undefined + let port: number | string | undefined if (isObject(config.server.hmr)) { port = config.server.hmr.clientPort || config.server.hmr.port } @@ -41,14 +41,14 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin { } return code - .replace(/__MODE__/g, JSON.stringify(config.mode)) - .replace(/__BASE__/g, JSON.stringify(config.base)) - .replace(/__DEFINES__/g, serializeDefine(config.define || {})) - .replace(/__HMR_PROTOCOL__/g, JSON.stringify(protocol)) - .replace(/__HMR_HOSTNAME__/g, JSON.stringify(host)) - .replace(/__HMR_PORT__/g, JSON.stringify(port)) - .replace(/__HMR_TIMEOUT__/g, JSON.stringify(timeout)) - .replace(/__HMR_ENABLE_OVERLAY__/g, JSON.stringify(overlay)) + .replace(`__MODE__`, JSON.stringify(config.mode)) + .replace(`__BASE__`, JSON.stringify(config.base)) + .replace(`__DEFINES__`, serializeDefine(config.define || {})) + .replace(`__HMR_PROTOCOL__`, JSON.stringify(protocol)) + .replace(`__HMR_HOSTNAME__`, JSON.stringify(host)) + .replace(`__HMR_PORT__`, JSON.stringify(port)) + .replace(`__HMR_TIMEOUT__`, JSON.stringify(timeout)) + .replace(`__HMR_ENABLE_OVERLAY__`, JSON.stringify(overlay)) } else if (!options?.ssr && code.includes('process.env.NODE_ENV')) { // replace process.env.NODE_ENV instead of defining a global // for it to avoid shimming a `process` object during dev, diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index db8e0a2679f74f..dbe9e7d1dfa00f 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -27,7 +27,7 @@ import type { } from 'rollup' import { dataToEsm } from '@rollup/pluginutils' import colors from 'picocolors' -import { CLIENT_PUBLIC_PATH } from '../constants' +import { CLIENT_PUBLIC_PATH, SPECIAL_QUERY_RE } from '../constants' import type { ResolveFn, ViteDevServer } from '../' import { getAssetFilename, @@ -36,7 +36,7 @@ import { checkPublicFile } from './asset' import MagicString from 'magic-string' -import type * as Postcss from 'postcss' +import type * as PostCSS from 'postcss' import type Sass from 'sass' // We need to disable check of extraneous import which is buggy for stylus, // and causes the CI tests fail, see: https://github.com/vitejs/vite/pull/2860 @@ -59,9 +59,15 @@ export interface CSSOptions { preprocessorOptions?: Record postcss?: | string - | (Postcss.ProcessOptions & { - plugins?: Postcss.Plugin[] + | (PostCSS.ProcessOptions & { + plugins?: PostCSS.Plugin[] }) + /** + * Enables css sourcemaps during dev + * @default false + * @experimental + */ + devSourcemap?: boolean } export interface CSSModulesOptions { @@ -163,7 +169,11 @@ export function cssPlugin(config: ResolvedConfig): Plugin { }, async transform(raw, id, options) { - if (!isCSSRequest(id) || commonjsProxyRE.test(id)) { + if ( + !isCSSRequest(id) || + commonjsProxyRE.test(id) || + SPECIAL_QUERY_RE.test(id) + ) { return } const ssr = options?.ssr === true @@ -280,7 +290,11 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { }, async transform(css, id, options) { - if (!isCSSRequest(id) || commonjsProxyRE.test(id)) { + if ( + !isCSSRequest(id) || + commonjsProxyRE.test(id) || + SPECIAL_QUERY_RE.test(id) + ) { return } @@ -301,9 +315,12 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { return `export default ${JSON.stringify(css)}` } - const sourcemap = this.getCombinedSourcemap() - await injectSourcesContent(sourcemap, cleanUrl(id), config.logger) - const cssContent = getCodeWithSourcemap('css', css, sourcemap) + let cssContent = css + if (config.css?.devSourcemap) { + const sourcemap = this.getCombinedSourcemap() + await injectSourcesContent(sourcemap, cleanUrl(id), config.logger) + cssContent = getCodeWithSourcemap('css', css, sourcemap) + } return [ `import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify( @@ -341,14 +358,21 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { styles.set(id, css) } + let code: string + if (usedRE.test(id)) { + if (inlined) { + code = `export default ${JSON.stringify( + await minifyCSS(css, config) + )}` + } else { + code = modulesCode || `export default ${JSON.stringify(css)}` + } + } else { + code = `export default ''` + } + return { - code: - modulesCode || - (usedRE.test(id) - ? `export default ${JSON.stringify( - inlined ? await minifyCSS(css, config) : css - )}` - : `export default ''`), + code, map: { mappings: '' }, // avoid the css module from being tree-shaken so that we can retrieve // it in renderChunk() @@ -600,11 +624,15 @@ async function compileCSS( ): Promise<{ code: string map?: SourceMapInput - ast?: Postcss.Result + ast?: PostCSS.Result modules?: Record deps?: Set }> { - const { modules: modulesOptions, preprocessorOptions } = config.css || {} + const { + modules: modulesOptions, + preprocessorOptions, + devSourcemap + } = config.css || {} const isModule = modulesOptions !== false && cssModuleRE.test(id) // although at serve time it can work without processing, we do need to // crawl them in order to register watch dependencies. @@ -653,6 +681,7 @@ async function compileCSS( } // important: set this for relative import resolving opts.filename = cleanUrl(id) + opts.enableSourcemap = devSourcemap ?? false const preprocessResult = await preProcessor( code, @@ -713,7 +742,7 @@ async function compileCSS( postcssPlugins.push( UrlRewritePostcssPlugin({ replacer: urlReplacer - }) as Postcss.Plugin + }) as PostCSS.Plugin ) if (isModule) { @@ -761,7 +790,9 @@ async function compileCSS( map: { inline: false, annotation: false, - sourcesContent: 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, } @@ -807,6 +838,16 @@ async function compileCSS( } } + if (!devSourcemap) { + return { + ast: postcssResult, + code: postcssResult.css, + map: { mappings: '' }, + modules, + deps + } + } + const rawPostcssMap = postcssResult.map.toJSON() const postcssMap = formatPostcssSourceMap( @@ -830,19 +871,33 @@ export function formatPostcssSourceMap( file: string ): ExistingRawSourceMap { const inputFileDir = path.dirname(file) - const sources = rawMap.sources + + const sources: string[] = [] + const sourcesContent: string[] = [] + for (const [i, source] of rawMap.sources.entries()) { // remove from sources, to prevent source map to be combined incorrectly - .filter((source) => source !== '') - .map((source) => { - const cleanSource = cleanUrl(decodeURIComponent(source)) - return normalizePath(path.resolve(inputFileDir, cleanSource)) - }) + if (source === '') continue + + const cleanSource = cleanUrl(decodeURIComponent(source)) + + // postcss returns virtual files + if (/^<.+>$/.test(cleanSource)) { + sources.push(`\0${cleanSource}`) + } else { + sources.push(normalizePath(path.resolve(inputFileDir, cleanSource))) + } + + if (rawMap.sourcesContent) { + sourcesContent.push(rawMap.sourcesContent[i]) + } + } return { file, mappings: rawMap.mappings, names: rawMap.names, sources, + sourcesContent, version: rawMap.version } } @@ -863,8 +918,8 @@ function combineSourcemapsIfExists( } interface PostCSSConfigResult { - options: Postcss.ProcessOptions - plugins: Postcss.Plugin[] + options: PostCSS.ProcessOptions + plugins: PostCSS.Plugin[] } async function resolvePostcssConfig( @@ -910,10 +965,12 @@ type CssUrlReplacer = ( // https://drafts.csswg.org/css-syntax-3/#identifier-code-point export const cssUrlRE = /(?<=^|[^\w\-\u0080-\uffff])url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/ +export const cssDataUriRE = + /(?<=^|[^\w\-\u0080-\uffff])data-uri\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/ export const importCssRE = /@import ('[^']+\.css'|"[^"]+\.css"|[^'")]+\.css)/ const cssImageSetRE = /image-set\(([^)]+)\)/ -const UrlRewritePostcssPlugin: Postcss.PluginCreator<{ +const UrlRewritePostcssPlugin: PostCSS.PluginCreator<{ replacer: CssUrlReplacer }> = (opts) => { if (!opts) { @@ -960,6 +1017,16 @@ function rewriteCssUrls( }) } +function rewriteCssDataUris( + css: string, + replacer: CssUrlReplacer +): Promise { + return asyncReplace(css, cssDataUriRE, async (match) => { + const [matched, rawUrl] = match + return await doUrlReplace(rawUrl, matched, replacer, 'data-uri') + }) +} + function rewriteImportCss( css: string, replacer: CssUrlReplacer @@ -985,7 +1052,8 @@ function rewriteCssImageSet( async function doUrlReplace( rawUrl: string, matched: string, - replacer: CssUrlReplacer + replacer: CssUrlReplacer, + funcName: string = 'url' ) { let wrap = '' const first = rawUrl[0] @@ -997,7 +1065,12 @@ async function doUrlReplace( return matched } - return `url(${wrap}${await replacer(rawUrl)}${wrap})` + const newUrl = await replacer(rawUrl) + if (wrap === '' && newUrl !== encodeURI(newUrl)) { + // The new url might need wrapping even if the original did not have it, e.g. if a space was added during replacement + wrap = "'" + } + return `${funcName}(${wrap}${newUrl}${wrap})` } async function doImportCSSReplace( @@ -1041,11 +1114,11 @@ async function hoistAtImports(css: string) { return (await postcss.default([AtImportHoistPlugin]).process(css)).css } -const AtImportHoistPlugin: Postcss.PluginCreator = () => { +const AtImportHoistPlugin: PostCSS.PluginCreator = () => { return { postcssPlugin: 'vite-hoist-at-imports', Once(root) { - const imports: Postcss.AtRule[] = [] + const imports: PostCSS.AtRule[] = [] root.walkAtRules((rule) => { if (rule.name === 'import') { // record in reverse so that can simply prepend to preserve order @@ -1078,6 +1151,7 @@ type StylePreprocessorOptions = { additionalData?: PreprocessorAdditionalData filename: string alias: Alias[] + enableSourcemap: boolean } type SassStylePreprocessorOptions = StylePreprocessorOptions & Sass.Options @@ -1167,7 +1241,8 @@ const scss: SassStylePreprocessor = async ( const { content: data, map: additionalMap } = await getSource( source, options.filename, - options.additionalData + options.additionalData, + options.enableSourcemap ) const finalOptions: Sass.Options = { ...options, @@ -1175,9 +1250,13 @@ const scss: SassStylePreprocessor = async ( file: options.filename, outFile: options.filename, importer, - sourceMap: true, - omitSourceMapUrl: true, - sourceMapRoot: path.dirname(options.filename) + ...(options.enableSourcemap + ? { + sourceMap: true, + omitSourceMapUrl: true, + sourceMapRoot: path.dirname(options.filename) + } + : {}) } try { @@ -1241,10 +1320,12 @@ async function rebaseUrls( const content = fs.readFileSync(file, 'utf-8') // no url() const hasUrls = cssUrlRE.test(content) + // data-uri() calls + const hasDataUris = cssDataUriRE.test(content) // no @import xxx.css const hasImportCss = importCssRE.test(content) - if (!hasUrls && !hasImportCss) { + if (!hasUrls && !hasDataUris && !hasImportCss) { return { file } } @@ -1273,6 +1354,10 @@ async function rebaseUrls( rebased = await rewriteCssUrls(rebased || content, rebaseFn) } + if (hasDataUris) { + rebased = await rewriteCssDataUris(rebased || content, rebaseFn) + } + return { file, contents: rebased @@ -1291,7 +1376,8 @@ const less: StylePreprocessor = async (source, root, options, resolvers) => { const { content, map: additionalMap } = await getSource( source, options.filename, - options.additionalData + options.additionalData, + options.enableSourcemap ) let result: Less.RenderOutput | undefined @@ -1299,10 +1385,14 @@ const less: StylePreprocessor = async (source, root, options, resolvers) => { result = await nodeLess.render(content, { ...options, plugins: [viteResolverPlugin, ...(options.plugins || [])], - sourceMap: { - outputSourceFiles: true, - sourceMapFileInline: false - } + ...(options.enableSourcemap + ? { + sourceMap: { + outputSourceFiles: true, + sourceMapFileInline: false + } + } + : {}) }) } catch (e) { const error = e as Less.RenderError @@ -1316,8 +1406,10 @@ const less: StylePreprocessor = async (source, root, options, resolvers) => { return { code: '', errors: [normalizedError], deps: [] } } - const map: ExistingRawSourceMap = JSON.parse(result.map) - delete map.sourcesContent + const map: ExistingRawSourceMap = result.map && JSON.parse(result.map) + if (map) { + delete map.sourcesContent + } return { code: result.css.toString(), @@ -1408,6 +1500,7 @@ const styl: StylePreprocessor = async (source, root, options) => { source, options.filename, options.additionalData, + options.enableSourcemap, '\n' ) // Get preprocessor options.imports dependencies as stylus @@ -1417,11 +1510,13 @@ const styl: StylePreprocessor = async (source, root, options) => { ) try { const ref = nodeStylus(content, options) - ref.set('sourcemap', { - comment: false, - inline: false, - basePath: root - }) + if (options.enableSourcemap) { + ref.set('sourcemap', { + comment: false, + inline: false, + basePath: root + }) + } const result = ref.render() @@ -1429,7 +1524,7 @@ const styl: StylePreprocessor = async (source, root, options) => { const deps = [...ref.deps(), ...importsDeps] // @ts-expect-error sourcemap exists - const map: ExistingRawSourceMap = ref.sourcemap + const map: ExistingRawSourceMap | undefined = ref.sourcemap return { code: result, @@ -1444,9 +1539,10 @@ const styl: StylePreprocessor = async (source, root, options) => { } function formatStylusSourceMap( - mapBefore: ExistingRawSourceMap, + mapBefore: ExistingRawSourceMap | undefined, root: string -): ExistingRawSourceMap { +): ExistingRawSourceMap | undefined { + if (!mapBefore) return undefined const map = { ...mapBefore } const resolveFromRoot = (p: string) => normalizePath(path.resolve(root, p)) @@ -1462,7 +1558,8 @@ function formatStylusSourceMap( async function getSource( source: string, filename: string, - additionalData?: PreprocessorAdditionalData, + additionalData: PreprocessorAdditionalData | undefined, + enableSourcemap: boolean, sep: string = '' ): Promise<{ content: string; map?: ExistingRawSourceMap }> { if (!additionalData) return { content: source } @@ -1475,6 +1572,10 @@ async function getSource( return newContent } + if (!enableSourcemap) { + return { content: additionalData + sep + source } + } + const ms = new MagicString(source) ms.appendLeft(0, sep) ms.appendLeft(0, additionalData) diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index c8a5617a92b253..25ad91582140c3 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -5,7 +5,8 @@ import type { OutputAsset, OutputBundle, OutputChunk, - RollupError + RollupError, + SourceMapInput } from 'rollup' import { cleanUrl, @@ -54,7 +55,7 @@ export const isHTMLRequest = (request: string): boolean => // HTML Proxy Caches are stored by config -> filePath -> index export const htmlProxyMap = new WeakMap< ResolvedConfig, - Map> + Map> >() // HTML Proxy Transform result are stored by config @@ -83,7 +84,7 @@ export function htmlInlineProxyPlugin(config: ResolvedConfig): Plugin { const file = cleanUrl(id) const url = file.replace(normalizePath(config.root), '') const result = htmlProxyMap.get(config)!.get(url)![index] - if (typeof result === 'string') { + if (result) { return result } else { throw new Error(`No matching HTML proxy module found from ${id}`) @@ -97,7 +98,7 @@ export function addToHTMLProxyCache( config: ResolvedConfig, filePath: string, index: number, - code: string + result: { code: string; map?: SourceMapInput } ): void { if (!htmlProxyMap.get(config)) { htmlProxyMap.set(config, new Map()) @@ -105,7 +106,7 @@ export function addToHTMLProxyCache( if (!htmlProxyMap.get(config)!.get(filePath)) { htmlProxyMap.get(config)!.set(filePath, []) } - htmlProxyMap.get(config)!.get(filePath)![index] = code + htmlProxyMap.get(config)!.get(filePath)![index] = result } export function addToHTMLProxyTransformResult( @@ -284,12 +285,9 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { .join('') // const filePath = id.replace(normalizePath(config.root), '') - addToHTMLProxyCache( - config, - filePath, - inlineModuleIndex, - contents - ) + addToHTMLProxyCache(config, filePath, inlineModuleIndex, { + code: contents + }) js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.js"` shouldRemove = true } @@ -364,11 +362,11 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { const styleNode = inlineStyle.value! const code = styleNode.content! const filePath = id.replace(normalizePath(config.root), '') - addToHTMLProxyCache(config, filePath, inlineModuleIndex, code) + addToHTMLProxyCache(config, filePath, inlineModuleIndex, { code }) // will transform with css plugin and cache result with css-post plugin js += `\nimport "${id}?html-proxy&inline-css&index=${inlineModuleIndex}.css"` - // will transfrom in `applyHtmlTransforms` + // will transform in `applyHtmlTransforms` s.overwrite( styleNode.loc.start.offset, styleNode.loc.end.offset, @@ -382,12 +380,9 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { const styleNode = node.children.pop() as TextNode const filePath = id.replace(normalizePath(config.root), '') inlineModuleIndex++ - addToHTMLProxyCache( - config, - filePath, - inlineModuleIndex, - styleNode.content - ) + addToHTMLProxyCache(config, filePath, inlineModuleIndex, { + code: styleNode.content + }) js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.css"` shouldRemove = true } @@ -599,7 +594,6 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { } } - const shortEmitName = path.posix.relative(config.root, id) // no use assets plugin because it will emit file let match: RegExpExecArray | null let s: MagicString | undefined @@ -617,8 +611,9 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { if (s) { result = s.toString() } + const relativeUrlPath = path.posix.relative(config.root, id) result = await applyHtmlTransforms(result, postHooks, { - path: '/' + shortEmitName, + path: '/' + relativeUrlPath, filename: id, bundle, chunk @@ -633,6 +628,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { delete bundle[chunk.fileName] } + const shortEmitName = path.relative(config.root, id) this.emitFile({ type: 'asset', fileName: shortEmitName, @@ -710,6 +706,8 @@ export function resolveHtmlTransforms( return [preHooks, postHooks] } +export const maybeVirtualHtmlSet = new Set() + export async function applyHtmlTransforms( html: string, hooks: IndexHtmlTransformHook[], @@ -720,6 +718,8 @@ export async function applyHtmlTransforms( const bodyTags: HtmlTagDescriptor[] = [] const bodyPrependTags: HtmlTagDescriptor[] = [] + maybeVirtualHtmlSet.add(ctx.filename) + for (const hook of hooks) { const res = await hook(html, ctx) if (!res) { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index a4d2acc44db953..31e9cd76faa8a0 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -122,6 +122,12 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { }, async transform(source, importer, options) { + // In a real app `server` is always defined, but it is undefined when + // running src/node/server/__tests__/pluginContainer.spec.ts + if (!server) { + return null + } + const ssr = options?.ssr === true const prettyImporter = prettifyUrl(importer, root) @@ -159,7 +165,13 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { ) } + const { moduleGraph } = server + // since we are already in the transform phase of the importer, it must + // have been loaded so its entry is guaranteed in the module graph. + const importerModule = moduleGraph.getModuleById(importer)! + if (!imports.length) { + importerModule.isSelfAccepting = false isDebug && debug( `${timeFrom(start)} ${colors.dim(`[no imports] ${prettyImporter}`)}` @@ -173,11 +185,6 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { let needQueryInjectHelper = false let s: MagicString | undefined const str = () => s || (s = new MagicString(source)) - // vite-only server context - const { moduleGraph } = server - // since we are already in the transform phase of the importer, it must - // have been loaded so its entry is guaranteed in the module graph. - const importerModule = moduleGraph.getModuleById(importer)! const importedUrls = new Set() const staticImportedUrls = new Set() const acceptedUrls = new Set<{ @@ -197,19 +204,20 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } let importerFile = importer - if ( - moduleListContains(config.optimizeDeps?.exclude, url) && - server._optimizeDepsMetadata - ) { - // if the dependency encountered in the optimized file was excluded from the optimization - // the dependency needs to be resolved starting from the original source location of the optimized file - // because starting from node_modules/.vite will not find the dependency if it was not hoisted - // (that is, if it is under node_modules directory in the package source of the optimized file) - for (const optimizedModule of Object.values( - server._optimizeDepsMetadata.optimized - )) { - if (optimizedModule.file === importerModule.file) { - importerFile = optimizedModule.src + if (moduleListContains(config.optimizeDeps?.exclude, url)) { + const optimizedDeps = server._optimizedDeps + if (optimizedDeps) { + await optimizedDeps.scanProcessing + + // if the dependency encountered in the optimized file was excluded from the optimization + // the dependency needs to be resolved starting from the original source location of the optimized file + // because starting from node_modules/.vite will not find the dependency if it was not hoisted + // (that is, if it is under node_modules directory in the package source of the optimized file) + for (const optimizedModule of optimizedDeps.metadata.depInfoList) { + if (!optimizedModule.src) continue // Ignore chunks + if (optimizedModule.file === importerModule.file) { + importerFile = optimizedModule.src + } } } } @@ -439,6 +447,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { importRewrites.push(async () => { let rewriteDone = false if ( + server?._optimizedDeps && isOptimizedDepFile(resolvedId, config) && !resolvedId.match(optimizedDepChunkRE) ) { @@ -450,7 +459,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { const file = cleanUrl(resolvedId) // Remove ?v={hash} const needsInterop = await optimizedDepNeedsInterop( - server._optimizeDepsMetadata!, + server._optimizedDeps!.metadata, file ) @@ -516,7 +525,10 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } } else if (!importer.startsWith(clientDir) && !ssr) { // check @vite-ignore which suppresses dynamic import warning - const hasViteIgnore = /\/\*\s*@vite-ignore\s*\*\//.test(rawUrl) + const hasViteIgnore = /\/\*\s*@vite-ignore\s*\*\//.test( + // complete expression inside parens + source.slice(dynamicIndex + 1, end) + ) const url = rawUrl .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '') diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index c8bef0231af757..91ce663b9f8111 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -86,6 +86,7 @@ function preload(baseModule: () => Promise<{}>, deps?: string[]) { export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { const ssr = !!config.build.ssr const insertPreload = !(ssr || !!config.build.lib) + const isWorker = config.isWorker const scriptRel = config.build.polyfillModulePreload ? `'modulepreload'` @@ -120,6 +121,11 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { return } + if (isWorker) { + // preload method use `document` and can't run in the worker + return + } + await init let imports: readonly ImportSpecifier[] = [] @@ -132,7 +138,6 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { if (!imports.length) { return null } - let s: MagicString | undefined const str = () => s || (s = new MagicString(source)) let needPreloadHelper = false @@ -241,7 +246,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { }, generateBundle({ format }, bundle) { - if (format !== 'es' || ssr) { + if (format !== 'es' || ssr || isWorker) { return } diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index d5a45eb085b0b9..2d34b99aebf1c5 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -9,7 +9,7 @@ import { importAnalysisPlugin } from './importAnalysis' import { cssPlugin, cssPostPlugin } from './css' import { assetPlugin } from './asset' import { clientInjectionsPlugin } from './clientInjections' -import { htmlInlineProxyPlugin } from './html' +import { buildHtmlPlugin, htmlInlineProxyPlugin } from './html' import { wasmPlugin } from './wasm' import { modulePreloadPolyfillPlugin } from './modulePreloadPolyfill' import { webWorkerPlugin } from './worker' @@ -64,12 +64,13 @@ export async function resolvePlugins( ), wasmPlugin(config), webWorkerPlugin(config), - workerImportMetaUrlPlugin(config), assetPlugin(config), ...normalPlugins, definePlugin(config), cssPostPlugin(config), config.build.ssr ? ssrRequireHookPlugin(config) : null, + isBuild && buildHtmlPlugin(config), + workerImportMetaUrlPlugin(config), ...buildPlugins.pre, ...postPlugins, ...buildPlugins.post, diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index 8fbdca8d08905f..adab1bd9756251 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -3,7 +3,7 @@ import type { Plugin } from '../plugin' import colors from 'picocolors' import { DEP_VERSION_RE } from '../constants' import { cleanUrl, createDebugger } from '../utils' -import { isOptimizedDepFile, optimizeDepInfoFromFile } from '../optimizer' +import { isOptimizedDepFile, optimizedDepInfoFromFile } from '../optimizer' import type { ViteDevServer } from '..' export const ERR_OPTIMIZE_DEPS_PROCESSING_ERROR = @@ -25,7 +25,7 @@ export function optimizedDepsPlugin(): Plugin { async load(id) { if (server && isOptimizedDepFile(id, server.config)) { - const metadata = server?._optimizeDepsMetadata + const metadata = server?._optimizedDeps?.metadata if (metadata) { const file = cleanUrl(id) const versionMatch = id.match(DEP_VERSION_RE) @@ -34,7 +34,7 @@ export function optimizedDepsPlugin(): Plugin { : undefined // Search in both the currently optimized and newly discovered deps - const info = optimizeDepInfoFromFile(metadata, file) + const info = optimizedDepInfoFromFile(metadata, file) if (info) { if (browserHash && info.browserHash !== browserHash) { throwOutdatedRequest(id) @@ -49,9 +49,9 @@ export function optimizedDepsPlugin(): Plugin { throwProcessingError(id) return } - const newMetadata = server._optimizeDepsMetadata + const newMetadata = server._optimizedDeps?.metadata if (metadata !== newMetadata) { - const currentInfo = optimizeDepInfoFromFile(newMetadata!, file) + const currentInfo = optimizedDepInfoFromFile(newMetadata!, file) if (info.browserHash !== currentInfo?.browserHash) { throwOutdatedRequest(id) } diff --git a/packages/vite/src/node/plugins/preAlias.ts b/packages/vite/src/node/plugins/preAlias.ts index 75a0d8e5e6f9dc..dadb16aa4c28a9 100644 --- a/packages/vite/src/node/plugins/preAlias.ts +++ b/packages/vite/src/node/plugins/preAlias.ts @@ -13,9 +13,9 @@ export function preAliasPlugin(): Plugin { configureServer(_server) { server = _server }, - resolveId(id, importer, options) { - if (!options?.ssr && bareImportRE.test(id)) { - return tryOptimizedResolve(id, server, importer) + async resolveId(id, importer, options) { + if (!options?.ssr && bareImportRE.test(id) && !options?.scan) { + return await tryOptimizedResolve(id, server, importer) } } } diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 5dfaffafcbb7e4..10e2989af1114f 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -7,6 +7,7 @@ import { SPECIAL_QUERY_RE, DEFAULT_EXTENSIONS, DEFAULT_MAIN_FIELDS, + KNOWN_ESM_MAIN_FIELDS, OPTIMIZABLE_ENTRY_RE, DEP_VERSION_RE } from '../constants' @@ -33,7 +34,8 @@ import { import { createIsOptimizedDepUrl, isOptimizedDepFile, - optimizeDepInfoFromFile + optimizedDepInfoFromFile, + optimizedDepInfoFromId } from '../optimizer' import type { OptimizedDepInfo } from '../optimizer' import type { ViteDevServer, SSROptions } from '..' @@ -83,6 +85,8 @@ export interface InternalResolveOptions extends ResolveOptions { // should also try import from `.ts/tsx/mts/cts` source file as fallback. isFromTsImporter?: boolean tryEsmOnly?: boolean + // True when resolving during the scan phase to discover dependencies + scan?: boolean } export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { @@ -106,7 +110,7 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { isOptimizedDepUrl = createIsOptimizedDepUrl(server.config) }, - resolveId(id, importer, resolveOpts) { + async resolveId(id, importer, resolveOpts) { const ssr = resolveOpts?.ssr === true if (id.startsWith(browserExternalId)) { return id @@ -127,7 +131,8 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { isRequire, ...baseOptions, - isFromTsImporter: isTsRequest(importer ?? '') + isFromTsImporter: isTsRequest(importer ?? ''), + scan: resolveOpts?.scan ?? baseOptions.scan } let res: string | PartialResolvedId | undefined @@ -136,9 +141,10 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { // tryFileResolve or /fs/ resolution but these files may not yet // exists if we are in the middle of a deps re-processing if (asSrc && isOptimizedDepUrl?.(id)) { - return id.startsWith(FS_PREFIX) + const optimizedPath = id.startsWith(FS_PREFIX) ? fsPathFromId(id) : normalizePath(ensureVolumeInPath(path.resolve(root, id.slice(1)))) + return optimizedPath } // explicit fs paths that starts with /@fs/* @@ -169,12 +175,15 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { const normalizedFsPath = normalizePath(fsPath) - if (server && isOptimizedDepFile(normalizedFsPath, server!.config)) { + if ( + server?._optimizedDeps && + isOptimizedDepFile(normalizedFsPath, server!.config) + ) { // Optimized files could not yet exist in disk, resolve to the full path // Inject the current browserHash version if the path doesn't have one if (!normalizedFsPath.match(DEP_VERSION_RE)) { - const browserHash = optimizeDepInfoFromFile( - server._optimizeDepsMetadata!, + const browserHash = optimizedDepInfoFromFile( + server._optimizedDeps!.metadata!, normalizedFsPath )?.browserHash if (browserHash) { @@ -252,7 +261,8 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { asSrc && server && !ssr && - (res = tryOptimizedResolve(id, server, importer)) + !options.scan && + (res = await tryOptimizedResolve(id, server, importer)) ) { return res } @@ -323,23 +333,28 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { } } -function tryFsResolve( - fsPath: string, - options: InternalResolveOptions, - tryIndex = true, - targetWeb = true -): string | undefined { - let file = fsPath +function splitFileAndPostfix(path: string) { + let file = path let postfix = '' - let postfixIndex = fsPath.indexOf('?') + let postfixIndex = path.indexOf('?') if (postfixIndex < 0) { - postfixIndex = fsPath.indexOf('#') + postfixIndex = path.indexOf('#') } if (postfixIndex > 0) { - file = fsPath.slice(0, postfixIndex) - postfix = fsPath.slice(postfixIndex) + file = path.slice(0, postfixIndex) + postfix = path.slice(postfixIndex) } + return { file, postfix } +} + +function tryFsResolve( + fsPath: string, + options: InternalResolveOptions, + tryIndex = true, + targetWeb = true +): string | undefined { + const { file, postfix } = splitFileAndPostfix(fsPath) let res: string | undefined @@ -606,7 +621,8 @@ export function tryNodeResolve( if ( !resolved.includes('node_modules') || // linked !server || // build - !server._registerMissingImport // initial esbuild scan phase + !server._optimizedDeps || // resolving before listening to the server + options.scan // initial esbuild scan phase ) { return { id: resolved } } @@ -627,14 +643,17 @@ export function tryNodeResolve( // otherwise we may introduce duplicated modules for externalized files // from pre-bundled deps. - const versionHash = server._optimizeDepsMetadata?.browserHash + const versionHash = server._optimizedDeps!.metadata.browserHash if (versionHash && isJsType) { resolved = injectQuery(resolved, `v=${versionHash}`) } } else { // this is a missing import, queue optimize-deps re-run and // get a resolved its optimized info - const optimizedInfo = server._registerMissingImport!(id, resolved, ssr) + const optimizedInfo = server._optimizedDeps!.registerMissingImport( + id, + resolved + ) resolved = getOptimizedUrl(optimizedInfo) } return { id: resolved! } @@ -644,24 +663,20 @@ export function tryNodeResolve( const getOptimizedUrl = (optimizedData: OptimizedDepInfo) => `${optimizedData.file}?v=${optimizedData.browserHash}` -export function tryOptimizedResolve( +export async function tryOptimizedResolve( id: string, server: ViteDevServer, importer?: string -): string | undefined { - const depData = server._optimizeDepsMetadata +): Promise { + const optimizedDeps = server._optimizedDeps - if (!depData) return + if (!optimizedDeps) return - // check if id has been optimized - const isOptimized = depData.optimized[id] - if (isOptimized) { - return getOptimizedUrl(isOptimized) - } + await optimizedDeps.scanProcessing - const isChunk = depData.chunks[id] - if (isChunk) { - return getOptimizedUrl(isChunk) + const depInfo = optimizedDepInfoFromId(optimizedDeps.metadata, id) + if (depInfo) { + return getOptimizedUrl(depInfo) } if (!importer) return @@ -669,7 +684,10 @@ export function tryOptimizedResolve( // further check if id is imported by nested dependency let resolvedSrc: string | undefined - for (const [pkgPath, optimizedData] of Object.entries(depData.optimized)) { + for (const optimizedData of optimizedDeps.metadata.depInfoList) { + if (!optimizedData.src) continue // Ignore chunks + + const pkgPath = optimizedData.id // check for scenarios, e.g. // pkgPath => "my-lib > foo" // id => "foo" @@ -726,7 +744,11 @@ export function resolvePackageEntry( : isObject(data.browser) && data.browser['.'] if (browserEntry) { // check if the package also has a "module" field. - if (typeof data.module === 'string' && data.module !== browserEntry) { + if ( + !options.isRequire && + typeof data.module === 'string' && + data.module !== browserEntry + ) { // if both are present, we may have a problem: some package points both // to ESM, with "module" targeting Node.js, while some packages points // "module" to browser ESM and "browser" to UMD. @@ -756,6 +778,11 @@ export function resolvePackageEntry( if (!entryPoint || entryPoint.endsWith('.mjs')) { for (const field of options.mainFields || DEFAULT_MAIN_FIELDS) { + // If the initiator is a `require` call, don't use the ESM entries + if (options.isRequire && KNOWN_ESM_MAIN_FIELDS.includes(field)) { + continue + } + if (typeof data[field] === 'string') { entryPoint = data[field] break @@ -826,6 +853,7 @@ function resolveExports( if (options.conditions) { conditions.push(...options.conditions) } + return _resolveExports(pkg, key, { browser: targetWeb, require: options.isRequire, @@ -856,12 +884,14 @@ function resolveDeepImport( // map relative based on exports data if (exportsField) { if (isObject(exportsField) && !Array.isArray(exportsField)) { - relativeId = resolveExports( - data, - cleanUrl(relativeId), - options, - targetWeb - ) + // resolve without postfix (see #7098) + const { file, postfix } = splitFileAndPostfix(relativeId) + const exportsId = resolveExports(data, file, options, targetWeb) + if (exportsId !== undefined) { + relativeId = exportsId + postfix + } else { + relativeId = undefined + } } else { // not exposed relativeId = undefined @@ -873,9 +903,11 @@ function resolveDeepImport( ) } } else if (targetWeb && isObject(browserField)) { - const mapped = mapWithBrowserField(relativeId, browserField) + // resolve without postfix (see #7098) + const { file, postfix } = splitFileAndPostfix(relativeId) + const mapped = mapWithBrowserField(file, browserField) if (mapped) { - relativeId = mapped + relativeId = mapped + postfix } else if (mapped === false) { return (webResolvedImports[id] = browserExternalId) } diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 70bfd916e21cab..4113b7153f9b35 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -6,13 +6,71 @@ import type Rollup from 'rollup' import { ENV_PUBLIC_PATH } from '../constants' import path from 'path' import { onRollupWarning } from '../build' +import type { TransformPluginContext, EmittedFile } from 'rollup' + +interface WorkerCache { + // save worker bundle emitted files avoid overwrites the same file. + // + assets: Map + chunks: Map + // worker bundle don't deps on any more worker runtime info an id only had an result. + // save worker bundled file id to avoid repeated execution of bundles + // + bundle: Map + // nested worker bundle context don't had file what emitted by outside bundle + // save the hash to id to rewrite truth id. + // + emitted: Map +} const WorkerFileId = 'worker_file' +const workerCache = new WeakMap() + +function emitWorkerFile( + ctx: Rollup.TransformPluginContext, + config: ResolvedConfig, + asset: EmittedFile, + type: 'assets' | 'chunks' +): string { + const fileName = asset.fileName! + const workerMap = workerCache.get(config)! + + if (workerMap[type].has(fileName)) { + return workerMap[type].get(fileName)! + } + const hash = ctx.emitFile(asset) + workerMap[type].set(fileName, hash) + workerMap.emitted.set(hash, fileName) + return hash +} + +function emitWorkerAssets( + ctx: Rollup.TransformPluginContext, + config: ResolvedConfig, + asset: EmittedFile +) { + const { format } = config.worker + return emitWorkerFile( + ctx, + config, + asset, + format === 'es' ? 'chunks' : 'assets' + ) +} + +function emitWorkerChunks( + ctx: Rollup.TransformPluginContext, + config: ResolvedConfig, + asset: EmittedFile +) { + return emitWorkerFile(ctx, config, asset, 'chunks') +} export async function bundleWorkerEntry( ctx: Rollup.TransformPluginContext, config: ResolvedConfig, - id: string + id: string, + query: Record | null ): Promise { // bundle the file as entry to support imports const rollup = require('rollup') as typeof Rollup @@ -26,22 +84,24 @@ export async function bundleWorkerEntry( }, preserveEntrySignatures: false }) - let code: string + let chunk: Rollup.OutputChunk try { const { - output: [outputCode, ...outputChunks] + output: [outputChunk, ...outputChunks] } = await bundle.generate({ format, sourcemap: config.build.sourcemap }) - code = outputCode.code + chunk = outputChunk outputChunks.forEach((outputChunk) => { if (outputChunk.type === 'asset') { - ctx.emitFile(outputChunk) - } - if (outputChunk.type === 'chunk') { - ctx.emitFile({ - fileName: `${config.build.assetsDir}/${outputChunk.fileName}`, + emitWorkerAssets(ctx, config, outputChunk) + } else if (outputChunk.type === 'chunk') { + emitWorkerChunks(ctx, config, { + fileName: path.posix.join( + config.build.assetsDir, + outputChunk.fileName + ), source: outputChunk.code, type: 'asset' }) @@ -50,15 +110,104 @@ export async function bundleWorkerEntry( } finally { await bundle.close() } + return emitSourcemapForWorkerEntry(ctx, config, id, query, chunk) +} + +function emitSourcemapForWorkerEntry( + context: TransformPluginContext, + config: ResolvedConfig, + id: string, + query: Record | null, + chunk: Rollup.OutputChunk +): Buffer { + let { code, map: sourcemap } = chunk + if (sourcemap) { + if (config.build.sourcemap === 'inline') { + // Manually add the sourcemap to the code if configured for inline sourcemaps. + // TODO: Remove when https://github.com/rollup/rollup/issues/3913 is resolved + // Currently seems that it won't be resolved until Rollup 3 + const dataUrl = sourcemap.toUrl() + code += `//# sourceMappingURL=${dataUrl}` + } else if ( + config.build.sourcemap === 'hidden' || + config.build.sourcemap === true + ) { + const basename = path.parse(cleanUrl(id)).name + const data = sourcemap.toString() + const content = Buffer.from(data) + const contentHash = getAssetHash(content) + const fileName = `${basename}.${contentHash}.js.map` + const filePath = path.posix.join(config.build.assetsDir, fileName) + if (!context.cache.has(contentHash)) { + context.cache.set(contentHash, true) + context.emitFile({ + fileName: filePath, + type: 'asset', + source: data + }) + } + + // Emit the comment that tells the JS debugger where it can find the + // sourcemap file. + // 'hidden' causes the sourcemap file to be created but + // the comment in the file to be omitted. + if (config.build.sourcemap === true) { + // inline web workers need to use the full sourcemap path + // non-inline web workers can use a relative path + const sourceMapUrl = query?.inline != null ? filePath : fileName + code += `//# sourceMappingURL=${sourceMapUrl}` + } + } + } + return Buffer.from(code) } +export async function workerFileToUrl( + ctx: Rollup.TransformPluginContext, + config: ResolvedConfig, + id: string, + query: Record | null +): Promise { + const workerMap = workerCache.get(config)! + + let hash = workerMap.bundle.get(id) + if (hash) { + // rewrite truth id, no need to replace by asset plugin + return config.base + workerMap.emitted.get(hash)! + } + const code = await bundleWorkerEntry(ctx, config, id, query) + const basename = path.parse(cleanUrl(id)).name + const contentHash = getAssetHash(code) + const fileName = path.posix.join( + config.build.assetsDir, + `${basename}.${contentHash}.js` + ) + hash = emitWorkerAssets(ctx, config, { + fileName, + type: 'asset', + source: code + }) + workerMap.bundle.set(id, hash) + return `__VITE_ASSET__${hash}__` +} + export function webWorkerPlugin(config: ResolvedConfig): Plugin { const isBuild = config.command === 'build' + const isWorker = config.isWorker return { name: 'vite:worker', + buildStart() { + workerCache.set(config, { + assets: new Map(), + chunks: new Map(), + bundle: new Map(), + emitted: new Map() + }) + }, + load(id) { if (isBuild) { const parsedQuery = parseRequest(id) @@ -87,12 +236,13 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { let url: string if (isBuild) { - const code = await bundleWorkerEntry(this, config, id) if (query.inline != null) { + const code = await bundleWorkerEntry(this, config, id, query) const { format } = config.worker const workerOptions = format === 'es' ? '{type: "module"}' : '{}' // inline as blob data url - return `const encodedJs = "${code.toString('base64')}"; + return { + code: `const encodedJs = "${code.toString('base64')}"; const blob = typeof window !== "undefined" && window.Blob && new Blob([atob(encodedJs)], { type: "text/javascript;charset=utf-8" }); export default function WorkerWrapper() { const objURL = blob && (window.URL || window.webkitURL).createObjectURL(blob); @@ -101,19 +251,13 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { } finally { objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL); } - }` + }`, + + // Empty sourcemap to supress Rollup warning + map: { mappings: '' } + } } else { - const basename = path.parse(cleanUrl(id)).name - const contentHash = getAssetHash(code) - const fileName = path.posix.join( - config.build.assetsDir, - `${basename}.${contentHash}.js` - ) - url = `__VITE_ASSET__${this.emitFile({ - fileName, - type: 'asset', - source: code - })}__` + url = await workerFileToUrl(this, config, id, query) } } else { url = await fileToUrl(cleanUrl(id), config, this) @@ -124,11 +268,20 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { query.sharedworker != null ? 'SharedWorker' : 'Worker' const workerOptions = { type: 'module' } - return `export default function WorkerWrapper() { - return new ${workerConstructor}(${JSON.stringify( - url - )}, ${JSON.stringify(workerOptions, null, 2)}) - }` + return { + code: `export default function WorkerWrapper() { + return new ${workerConstructor}(${JSON.stringify( + url + )}, ${JSON.stringify(workerOptions, null, 2)}) + }`, + map: { mappings: '' } // Empty sourcemap to supress Rolup warning + } + }, + + renderChunk(code) { + if (isWorker && code.includes('import.meta.url')) { + return code.replace('import.meta.url', 'self.location.href') + } } } } diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index f3ed3cf1b8cbe0..c9a2903d9a4142 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -1,20 +1,22 @@ import JSON5 from 'json5' import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' -import { getAssetHash, fileToUrl } from './asset' +import { fileToUrl } from './asset' import { blankReplacer, cleanUrl, injectQuery, multilineCommentsRE, - singlelineCommentsRE + singlelineCommentsRE, + stringsRE } from '../utils' import path from 'path' -import { bundleWorkerEntry } from './worker' +import { workerFileToUrl } from './worker' import { parseRequest } from '../utils' import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants' import MagicString from 'magic-string' import type { ViteDevServer } from '..' +import type { RollupError } from 'rollup' type WorkerType = 'classic' | 'module' | 'ignore' @@ -25,14 +27,26 @@ function getWorkerType( noCommentsCode: string, i: number ): WorkerType { + function err(e: string, pos: number) { + const error = new Error(e) as RollupError + error.pos = pos + throw error + } + const commaIndex = noCommentsCode.indexOf(',', i) if (commaIndex === -1) { return 'classic' } const endIndex = noCommentsCode.indexOf(')', i) + // case: ') ... ,' mean no worker options params + if (commaIndex > endIndex) { + return 'classic' + } + // need to find in comment code let workerOptsString = code.substring(commaIndex + 1, endIndex) + const hasViteIgnore = /\/\*\s*@vite-ignore\s*\*\//.test(workerOptsString) if (hasViteIgnore) { return 'ignore' @@ -49,9 +63,10 @@ function getWorkerType( workerOpts = JSON5.parse(workerOptsString) } catch (e) { // can't parse by JSON5, so the worker options had unexpect char. - throw new Error( + err( 'Vite is unable to parse the worker options as the value is not static.' + - 'To ignore this error, please use /* @vite-ignore */ in the worker options.' + 'To ignore this error, please use /* @vite-ignore */ in the worker options.', + commaIndex + 1 ) } @@ -108,12 +123,21 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { const noCommentsCode = code .replace(multilineCommentsRE, blankReplacer) .replace(singlelineCommentsRE, blankReplacer) + + const noStringCode = noCommentsCode.replace( + stringsRE, + (m) => `'${' '.repeat(m.length - 2)}'` + ) let match: RegExpExecArray | null let s: MagicString | null = null - while ((match = importMetaUrlRE.exec(noCommentsCode))) { - const { 0: allExp, 2: exp, 3: rawUrl, index } = match + while ((match = importMetaUrlRE.exec(noStringCode))) { + const { 0: allExp, 2: exp, 3: emptyUrl, index } = match const urlIndex = allExp.indexOf(exp) + index + const urlStart = allExp.indexOf(emptyUrl) + index + const urlEnd = urlStart + emptyUrl.length + const rawUrl = code.slice(urlStart, urlEnd) + if (options?.ssr) { this.error( `\`new URL(url, import.meta.url)\` is not supported in SSR.`, @@ -138,18 +162,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { const file = path.resolve(path.dirname(id), rawUrl.slice(1, -1)) let url: string if (isBuild) { - const content = await bundleWorkerEntry(this, config, file) - const basename = path.parse(cleanUrl(file)).name - const contentHash = getAssetHash(content) - const fileName = path.posix.join( - config.build.assetsDir, - `${basename}.${contentHash}.js` - ) - url = `__VITE_ASSET__${this.emitFile({ - fileName, - type: 'asset', - source: content - })}__` + url = await workerFileToUrl(this, config, file, query) } else { url = await fileToUrl(cleanUrl(file), config, this) url = injectQuery(url, WORKER_FILE_ID) diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index 56dee04e2e2e07..c00f62a9cb8f0c 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -78,8 +78,9 @@ export async function preview( } // proxy - if (config.preview.proxy) { - app.use(proxyMiddleware(httpServer, config)) + const { proxy } = config.preview + if (proxy) { + app.use(proxyMiddleware(httpServer, proxy, config)) } app.use(compression()) diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index edb73785247b6f..8d33554706dee2 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -18,7 +18,7 @@ const normalizedClientDir = normalizePath(CLIENT_DIR) export interface HmrOptions { protocol?: string host?: string - port?: number | false + port?: number clientPort?: number path?: string timeout?: number @@ -44,6 +44,7 @@ export async function handleHMRUpdate( ): Promise { const { ws, config, moduleGraph } = server const shortFile = getShortName(file, config.root) + const fileName = path.basename(file) const isConfig = file === config.configFile const isConfigDependency = config.configFileDependencies.some( @@ -51,7 +52,7 @@ export async function handleHMRUpdate( ) const isEnv = config.inlineConfig.envFile !== false && - (file === '.env' || file.startsWith('.env.')) + (fileName === '.env' || fileName.startsWith('.env.')) if (isConfig || isConfigDependency || isEnv) { // auto restart server debugHmr(`[config change] ${colors.dim(shortFile)}`) @@ -225,6 +226,13 @@ function propagateUpdate( }>, currentChain: ModuleNode[] = [node] ): boolean /* hasDeadEnd */ { + // #7561 + // if the imports of `node` have not been analyzed, then `node` has not + // been loaded in the browser and we should stop propagation. + if (node.id && node.isSelfAccepting === undefined) { + return false + } + if (node.isSelfAccepting) { boundaries.add({ boundary: node, diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index d56a6a705d5416..de80ac1147ff0f 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -44,8 +44,6 @@ import { transformRequest } from './transformRequest' import type { ESBuildTransformResult } from '../plugins/esbuild' import { transformWithEsbuild } from '../plugins/esbuild' import type { TransformOptions as EsbuildTransformOptions } from 'esbuild' -import type { DepOptimizationMetadata, OptimizedDepInfo } from '../optimizer' -import { createOptimizeDepsRun } from '../optimizer' import { ssrLoadModule } from '../ssr/ssrModuleLoader' import { resolveSSRExternal } from '../ssr/ssrExternal' import { @@ -53,7 +51,8 @@ import { ssrRewriteStacktrace } from '../ssr/ssrStacktrace' import { ssrTransform } from '../ssr/ssrTransform' -import { createMissingImporterRegisterFn } from '../optimizer/registerMissing' +import { createOptimizedDeps } from '../optimizer/registerMissing' +import type { OptimizedDeps } from '../optimizer' import { resolveHostname } from '../utils' import { searchForWorkspaceRoot } from './searchRoot' import { CLIENT_DIR } from '../constants' @@ -257,7 +256,7 @@ export interface ViteDevServer { /** * @internal */ - _optimizeDepsMetadata: DepOptimizationMetadata | null + _optimizedDeps: OptimizedDeps | null /** * Deps that are externalized * @internal @@ -284,16 +283,6 @@ export interface ViteDevServer { * @internal */ _forceOptimizeOnRestart: boolean - /** - * @internal - */ - _registerMissingImport: - | (( - id: string, - resolved: string, - ssr: boolean | undefined - ) => OptimizedDepInfo) - | null /** * @internal */ @@ -372,16 +361,18 @@ export async function createServer( }, transformIndexHtml: null!, // to be immediately set async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) { - let configFileDependencies: string[] = [] - const metadata = server._optimizeDepsMetadata - if (metadata) { - configFileDependencies = Object.keys(metadata.optimized) + if (!server._ssrExternals) { + let knownImports: string[] = [] + const optimizedDeps = server._optimizedDeps + if (optimizedDeps) { + await optimizedDeps.scanProcessing + knownImports = [ + ...Object.keys(optimizedDeps.metadata.optimized), + ...Object.keys(optimizedDeps.metadata.discovered) + ] + } + server._ssrExternals = resolveSSRExternal(config, knownImports) } - - server._ssrExternals ||= resolveSSRExternal( - config, - configFileDependencies - ) return ssrLoadModule( url, server, @@ -434,12 +425,11 @@ export async function createServer( return server._restartPromise }, - _optimizeDepsMetadata: null, + _optimizedDeps: null, _ssrExternals: null, _globImporters: Object.create(null), _restartPromise: null, _forceOptimizeOnRestart: false, - _registerMissingImport: null, _pendingRequests: new Map() } @@ -526,7 +516,7 @@ export async function createServer( // proxy const { proxy } = serverConfig if (proxy) { - middlewares.use(proxyMiddleware(httpServer, config)) + middlewares.use(proxyMiddleware(httpServer, proxy, config)) } // base @@ -581,39 +571,15 @@ export async function createServer( // error handler middlewares.use(errorMiddleware(server, !!middlewareMode)) - const runOptimize = async () => { - const optimizeDeps = await createOptimizeDepsRun( - config, - config.server.force - ) - - // Don't await for the optimization to finish, we can start the - // server right away here - server._optimizeDepsMetadata = optimizeDeps.metadata - - // Run deps optimization in parallel - const initialProcessingPromise = optimizeDeps - .run() - .then((result) => result.commit()) - - // While running the first optimizeDeps, _registerMissingImport is null - // so the resolve plugin resolves straight to node_modules during the - // deps discovery scan phase - server._registerMissingImport = createMissingImporterRegisterFn( - server, - initialProcessingPromise - ) - } - if (!middlewareMode && httpServer) { let isOptimized = false - // overwrite listen to run optimizer before server start + // overwrite listen to init optimizer before server start const listen = httpServer.listen.bind(httpServer) httpServer.listen = (async (port: number, ...args: any[]) => { if (!isOptimized) { try { await container.buildStart({}) - await runOptimize() + server._optimizedDeps = createOptimizedDeps(server) isOptimized = true } catch (e) { httpServer.emit('error', e) @@ -624,7 +590,7 @@ export async function createServer( }) as any } else { await container.buildStart({}) - await runOptimize() + server._optimizedDeps = createOptimizedDeps(server) } return server @@ -641,7 +607,7 @@ async function startServer( } const options = server.config.server - const port = inlinePort || options.port || 3000 + const port = inlinePort ?? options.port ?? 3000 const hostname = resolveHostname(options.host) const protocol = options.https ? 'https' : 'http' diff --git a/packages/vite/src/node/server/middlewares/compression.ts b/packages/vite/src/node/server/middlewares/compression.ts index 7a97044985ac0e..bafae7ef2023c6 100644 --- a/packages/vite/src/node/server/middlewares/compression.ts +++ b/packages/vite/src/node/server/middlewares/compression.ts @@ -13,7 +13,7 @@ const noop = () => {} const mimes = /text|javascript|\/json|xml/i const threshold = 1024 const level = -1 -const brotli = false +let brotli = false const gzip = true const getChunkSize = (chunk, enc) => (chunk ? Buffer.byteLength(chunk, enc) : 0) diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index 07b2693c07f995..ca2538bd9507ed 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -1,7 +1,7 @@ import fs from 'fs' import path from 'path' import MagicString from 'magic-string' -import type { AttributeNode, ElementNode } from '@vue/compiler-dom' +import type { AttributeNode, ElementNode, TextNode } from '@vue/compiler-dom' import { NodeTypes } from '@vue/compiler-dom' import type { Connect } from 'types/connect' import type { IndexHtmlTransformHook } from '../../plugins/html' @@ -38,7 +38,9 @@ function getHtmlFilename(url: string, server: ViteDevServer) { if (url.startsWith(FS_PREFIX)) { return decodeURIComponent(fsPathFromId(url)) } else { - return decodeURIComponent(path.join(server.config.root, url.slice(1))) + return decodeURIComponent( + normalizePath(path.join(server.config.root, url.slice(1))) + ) } } @@ -90,7 +92,7 @@ const processNodeUrl = ( } const devHtmlHook: IndexHtmlTransformHook = async ( html, - { path: htmlPath, server, originalUrl } + { path: htmlPath, filename, server, originalUrl } ) => { const { config, moduleGraph } = server! const base = config.base || '/' @@ -104,12 +106,17 @@ const devHtmlHook: IndexHtmlTransformHook = async ( const url = filePath.replace(normalizePath(config.root), '') - const contents = node.children - .map((child: any) => child.content || '') - .join('') + const contentNode = node.children[0] as TextNode + + const code = contentNode.content + const map = new MagicString(html) + .snip(contentNode.loc.start.offset, contentNode.loc.end.offset) + .generateMap({ hires: true }) + map.sources = [filename] + map.file = filename // add HTML Proxy to Map - addToHTMLProxyCache(config, url, inlineModuleIndex, contents) + addToHTMLProxyCache(config, url, inlineModuleIndex, { code, map }) // inline js module. convert to src="proxy" const modulePath = `${ @@ -141,7 +148,7 @@ const devHtmlHook: IndexHtmlTransformHook = async ( if (src) { processNodeUrl(src, s, config, htmlPath, originalUrl, moduleGraph) - } else if (isModule) { + } else if (isModule && node.children.length) { addInlineModule(node, 'js') } } diff --git a/packages/vite/src/node/server/middlewares/proxy.ts b/packages/vite/src/node/server/middlewares/proxy.ts index aa1100f13d5229..97de98a8331a7a 100644 --- a/packages/vite/src/node/server/middlewares/proxy.ts +++ b/packages/vite/src/node/server/middlewares/proxy.ts @@ -5,7 +5,7 @@ import { HMR_HEADER } from '../ws' import type { Connect } from 'types/connect' import type { HttpProxy } from 'types/http-proxy' import colors from 'picocolors' -import type { ResolvedConfig } from '../..' +import type { CommonServerOptions, ResolvedConfig } from '../..' const debug = createDebugger('vite:proxy') @@ -30,10 +30,9 @@ export interface ProxyOptions extends HttpProxy.ServerOptions { export function proxyMiddleware( httpServer: http.Server | null, + options: NonNullable, config: ResolvedConfig ): Connect.NextHandleFunction { - const options = config.server.proxy! - // lazy require only when proxy is used const proxies: Record = {} diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index a6623338783cc8..5fb4f7fad2e055 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -111,7 +111,7 @@ export function serveRawFsMiddleware( // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...` return function viteServeRawFsMiddleware(req, res, next) { - let url = req.url! + let url = decodeURI(req.url!) // In some cases (e.g. linked monorepos) files outside of root will // reference assets that are also out of served root. In such cases // the paths are rewritten to `/@fs/` prefixed paths and must be served by diff --git a/packages/vite/src/node/server/moduleGraph.ts b/packages/vite/src/node/server/moduleGraph.ts index 44e76deef98b6f..e470fafb05d8fd 100644 --- a/packages/vite/src/node/server/moduleGraph.ts +++ b/packages/vite/src/node/server/moduleGraph.ts @@ -27,7 +27,7 @@ export class ModuleNode { importers = new Set() importedModules = new Set() acceptedHmrDeps = new Set() - isSelfAccepting = false + isSelfAccepting?: boolean transformResult: TransformResult | null = null ssrTransformResult: TransformResult | null = null ssrModule: Record | null = null diff --git a/packages/vite/src/node/server/pluginContainer.ts b/packages/vite/src/node/server/pluginContainer.ts index 2aab3e03097c99..5efc2670c0f81a 100644 --- a/packages/vite/src/node/server/pluginContainer.ts +++ b/packages/vite/src/node/server/pluginContainer.ts @@ -90,6 +90,10 @@ export interface PluginContainer { options?: { skip?: Set ssr?: boolean + /** + * @internal + */ + scan?: boolean } ): Promise transform( @@ -212,6 +216,7 @@ export async function createPluginContainer( class Context implements PluginContext { meta = minimalContext.meta ssr = false + _scan = false _activePlugin: Plugin | null _activeId: string | null = null _activeCode: string | null = null @@ -241,7 +246,11 @@ export async function createPluginContainer( skip = new Set(this._resolveSkips) skip.add(this._activePlugin) } - let out = await container.resolveId(id, importer, { skip, ssr: this.ssr }) + let out = await container.resolveId(id, importer, { + skip, + ssr: this.ssr, + scan: this._scan + }) if (typeof out === 'string') out = { id: out } return out as ResolvedId | null } @@ -433,7 +442,7 @@ export async function createPluginContainer( ? new MagicString(this.originalCode).generateMap({ includeContent: true, hires: true, - source: this.filename + source: cleanUrl(this.filename) }) : null } @@ -487,8 +496,10 @@ export async function createPluginContainer( async resolveId(rawId, importer = join(root, 'index.html'), options) { const skip = options?.skip const ssr = options?.ssr + const scan = !!options?.scan const ctx = new Context() ctx.ssr = !!ssr + ctx._scan = scan ctx._resolveSkips = skip const resolveStart = isDebug ? performance.now() : 0 @@ -505,7 +516,7 @@ export async function createPluginContainer( ctx as any, rawId, importer, - { ssr } + { ssr, scan } ) if (!result) continue diff --git a/packages/vite/src/node/server/sourcemap.ts b/packages/vite/src/node/server/sourcemap.ts index 68684a3c2d6f2a..dc77c4a4714298 100644 --- a/packages/vite/src/node/server/sourcemap.ts +++ b/packages/vite/src/node/server/sourcemap.ts @@ -1,8 +1,9 @@ import path from 'path' import { promises as fs } from 'fs' import type { Logger } from '../logger' -import { createDebugger } from '../utils' +import { createDebugger, normalizePath } from '../utils' import type { SourceMap } from 'rollup' +import { maybeVirtualHtmlSet } from '../plugins/html' const isDebug = !!process.env.DEBUG const debug = createDebugger('vite:sourcemap', { @@ -42,6 +43,7 @@ export async function injectSourcesContent( sourcePath = path.resolve(sourceRoot, sourcePath) } return fs.readFile(sourcePath, 'utf-8').catch(() => { + if (maybeVirtualHtmlSet.has(normalizePath(sourcePath))) return null missingSources.push(sourcePath) return null }) diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index 479fc63daef2b6..6d4e66ec1a22ae 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -3,37 +3,99 @@ import type { Server } from 'http' import { STATUS_CODES } from 'http' import type { ServerOptions as HttpsServerOptions } from 'https' import { createServer as createHttpsServer } from 'https' -import type { ServerOptions } from 'ws' -import { WebSocketServer as WebSocket } from 'ws' +import type { ServerOptions, WebSocket as WebSocketRaw } from 'ws' +import { WebSocketServer as WebSocketServerRaw } from 'ws' import type { WebSocket as WebSocketTypes } from 'types/ws' -import type { ErrorPayload, HMRPayload } from 'types/hmrPayload' +import type { CustomPayload, ErrorPayload, HMRPayload } from 'types/hmrPayload' +import type { InferCustomEventPayload } from 'types/customEvent' import type { ResolvedConfig } from '..' import { isObject } from '../utils' import type { Socket } from 'net' + export const HMR_HEADER = 'vite-hmr' +export type WebSocketCustomListener = ( + data: T, + client: WebSocketClient +) => void + export interface WebSocketServer { - on: WebSocketTypes.Server['on'] - off: WebSocketTypes.Server['off'] + /** + * Get all connected clients. + */ + clients: Set + /** + * Boardcast events to all clients + */ send(payload: HMRPayload): void + /** + * Send custom event + */ + send(event: T, payload?: InferCustomEventPayload): void + /** + * Disconnect all clients and terminate the server. + */ close(): Promise + /** + * Handle custom event emitted by `import.meta.hot.send` + */ + on: WebSocketTypes.Server['on'] & { + ( + event: T, + listener: WebSocketCustomListener> + ): void + } + /** + * Unregister event listener. + */ + off: WebSocketTypes.Server['off'] & { + (event: string, listener: Function): void + } +} + +export interface WebSocketClient { + /** + * Send event to the client + */ + send(payload: HMRPayload): void + /** + * Send custom event + */ + send(event: string, payload?: CustomPayload['data']): void + /** + * The raw WebSocket instance + * @advanced + */ + socket: WebSocketTypes } +const wsServerEvents = [ + 'connection', + 'error', + 'headers', + 'listening', + 'message' +] + export function createWebSocketServer( server: Server | null, config: ResolvedConfig, httpsOptions?: HttpsServerOptions ): WebSocketServer { - let wss: WebSocket + let wss: WebSocketServerRaw let httpsServer: Server | undefined = undefined const hmr = isObject(config.server.hmr) && config.server.hmr - const wsServer = - (hmr && hmr.server) || - ((!(hmr && hmr.port) || hmr.port !== config.server.port) && server) + const hmrServer = hmr && hmr.server + const hmrPort = hmr && hmr.port + // TODO: the main server port may not have been chosen yet as it may use the next available + const portsAreCompatible = !hmrPort || hmrPort === config.server.port + const wsServer = hmrServer || (portsAreCompatible && server) + const customListeners = new Map>>() + const clientsMap = new WeakMap() if (wsServer) { - wss = new WebSocket({ noServer: true }) + wss = new WebSocketServerRaw({ noServer: true }) wsServer.on('upgrade', (req, socket, head) => { if (req.headers['sec-websocket-protocol'] === HMR_HEADER) { wss.handleUpgrade(req, socket as Socket, head, (ws) => { @@ -43,7 +105,7 @@ export function createWebSocketServer( }) } else { const websocketServerOptions: ServerOptions = {} - const port = (hmr && hmr.port) || 24678 + const port = hmrPort || 24678 const host = (hmr && hmr.host) || undefined if (httpsOptions) { // if we're serving the middlewares over https, the ws library doesn't support automatically creating an https server, so we need to do it ourselves @@ -74,10 +136,22 @@ export function createWebSocketServer( } // vite dev server in middleware mode - wss = new WebSocket(websocketServerOptions) + wss = new WebSocketServerRaw(websocketServerOptions) } wss.on('connection', (socket) => { + socket.on('message', (raw) => { + if (!customListeners.size) return + let parsed: any + try { + parsed = JSON.parse(String(raw)) + } catch {} + if (!parsed || parsed.type !== 'custom' || !parsed.event) return + const listeners = customListeners.get(parsed.event) + if (!listeners?.size) return + const client = getSocketClent(socket) + listeners.forEach((listener) => listener(parsed.data, client)) + }) socket.send(JSON.stringify({ type: 'connected' })) if (bufferedError) { socket.send(JSON.stringify(bufferedError)) @@ -94,6 +168,30 @@ export function createWebSocketServer( } }) + // Provide a wrapper to the ws client so we can send messages in JSON format + // To be consistent with server.ws.send + function getSocketClent(socket: WebSocketRaw) { + if (!clientsMap.has(socket)) { + clientsMap.set(socket, { + send: (...args) => { + let payload: HMRPayload + if (typeof args[0] === 'string') { + payload = { + type: 'custom', + event: args[0], + data: args[1] + } + } else { + payload = args[0] + } + socket.send(JSON.stringify(payload)) + }, + socket + }) + } + return clientsMap.get(socket)! + } + // On page reloads, if a file fails to compile and returns 500, the server // sends the error payload before the client connection is established. // If we have no open clients, buffer the error and send it to the next @@ -101,9 +199,39 @@ export function createWebSocketServer( let bufferedError: ErrorPayload | null = null return { - on: wss.on.bind(wss), - off: wss.off.bind(wss), - send(payload: HMRPayload) { + on: ((event: string, fn: () => void) => { + if (wsServerEvents.includes(event)) wss.on(event, fn) + else { + if (!customListeners.has(event)) { + customListeners.set(event, new Set()) + } + customListeners.get(event)!.add(fn) + } + }) as WebSocketServer['on'], + off: ((event: string, fn: () => void) => { + if (wsServerEvents.includes(event)) { + wss.off(event, fn) + } else { + customListeners.get(event)?.delete(fn) + } + }) as WebSocketServer['off'], + + get clients() { + return new Set(Array.from(wss.clients).map(getSocketClent)) + }, + + send(...args: any[]) { + let payload: HMRPayload + if (typeof args[0] === 'string') { + payload = { + type: 'custom', + event: args[0], + data: args[1] + } + } else { + payload = args[0] + } + if (payload.type === 'error' && !wss.clients.size) { bufferedError = payload return diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 4e0f230fd7edfd..8c7859e3850454 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -83,6 +83,7 @@ export function resolveFrom( ): string { return resolve.sync(id, { basedir, + paths: [], extensions: ssr ? ssrExtensions : DEFAULT_EXTENSIONS, // necessary to work with pnpm preserveSymlinks: preserveSymlinks || isRunningWithYarnPnp || false @@ -615,13 +616,16 @@ export function combineSourcemaps( // hack for parse broken with normalized absolute paths on windows (C:/path/to/something). // escape them to linux like paths - sourcemapList.forEach((sourcemap) => { - sourcemap.sources = sourcemap.sources.map((source) => + // also avoid mutation here to prevent breaking plugin's using cache to generate sourcemaps like vue (see #7442) + sourcemapList = sourcemapList.map((sourcemap) => { + const newSourcemaps = { ...sourcemap } + newSourcemaps.sources = sourcemap.sources.map((source) => source ? escapeToLinuxLikePath(source) : null ) if (sourcemap.sourceRoot) { - sourcemap.sourceRoot = escapeToLinuxLikePath(sourcemap.sourceRoot) + newSourcemaps.sourceRoot = escapeToLinuxLikePath(sourcemap.sourceRoot) } + return newSourcemaps }) const escapedFilename = escapeToLinuxLikePath(filename) @@ -729,3 +733,4 @@ export function parseRequest(id: string): Record | null { } export const blankReplacer = (match: string) => ' '.repeat(match.length) +export const stringsRE = /"[^"]*"|'[^']*'|`[^`]*`/g diff --git a/packages/vite/types/customEvent.d.ts b/packages/vite/types/customEvent.d.ts index c38a4ac940ff6e..af4db5d14fbe97 100644 --- a/packages/vite/types/customEvent.d.ts +++ b/packages/vite/types/customEvent.d.ts @@ -1,5 +1,16 @@ -// See https://stackoverflow.com/a/63549561. -export type CustomEventName = (T extends `vite:${T}` - ? never - : T) & - (`vite:${T}` extends T ? never : T) +import type { + ErrorPayload, + FullReloadPayload, + PrunePayload, + UpdatePayload +} from './hmrPayload' + +export interface CustomEventMap { + 'vite:beforeUpdate': UpdatePayload + 'vite:beforePrune': PrunePayload + 'vite:beforeFullReload': FullReloadPayload + 'vite:error': ErrorPayload +} + +export type InferCustomEventPayload = + T extends keyof CustomEventMap ? CustomEventMap[T] : any diff --git a/packages/vite/types/hot.d.ts b/packages/vite/types/hot.d.ts new file mode 100644 index 00000000000000..f06846ff59d530 --- /dev/null +++ b/packages/vite/types/hot.d.ts @@ -0,0 +1,25 @@ +import type { InferCustomEventPayload } from './customEvent' + +export interface ViteHotContext { + readonly data: any + + accept(): void + accept(cb: (mod: any) => void): void + accept(dep: string, cb: (mod: any) => void): void + accept(deps: readonly string[], cb: (mods: any[]) => void): void + + /** + * @deprecated + */ + acceptDeps(): never + + dispose(cb: (data: any) => void): void + decline(): void + invalidate(): void + + on( + event: T, + cb: (payload: InferCustomEventPayload) => void + ): void + send(event: T, data?: InferCustomEventPayload): void +} diff --git a/packages/vite/types/importMeta.d.ts b/packages/vite/types/importMeta.d.ts index 56b82125381e20..9b57fd120a7ba9 100644 --- a/packages/vite/types/importMeta.d.ts +++ b/packages/vite/types/importMeta.d.ts @@ -20,46 +20,7 @@ interface GlobOptions { interface ImportMeta { url: string - readonly hot?: { - readonly data: any - - accept(): void - accept(cb: (mod: any) => void): void - accept(dep: string, cb: (mod: any) => void): void - accept(deps: readonly string[], cb: (mods: any[]) => void): void - - /** - * @deprecated - */ - acceptDeps(): never - - dispose(cb: (data: any) => void): void - decline(): void - invalidate(): void - - on: { - ( - event: 'vite:beforeUpdate', - cb: (payload: import('./hmrPayload').UpdatePayload) => void - ): void - ( - event: 'vite:beforePrune', - cb: (payload: import('./hmrPayload').PrunePayload) => void - ): void - ( - event: 'vite:beforeFullReload', - cb: (payload: import('./hmrPayload').FullReloadPayload) => void - ): void - ( - event: 'vite:error', - cb: (payload: import('./hmrPayload').ErrorPayload) => void - ): void - ( - event: import('./customEvent').CustomEventName, - cb: (data: any) => void - ): void - } - } + readonly hot?: import('./hot').ViteHotContext readonly env: ImportMetaEnv diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a9145995c4bcf..a54c2eadfcfff2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,77 +4,79 @@ overrides: vite: workspace:* '@vitejs/plugin-vue': workspace:* +packageExtensionsChecksum: 696422bac84dd936748019990f84746e + importers: .: specifiers: - '@microsoft/api-extractor': ^7.19.5 + '@microsoft/api-extractor': ^7.20.0 '@types/fs-extra': ^9.0.13 '@types/jest': ^27.4.1 '@types/node': ^16.11.26 '@types/prompts': ^2.0.14 '@types/semver': ^7.3.9 - '@typescript-eslint/eslint-plugin': ^5.16.0 - '@typescript-eslint/parser': ^5.16.0 + '@typescript-eslint/eslint-plugin': ^5.17.0 + '@typescript-eslint/parser': ^5.17.0 conventional-changelog-cli: ^2.2.2 cross-env: ^7.0.3 esbuild: ^0.14.27 - eslint: ^8.11.0 + eslint: ^8.12.0 eslint-define-config: ^1.3.0 eslint-plugin-node: ^11.1.0 execa: ^5.1.1 fs-extra: ^10.0.1 jest: ^27.5.1 lint-staged: ^12.3.7 - minimist: ^1.2.5 + minimist: ^1.2.6 node-fetch: ^2.6.6 npm-run-all: ^4.1.5 picocolors: ^1.0.0 - playwright-chromium: ^1.20.0 - prettier: 2.6.0 + playwright-chromium: ^1.20.2 + prettier: 2.6.2 prompts: ^2.4.2 rimraf: ^3.0.2 rollup: ^2.59.0 semver: ^7.3.5 simple-git-hooks: ^2.7.0 sirv: ^2.0.2 - ts-jest: ^27.1.3 + ts-jest: ^27.1.4 ts-node: ^10.4.0 typescript: ~4.5.4 vite: workspace:* vitepress: ^0.22.3 devDependencies: - '@microsoft/api-extractor': 7.19.5 + '@microsoft/api-extractor': 7.20.0 '@types/fs-extra': 9.0.13 '@types/jest': 27.4.1 '@types/node': 16.11.26 '@types/prompts': 2.0.14 '@types/semver': 7.3.9 - '@typescript-eslint/eslint-plugin': 5.16.0_1324a086488ab25887f801dfb12f1aba - '@typescript-eslint/parser': 5.16.0_eslint@8.11.0+typescript@4.5.4 + '@typescript-eslint/eslint-plugin': 5.17.0_d58f0eed9ebf32fdeceb6a8716e9bcf1 + '@typescript-eslint/parser': 5.17.0_eslint@8.12.0+typescript@4.5.4 conventional-changelog-cli: 2.2.2 cross-env: 7.0.3 esbuild: 0.14.27 - eslint: 8.11.0 + eslint: 8.12.0 eslint-define-config: 1.3.0 - eslint-plugin-node: 11.1.0_eslint@8.11.0 + eslint-plugin-node: 11.1.0_eslint@8.12.0 execa: 5.1.1 fs-extra: 10.0.1 jest: 27.5.1_ts-node@10.4.0 lint-staged: 12.3.7 - minimist: 1.2.5 + minimist: 1.2.6 node-fetch: 2.6.6 npm-run-all: 4.1.5 picocolors: 1.0.0 - playwright-chromium: 1.20.0 - prettier: 2.6.0 + playwright-chromium: 1.20.2 + prettier: 2.6.2 prompts: 2.4.2 rimraf: 3.0.2 rollup: 2.62.0 semver: 7.3.5 simple-git-hooks: 2.7.0 sirv: 2.0.2 - ts-jest: 27.1.3_4dfe14e0e8266437469ae0475a5c09ac + ts-jest: 27.1.4_4dfe14e0e8266437469ae0475a5c09ac ts-node: 10.4.0_44ef5af6cbbc24239b4e70b5c7b0d7a6 typescript: 4.5.4 vite: link:packages/vite @@ -83,17 +85,19 @@ importers: packages/create-vite: specifiers: kolorist: ^1.5.1 - minimist: ^1.2.5 + minimist: ^1.2.6 prompts: ^2.4.2 dependencies: kolorist: 1.5.1 - minimist: 1.2.5 + minimist: 1.2.6 prompts: 2.4.2 packages/playground: specifiers: + convert-source-map: ^1.8.0 css-color-names: ^1.0.1 devDependencies: + convert-source-map: 1.8.0 css-color-names: 1.0.1 packages/playground/alias: @@ -152,13 +156,11 @@ importers: packages/playground/css-sourcemap: specifiers: - convert-source-map: ^1.8.0 less: ^4.1.2 magic-string: ^0.25.7 sass: ^1.43.4 stylus: ^0.55.0 devDependencies: - convert-source-map: 1.8.0 less: 4.1.2 magic-string: 0.25.7 sass: 1.45.1 @@ -223,6 +225,9 @@ importers: packages/playground/html: specifiers: {} + packages/playground/js-sourcemap: + specifiers: {} + packages/playground/json: specifiers: json-module: file:./json-module @@ -456,6 +461,8 @@ importers: '@babel/runtime': ^7.16.0 es5-ext: 0.10.53 normalize.css: ^8.0.1 + require-pkg-with-browser-and-module-field: link:./require-pkg-with-browser-and-module-field + require-pkg-with-esm-entries: link:./require-pkg-with-esm-entries resolve-browser-field: link:./browser-field resolve-custom-condition: link:./custom-condition resolve-custom-main-field: link:./custom-main-field @@ -466,6 +473,8 @@ importers: '@babel/runtime': 7.16.5 es5-ext: 0.10.53 normalize.css: 8.0.1 + require-pkg-with-browser-and-module-field: link:require-pkg-with-browser-and-module-field + require-pkg-with-esm-entries: link:require-pkg-with-esm-entries resolve-browser-field: link:browser-field resolve-custom-condition: link:custom-condition resolve-custom-main-field: link:custom-main-field @@ -497,6 +506,18 @@ importers: packages/playground/resolve/inline-package: specifiers: {} + packages/playground/resolve/require-pkg-with-browser-and-module-field: + specifiers: + bignumber.js: 9.0.2 + dependencies: + bignumber.js: 9.0.2 + + packages/playground/resolve/require-pkg-with-esm-entries: + specifiers: + callbag-from-event: 1.3.0 + dependencies: + callbag-from-event: 1.3.0 + packages/playground/ssr-deps: specifiers: bcrypt: ^5.0.1 @@ -653,6 +674,12 @@ importers: devDependencies: '@vitejs/plugin-vue': link:../../plugin-vue + packages/playground/tailwind-sourcemap: + specifiers: + tailwindcss: ^3.0.23 + dependencies: + tailwindcss: 3.0.23_ts-node@10.4.0 + packages/playground/tsconfig-json: specifiers: {} @@ -703,7 +730,6 @@ importers: packages/playground/vue-sourcemap: specifiers: '@vitejs/plugin-vue': workspace:* - convert-source-map: ^1.8.0 less: ^4.1.2 sass: ^1.43.4 vue: ^3.2.31 @@ -711,7 +737,6 @@ importers: vue: 3.2.31 devDependencies: '@vitejs/plugin-vue': link:../../plugin-vue - convert-source-map: 1.8.0 less: 4.1.2 sass: 1.45.1 @@ -746,7 +771,7 @@ importers: '@babel/plugin-transform-react-jsx-self': ^7.16.7 '@babel/plugin-transform-react-jsx-source': ^7.16.7 '@rollup/pluginutils': ^4.2.0 - react-refresh: ^0.11.0 + react-refresh: ^0.12.0 resolve: ^1.22.0 dependencies: '@babel/core': 7.17.8 @@ -755,7 +780,7 @@ importers: '@babel/plugin-transform-react-jsx-self': 7.16.7_@babel+core@7.17.8 '@babel/plugin-transform-react-jsx-source': 7.16.7_@babel+core@7.17.8 '@rollup/pluginutils': 4.2.0 - react-refresh: 0.11.0 + react-refresh: 0.12.0 resolve: 1.22.0 packages/plugin-vue: @@ -801,7 +826,7 @@ importers: '@babel/types': ^7.17.0 '@jridgewell/trace-mapping': ^0.3.4 '@rollup/plugin-alias': ^3.1.9 - '@rollup/plugin-commonjs': ^21.0.2 + '@rollup/plugin-commonjs': ^21.0.3 '@rollup/plugin-dynamic-import-vars': ^1.4.2 '@rollup/plugin-json': ^4.1.0 '@rollup/plugin-node-resolve': 13.1.3 @@ -818,7 +843,7 @@ importers: '@types/node': ^16.11.26 '@types/resolve': ^1.20.1 '@types/sass': ~1.43.1 - '@types/stylus': ^0.48.36 + '@types/stylus': ^0.48.37 '@types/ws': ^8.5.3 '@vue/compiler-dom': ^3.2.31 acorn: ^8.7.0 @@ -832,7 +857,7 @@ importers: debug: ^4.3.4 dotenv: ^14.3.2 dotenv-expand: ^5.1.0 - es-module-lexer: ^0.10.4 + es-module-lexer: ^0.10.5 esbuild: ^0.14.27 estree-walker: ^2.0.2 etag: ^1.8.1 @@ -842,16 +867,16 @@ importers: json5: ^2.2.1 launch-editor-middleware: ^2.3.0 magic-string: ^0.26.1 - micromatch: ^4.0.4 + micromatch: ^4.0.5 mrmime: ^1.0.0 - node-forge: ^1.3.0 + node-forge: ^1.3.1 okie: ^1.0.1 open: ^8.4.0 periscopic: ^2.0.3 picocolors: ^1.0.0 postcss: ^8.4.12 - postcss-import: ^14.0.2 - postcss-load-config: ^3.1.3 + postcss-import: ^14.1.0 + postcss-load-config: ^3.1.4 postcss-modules: ^4.3.1 resolve: ^1.22.0 resolve.exports: ^1.1.0 @@ -862,7 +887,7 @@ importers: source-map-support: ^0.5.21 strip-ansi: ^6.0.1 terser: ^5.12.1 - tsconfck: ^1.2.0 + tsconfck: ^1.2.2 tslib: ^2.3.1 types: link:./types ws: ^8.5.0 @@ -879,7 +904,7 @@ importers: '@babel/types': 7.17.0 '@jridgewell/trace-mapping': 0.3.4 '@rollup/plugin-alias': 3.1.9_rollup@2.62.0 - '@rollup/plugin-commonjs': 21.0.2_rollup@2.62.0 + '@rollup/plugin-commonjs': 21.0.3_rollup@2.62.0 '@rollup/plugin-dynamic-import-vars': 1.4.2_rollup@2.62.0 '@rollup/plugin-json': 4.1.0_rollup@2.62.0 '@rollup/plugin-node-resolve': 13.1.3_rollup@2.62.0 @@ -896,7 +921,7 @@ importers: '@types/node': 16.11.26 '@types/resolve': 1.20.1 '@types/sass': 1.43.1 - '@types/stylus': 0.48.36 + '@types/stylus': 0.48.37 '@types/ws': 8.5.3 '@vue/compiler-dom': 3.2.31 acorn: 8.7.0 @@ -910,7 +935,7 @@ importers: debug: 4.3.4 dotenv: 14.3.2 dotenv-expand: 5.1.0 - es-module-lexer: 0.10.4 + es-module-lexer: 0.10.5 estree-walker: 2.0.2 etag: 1.8.1 fast-glob: 3.2.11 @@ -918,15 +943,15 @@ importers: json5: 2.2.1 launch-editor-middleware: 2.3.0 magic-string: 0.26.1 - micromatch: 4.0.4 + micromatch: 4.0.5 mrmime: 1.0.0 - node-forge: 1.3.0 + node-forge: 1.3.1 okie: 1.0.1 open: 8.4.0 periscopic: 2.0.3 picocolors: 1.0.0 - postcss-import: 14.0.2_postcss@8.4.12 - postcss-load-config: 3.1.3_ts-node@10.4.0 + postcss-import: 14.1.0_postcss@8.4.12 + postcss-load-config: 3.1.4_postcss@8.4.12+ts-node@10.4.0 postcss-modules: 4.3.1_postcss@8.4.12 resolve.exports: 1.1.0 rollup-plugin-license: 2.6.1_rollup@2.62.0 @@ -935,7 +960,7 @@ importers: source-map-support: 0.5.21 strip-ansi: 6.0.1 terser: 5.12.1 - tsconfck: 1.2.0_typescript@4.5.4 + tsconfck: 1.2.2_typescript@4.5.4 tslib: 2.3.1 types: link:types ws: 8.5.0 @@ -1104,7 +1129,7 @@ packages: convert-source-map: 1.8.0 debug: 4.3.4 gensync: 1.0.0-beta.2 - json5: 2.2.0 + json5: 2.2.1 semver: 6.3.0 transitivePeerDependencies: - supports-color @@ -2157,17 +2182,6 @@ packages: - supports-color dev: true - /@jest/types/27.4.2: - resolution: {integrity: sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - dependencies: - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 16.11.26 - '@types/yargs': 16.0.4 - chalk: 4.1.2 - dev: true - /@jest/types/27.5.1: resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2209,19 +2223,19 @@ packages: - supports-color dev: false - /@microsoft/api-extractor-model/7.15.4: - resolution: {integrity: sha512-9bIXQKKQr5jAH1c9atXrukr4ua30fhqxMwWIOOjEnexPBPu3nhy9lC4/GpE0kPUp1Al3wSXgFnOEGzEH+HFz+w==} + /@microsoft/api-extractor-model/7.16.0: + resolution: {integrity: sha512-0FOrbNIny8mzBrzQnSIkEjAXk0JMSnPmWYxt3ZDTPVg9S8xIPzB6lfgTg9+Mimu0RKCpGKBpd+v2WcR5vGzyUQ==} dependencies: '@microsoft/tsdoc': 0.13.2 '@microsoft/tsdoc-config': 0.15.2 '@rushstack/node-core-library': 3.45.1 dev: true - /@microsoft/api-extractor/7.19.5: - resolution: {integrity: sha512-ra5r8P7PocOpemrZRccI3Tf1+wukI0gT6ncRB448QSxSYlmqKuvez95YUSYPwHIN/ztKL0cn5PfMOauU1lZfGQ==} + /@microsoft/api-extractor/7.20.0: + resolution: {integrity: sha512-WKAu5JpkRXWKL3AyxmFXuwNNPpBlsAefwZIDl8M5mhEqRji4w+gexb0pku3Waa0flm3vm0Cwpm+kGYYJ4/gzAA==} hasBin: true dependencies: - '@microsoft/api-extractor-model': 7.15.4 + '@microsoft/api-extractor-model': 7.16.0 '@microsoft/tsdoc': 0.13.2 '@microsoft/tsdoc-config': 0.15.2 '@rushstack/node-core-library': 3.45.1 @@ -2317,8 +2331,8 @@ packages: slash: 3.0.0 dev: true - /@rollup/plugin-commonjs/21.0.2_rollup@2.62.0: - resolution: {integrity: sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==} + /@rollup/plugin-commonjs/21.0.3_rollup@2.62.0: + resolution: {integrity: sha512-ThGfwyvcLc6cfP/MWxA5ACF+LZCvsuhUq7V5134Az1oQWsiC7lNpLT4mJI86WQunK7BYmpUiHmMk2Op6OAHs0g==} engines: {node: '>= 8.0.0'} peerDependencies: rollup: ^2.38.3 @@ -2660,8 +2674,8 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true - /@types/stylus/0.48.36: - resolution: {integrity: sha512-7klEq45BUE8ZJWkYWy1E442DcCs0wi0FkFY1Tjr6EJ7edL77t9w/QmOwlkFumBMqHlatDBtrA2xgfRrGqkUkzg==} + /@types/stylus/0.48.37: + resolution: {integrity: sha512-IkLnS/GzdDK3rgAmQwLr8LqPvUMa43SHlCnXqsfXNukwaIpiXBNgSHil3ro8aemhF4k4ZiMoa4URE7mwBHPJnQ==} dependencies: '@types/node': 16.11.26 dev: true @@ -2690,8 +2704,8 @@ packages: dev: true optional: true - /@typescript-eslint/eslint-plugin/5.16.0_1324a086488ab25887f801dfb12f1aba: - resolution: {integrity: sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw==} + /@typescript-eslint/eslint-plugin/5.17.0_d58f0eed9ebf32fdeceb6a8716e9bcf1: + resolution: {integrity: sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -2701,12 +2715,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.16.0_eslint@8.11.0+typescript@4.5.4 - '@typescript-eslint/scope-manager': 5.16.0 - '@typescript-eslint/type-utils': 5.16.0_eslint@8.11.0+typescript@4.5.4 - '@typescript-eslint/utils': 5.16.0_eslint@8.11.0+typescript@4.5.4 + '@typescript-eslint/parser': 5.17.0_eslint@8.12.0+typescript@4.5.4 + '@typescript-eslint/scope-manager': 5.17.0 + '@typescript-eslint/type-utils': 5.17.0_eslint@8.12.0+typescript@4.5.4 + '@typescript-eslint/utils': 5.17.0_eslint@8.12.0+typescript@4.5.4 debug: 4.3.4 - eslint: 8.11.0 + eslint: 8.12.0 functional-red-black-tree: 1.0.1 ignore: 5.2.0 regexpp: 3.2.0 @@ -2717,8 +2731,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.16.0_eslint@8.11.0+typescript@4.5.4: - resolution: {integrity: sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA==} + /@typescript-eslint/parser/5.17.0_eslint@8.12.0+typescript@4.5.4: + resolution: {integrity: sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2727,26 +2741,26 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.16.0 - '@typescript-eslint/types': 5.16.0 - '@typescript-eslint/typescript-estree': 5.16.0_typescript@4.5.4 + '@typescript-eslint/scope-manager': 5.17.0 + '@typescript-eslint/types': 5.17.0 + '@typescript-eslint/typescript-estree': 5.17.0_typescript@4.5.4 debug: 4.3.4 - eslint: 8.11.0 + eslint: 8.12.0 typescript: 4.5.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager/5.16.0: - resolution: {integrity: sha512-P+Yab2Hovg8NekLIR/mOElCDPyGgFZKhGoZA901Yax6WR6HVeGLbsqJkZ+Cvk5nts/dAlFKm8PfL43UZnWdpIQ==} + /@typescript-eslint/scope-manager/5.17.0: + resolution: {integrity: sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.16.0 - '@typescript-eslint/visitor-keys': 5.16.0 + '@typescript-eslint/types': 5.17.0 + '@typescript-eslint/visitor-keys': 5.17.0 dev: true - /@typescript-eslint/type-utils/5.16.0_eslint@8.11.0+typescript@4.5.4: - resolution: {integrity: sha512-SKygICv54CCRl1Vq5ewwQUJV/8padIWvPgCxlWPGO/OgQLCijY9G7lDu6H+mqfQtbzDNlVjzVWQmeqbLMBLEwQ==} + /@typescript-eslint/type-utils/5.17.0_eslint@8.12.0+typescript@4.5.4: + resolution: {integrity: sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -2755,22 +2769,22 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/utils': 5.16.0_eslint@8.11.0+typescript@4.5.4 + '@typescript-eslint/utils': 5.17.0_eslint@8.12.0+typescript@4.5.4 debug: 4.3.4 - eslint: 8.11.0 + eslint: 8.12.0 tsutils: 3.21.0_typescript@4.5.4 typescript: 4.5.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types/5.16.0: - resolution: {integrity: sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g==} + /@typescript-eslint/types/5.17.0: + resolution: {integrity: sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/5.16.0_typescript@4.5.4: - resolution: {integrity: sha512-SE4VfbLWUZl9MR+ngLSARptUv2E8brY0luCdgmUevU6arZRY/KxYoLI/3V/yxaURR8tLRN7bmZtJdgmzLHI6pQ==} + /@typescript-eslint/typescript-estree/5.17.0_typescript@4.5.4: + resolution: {integrity: sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -2778,8 +2792,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.16.0 - '@typescript-eslint/visitor-keys': 5.16.0 + '@typescript-eslint/types': 5.17.0 + '@typescript-eslint/visitor-keys': 5.17.0 debug: 4.3.4 globby: 11.0.4 is-glob: 4.0.3 @@ -2790,29 +2804,29 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.16.0_eslint@8.11.0+typescript@4.5.4: - resolution: {integrity: sha512-iYej2ER6AwmejLWMWzJIHy3nPJeGDuCqf8Jnb+jAQVoPpmWzwQOfa9hWVB8GIQE5gsCv/rfN4T+AYb/V06WseQ==} + /@typescript-eslint/utils/5.17.0_eslint@8.12.0+typescript@4.5.4: + resolution: {integrity: sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@types/json-schema': 7.0.9 - '@typescript-eslint/scope-manager': 5.16.0 - '@typescript-eslint/types': 5.16.0 - '@typescript-eslint/typescript-estree': 5.16.0_typescript@4.5.4 - eslint: 8.11.0 + '@typescript-eslint/scope-manager': 5.17.0 + '@typescript-eslint/types': 5.17.0 + '@typescript-eslint/typescript-estree': 5.17.0_typescript@4.5.4 + eslint: 8.12.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.11.0 + eslint-utils: 3.0.0_eslint@8.12.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys/5.16.0: - resolution: {integrity: sha512-jqxO8msp5vZDhikTwq9ubyMHqZ67UIvawohr4qF3KhlpL7gzSjOd+8471H3nh5LyABkaI85laEKKU8SnGUK5/g==} + /@typescript-eslint/visitor-keys/5.17.0: + resolution: {integrity: sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.16.0 + '@typescript-eslint/types': 5.17.0 eslint-visitor-keys: 3.3.0 dev: true @@ -3067,7 +3081,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.3 + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -3150,7 +3164,7 @@ packages: engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 - picomatch: 2.3.0 + picomatch: 2.3.1 /aproba/2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} @@ -3253,7 +3267,7 @@ packages: /axios/0.24.0: resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} dependencies: - follow-redirects: 1.14.6 + follow-redirects: 1.14.6_debug@4.3.4 transitivePeerDependencies: - debug dev: false @@ -3365,6 +3379,10 @@ packages: - supports-color dev: false + /bignumber.js/9.0.2: + resolution: {integrity: sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==} + dev: false + /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -3466,6 +3484,16 @@ packages: get-intrinsic: 1.1.1 dev: true + /callbag-from-event/1.3.0: + resolution: {integrity: sha512-cAu82hKKFmMtKTmd50p/nlMfs1oKz+PGUZmmwhbzPbw4YtjNgTKg6pXjpcQprhBQdrqg/v8pHcAS8Qs6X7r8fw==} + dependencies: + callbag: 1.5.0 + dev: false + + /callbag/1.5.0: + resolution: {integrity: sha512-PH3id0HEb/cNS+BehYlF4Z5wzjKAIUao6ab2hWtMs2bi6aW+0PXl0jymqwnFyT2cQO2h30ggUgpQlmzOpAIKNg==} + dev: false + /callsites/3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -3549,7 +3577,6 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.2 - dev: true /chownr/2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} @@ -4165,7 +4192,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true /debug/4.3.4_supports-color@9.2.1: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -4425,8 +4451,8 @@ packages: unbox-primitive: 1.0.1 dev: true - /es-module-lexer/0.10.4: - resolution: {integrity: sha512-n5bOGUnrmuCKyMkmHNtC1ObnUx8AgFcKWe2mbxb6jYzuK81W0Rk3Z//sCoGJuxWzos8R2w48TemGIFqZsTY6YA==} + /es-module-lexer/0.10.5: + resolution: {integrity: sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==} dev: true /es-to-primitive/1.2.1: @@ -4688,25 +4714,25 @@ packages: engines: {node: '>= 16.9.0', npm: '>= 7.0.0', pnpm: '>= 6.32.2'} dev: true - /eslint-plugin-es/3.0.1_eslint@8.11.0: + /eslint-plugin-es/3.0.1_eslint@8.12.0: resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=4.19.1' dependencies: - eslint: 8.11.0 + eslint: 8.12.0 eslint-utils: 2.1.0 regexpp: 3.2.0 dev: true - /eslint-plugin-node/11.1.0_eslint@8.11.0: + /eslint-plugin-node/11.1.0_eslint@8.12.0: resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=5.16.0' dependencies: - eslint: 8.11.0 - eslint-plugin-es: 3.0.1_eslint@8.11.0 + eslint: 8.12.0 + eslint-plugin-es: 3.0.1_eslint@8.12.0 eslint-utils: 2.1.0 ignore: 5.2.0 minimatch: 3.0.4 @@ -4737,13 +4763,13 @@ packages: eslint-visitor-keys: 1.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.11.0: + /eslint-utils/3.0.0_eslint@8.12.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.11.0 + eslint: 8.12.0 eslint-visitor-keys: 2.1.0 dev: true @@ -4762,8 +4788,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.11.0: - resolution: {integrity: sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==} + /eslint/8.12.0: + resolution: {integrity: sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: @@ -4772,11 +4798,11 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.3 + debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.11.0 + eslint-utils: 3.0.0_eslint@8.12.0 eslint-visitor-keys: 3.3.0 espree: 9.3.1 esquery: 1.4.0 @@ -5064,7 +5090,7 @@ packages: resolution: {integrity: sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==} dev: true - /follow-redirects/1.14.6: + /follow-redirects/1.14.6_debug@4.3.4: resolution: {integrity: sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==} engines: {node: '>=4.0'} peerDependencies: @@ -5072,6 +5098,8 @@ packages: peerDependenciesMeta: debug: optional: true + dependencies: + debug: 4.3.4 /form-data/3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} @@ -5326,7 +5354,7 @@ packages: engines: {node: '>=0.4.7'} hasBin: true dependencies: - minimist: 1.2.5 + minimist: 1.2.6 neo-async: 2.6.2 source-map: 0.6.1 wordwrap: 1.0.0 @@ -5472,7 +5500,7 @@ packages: engines: {node: '>=8.0.0'} dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.14.6 + follow-redirects: 1.14.6_debug@4.3.4 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -5483,7 +5511,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.3 + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -6282,18 +6310,6 @@ packages: - supports-color dev: true - /jest-util/27.4.2: - resolution: {integrity: sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - dependencies: - '@jest/types': 27.4.2 - '@types/node': 16.11.26 - chalk: 4.1.2 - ci-info: 3.3.0 - graceful-fs: 4.2.9 - picomatch: 2.3.0 - dev: true - /jest-util/27.5.1: resolution: {integrity: sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -6463,6 +6479,7 @@ packages: hasBin: true dependencies: minimist: 1.2.5 + dev: false /json5/2.2.1: resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} @@ -6569,6 +6586,11 @@ packages: resolution: {integrity: sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==} engines: {node: '>=10'} + /lilconfig/2.0.5: + resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==} + engines: {node: '>=10'} + dev: true + /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -6812,6 +6834,14 @@ packages: braces: 3.0.2 picomatch: 2.3.0 + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + /mime-db/1.46.0: resolution: {integrity: sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==} engines: {node: '>= 0.6'} @@ -6925,6 +6955,10 @@ packages: /minimist/1.2.5: resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} + dev: false + + /minimist/1.2.6: + resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} /minipass/3.1.6: resolution: {integrity: sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==} @@ -7054,8 +7088,8 @@ packages: engines: {node: '>= 6.0.0'} dev: true - /node-forge/1.3.0: - resolution: {integrity: sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==} + /node-forge/1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} dev: true @@ -7399,6 +7433,10 @@ packages: resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} engines: {node: '>=8.6'} + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + /pidtree/0.3.1: resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} engines: {node: '>=0.10'} @@ -7446,21 +7484,21 @@ packages: find-up: 4.1.0 dev: true - /playwright-chromium/1.20.0: - resolution: {integrity: sha512-5qyHaW2kFguX4eAne2MgS7AxZBfQ+hN2X0t5SD5mxOTAgwciEAhMNc06o9NxILnRcmF8QSXGg19OSb8VMUBLzg==} + /playwright-chromium/1.20.2: + resolution: {integrity: sha512-KsiPLRC1v56qLWqjzeEoDZNVW/eFrP5ad0PFQAa74u5EwnnId89LgOHEZFy487tt3xJdv3Ayyfdn8zwsUpS3Qg==} engines: {node: '>=12'} hasBin: true requiresBuild: true dependencies: - playwright-core: 1.20.0 + playwright-core: 1.20.2 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate dev: true - /playwright-core/1.20.0: - resolution: {integrity: sha512-d25IRcdooS278Cijlp8J8A5fLQZ+/aY3dKRJvgX5yjXA69N0huIUdnh3xXSgn+LsQ9DCNmB7Ngof3eY630jgdA==} + /playwright-core/1.20.2: + resolution: {integrity: sha512-iV6+HftSPalynkq0CYJala1vaTOq7+gU9BRfKCdM9bAxNq/lFLrwbluug2Wt5OoUwbMABcnTThIEm3/qUhCdJQ==} engines: {node: '>=12'} hasBin: true dependencies: @@ -7498,8 +7536,8 @@ packages: engines: {node: '>=12.13.0'} dev: true - /postcss-import/14.0.2_postcss@8.4.12: - resolution: {integrity: sha512-BJ2pVK4KhUyMcqjuKs9RijV5tatNzNa73e/32aBVE/ejYPe37iH+6vAu9WvqUkB5OAYgLHzbSvzHnorybJCm9g==} + /postcss-import/14.1.0_postcss@8.4.12: + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.0.0 @@ -7518,10 +7556,21 @@ packages: postcss: 8.4.5 dev: false + /postcss-js/4.0.0_postcss@8.4.12: + resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.3.3 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.12 + dev: false + /postcss-load-config/3.1.0_ts-node@10.4.0: resolution: {integrity: sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==} engines: {node: '>= 10'} peerDependencies: + postcss: '*' ts-node: '>=9.0.0' peerDependenciesMeta: ts-node: @@ -7533,16 +7582,36 @@ packages: yaml: 1.10.2 dev: false - /postcss-load-config/3.1.3_ts-node@10.4.0: + /postcss-load-config/3.1.3_postcss@8.4.12+ts-node@10.4.0: resolution: {integrity: sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw==} engines: {node: '>= 10'} peerDependencies: + postcss: '*' ts-node: '>=9.0.0' peerDependenciesMeta: ts-node: optional: true dependencies: lilconfig: 2.0.4 + postcss: 8.4.12 + ts-node: 10.4.0_44ef5af6cbbc24239b4e70b5c7b0d7a6 + yaml: 1.10.2 + dev: false + + /postcss-load-config/3.1.4_postcss@8.4.12+ts-node@10.4.0: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.0.5 + postcss: 8.4.12 ts-node: 10.4.0_44ef5af6cbbc24239b4e70b5c7b0d7a6 yaml: 1.10.2 dev: true @@ -7612,6 +7681,16 @@ packages: dependencies: postcss-selector-parser: 6.0.8 + /postcss-nested/5.0.6_postcss@8.4.12: + resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.12 + postcss-selector-parser: 6.0.8 + dev: false + /postcss-selector-parser/6.0.8: resolution: {integrity: sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==} engines: {node: '>=4'} @@ -7619,6 +7698,14 @@ packages: cssesc: 3.0.0 util-deprecate: 1.0.2 + /postcss-selector-parser/6.0.9: + resolution: {integrity: sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: false + /postcss-value-parser/3.3.1: resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==} dev: false @@ -7656,8 +7743,8 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier/2.6.0: - resolution: {integrity: sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==} + /prettier/2.6.2: + resolution: {integrity: sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==} engines: {node: '>=10.13.0'} hasBin: true dev: true @@ -7930,8 +8017,8 @@ packages: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} dev: true - /react-refresh/0.11.0: - resolution: {integrity: sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==} + /react-refresh/0.12.0: + resolution: {integrity: sha512-suLIhrU2IHKL5JEKR/fAwJv7bbeq4kJ+pJopf77jHwuR+HmJS/HbrPIGsTBUVfw7tXPOmYv7UJ7PCaN49e8x4A==} engines: {node: '>=0.10.0'} dev: false @@ -8053,7 +8140,7 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} dependencies: - picomatch: 2.3.0 + picomatch: 2.3.1 /redent/3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} @@ -8850,6 +8937,38 @@ packages: - ts-node dev: false + /tailwindcss/3.0.23_ts-node@10.4.0: + resolution: {integrity: sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==} + engines: {node: '>=12.13.0'} + hasBin: true + peerDependencies: + autoprefixer: ^10.0.2 + dependencies: + arg: 5.0.1 + chalk: 4.1.2 + chokidar: 3.5.3 + color-name: 1.1.4 + cosmiconfig: 7.0.1 + detective: 5.2.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.2.11 + glob-parent: 6.0.2 + is-glob: 4.0.3 + normalize-path: 3.0.0 + object-hash: 2.2.0 + postcss: 8.4.12 + postcss-js: 4.0.0_postcss@8.4.12 + postcss-load-config: 3.1.3_postcss@8.4.12+ts-node@10.4.0 + postcss-nested: 5.0.6_postcss@8.4.12 + postcss-selector-parser: 6.0.9 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + resolve: 1.22.0 + transitivePeerDependencies: + - ts-node + dev: false + /tar/6.1.11: resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} engines: {node: '>= 10'} @@ -9014,15 +9133,15 @@ packages: utf8-byte-length: 1.0.4 dev: true - /ts-jest/27.1.3_4dfe14e0e8266437469ae0475a5c09ac: - resolution: {integrity: sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==} + /ts-jest/27.1.4_4dfe14e0e8266437469ae0475a5c09ac: + resolution: {integrity: sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true peerDependencies: '@babel/core': '>=7.0.0-beta.0 <8' '@types/jest': ^27.0.0 babel-jest: '>=27.0.0 <28' - esbuild: ~0.14.0 + esbuild: '*' jest: ^27.0.0 typescript: '>=3.8 <5.0' peerDependenciesMeta: @@ -9040,8 +9159,8 @@ packages: esbuild: 0.14.27 fast-json-stable-stringify: 2.1.0 jest: 27.5.1_ts-node@10.4.0 - jest-util: 27.4.2 - json5: 2.2.0 + jest-util: 27.5.1 + json5: 2.2.1 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.3.5 @@ -9079,8 +9198,8 @@ packages: yn: 3.1.1 dev: true - /tsconfck/1.2.0_typescript@4.5.4: - resolution: {integrity: sha512-uKBM6x6i5e7Tfof5Zhll2ypgYKwWZhsxsHOoCH/7enlcHjjlcZ8FnxFrqgVFtpN6ymzZX3zoDaVGIlkh/g4x6w==} + /tsconfck/1.2.2_typescript@4.5.4: + resolution: {integrity: sha512-x5YpjOqjJnMs1EsJvQBQbrysrY32eGoZRRr5YvbN1hwlrXKc7jiphCOUrT7xbFdOWk8sh+EtMYbGPbTO8rDmcw==} engines: {node: ^12.20 || ^14.13.1 || >= 16} hasBin: true peerDependencies: diff --git a/scripts/jestGlobalSetup.cjs b/scripts/jestGlobalSetup.cjs index 9640f70c5a8291..7341cba40968d9 100644 --- a/scripts/jestGlobalSetup.cjs +++ b/scripts/jestGlobalSetup.cjs @@ -21,11 +21,21 @@ module.exports = async () => { const tempDir = path.resolve(__dirname, '../packages/temp') await fs.remove(tempDir) - await fs.copy(path.resolve(__dirname, '../packages/playground'), tempDir, { - dereference: false, - filter(file) { - file = file.replace(/\\/g, '/') - return !file.includes('__tests__') && !file.match(/dist(\/|$)/) - } - }) + await fs + .copy(path.resolve(__dirname, '../packages/playground'), tempDir, { + dereference: false, + filter(file) { + file = file.replace(/\\/g, '/') + return !file.includes('__tests__') && !file.match(/dist(\/|$)/) + } + }) + .catch(async (error) => { + if (error.code === 'EPERM' && error.syscall === 'symlink') { + throw new Error( + 'Could not create symlinks. On Windows, consider activating Developer Mode to allow non-admin users to create symlinks by following the instructions at https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development.' + ) + } else { + throw error + } + }) } diff --git a/scripts/jestGlobalTeardown.cjs b/scripts/jestGlobalTeardown.cjs index 583aa1d1caf4ce..fc3370d626683a 100644 --- a/scripts/jestGlobalTeardown.cjs +++ b/scripts/jestGlobalTeardown.cjs @@ -4,6 +4,6 @@ const path = require('path') module.exports = async () => { await global.__BROWSER_SERVER__.close() if (!process.env.VITE_PRESERVE_BUILD_ARTIFACTS) { - await fs.remove(path.resolve(__dirname, '../packages/temp')) + fs.removeSync(path.resolve(__dirname, '../packages/temp')) } } diff --git a/scripts/jestPerTestSetup.ts b/scripts/jestPerTestSetup.ts index 530634f7b993d3..fcdca77ee9a6eb 100644 --- a/scripts/jestPerTestSetup.ts +++ b/scripts/jestPerTestSetup.ts @@ -4,12 +4,12 @@ import { resolve, dirname } from 'path' import sirv from 'sirv' import type { ViteDevServer, - UserConfig, + InlineConfig, PluginOption, ResolvedConfig, Logger } from 'vite' -import { createServer, build } from 'vite' +import { createServer, build, mergeConfig } from 'vite' import type { Page, ConsoleMessage } from 'playwright-chromium' import type { RollupError, RollupWatcher, RollupWatcherEvent } from 'rollup' @@ -90,9 +90,16 @@ beforeAll(async () => { } } + const testCustomConfig = resolve(dirname(testPath), 'vite.config.js') + let config: InlineConfig | undefined + if (fs.existsSync(testCustomConfig)) { + // test has custom server configuration. + config = require(testCustomConfig) + } + const serverLogs: string[] = [] - const options: UserConfig = { + const options: InlineConfig = { root: rootDir, logLevel: 'silent', server: { @@ -120,7 +127,9 @@ beforeAll(async () => { if (!isBuildTest) { process.env.VITE_INLINE = 'inline-serve' - server = await (await createServer(options)).listen() + server = await ( + await createServer(mergeConfig(options, config || {})) + ).listen() // use resolved port/base from server const base = server.config.base === '/' ? '' : server.config.base const url = @@ -137,14 +146,14 @@ beforeAll(async () => { } }) options.plugins = [resolvedPlugin()] - const rollupOutput = await build(options) + const rollupOutput = await build(mergeConfig(options, config || {})) const isWatch = !!resolvedConfig!.build.watch // in build watch,call startStaticServer after the build is complete if (isWatch) { global.watcher = rollupOutput as RollupWatcher await notifyRebuildComplete(global.watcher) } - const url = (global.viteTestUrl = await startStaticServer(isWatch)) + const url = (global.viteTestUrl = await startStaticServer(config)) await page.goto(url) } } @@ -173,13 +182,15 @@ afterAll(async () => { } }) -function startStaticServer(isWatch: boolean): Promise { - // check if the test project has base config - const configFile = resolve(rootDir, 'vite.config.js') - let config: UserConfig | undefined - try { - config = require(configFile) - } catch (e) {} +function startStaticServer(config?: InlineConfig): Promise { + if (!config) { + // check if the test project has base config + const configFile = resolve(rootDir, 'vite.config.js') + try { + config = require(configFile) + } catch (e) {} + } + // fallback internal base to '' const base = (config?.base ?? '/') === '/' ? '' : config?.base ?? '' @@ -190,7 +201,7 @@ function startStaticServer(isWatch: boolean): Promise { } // start static file server - const serve = sirv(resolve(rootDir, 'dist'), { dev: isWatch }) + const serve = sirv(resolve(rootDir, 'dist'), { dev: !!config?.build?.watch }) const httpServer = (server = http.createServer((req, res) => { if (req.url === '/ping') { res.statusCode = 200 diff --git a/scripts/release.ts b/scripts/release.ts index d536eca1f70586..5c32c13b5cf5c8 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -88,7 +88,7 @@ async function main(): Promise { '--commit-path', '.' ] - if (pkgName !== 'vite') changelogArgs.push('--lerna-package', 'plugin-vue') + if (pkgName !== 'vite') changelogArgs.push('--lerna-package', pkgName) await run('npx', changelogArgs, { cwd: pkgDir }) const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })